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