1 /* smtpauth.c -- authenticate to SMTP server, then give normal protocol
10 #include <sfio/stdio.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
16 #include <netinet/in.h>
25 #include <sys/socket.h>
27 #include <netinet/in.h>
30 #ifdef HAVE_SYS_SELECT_H
31 #include <sys/select.h>
40 extern char *getpass();
41 extern struct hostent *gethostbyname();
43 static char *authname = NULL;
44 static char *username = NULL;
45 static char *realm = NULL;
53 int iptostring(const struct sockaddr *addr, socklen_t addrlen,
54 char *out, unsigned outlen) {
55 char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
58 if(!addr || !out) return SASL_BADPARAM;
60 niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
62 if (addr->sa_family == AF_INET6)
63 niflags |= NI_WITHSCOPEID;
65 if (getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
69 if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
72 snprintf(out, outlen, "%s;%s", hbuf, pbuf);
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");
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] == '-'))
90 static int ask_code(const char *s)
94 if (s==NULL) return -1;
96 if (strlen(s) < 3) return -1;
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))
106 ret = ((s[0]-'0')*100)+((s[1]-'0')*10)+(s[2]-'0');
111 static void chop(char *s)
116 p = s + strlen(s) - 1;
120 if (p >= s && p[0] == '\r') {
125 void interaction (int id, const char *prompt,
126 char **tresult, unsigned int *tlen)
130 if (id==SASL_CB_PASS) {
131 fprintf(stderr, "%s: ", prompt);
132 *tresult = strdup(getpass("")); /* leaks! */
133 *tlen= strlen(*tresult);
135 } else if (id==SASL_CB_USER) {
136 if (username != NULL) {
137 strcpy(result, username);
139 strcpy(result, getpwuid(getuid())->pw_name);
141 } else if (id==SASL_CB_AUTHNAME) {
142 if (authname != NULL) {
143 strcpy(result, authname);
145 strcpy(result, getpwuid(getuid())->pw_name);
147 } else if ((id==SASL_CB_GETREALM) && (realm != NULL)) {
148 strcpy(result, realm);
152 fprintf(stderr, "%s: ",prompt);
153 fgets(result, sizeof(result) - 1, stdin);
155 result[c - 1] = '\0';
158 *tlen = strlen(result);
159 *tresult = (char *) malloc(*tlen+1); /* leaks! */
160 memset(*tresult, 0, *tlen+1);
161 memcpy((char *) *tresult, result, *tlen);
164 void fillin_interactions(sasl_interact_t *tlist)
166 while (tlist->id != SASL_CB_LIST_END)
168 interaction(tlist->id, tlist->prompt,
169 (void *) &(tlist->result),
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 }
183 static sasl_security_properties_t *make_secprops(int min,int max)
185 sasl_security_properties_t *ret=(sasl_security_properties_t *)
186 malloc(sizeof(sasl_security_properties_t));
188 ret->maxbufsize = 8192;
192 ret->security_flags = 0;
193 ret->property_names = NULL;
194 ret->property_values = NULL;
201 int main(int argc, char **argv)
203 char *mechlist = NULL;
204 const char *mechusing = NULL;
205 int minssf = 0, maxssf = 128;
207 Sfio_t *server_in, *server_out;
208 sasl_conn_t *conn = NULL;
209 sasl_interact_t *client_interact = NULL;
212 unsigned int inlen, outlen;
217 struct servent *service;
220 struct sockaddr_in addr;
221 char remote_ip[64], local_ip[64];
233 while ((c = getopt(argc, argv, "vElm:s:u:a:d:")) != EOF) {
244 maxssf = atoi(optarg);
264 sprintf(buf, "%s-%d", optarg, getpid());
265 debug = sfopen(NULL, buf, "w");
266 sfsetbuf(debug, NULL, 0);
276 if (optind != argc - 1) {
281 p = strchr(host, ':');
291 service = getservbyname(p, "tcp");
293 port = service->s_port;
296 if (!port) usage(argv[0]);
300 if ((hp = gethostbyname(host)) == NULL) {
301 perror("gethostbyname");
305 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
310 addr.sin_family = AF_INET;
311 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
312 addr.sin_port = port;
314 if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
319 server_in = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_READ);
320 server_out = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_WRITE);
326 if (fgets(buf, sizeof(buf)-1, server_in)) {
327 if (greeting[0] == '\0') {
328 strncpy(greeting, buf, sizeof(greeting) - 1);
331 if (verbose) fprintf(debug, "%s", buf);
332 code = ask_code(buf);
333 if (ISCONT(buf) && ISGOOD(code)) continue;
340 if (!ISGOOD(code)) goto done;
343 gethostname(buf, sizeof(buf)-1);
345 if(verbose) fprintf(debug, "LHLO %s\r\n", buf);
346 fprintf(server_out, "LHLO %s\r\n", buf);
348 if (verbose) fprintf(debug, "EHLO %s\r\n", buf);
349 fprintf(server_out, "EHLO %s\r\n", buf);
355 if (!fgets(buf, sizeof(buf)-1, server_in)) {
359 if (verbose) fprintf(debug, "%s", buf);
360 code = ask_code(buf);
362 /* we're only looking for AUTH */
363 if (!strncasecmp(buf + 4, "AUTH ", 5)) {
365 if (!mechlist) mechlist = strdup(buf + 9);
368 if (ISCONT(buf) && ISGOOD(code)) {
374 if (!ISGOOD(code)) goto done;
376 /* attempt authentication */
378 if (verbose > 2) fprintf(debug, "no authentication\n");
382 if (!r) r = sasl_client_init(callbacks);
384 struct sockaddr_in saddr_r;
385 int addrsize = sizeof(struct sockaddr_in);
387 if (getpeername(sock, (struct sockaddr *) &saddr_r, &addrsize) < 0) {
388 perror("getpeername");
391 r = iptostring((struct sockaddr *)&saddr_r,
392 sizeof(struct sockaddr_in), remote_ip, 64);
395 struct sockaddr_in saddr_l;
396 int addrsize = sizeof(struct sockaddr_in);
398 if (getsockname(sock, (struct sockaddr *) &saddr_l, &addrsize) < 0) {
399 perror("getsockname");
402 r = iptostring((struct sockaddr *)&saddr_l,
403 sizeof(struct sockaddr_in), local_ip, 64);
408 r = sasl_client_new("lmtp", host, local_ip, remote_ip,
411 r = sasl_client_new("smtp", host, local_ip, remote_ip,
417 sasl_security_properties_t *secprops = make_secprops(minssf, maxssf);
418 r = sasl_setprop(conn, SASL_SEC_PROPS, secprops);
424 r = sasl_client_start(conn, mechlist,
425 &client_interact, &out, &outlen, &mechusing);
426 if (r == SASL_INTERACT) {
427 fillin_interactions(client_interact);
429 } while (r == SASL_INTERACT);
431 if (r == SASL_OK || r == SASL_CONTINUE) {
433 r = sasl_encode64(out, outlen, out64, sizeof out64, NULL);
436 fprintf(debug, "AUTH %s %s\r\n", mechusing, out64);
437 fprintf(server_out, "AUTH %s %s\r\n", mechusing, out64);
440 if (verbose) fprintf(debug, "AUTH %s\r\n", mechusing);
441 fprintf(server_out, "AUTH %s\r\n", mechusing);
444 fprintf(debug, "\nclient start failed: %s\n", sasl_errdetail(conn));
449 /* jump to doneauth if we succeed */
450 while (r == SASL_OK || r == SASL_CONTINUE) {
452 if (!fgets(buf, sizeof(buf)-1, server_in)) {
456 if (verbose) fprintf(debug, "%s", buf);
457 code = ask_code(buf);
458 if (ISCONT(buf)) continue;
464 sfdcsasl(server_in, conn);
465 sfdcsasl(server_out, conn);
467 } else if (code != 334) {
468 /* unexpected response */
471 r = sasl_decode64(buf + 4, strlen(buf) - 6, in, 4096, &inlen);
472 if (r != SASL_OK) break;
475 r = sasl_client_step(conn, in, inlen, &client_interact,
477 if (r == SASL_INTERACT) {
478 fillin_interactions(client_interact);
480 } while (r == SASL_INTERACT);
482 if (r == SASL_OK || r == SASL_CONTINUE) {
483 r = sasl_encode64(out, outlen, out64, sizeof out64, NULL);
486 if (verbose) fprintf(debug, "%s\r\n", out64);
487 fprintf(server_out, "%s\r\n", out64);
493 fprintf(debug, "%d authentication failed\n", code);
495 fprintf(debug, "400 authentication failed: %s\n",
496 sasl_errstring(r, NULL, NULL));
501 /* ready for application */
503 printf("%s", greeting);
504 printf("220 %s %s\r\n", host, conn ? "authenticated" : "no auth");
506 fcntl(0, F_SETFL, O_NONBLOCK);
507 fcntl(sock, F_SETFL, O_NONBLOCK);
508 sfset(stdin, SF_SHARE, 0);
510 /* feed data back 'n forth */
516 flist[1] = server_in;
519 if (verbose > 5) fprintf(debug, "poll\n");
520 r = sfpoll(flist, 2, -1);
521 if (verbose > 5) fprintf(debug, "poll 2\n");
524 if (flist[r] == server_in) {
526 if (verbose > 5) fprintf(debug, "server!\n");
528 sz = sfread(server_in, buf, sizeof(buf)-1);
529 if (sz == 0 && (errno == EAGAIN)) goto top;
530 if (sz <= 0) goto out;
532 if (verbose > 5) fprintf(debug, "server 2 '%s'!\n", buf);
533 sfwrite(stdout, buf, sz);
534 } while (sfpoll(&server_in, 1, 0));
536 } else if (flist[r] == stdin) {
541 if (verbose > 5) fprintf(debug, "stdin!\n");
543 sz = sfread(stdin, buf, sizeof(buf)-1);
544 if (sz == 0 && (errno == EAGAIN)) goto top;
545 if (sz <= 0) goto out;
547 if (verbose > 5) fprintf(debug, "stdin 2 '%s'!\n", buf);
551 /* fix emacs stupidness */
552 for (i = 0; i < sz - 1; i++) {
553 if (buf[i] == '\n' && buf[i+1] == '\n')
556 if (buf[sz-2] != '\r' && buf[sz-1] == '\n') {
557 sfungetc(stdin, buf[sz--]);
561 if (verbose > 5) fprintf(debug, "emacs '%s'!\n", buf);
563 sfwrite(server_out, buf, sz);
564 if (verbose > 7) fprintf(debug, "stdin 3!\n");
565 } while (sfpoll(p, 1, 0));
573 if (verbose > 3) fprintf(debug, "exiting! %d %s\n", sz, strerror(errno));
578 if (verbose > 1) fprintf(debug, "ok\n");
581 if (TEMPFAIL(code)) {
582 if (verbose > 1) fprintf(debug, "tempfail\n");
585 if (PERMFAIL(code)) {
586 if (verbose > 1) fprintf(debug, "permfail\n");
587 exit(EX_UNAVAILABLE);
590 if (verbose > 1) fprintf(debug, "unknown failure\n");