import cyrus-sasl-2.1.23
[cyrus-sasl.git] / utils / smtptest.c
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: */
40 extern char *getpass();
41 extern struct hostent *gethostbyname();
42
43 static char *authname = NULL;
44 static char *username = NULL;
45 static char *realm = NULL;
46
47 extern char *optarg;
48 extern int optind;
49
50 int verbose = 0;
51 int emacs = 0;
52
53 int 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
77 void 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
90 static 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
111 static 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
125 void 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
164 void 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
175 static 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
183 static 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
199 Sfio_t *debug;
200
201 int 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     char out64[4096];
214     int c;
215
216     char *host;
217     struct servent *service;
218     int port;
219     struct hostent *hp;
220     struct sockaddr_in addr;
221     char remote_ip[64], local_ip[64];
222     int sock;
223
224     char buf[1024];
225     int sz;
226     char greeting[1024];
227     int code;
228     int do_lmtp=0;
229     int r = 0;
230
231     debug = stderr;
232
233     while ((c = getopt(argc, argv, "vElm:s:u:a:d:")) != EOF) {
234         switch (c) {
235         case 'm':
236             mechlist = optarg;
237             break;
238
239         case 'l':
240             do_lmtp = 1;
241             break;
242
243         case 's':
244             maxssf = atoi(optarg);
245             break;
246             
247         case 'u':
248             username = optarg;
249             break;
250
251         case 'a':
252             authname = optarg;
253             break;
254
255         case 'v':
256             verbose++;
257             break;
258             
259         case 'E':
260             emacs++;
261             break;
262
263         case 'd':
264             sprintf(buf, "%s-%d", optarg, getpid());
265             debug = sfopen(NULL, buf, "w");
266             sfsetbuf(debug, NULL, 0);
267             break;
268
269         case '?':
270         default:
271             usage(argv[0]);
272             break;
273         }
274     }
275
276     if (optind != argc - 1) {
277         usage(argv[0]);
278     }
279
280     host = argv[optind];
281     p = strchr(host, ':');
282     if (p) {
283         *p++ = '\0';
284     } else {
285         if(do_lmtp) {
286             p = "lmtp";
287         } else {
288             p = "smtp";
289         }
290     }
291     service = getservbyname(p, "tcp");
292     if (service) {
293         port = service->s_port;
294     } else {
295         port = atoi(p);
296         if (!port) usage(argv[0]);
297         port = htons(port);
298     }
299
300     if ((hp = gethostbyname(host)) == NULL) {
301         perror("gethostbyname");
302         exit(EX_NOHOST);
303     }
304
305     if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
306         perror("socket");
307         exit(EX_OSERR);
308     }
309
310     addr.sin_family = AF_INET;
311     memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
312     addr.sin_port = port;
313
314     if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
315         perror("connect");
316         exit(EX_NOHOST);
317     }
318
319     server_in = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_READ);
320     server_out = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_WRITE);
321
322     /* read greeting */
323     greeting[0] = '\0';
324     for (;;) {
325         sfsync(server_out);
326         if (fgets(buf, sizeof(buf)-1, server_in)) {
327             if (greeting[0] == '\0') {
328                 strncpy(greeting, buf, sizeof(greeting) - 1);
329             }
330
331             if (verbose) fprintf(debug, "%s", buf);
332             code = ask_code(buf);
333             if (ISCONT(buf) && ISGOOD(code)) continue;
334         } else {
335             code = 400;
336         }
337         break;
338     }
339
340     if (!ISGOOD(code)) goto done;
341
342     /* EHLO */
343     gethostname(buf, sizeof(buf)-1);
344     if(do_lmtp) {
345         if(verbose) fprintf(debug, "LHLO %s\r\n", buf);
346         fprintf(server_out, "LHLO %s\r\n", buf);
347     } else {
348         if (verbose) fprintf(debug, "EHLO %s\r\n", buf);
349         fprintf(server_out, "EHLO %s\r\n", buf);
350     }
351     
352     /* read responses */
353     for (;;) {
354         sfsync(server_out);
355         if (!fgets(buf, sizeof(buf)-1, server_in)) {
356             code = 400;
357             goto done;
358         }
359         if (verbose) fprintf(debug, "%s", buf);
360         code = ask_code(buf);
361         if (code == 250) {
362             /* we're only looking for AUTH */
363             if (!strncasecmp(buf + 4, "AUTH ", 5)) {
364                 chop(buf);
365                 if (!mechlist) mechlist = strdup(buf + 9);
366             }
367         }
368         if (ISCONT(buf) && ISGOOD(code)) {
369             continue;
370         } else {
371             break;
372         }
373     }
374     if (!ISGOOD(code)) goto done;
375
376     /* attempt authentication */
377     if (!mechlist) {
378         if (verbose > 2) fprintf(debug, "no authentication\n");
379         goto doneauth;
380     }
381
382     if (!r) r = sasl_client_init(callbacks);
383     if (!r) {
384         struct sockaddr_in saddr_r;
385         int addrsize = sizeof(struct sockaddr_in);
386
387         if (getpeername(sock, (struct sockaddr *) &saddr_r, &addrsize) < 0) {
388             perror("getpeername");
389             exit(EX_NOHOST);
390         }
391         r = iptostring((struct sockaddr *)&saddr_r,
392                        sizeof(struct sockaddr_in), remote_ip, 64);
393     }
394     if (!r) {
395         struct sockaddr_in saddr_l;
396         int addrsize = sizeof(struct sockaddr_in);
397
398         if (getsockname(sock, (struct sockaddr *) &saddr_l, &addrsize) < 0) {
399             perror("getsockname");
400             exit(EX_OSERR);
401         }
402         r = iptostring((struct sockaddr *)&saddr_l,
403                        sizeof(struct sockaddr_in), local_ip, 64);
404     }
405
406     if (!r) {
407         if(do_lmtp) {
408             r = sasl_client_new("lmtp", host, local_ip, remote_ip,
409                                 NULL, 0, &conn);
410         } else {
411             r = sasl_client_new("smtp", host, local_ip, remote_ip,
412                                 NULL, 0, &conn);
413         }
414     }
415     
416     if (!r) {
417         sasl_security_properties_t *secprops = make_secprops(minssf, maxssf);
418         r = sasl_setprop(conn, SASL_SEC_PROPS, secprops);
419         free(secprops);
420     }
421     
422     if (!r) {
423         do {
424             r = sasl_client_start(conn, mechlist,
425                                   &client_interact, &out, &outlen, &mechusing);
426             if (r == SASL_INTERACT) {
427                 fillin_interactions(client_interact);
428             }
429         } while (r == SASL_INTERACT);
430
431         if (r == SASL_OK || r == SASL_CONTINUE) {
432             if (outlen > 0) {
433                 r = sasl_encode64(out, outlen, out64, sizeof out64, NULL);
434                 if (!r) {
435                     if (verbose) 
436                         fprintf(debug, "AUTH %s %s\r\n", mechusing, out64);
437                     fprintf(server_out, "AUTH %s %s\r\n", mechusing, out64);
438                 }
439             } else {
440                 if (verbose) fprintf(debug, "AUTH %s\r\n", mechusing);
441                 fprintf(server_out, "AUTH %s\r\n", mechusing);
442             }
443         } else {
444             fprintf(debug, "\nclient start failed: %s\n", sasl_errdetail(conn));
445         }
446         
447     }
448
449     /* jump to doneauth if we succeed */
450     while (r == SASL_OK || r == SASL_CONTINUE) {
451         sfsync(server_out);
452         if (!fgets(buf, sizeof(buf)-1, server_in)) {
453             code = 400;
454             goto done;
455         }
456         if (verbose) fprintf(debug, "%s", buf);
457         code = ask_code(buf);
458         if (ISCONT(buf)) continue;
459         if (ISGOOD(code)) {
460             if (code != 235) {
461                 /* weird! */
462             }
463             /* yay, we won! */
464             sfdcsasl(server_in, conn);
465             sfdcsasl(server_out, conn);
466             goto doneauth;
467         } else if (code != 334) {
468             /* unexpected response */
469             break;
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 }