/* smtpauth.c -- authenticate to SMTP server, then give normal protocol * * uses sfio * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include "sfsasl.h" /* from OS: */ extern char *getpass(); extern struct hostent *gethostbyname(); static char *authname = NULL; static char *username = NULL; static char *realm = NULL; extern char *optarg; extern int optind; int verbose = 0; int emacs = 0; int iptostring(const struct sockaddr *addr, socklen_t addrlen, char *out, unsigned outlen) { char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; if(!addr || !out) return SASL_BADPARAM; getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV); if(outlen < strlen(hbuf) + strlen(pbuf) + 2) return SASL_BUFOVER; snprintf(out, outlen, "%s;%s", hbuf, pbuf); return SASL_OK; } void usage(char *p) { fprintf(stderr, "%s [-v] [-l] [-u username] [-a authname] [-s ssf] [-m mech] host[:port]\n", p); fprintf(stderr, " -v\tVerbose Output\n"); fprintf(stderr, " -l\tLMTP semantics\n"); exit(EX_USAGE); } #define ISGOOD(r) (((r) / 100) == 2) #define TEMPFAIL(r) (((r) / 100) == 4) #define PERMFAIL(r) (((r) / 100) == 5) #define ISCONT(s) (s && (s[3] == '-')) static int ask_code(const char *s) { int ret = 0; if (s==NULL) return -1; if (strlen(s) < 3) return -1; /* check to make sure 0-2 are digits */ if ((isdigit((int) s[0])==0) || (isdigit((int) s[1])==0) || (isdigit((int) s[2])==0)) { return -1; } ret = ((s[0]-'0')*100)+((s[1]-'0')*10)+(s[2]-'0'); return ret; } static void chop(char *s) { char *p; assert(s); p = s + strlen(s) - 1; if (p[0] == '\n') { *p-- = '\0'; } if (p >= s && p[0] == '\r') { *p-- = '\0'; } } void interaction (int id, const char *prompt, char **tresult, unsigned int *tlen) { char result[1024]; if (id==SASL_CB_PASS) { fprintf(stderr, "%s: ", prompt); *tresult = strdup(getpass("")); /* leaks! */ *tlen= strlen(*tresult); return; } else if (id==SASL_CB_USER) { if (username != NULL) { strcpy(result, username); } else { strcpy(result, getpwuid(getuid())->pw_name); } } else if (id==SASL_CB_AUTHNAME) { if (authname != NULL) { strcpy(result, authname); } else { strcpy(result, getpwuid(getuid())->pw_name); } } else if ((id==SASL_CB_GETREALM) && (realm != NULL)) { strcpy(result, realm); } else { int c; fprintf(stderr, "%s: ",prompt); fgets(result, sizeof(result) - 1, stdin); c = strlen(result); result[c - 1] = '\0'; } *tlen = strlen(result); *tresult = (char *) malloc(*tlen+1); /* leaks! */ memset(*tresult, 0, *tlen+1); memcpy((char *) *tresult, result, *tlen); } void fillin_interactions(sasl_interact_t *tlist) { while (tlist->id != SASL_CB_LIST_END) { interaction(tlist->id, tlist->prompt, (void *) &(tlist->result), &(tlist->len)); tlist++; } } static sasl_callback_t callbacks[] = { { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; static sasl_security_properties_t *make_secprops(int min,int max) { sasl_security_properties_t *ret=(sasl_security_properties_t *) malloc(sizeof(sasl_security_properties_t)); ret->maxbufsize = 8192; ret->min_ssf = min; ret->max_ssf = max; ret->security_flags = 0; ret->property_names = NULL; ret->property_values = NULL; return ret; } Sfio_t *debug; int main(int argc, char **argv) { char *mechlist = NULL; const char *mechusing = NULL; int minssf = 0, maxssf = 128; char *p; Sfio_t *server_in, *server_out; sasl_conn_t *conn = NULL; sasl_interact_t *client_interact = NULL; char in[4096]; const char *out; unsigned int inlen, outlen; char out64[4096]; int c; char *host; struct servent *service; int port; struct hostent *hp; struct sockaddr_in addr; char remote_ip[64], local_ip[64]; int sock; char buf[1024]; int sz; char greeting[1024]; int code; int do_lmtp=0; int r = 0; debug = stderr; while ((c = getopt(argc, argv, "vElm:s:u:a:d:")) != EOF) { switch (c) { case 'm': mechlist = optarg; break; case 'l': do_lmtp = 1; break; case 's': maxssf = atoi(optarg); break; case 'u': username = optarg; break; case 'a': authname = optarg; break; case 'v': verbose++; break; case 'E': emacs++; break; case 'd': sprintf(buf, "%s-%d", optarg, getpid()); debug = sfopen(NULL, buf, "w"); sfsetbuf(debug, NULL, 0); break; case '?': default: usage(argv[0]); break; } } if (optind != argc - 1) { usage(argv[0]); } host = argv[optind]; p = strchr(host, ':'); if (p) { *p++ = '\0'; } else { if(do_lmtp) { p = "lmtp"; } else { p = "smtp"; } } service = getservbyname(p, "tcp"); if (service) { port = service->s_port; } else { port = atoi(p); if (!port) usage(argv[0]); port = htons(port); } if ((hp = gethostbyname(host)) == NULL) { perror("gethostbyname"); exit(EX_NOHOST); } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(EX_OSERR); } addr.sin_family = AF_INET; memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); addr.sin_port = port; if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) { perror("connect"); exit(EX_NOHOST); } server_in = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_READ); server_out = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_WRITE); /* read greeting */ greeting[0] = '\0'; for (;;) { sfsync(server_out); if (fgets(buf, sizeof(buf)-1, server_in)) { if (greeting[0] == '\0') { strncpy(greeting, buf, sizeof(greeting) - 1); } if (verbose) fprintf(debug, "%s", buf); code = ask_code(buf); if (ISCONT(buf) && ISGOOD(code)) continue; } else { code = 400; } break; } if (!ISGOOD(code)) goto done; /* EHLO */ gethostname(buf, sizeof(buf)-1); if(do_lmtp) { if(verbose) fprintf(debug, "LHLO %s\r\n", buf); fprintf(server_out, "LHLO %s\r\n", buf); } else { if (verbose) fprintf(debug, "EHLO %s\r\n", buf); fprintf(server_out, "EHLO %s\r\n", buf); } /* read responses */ for (;;) { sfsync(server_out); if (!fgets(buf, sizeof(buf)-1, server_in)) { code = 400; goto done; } if (verbose) fprintf(debug, "%s", buf); code = ask_code(buf); if (code == 250) { /* we're only looking for AUTH */ if (!strncasecmp(buf + 4, "AUTH ", 5)) { chop(buf); if (!mechlist) mechlist = strdup(buf + 9); } } if (ISCONT(buf) && ISGOOD(code)) { continue; } else { break; } } if (!ISGOOD(code)) goto done; /* attempt authentication */ if (!mechlist) { if (verbose > 2) fprintf(debug, "no authentication\n"); goto doneauth; } if (!r) r = sasl_client_init(callbacks); if (!r) { struct sockaddr_in saddr_r; int addrsize = sizeof(struct sockaddr_in); if (getpeername(sock, (struct sockaddr *) &saddr_r, &addrsize) < 0) { perror("getpeername"); exit(EX_NOHOST); } r = iptostring((struct sockaddr *)&saddr_r, sizeof(struct sockaddr_in), remote_ip, 64); } if (!r) { struct sockaddr_in saddr_l; int addrsize = sizeof(struct sockaddr_in); if (getsockname(sock, (struct sockaddr *) &saddr_l, &addrsize) < 0) { perror("getsockname"); exit(EX_OSERR); } r = iptostring((struct sockaddr *)&saddr_l, sizeof(struct sockaddr_in), local_ip, 64); } if (!r) { if(do_lmtp) { r = sasl_client_new("lmtp", host, local_ip, remote_ip, NULL, 0, &conn); } else { r = sasl_client_new("smtp", host, local_ip, remote_ip, NULL, 0, &conn); } } if (!r) { sasl_security_properties_t *secprops = make_secprops(minssf, maxssf); r = sasl_setprop(conn, SASL_SEC_PROPS, secprops); free(secprops); } if (!r) { do { r = sasl_client_start(conn, mechlist, &client_interact, &out, &outlen, &mechusing); if (r == SASL_INTERACT) { fillin_interactions(client_interact); } } while (r == SASL_INTERACT); if (r == SASL_OK || r == SASL_CONTINUE) { if (outlen > 0) { r = sasl_encode64(out, outlen, out64, sizeof out64, NULL); if (!r) { if (verbose) fprintf(debug, "AUTH %s %s\r\n", mechusing, out64); fprintf(server_out, "AUTH %s %s\r\n", mechusing, out64); } } else { if (verbose) fprintf(debug, "AUTH %s\r\n", mechusing); fprintf(server_out, "AUTH %s\r\n", mechusing); } } else { fprintf(debug, "\nclient start failed: %s\n", sasl_errdetail(conn)); } } /* jump to doneauth if we succeed */ while (r == SASL_OK || r == SASL_CONTINUE) { sfsync(server_out); if (!fgets(buf, sizeof(buf)-1, server_in)) { code = 400; goto done; } if (verbose) fprintf(debug, "%s", buf); code = ask_code(buf); if (ISCONT(buf)) continue; if (ISGOOD(code)) { if (code != 235) { /* weird! */ } /* yay, we won! */ sfdcsasl(server_in, conn); sfdcsasl(server_out, conn); goto doneauth; } else if (code != 334) { /* unexpected response */ break; } r = sasl_decode64(buf + 4, strlen(buf) - 6, in, 4096, &inlen); if (r != SASL_OK) break; do { r = sasl_client_step(conn, in, inlen, &client_interact, &out, &outlen); if (r == SASL_INTERACT) { fillin_interactions(client_interact); } } while (r == SASL_INTERACT); if (r == SASL_OK || r == SASL_CONTINUE) { r = sasl_encode64(out, outlen, out64, sizeof out64, NULL); } if (r == SASL_OK) { if (verbose) fprintf(debug, "%s\r\n", out64); fprintf(server_out, "%s\r\n", out64); } } /* auth failed! */ if (!r) { fprintf(debug, "%d authentication failed\n", code); } else { fprintf(debug, "400 authentication failed: %s\n", sasl_errstring(r, NULL, NULL)); } exit(EX_SOFTWARE); doneauth: /* ready for application */ greeting[3] = '-'; printf("%s", greeting); printf("220 %s %s\r\n", host, conn ? "authenticated" : "no auth"); fcntl(0, F_SETFL, O_NONBLOCK); fcntl(sock, F_SETFL, O_NONBLOCK); sfset(stdin, SF_SHARE, 0); /* feed data back 'n forth */ for (;;) { Sfio_t *flist[3]; top: flist[0] = stdin; flist[1] = server_in; /* sfpoll */ if (verbose > 5) fprintf(debug, "poll\n"); r = sfpoll(flist, 2, -1); if (verbose > 5) fprintf(debug, "poll 2\n"); while (r--) { if (flist[r] == server_in) { do { if (verbose > 5) fprintf(debug, "server!\n"); errno = 0; sz = sfread(server_in, buf, sizeof(buf)-1); if (sz == 0 && (errno == EAGAIN)) goto top; if (sz <= 0) goto out; buf[sz] = '\0'; if (verbose > 5) fprintf(debug, "server 2 '%s'!\n", buf); sfwrite(stdout, buf, sz); } while (sfpoll(&server_in, 1, 0)); sfsync(stdout); } else if (flist[r] == stdin) { Sfio_t *p[1]; p[0] = stdin; do { if (verbose > 5) fprintf(debug, "stdin!\n"); errno = 0; sz = sfread(stdin, buf, sizeof(buf)-1); if (sz == 0 && (errno == EAGAIN)) goto top; if (sz <= 0) goto out; buf[sz] = '\0'; if (verbose > 5) fprintf(debug, "stdin 2 '%s'!\n", buf); if (emacs) { int i; /* fix emacs stupidness */ for (i = 0; i < sz - 1; i++) { if (buf[i] == '\n' && buf[i+1] == '\n') buf[i++] = '\r'; } if (buf[sz-2] != '\r' && buf[sz-1] == '\n') { sfungetc(stdin, buf[sz--]); buf[sz] = '\0'; } if (verbose > 5) fprintf(debug, "emacs '%s'!\n", buf); } sfwrite(server_out, buf, sz); if (verbose > 7) fprintf(debug, "stdin 3!\n"); } while (sfpoll(p, 1, 0)); sfsync(server_out); } else { abort(); } } } out: if (verbose > 3) fprintf(debug, "exiting! %d %s\n", sz, strerror(errno)); exit(EX_OK); done: if (ISGOOD(code)) { if (verbose > 1) fprintf(debug, "ok\n"); exit(EX_OK); } if (TEMPFAIL(code)) { if (verbose > 1) fprintf(debug, "tempfail\n"); exit(EX_TEMPFAIL); } if (PERMFAIL(code)) { if (verbose > 1) fprintf(debug, "permfail\n"); exit(EX_UNAVAILABLE); } if (verbose > 1) fprintf(debug, "unknown failure\n"); exit(EX_TEMPFAIL); }