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