1/*
2 * Copyright (c) 1989 Regents of the University of California.
3 * All rights reserved.  The Berkeley software License Agreement
4 * specifies the terms and conditions for redistribution.
5 */
6
7#include <popper.h>
8RCSID("$Id$");
9
10
11#if defined(KRB5)
12
13static int
14pop_net_read(POP *p, int fd, void *buf, size_t len)
15{
16#ifdef KRB5
17    return krb5_net_read(p->context, &fd, buf, len);
18#else
19#error must define KRB5
20#endif
21}
22#endif
23
24static char *addr_log;
25
26static void
27pop_write_addr(POP *p, struct sockaddr *addr)
28{
29    char ts[32];
30    char as[128];
31    time_t t;
32    FILE *f;
33    if(addr_log == NULL)
34	return;
35    t = time(NULL);
36    strftime(ts, sizeof(ts), "%Y%m%d%H%M%S", localtime(&t));
37    if(inet_ntop (addr->sa_family, socket_get_address(addr),
38		  as, sizeof(as)) == NULL) {
39        pop_log(p, POP_PRIORITY, "failed to print address");
40	return;
41    }
42
43    f = fopen(addr_log, "a");
44    if(f == NULL) {
45        pop_log(p, POP_PRIORITY, "failed to open address log (%s)", addr_log);
46	return;
47    }
48    fprintf(f, "%s %s\n", as, ts);
49    fclose(f);
50}
51
52#ifdef KRB5
53static int
54krb5_authenticate (POP *p, int s, u_char *buf, struct sockaddr *addr)
55{
56    krb5_error_code ret;
57    krb5_auth_context auth_context = NULL;
58    uint32_t len;
59    krb5_ticket *ticket;
60    char *server;
61
62    if (memcmp (buf, "\x00\x00\x00\x13", 4) != 0)
63	return -1;
64    len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3]);
65
66    if (krb5_net_read(p->context, &s, buf, len) != len)
67	return -1;
68    if (len != sizeof(KRB5_SENDAUTH_VERSION)
69	|| memcmp (buf, KRB5_SENDAUTH_VERSION, len) != 0)
70	return -1;
71
72    ret = krb5_recvauth (p->context,
73			 &auth_context,
74			 &s,
75			 "KPOPV1.0",
76			 NULL, /* let rd_req figure out what server to use */
77			 KRB5_RECVAUTH_IGNORE_VERSION,
78			 NULL,
79			 &ticket);
80    if (ret) {
81	pop_log(p, POP_PRIORITY, "krb5_recvauth: %s",
82		krb5_get_err_text(p->context, ret));
83	return -1;
84    }
85
86
87    ret = krb5_unparse_name(p->context, ticket->server, &server);
88    if(ret) {
89	pop_log(p, POP_PRIORITY, "krb5_unparse_name: %s",
90		krb5_get_err_text(p->context, ret));
91	ret = -1;
92	goto out;
93    }
94    /* does this make sense? */
95    if(strncmp(server, "pop/", 4) != 0) {
96	pop_log(p, POP_PRIORITY,
97		"Got ticket for service `%s'", server);
98	ret = -1;
99	goto out;
100    } else if(p->debug)
101	pop_log(p, POP_DEBUG,
102		"Accepted ticket for service `%s'", server);
103    free(server);
104 out:
105    krb5_auth_con_free (p->context, auth_context);
106    krb5_copy_principal (p->context, ticket->client, &p->principal);
107    krb5_free_ticket (p->context, ticket);
108
109    return ret;
110}
111#endif
112
113static int
114krb_authenticate(POP *p, struct sockaddr *addr)
115{
116#if defined(KRB5)
117    u_char buf[BUFSIZ];
118
119    if (pop_net_read (p, 0, buf, 4) != 4) {
120	pop_msg(p, POP_FAILURE, "Reading four bytes: %s",
121		strerror(errno));
122	exit (1);
123    }
124    if (krb5_authenticate (p, 0, buf, addr) == 0){
125	pop_write_addr(p, addr);
126	p->version = 5;
127	return POP_SUCCESS;
128    }
129#endif
130    exit (1);
131
132    return(POP_SUCCESS);
133}
134
135static int
136plain_authenticate (POP *p, struct sockaddr *addr)
137{
138    return(POP_SUCCESS);
139}
140
141static int kerberos_flag;
142static char *auth_str;
143static int debug_flag;
144static int interactive_flag;
145static char *port_str;
146static char *trace_file;
147static int timeout;
148static int help_flag;
149static int version_flag;
150
151static struct getargs args[] = {
152#if defined(KRB5)
153    { "kerberos", 'k', arg_flag, &kerberos_flag, "use kerberos" },
154#endif
155    { "auth-mode", 'a', arg_string, &auth_str, "required authentication",
156      "plaintext"
157#ifdef OTP
158      "|otp"
159#endif
160#ifdef SASL
161      "|sasl"
162#endif
163    },
164    { "debug", 'd', arg_flag, &debug_flag },
165    { "interactive", 'i', arg_flag, &interactive_flag, "create new socket" },
166    { "port", 'p', arg_string, &port_str, "port to listen to", "port" },
167    { "trace-file", 't', arg_string, &trace_file, "trace all command to file", "file" },
168    { "timeout", 'T', arg_integer, &timeout, "timeout", "seconds" },
169    { "address-log", 0, arg_string, &addr_log, "enable address log", "file" },
170    { "help", 'h', arg_flag, &help_flag },
171    { "version", 'v', arg_flag, &version_flag }
172};
173
174static int num_args = sizeof(args) / sizeof(args[0]);
175
176/*
177 *  init:   Start a Post Office Protocol session
178 */
179
180static int
181pop_getportbyname(POP *p, const char *service,
182		  const char *proto, short def)
183{
184#ifdef KRB5
185    return krb5_getportbyname(p->context, service, proto, def);
186#else
187    return htons(default);
188#endif
189}
190
191int
192pop_init(POP *p,int argcount,char **argmessage)
193{
194    struct sockaddr_storage cs_ss;
195    struct sockaddr *cs = (struct sockaddr *)&cs_ss;
196    socklen_t		    len;
197    char                *   trace_file_name = "/tmp/popper-trace";
198    int			    portnum = 0;
199    int 		    optind = 0;
200    int			    error;
201
202    /*  Initialize the POP parameter block */
203    memset (p, 0, sizeof(POP));
204
205    setprogname(argmessage[0]);
206
207    /*  Save my name in a global variable */
208    p->myname = (char*)getprogname();
209
210    /*  Get the name of our host */
211    gethostname(p->myhost,MaxHostNameLen);
212
213#ifdef KRB5
214    {
215	krb5_error_code ret;
216
217	ret = krb5_init_context (&p->context);
218	if (ret)
219	    errx (1, "krb5_init_context failed: %d", ret);
220
221	krb5_openlog(p->context, p->myname, &p->logf);
222	krb5_set_warn_dest(p->context, p->logf);
223    }
224#else
225    /*  Open the log file */
226    roken_openlog(p->myname,POP_LOGOPTS,POP_FACILITY);
227#endif
228
229    p->auth_level = AUTH_NONE;
230
231    if(getarg(args, num_args, argcount, argmessage, &optind)){
232	arg_printusage(args, num_args, NULL, "");
233	exit(1);
234    }
235    if(help_flag){
236	arg_printusage(args, num_args, NULL, "");
237	exit(0);
238    }
239    if(version_flag){
240	print_version(NULL);
241	exit(0);
242    }
243
244    argcount -= optind;
245    argmessage += optind;
246
247    if (argcount != 0) {
248	arg_printusage(args, num_args, NULL, "");
249	exit(1);
250    }
251
252    if(auth_str){
253	if (strcasecmp (auth_str, "plaintext") == 0 ||
254	    strcasecmp (auth_str, "none") == 0)
255	    p->auth_level = AUTH_NONE;
256	else if(strcasecmp(auth_str, "otp") == 0) {
257#ifdef OTP
258	    p->auth_level = AUTH_OTP;
259#else
260	    pop_log (p, POP_PRIORITY, "support for OTP not enabled");
261	    exit(1);
262#endif
263	} else if(strcasecmp(auth_str, "sasl") == 0) {
264#ifdef SASL
265	    p->auth_level = AUTH_SASL;
266#else
267	    pop_log (p, POP_PRIORITY, "support for SASL not enabled");
268	    exit(1);
269#endif
270	} else {
271	    pop_log (p, POP_PRIORITY, "bad value for -a: %s", auth_str);
272	    exit(1);
273	}
274    }
275    /*  Debugging requested */
276    p->debug = debug_flag;
277
278    if(port_str)
279	portnum = htons(atoi(port_str));
280    if(trace_file){
281	p->debug++;
282	if ((p->trace = fopen(trace_file, "a+")) == NULL) {
283	    pop_log(p, POP_PRIORITY,
284		    "Unable to open trace file \"%s\", err = %d",
285		    optarg,errno);
286	    exit (1);
287	}
288	trace_file_name = trace_file;
289    }
290
291#if defined(KRB5)
292    p->kerberosp = kerberos_flag;
293#endif
294
295    if(timeout)
296	pop_timeout = timeout;
297
298    /* Fake inetd */
299    if (interactive_flag) {
300	if (portnum == 0)
301	    portnum = p->kerberosp ?
302		pop_getportbyname(p, "kpop", "tcp", 1109) :
303	    pop_getportbyname(p, "pop", "tcp", 110);
304	mini_inetd (portnum, NULL);
305    }
306
307    /*  Get the address and socket of the client to whom I am speaking */
308    len = sizeof(cs_ss);
309    if (getpeername(STDIN_FILENO, cs, &len) < 0) {
310        pop_log(p,POP_PRIORITY,
311            "Unable to obtain socket and address of client, err = %d",errno);
312        exit (1);
313    }
314
315    /*  Save the dotted decimal form of the client's IP address
316        in the POP parameter block */
317    inet_ntop (cs->sa_family, socket_get_address (cs),
318	       p->ipaddr, sizeof(p->ipaddr));
319
320    /*  Save the client's port */
321    p->ipport = ntohs(socket_get_port (cs));
322
323    /*  Get the canonical name of the host to whom I am speaking */
324    error = getnameinfo_verified (cs, len, p->client, sizeof(p->client),
325				  NULL, 0, 0);
326    if (error) {
327	pop_log (p, POP_PRIORITY,
328		 "getnameinfo: %s", gai_strerror (error));
329	strlcpy (p->client, p->ipaddr, sizeof(p->client));
330    }
331
332    /*  Create input file stream for TCP/IP communication */
333    if ((p->input = fdopen(STDIN_FILENO,"r")) == NULL){
334        pop_log(p,POP_PRIORITY,
335            "Unable to open communication stream for input, err = %d",errno);
336        exit (1);
337    }
338
339    /*  Create output file stream for TCP/IP communication */
340    if ((p->output = fdopen(STDOUT_FILENO,"w")) == NULL){
341        pop_log(p,POP_PRIORITY,
342            "Unable to open communication stream for output, err = %d",errno);
343        exit (1);
344    }
345
346    pop_log(p,POP_PRIORITY,
347        "(v%s) Servicing request from \"%s\" at %s\n",
348            VERSION,p->client,p->ipaddr);
349
350#ifdef DEBUG
351    if (p->trace)
352        pop_log(p,POP_PRIORITY,
353            "Tracing session and debugging information in file \"%s\"",
354                trace_file_name);
355    else if (p->debug)
356        pop_log(p,POP_PRIORITY,"Debugging turned on");
357#endif /* DEBUG */
358
359
360    if(p->kerberosp)
361	return krb_authenticate(p, cs);
362    else
363	return plain_authenticate(p, cs);
364}
365