1/* smtpauth.c -- authenticate to SMTP server, then give normal protocol
2 *
3 * uses sfio
4 *
5 */
6
7#include <config.h>
8
9#include <sfio.h>
10#include <sfio/stdio.h>
11#include <ctype.h>
12#include <pwd.h>
13#include <sys/types.h>
14#include <sys/socket.h>
15#include <sys/file.h>
16#include <netinet/in.h>
17#include <netdb.h>
18#include <unistd.h>
19#include <string.h>
20#include <assert.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <stdlib.h>
24
25#include <sys/socket.h>
26#include <sys/file.h>
27#include <netinet/in.h>
28#include <netdb.h>
29
30#ifdef HAVE_SYS_SELECT_H
31#include <sys/select.h>
32#endif
33
34#include <sasl.h>
35#include <saslutil.h>
36
37#include "sfsasl.h"
38
39/* from OS: */
40extern char *getpass();
41extern struct hostent *gethostbyname();
42
43static char *authname = NULL;
44static char *username = NULL;
45static char *realm = NULL;
46
47extern char *optarg;
48extern int optind;
49
50int verbose = 0;
51int emacs = 0;
52
53int iptostring(const struct sockaddr *addr, socklen_t addrlen,
54		     char *out, unsigned outlen) {
55    char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
56    int niflags;
57
58    if(!addr || !out) return SASL_BADPARAM;
59
60    niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
61#ifdef NI_WITHSCOPEID
62    if (addr->sa_family == AF_INET6)
63	niflags |= NI_WITHSCOPEID;
64#endif
65    if (getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
66		    niflags) != 0)
67	return SASL_BADPARAM;
68
69    if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
70	return SASL_BUFOVER;
71
72    snprintf(out, outlen, "%s;%s", hbuf, pbuf);
73
74    return SASL_OK;
75}
76
77void usage(char *p)
78{
79    fprintf(stderr, "%s [-v] [-l] [-u username] [-a authname] [-s ssf] [-m mech] host[:port]\n", p);
80    fprintf(stderr, " -v\tVerbose Output\n");
81    fprintf(stderr, " -l\tLMTP semantics\n");
82    exit(EX_USAGE);
83}
84
85#define ISGOOD(r) (((r) / 100) == 2)
86#define TEMPFAIL(r) (((r) / 100) == 4)
87#define PERMFAIL(r) (((r) / 100) == 5)
88#define ISCONT(s) (s && (s[3] == '-'))
89
90static int ask_code(const char *s)
91{
92    int ret = 0;
93
94    if (s==NULL) return -1;
95
96    if (strlen(s) < 3) return -1;
97
98    /* check to make sure 0-2 are digits */
99    if ((isdigit((int) s[0])==0) ||
100	(isdigit((int) s[1])==0) ||
101	(isdigit((int) s[2])==0))
102    {
103	return -1;
104    }
105
106    ret = ((s[0]-'0')*100)+((s[1]-'0')*10)+(s[2]-'0');
107
108    return ret;
109}
110
111static void chop(char *s)
112{
113    char *p;
114
115    assert(s);
116    p = s + strlen(s) - 1;
117    if (p[0] == '\n') {
118	*p-- = '\0';
119    }
120    if (p >= s && p[0] == '\r') {
121	*p-- = '\0';
122    }
123}
124
125void interaction (int id, const char *prompt,
126		  char **tresult, unsigned int *tlen)
127{
128    char result[1024];
129
130    if (id==SASL_CB_PASS) {
131	fprintf(stderr, "%s: ", prompt);
132	*tresult = strdup(getpass("")); /* leaks! */
133	*tlen=   strlen(*tresult);
134	return;
135    } else if (id==SASL_CB_USER) {
136	if (username != NULL) {
137	    strcpy(result, username);
138	} else {
139	    strcpy(result, getpwuid(getuid())->pw_name);
140	}
141    } else if (id==SASL_CB_AUTHNAME) {
142	if (authname != NULL) {
143	    strcpy(result, authname);
144	} else {
145	    strcpy(result, getpwuid(getuid())->pw_name);
146	}
147    } else if ((id==SASL_CB_GETREALM) && (realm != NULL)) {
148      strcpy(result, realm);
149    } else {
150	int c;
151
152	fprintf(stderr, "%s: ",prompt);
153	fgets(result, sizeof(result) - 1, stdin);
154	c = strlen(result);
155	result[c - 1] = '\0';
156    }
157
158    *tlen = strlen(result);
159    *tresult = (char *) malloc(*tlen+1); /* leaks! */
160    memset(*tresult, 0, *tlen+1);
161    memcpy((char *) *tresult, result, *tlen);
162}
163
164void fillin_interactions(sasl_interact_t *tlist)
165{
166    while (tlist->id != SASL_CB_LIST_END)
167    {
168	interaction(tlist->id, tlist->prompt,
169		    (void *) &(tlist->result),
170		    &(tlist->len));
171	tlist++;
172    }
173}
174
175static sasl_callback_t callbacks[] = {
176    { SASL_CB_GETREALM, NULL, NULL },
177    { SASL_CB_USER, NULL, NULL },
178    { SASL_CB_AUTHNAME, NULL, NULL },
179    { SASL_CB_PASS, NULL, NULL },
180    { SASL_CB_LIST_END, NULL, NULL }
181};
182
183static sasl_security_properties_t *make_secprops(int min,int max)
184{
185    sasl_security_properties_t *ret=(sasl_security_properties_t *)
186	malloc(sizeof(sasl_security_properties_t));
187
188    ret->maxbufsize = 8192;
189    ret->min_ssf = min;
190    ret->max_ssf = max;
191
192    ret->security_flags = 0;
193    ret->property_names = NULL;
194    ret->property_values = NULL;
195
196    return ret;
197}
198
199Sfio_t *debug;
200
201int main(int argc, char **argv)
202{
203    char *mechlist = NULL;
204    const char *mechusing = NULL;
205    int minssf = 0, maxssf = 128;
206    char *p;
207    Sfio_t *server_in, *server_out;
208    sasl_conn_t *conn = NULL;
209    sasl_interact_t *client_interact = NULL;
210    char in[4096];
211    const char *out;
212    unsigned int inlen, outlen;
213    unsigned len;
214    char out64[4096];
215    int c;
216
217    char *host;
218    struct servent *service;
219    int port;
220    struct hostent *hp;
221    struct sockaddr_in addr;
222    char remote_ip[64], local_ip[64];
223    int sock;
224
225    char buf[1024];
226    int sz;
227    char greeting[1024];
228    int code;
229    int do_lmtp=0;
230    int r = 0;
231
232    debug = stderr;
233
234    while ((c = getopt(argc, argv, "vElm:s:u:a:d:")) != EOF) {
235	switch (c) {
236	case 'm':
237	    mechlist = optarg;
238	    break;
239
240	case 'l':
241	    do_lmtp = 1;
242	    break;
243
244	case 's':
245	    maxssf = atoi(optarg);
246	    break;
247
248	case 'u':
249	    username = optarg;
250	    break;
251
252	case 'a':
253	    authname = optarg;
254	    break;
255
256	case 'v':
257	    verbose++;
258	    break;
259
260	case 'E':
261	    emacs++;
262	    break;
263
264	case 'd':
265	    sprintf(buf, "%s-%d", optarg, getpid());
266	    debug = sfopen(NULL, buf, "w");
267	    sfsetbuf(debug, NULL, 0);
268	    break;
269
270	case '?':
271	default:
272	    usage(argv[0]);
273	    break;
274	}
275    }
276
277    if (optind != argc - 1) {
278	usage(argv[0]);
279    }
280
281    host = argv[optind];
282    p = strchr(host, ':');
283    if (p) {
284	*p++ = '\0';
285    } else {
286	if(do_lmtp) {
287	    p = "lmtp";
288	} else {
289	    p = "smtp";
290	}
291    }
292    service = getservbyname(p, "tcp");
293    if (service) {
294	port = service->s_port;
295    } else {
296	port = atoi(p);
297	if (!port) usage(argv[0]);
298	port = htons(port);
299    }
300
301    if ((hp = gethostbyname(host)) == NULL) {
302	perror("gethostbyname");
303	exit(EX_NOHOST);
304    }
305
306    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
307	perror("socket");
308	exit(EX_OSERR);
309    }
310
311    addr.sin_family = AF_INET;
312    memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
313    addr.sin_port = port;
314
315    if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
316	perror("connect");
317	exit(EX_NOHOST);
318    }
319
320    server_in = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_READ);
321    server_out = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_WRITE);
322
323    /* read greeting */
324    greeting[0] = '\0';
325    for (;;) {
326	sfsync(server_out);
327	if (fgets(buf, sizeof(buf)-1, server_in)) {
328	    if (greeting[0] == '\0') {
329		strncpy(greeting, buf, sizeof(greeting) - 1);
330	    }
331
332	    if (verbose) fprintf(debug, "%s", buf);
333	    code = ask_code(buf);
334	    if (ISCONT(buf) && ISGOOD(code)) continue;
335	} else {
336	    code = 400;
337	}
338	break;
339    }
340
341    if (!ISGOOD(code)) goto done;
342
343    /* EHLO */
344    gethostname(buf, sizeof(buf)-1);
345    if(do_lmtp) {
346	if(verbose) fprintf(debug, "LHLO %s\r\n", buf);
347	fprintf(server_out, "LHLO %s\r\n", buf);
348    } else {
349	if (verbose) fprintf(debug, "EHLO %s\r\n", buf);
350	fprintf(server_out, "EHLO %s\r\n", buf);
351    }
352
353    /* read responses */
354    for (;;) {
355	sfsync(server_out);
356	if (!fgets(buf, sizeof(buf)-1, server_in)) {
357	    code = 400;
358	    goto done;
359	}
360	if (verbose) fprintf(debug, "%s", buf);
361	code = ask_code(buf);
362	if (code == 250) {
363	    /* we're only looking for AUTH */
364	    if (!strncasecmp(buf + 4, "AUTH ", 5)) {
365		chop(buf);
366		if (!mechlist) mechlist = strdup(buf + 9);
367	    }
368	}
369	if (ISCONT(buf) && ISGOOD(code)) {
370	    continue;
371	} else {
372	    break;
373	}
374    }
375    if (!ISGOOD(code)) goto done;
376
377    /* attempt authentication */
378    if (!mechlist) {
379	if (verbose > 2) fprintf(debug, "no authentication\n");
380	goto doneauth;
381    }
382
383    if (!r) r = sasl_client_init(callbacks);
384    if (!r) {
385	struct sockaddr_in saddr_r;
386	int addrsize = sizeof(struct sockaddr_in);
387
388	if (getpeername(sock, (struct sockaddr *) &saddr_r, &addrsize) < 0) {
389	    perror("getpeername");
390	    exit(EX_NOHOST);
391	}
392	r = iptostring((struct sockaddr *)&saddr_r,
393		       sizeof(struct sockaddr_in), remote_ip, 64);
394    }
395    if (!r) {
396	struct sockaddr_in saddr_l;
397	int addrsize = sizeof(struct sockaddr_in);
398
399	if (getsockname(sock, (struct sockaddr *) &saddr_l, &addrsize) < 0) {
400	    perror("getsockname");
401	    exit(EX_OSERR);
402	}
403	r = iptostring((struct sockaddr *)&saddr_l,
404		       sizeof(struct sockaddr_in), local_ip, 64);
405    }
406
407    if (!r) {
408	if(do_lmtp) {
409	    r = sasl_client_new("lmtp", host, local_ip, remote_ip,
410				NULL, 0, &conn);
411	} else {
412	    r = sasl_client_new("smtp", host, local_ip, remote_ip,
413				NULL, 0, &conn);
414	}
415    }
416
417    if (!r) {
418	sasl_security_properties_t *secprops = make_secprops(minssf, maxssf);
419	r = sasl_setprop(conn, SASL_SEC_PROPS, secprops);
420	free(secprops);
421    }
422
423    if (!r) {
424	do {
425	    r = sasl_client_start(conn, mechlist,
426				  &client_interact, &out, &outlen, &mechusing);
427	    if (r == SASL_INTERACT) {
428		fillin_interactions(client_interact);
429	    }
430	} while (r == SASL_INTERACT);
431
432	if (r == SASL_OK || r == SASL_CONTINUE) {
433	    if (outlen > 0) {
434		r = sasl_encode64(out, outlen, out64, sizeof out64, NULL);
435		if (!r) {
436		    if (verbose)
437			fprintf(debug, "AUTH %s %s\r\n", mechusing, out64);
438		    fprintf(server_out, "AUTH %s %s\r\n", mechusing, out64);
439		}
440	    } else {
441		if (verbose) fprintf(debug, "AUTH %s\r\n", mechusing);
442		fprintf(server_out, "AUTH %s\r\n", mechusing);
443	    }
444	} else {
445	    fprintf(debug, "\nclient start failed: %s\n", sasl_errdetail(conn));
446	}
447
448    }
449
450    /* jump to doneauth if we succeed */
451    while (r == SASL_OK || r == SASL_CONTINUE) {
452	sfsync(server_out);
453	if (!fgets(buf, sizeof(buf)-1, server_in)) {
454	    code = 400;
455	    goto done;
456	}
457	if (verbose) fprintf(debug, "%s", buf);
458	code = ask_code(buf);
459	if (ISCONT(buf)) continue;
460	if (ISGOOD(code)) {
461	    if (code != 235) {
462		/* weird! */
463	    }
464	    /* yay, we won! */
465	    sfdcsasl(server_in, conn);
466	    sfdcsasl(server_out, conn);
467	    goto doneauth;
468	} else if (code != 334) {
469	    /* unexpected response */
470	    break;
471	}
472	len = strlen(buf);
473	if (len > 0 && buf[len-1] == '\n') {
474	    buf[len-1] = '\0';
475	}
476	r = sasl_decode64(buf + 4, strlen(buf) - 6, in, 4096, &inlen);
477	if (r != SASL_OK) break;
478
479	do {
480	    r = sasl_client_step(conn, in, inlen, &client_interact,
481				 &out, &outlen);
482	    if (r == SASL_INTERACT) {
483		fillin_interactions(client_interact);
484	    }
485	} while (r == SASL_INTERACT);
486
487	if (r == SASL_OK || r == SASL_CONTINUE) {
488	    r = sasl_encode64(out, outlen, out64, sizeof out64, NULL);
489	}
490	if (r == SASL_OK) {
491	    if (verbose) fprintf(debug, "%s\r\n", out64);
492	    fprintf(server_out, "%s\r\n", out64);
493	}
494    }
495
496    /* auth failed! */
497    if (!r) {
498	fprintf(debug, "%d authentication failed\n", code);
499    } else {
500	fprintf(debug, "400 authentication failed: %s\n",
501		sasl_errstring(r, NULL, NULL));
502    }
503    exit(EX_SOFTWARE);
504
505 doneauth:
506    /* ready for application */
507    greeting[3] = '-';
508    printf("%s", greeting);
509    printf("220 %s %s\r\n", host, conn ? "authenticated" : "no auth");
510
511    fcntl(0, F_SETFL, O_NONBLOCK);
512    fcntl(sock, F_SETFL, O_NONBLOCK);
513    sfset(stdin, SF_SHARE, 0);
514
515    /* feed data back 'n forth */
516    for (;;) {
517	Sfio_t *flist[3];
518
519    top:
520	flist[0] = stdin;
521	flist[1] = server_in;
522
523	/* sfpoll */
524	if (verbose > 5) fprintf(debug, "poll\n");
525	r = sfpoll(flist, 2, -1);
526	if (verbose > 5) fprintf(debug, "poll 2\n");
527
528	while (r--) {
529	    if (flist[r] == server_in) {
530		do {
531		    if (verbose > 5) fprintf(debug, "server!\n");
532		    errno = 0;
533		    sz = sfread(server_in, buf, sizeof(buf)-1);
534		    if (sz == 0 && (errno == EAGAIN)) goto top;
535		    if (sz <= 0) goto out;
536		    buf[sz] = '\0';
537		    if (verbose > 5) fprintf(debug, "server 2 '%s'!\n", buf);
538		    sfwrite(stdout, buf, sz);
539		} while (sfpoll(&server_in, 1, 0));
540		sfsync(stdout);
541	    } else if (flist[r] == stdin) {
542		Sfio_t *p[1];
543
544		p[0] = stdin;
545		do {
546		    if (verbose > 5) fprintf(debug, "stdin!\n");
547		    errno = 0;
548		    sz = sfread(stdin, buf, sizeof(buf)-1);
549		    if (sz == 0 && (errno == EAGAIN)) goto top;
550		    if (sz <= 0) goto out;
551		    buf[sz] = '\0';
552		    if (verbose > 5) fprintf(debug, "stdin 2 '%s'!\n", buf);
553		    if (emacs) {
554			int i;
555
556			/* fix emacs stupidness */
557			for (i = 0; i < sz - 1; i++) {
558			    if (buf[i] == '\n' && buf[i+1] == '\n')
559				buf[i++] = '\r';
560			}
561			if (buf[sz-2] != '\r' && buf[sz-1] == '\n') {
562			    sfungetc(stdin, buf[sz--]);
563			    buf[sz] = '\0';
564			}
565
566			if (verbose > 5) fprintf(debug, "emacs '%s'!\n", buf);
567		    }
568		    sfwrite(server_out, buf, sz);
569		    if (verbose > 7) fprintf(debug, "stdin 3!\n");
570		} while (sfpoll(p, 1, 0));
571		sfsync(server_out);
572	    } else {
573		abort();
574	    }
575	}
576    }
577 out:
578    if (verbose > 3) fprintf(debug, "exiting! %d %s\n", sz, strerror(errno));
579    exit(EX_OK);
580
581 done:
582    if (ISGOOD(code)) {
583	if (verbose > 1) fprintf(debug, "ok\n");
584	exit(EX_OK);
585    }
586    if (TEMPFAIL(code)) {
587	if (verbose > 1) fprintf(debug, "tempfail\n");
588	exit(EX_TEMPFAIL);
589    }
590    if (PERMFAIL(code)) {
591	if (verbose > 1) fprintf(debug, "permfail\n");
592	exit(EX_UNAVAILABLE);
593    }
594
595    if (verbose > 1) fprintf(debug, "unknown failure\n");
596    exit(EX_TEMPFAIL);
597}
598