#include "sig.h" #include "readwrite.h" #include "stralloc.h" #include "substdio.h" #include "alloc.h" #include "auto_qmail.h" #include "control.h" #include "received.h" #include "constmap.h" #include "error.h" #include "ipme.h" #include "ip.h" #include "qmail.h" #include "str.h" #include "fmt.h" #include "scan.h" #include "byte.h" #include "case.h" #include "env.h" #include "now.h" #include "exit.h" #include "rcpthosts.h" #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" #include "wait.h" #include "fd.h" #ifdef NASTY_DEBUG # include #endif /* NASTY_DEBUG */ void dummy() {} /* needed to make waitpid() work */ // AUTHCRAM is not #defined for now, because: // 1) It requires cmd5checkpw (not just checkpassword), and // a) cmd5checkpw requires separate administration of the poppasswed file. // b) cmd5checkpw requires leaving plaintext passwords on disk, yecchhhh. // 2) We don't need the added security if we only offer auth // over channels that are already protected by ssl. #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; const char *protocol = "SMTP"; /* There is an inconsistency in the design that we have to work around. When using stunnel to implement SMTPS, the password checker is runtime configurable, since stunnel passes it to us as a cmdline argument. However, STARTTLS has no idea how stunnel is configured (if it exists at all), so we have to use a compiled-in pwchecker. */ char *pwchecker[] = {"/var/qmail/rbin/checkpassword", "-", 0}; #ifdef TLS #include #include "tls.h" #include "ssl_timeoutio.h" void tls_init(); int tls_verify(); void tls_nogateway(); int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ #endif int safewrite(fd,buf,len) int fd; char *buf; int len; { int r; #ifdef TLS if (ssl && fd == ssl_wfd) r = ssl_timeoutwrite(timeout, ssl_rfd, ssl_wfd, ssl, buf, len); else #endif r = timeoutwrite(timeout,fd,buf,len); if (r <= 0) _exit(1); return r; } char ssoutbuf[512]; substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf); void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } void die_read() { _exit(1); } void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); } void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); } void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); } void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); } void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); } void err_badbounce() { out("550 sorry, bounce messages should have a single envelope recipient (#5.7.1)\r\n"); } void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } #ifndef TLS void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } #else void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts"); tls_nogateway(); out(" (#5.7.1)\r\n"); } #endif void err_unimpl(arg) char *arg; { out("502 unimplemented (#5.5.1)\r\n"); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } void err_relay() { out("553 we don't relay (#5.7.1)\r\n"); } void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); } void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); } void err_noop(arg) char *arg; { out("250 ok\r\n"); } void err_vrfy(arg) char *arg; { out("252 send some mail, i'll try my best\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; } int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; } int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; } int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; } void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); } void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); } int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; } int err_authabrt() { out("501 auth exchange cancelled (#5.0.0)\r\n"); return -1; } int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; } stralloc greeting = {0}; void smtp_greet(code) char *code; { substdio_puts(&ssout,code); substdio_put(&ssout,greeting.s,greeting.len); } void smtp_help(arg) char *arg; { out("214 netqmail home page: http://qmail.org/netqmail\r\n"); } void smtp_quit(arg) char *arg; { smtp_greet("221 "); out("\r\n"); flush(); _exit(0); } char *remoteip; char *remotehost; char *remoteinfo; char *local; char *relayclient; stralloc helohost = {0}; char *fakehelo; /* pointer into helohost, or 0 */ void dohelo(arg) char *arg; { if (!stralloc_copys(&helohost,arg)) die_nomem(); if (!stralloc_0(&helohost)) die_nomem(); fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; } int liphostok = 0; stralloc liphost = {0}; int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; void setup() { char *x; unsigned long u; if (control_init() == -1) die_control(); if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) die_control(); liphostok = control_rldef(&liphost,"control/localiphost",1,(char *) 0); if (liphostok == -1) die_control(); if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); if (timeout <= 0) timeout = 1; if (rcpthosts_init() == -1) die_control(); bmfok = control_readfile(&bmf,"control/badmailfrom",0); if (bmfok == -1) die_control(); if (bmfok) if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } if (!(databytes + 1)) --databytes; remoteip = env_get("TCPREMOTEIP"); if (!remoteip) remoteip = "unknown"; local = env_get("TCPLOCALHOST"); if (!local) local = env_get("TCPLOCALIP"); if (!local) local = "unknown"; remotehost = env_get("TCPREMOTEHOST"); if (!remotehost) remotehost = "unknown"; remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); #ifdef TLS if (env_get("SMTPS")) { smtps = 1; tls_init(); } else #endif dohelo(remotehost); } stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ int addrparse(arg) char *arg; { int i; char ch; char terminator; struct ip_address ip; int flagesc; int flagquoted; terminator = '>'; i = str_chr(arg,'<'); if (arg[i]) arg += i + 1; else { /* partner should go read rfc 821 */ terminator = ' '; /* The following fails insidiously if there's no ':' */ arg += str_chr(arg,':'); if (*arg == ':') ++arg; while (*arg == ' ') ++arg; } /* strip source route */ if (*arg == '@') while (*arg) if (*arg++ == ':') break; if (!stralloc_copys(&addr,"")) die_nomem(); flagesc = 0; flagquoted = 0; for (i = 0;ch = arg[i];++i) { /* copy arg to addr, stripping quotes */ if (flagesc) { if (!stralloc_append(&addr,&ch)) die_nomem(); flagesc = 0; } else { if (!flagquoted && (ch == terminator)) break; switch(ch) { case '\\': flagesc = 1; break; case '"': flagquoted = !flagquoted; break; default: if (!stralloc_append(&addr,&ch)) die_nomem(); } } } /* could check for termination failure here, but why bother? */ if (!stralloc_append(&addr,"")) die_nomem(); if (liphostok) { i = byte_rchr(addr.s,addr.len,'@'); if (i < addr.len) /* if not, partner should go read rfc 821 */ if (addr.s[i + 1] == '[') if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) if (ipme_is(&ip)) { addr.len = i + 1; if (!stralloc_cat(&addr,&liphost)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } } /* returning 0 will provoke a "555 syntax error" */ if (addr.len > 900) return 0; if (addr.len == 0 || addr.s[0] == 0) return 0; return 1; } int bmfcheck() { int j; if (!bmfok) return 0; if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; j = byte_rchr(addr.s,addr.len,'@'); if (j < addr.len) if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; return 0; } int addrallowed() { int r; r = rcpthosts(addr.s,str_len(addr.s)); if (r == -1) die_control(); #ifdef TLS if (r == 0) if (tls_verify()) r = -2; #endif return r; } int addrrelay() { int j; j = addr.len; while(--j >= 0) if (addr.s[j] == '@') break; if (j < 0) j = addr.len; while(--j >= 0) { if (addr.s[j] == '@') return 1; if (addr.s[j] == '%') return 1; if (addr.s[j] == '!') return 1; } return 0; } int seenmail = 0; int flagbarf; /* defined if seenmail */ stralloc mailfrom = {0}; stralloc rcptto = {0}; int recipcount; char *hostname; char **childargs; void smtp_helo(arg) char *arg; { smtp_greet("250 "); out("\r\n"); seenmail = 0; dohelo(arg); } /* ESMTP extensions are published here */ void smtp_ehlo(arg) char *arg; { #ifdef TLS struct stat st; #endif smtp_greet("250-"); out("\r\n"); #ifdef TLS if (!ssl && (stat("control/servercert.pem", &st) == 0)) out("250-STARTTLS\r\n"); #endif if (hostname && childargs) { #ifdef AUTHCRAM out("250-AUTH LOGIN CRAM-MD5 PLAIN\r\n"); out("250-AUTH=LOGIN CRAM-MD5 PLAIN\r\n"); #else out("250-AUTH LOGIN PLAIN\r\n"); out("250-AUTH=LOGIN PLAIN\r\n"); #endif } /* else auth not available; channel lacks privacy */ out("250-PIPELINING\r\n"); out("250 8BITMIME\r\n"); /* last one has a space, not a dash */ seenmail = 0; dohelo(arg); } void smtp_rset(arg) char *arg; { seenmail = 0; out("250 flushed\r\n"); } void smtp_mail(arg) char *arg; { if (!addrparse(arg)) { err_syntax(); return; } flagbarf = bmfcheck(); seenmail = 1; if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); out("250 ok\r\n"); } void smtp_rcpt(arg) char *arg; { if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } if (addrrelay()) { err_relay(); return; } if (flagbarf) { err_bmf(); return; } if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } else if (!addrallowed()) { err_nogateway(); return; } if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); recipcount++; out("250 ok\r\n"); } int saferead(fd,buf,len) int fd; char *buf; int len; { int r; flush(); #ifdef TLS if (ssl && fd == ssl_rfd) r = ssl_timeoutread(timeout, ssl_rfd, ssl_wfd, ssl, buf, len); else #endif r = timeoutread(timeout,fd,buf,len); if (r == -1) if (errno == error_timeout) die_alarm(); if (r <= 0) die_read(); return r; } char ssinbuf[1024]; substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf); #ifdef TLS void flush_io() { ssin.p = 0; flush(); } #endif struct qmail qqt; unsigned int bytestooverflow = 0; void put(ch) char *ch; { if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qqt); qmail_put(&qqt,ch,1); } void blast(hops) int *hops; { char ch; int state; int flaginheader; int pos; /* number of bytes since most recent \n, if fih */ int flagmaybex; /* 1 if this line might match RECEIVED, if fih */ int flagmaybey; /* 1 if this line might match \r\n, if fih */ int flagmaybez; /* 1 if this line might match DELIVERED, if fih */ state = 1; *hops = 0; flaginheader = 1; pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; for (;;) { substdio_get(&ssin,&ch,1); if (flaginheader) { if (pos < 9) { if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0; if (flagmaybez) if (pos == 8) ++*hops; if (pos < 8) if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0; if (flagmaybex) if (pos == 7) ++*hops; if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0; if (flagmaybey) if (pos == 1) flaginheader = 0; ++pos; } if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; } } switch(state) { case 0: if (ch == '\n') straynewline(); if (ch == '\r') { state = 4; continue; } break; case 1: /* \r\n */ if (ch == '\n') straynewline(); if (ch == '.') { state = 2; continue; } if (ch == '\r') { state = 4; continue; } state = 0; break; case 2: /* \r\n + . */ if (ch == '\n') straynewline(); if (ch == '\r') { state = 3; continue; } state = 0; break; case 3: /* \r\n + .\r */ if (ch == '\n') return; put("."); put("\r"); if (ch == '\r') { state = 4; continue; } state = 0; break; case 4: /* + \r */ if (ch == '\n') { state = 1; break; } if (ch != '\r') { put("\r"); state = 0; } } put(&ch); } } char accept_buf[FMT_ULONG]; void acceptmessage(qp) unsigned long qp; { datetime_sec when; when = now(); out("250 ok "); accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0; out(accept_buf); out(" qp "); accept_buf[fmt_ulong(accept_buf,qp)] = 0; out(accept_buf); out("\r\n"); } void smtp_data(arg) char *arg; { int hops; unsigned long qp; char *qqx; if (!seenmail) { err_wantmail(); return; } if (!rcptto.len) { err_wantrcpt(); return; } if (mailfrom.len == 1 && recipcount > 1) { err_badbounce(); return; } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qqt) == -1) { err_qqt(); return; } qp = qmail_qp(&qqt); out("354 go ahead\r\n"); received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo); blast(&hops); hops = (hops >= MAXHOPS); if (hops) qmail_fail(&qqt); qmail_from(&qqt,mailfrom.s); qmail_put(&qqt,rcptto.s,rcptto.len); qqx = qmail_close(&qqt); if (!*qqx) { acceptmessage(qp); return; } if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n"); } char unique[FMT_ULONG + FMT_ULONG + 3]; static stralloc authin = {0}; static stralloc user = {0}; static stralloc pass = {0}; static stralloc resp = {0}; static stralloc slop = {0}; substdio ssup; char upbuf[128]; int authd = 0; int authgetl(void) { int i; if (!stralloc_copys(&authin, "")) die_nomem(); for (;;) { if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */ i = substdio_get(&ssin,authin.s + authin.len,1); if (i != 1) die_read(); if (authin.s[authin.len] == '\n') break; ++authin.len; } if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len; authin.s[authin.len] = 0; if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); } if (authin.len == 0) { return err_input(); } return authin.len; } int authenticate(void) { int child; int wstat; int pi[2]; if (!stralloc_0(&user)) die_nomem(); if (!stralloc_0(&pass)) die_nomem(); if (!stralloc_0(&resp)) die_nomem(); if (pipe(pi) == -1) return err_pipe(); switch(child = fork()) { case -1: return err_fork(); case 0: close(pi[1]); //xx Not sure why this fd_copy is helpful; //xx why not let checkpasswd talk to the logfile via stderr? //xx if (fd_copy(2,1) == -1) _exit(1); if (0 > fd_copy(3,pi[0])) _exit(1); sig_pipedefault(); execvp(*childargs, childargs); _exit(1); } close(pi[0]); substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof upbuf); if (substdio_put(&ssup,user.s,user.len) == -1) return err_write(); if (substdio_put(&ssup,pass.s,pass.len) == -1) return err_write(); if (substdio_put(&ssup,resp.s,resp.len) == -1) return err_write(); if (substdio_flush(&ssup) == -1) return err_write(); close(pi[1]); byte_zero(pass.s,pass.len); byte_zero(upbuf,sizeof upbuf); if (wait_pid(&wstat,child) == -1) { #ifdef NASTY_DEBUG fprintf(stderr, "kidpid: %d errno: %d : ", child, errno); perror(0); #endif /* NASTY_DEBUG */ return err_child(); } if (wait_crashed(wstat)) return err_child(); if (wait_exitcode(wstat)) { sleep(5); return 1; } /* no */ return 0; /* yes */ } int auth_login(arg) char *arg; { int r; if (*arg) { if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input(); } else { out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */ if (authgetl() < 0) return -1; if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input(); } if (r == -1) die_nomem(); out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */ if (authgetl() < 0) return -1; if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input(); if (r == -1) die_nomem(); if (!user.len || !pass.len) return err_input(); return authenticate(); } int auth_plain(arg) char *arg; { int r, id = 0; if (*arg) { if (r = b64decode(arg,str_len(arg),&slop) == 1) return err_input(); } else { out("334 \r\n"); flush(); if (authgetl() < 0) return -1; if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input(); } if (r == -1 || !stralloc_0(&slop)) die_nomem(); while (slop.s[id]) id++; /* ignore authorize-id */ if (slop.len > id + 1) if (!stralloc_copys(&user,slop.s + id + 1)) die_nomem(); if (slop.len > id + user.len + 2) if (!stralloc_copys(&pass,slop.s + id + user.len + 2)) die_nomem(); if (!user.len || !pass.len) return err_input(); return authenticate(); } #ifdef AUTHCRAM int auth_cram() { int i, r; char *s; s = unique; s += fmt_uint(s,getpid()); *s++ = '.'; s += fmt_ulong(s,(unsigned long) now()); *s++ = '@'; *s++ = 0; if (!stralloc_copys(&pass,"<")) die_nomem(); if (!stralloc_cats(&pass,unique)) die_nomem(); if (!stralloc_cats(&pass,hostname)) die_nomem(); if (!stralloc_cats(&pass,">")) die_nomem(); if (b64encode(&pass,&slop) < 0) die_nomem(); if (!stralloc_0(&slop)) die_nomem(); out("334 "); out(slop.s); out("\r\n"); flush(); if (authgetl() < 0) return -1; if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input(); if (r == -1 || !stralloc_0(&slop)) die_nomem(); i = str_chr(slop.s,' '); s = slop.s + i; while (*s == ' ') ++s; slop.s[i] = 0; if (!stralloc_copys(&user,slop.s)) die_nomem(); if (!stralloc_copys(&resp,s)) die_nomem(); if (!user.len || !resp.len) return err_input(); return authenticate(); } #endif struct authcmd { char *text; int (*fun)(); } authcmds[] = { { "login", auth_login } , { "plain", auth_plain } #ifdef AUTHCRAM , { "cram-md5", auth_cram } #endif , { 0, err_noauth } }; void smtp_auth(arg) char *arg; { int i; char *cmd = arg; if (!hostname || !childargs) { out("503 auth not available; channel lacks privacy (#5.3.3)\r\n"); return; } if (authd) { err_authd(); return; } if (seenmail) { err_authmail(); return; } if (!stralloc_copys(&user,"")) die_nomem(); if (!stralloc_copys(&pass,"")) die_nomem(); if (!stralloc_copys(&resp,"")) die_nomem(); i = str_chr(cmd,' '); arg = cmd + i; while (*arg == ' ') ++arg; cmd[i] = 0; for (i = 0;authcmds[i].text;++i) if (case_equals(authcmds[i].text,cmd)) break; switch (authcmds[i].fun(arg)) { case 0: authd = 1; // There is a crucial difference between relayclient==0 // and relayclient=="". // Allow relaying for authorized users: relayclient = ""; // The following may be used to exempt authorized users // from some spam-filtering: if (!env_put("QMAIL_AUTHORIZED=yes")) die_nomem(); remoteinfo = user.s; if (!env_unset("TCPREMOTEINFO")) die_read(); if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem(); out("235 ok, go ahead (#2.0.0)\r\n"); break; case 1: out("535 authorization failed (#5.7.0)\r\n"); } } #ifdef TLS stralloc proto = {0}; int ssl_verified = 0; const char *ssl_verify_err = 0; void smtp_tls(char *arg) { if (ssl) err_unimpl(); else if (*arg) out("501 Syntax error (no parameters allowed) (#5.5.4)\r\n"); else tls_init(); } RSA *tmp_rsa_cb(SSL *ssl, int export, int keylen) { if (!export) keylen = 2048; if (keylen == 2048) { FILE *in = fopen("control/rsa2048.pem", "r"); if (in) { RSA *rsa = PEM_read_RSAPrivateKey(in, NULL, NULL, NULL); fclose(in); if (rsa) return rsa; } } return RSA_generate_key(keylen, RSA_F4, NULL, NULL); } DH *tmp_dh_cb(SSL *ssl, int export, int keylen) { if (!export) keylen = 2048; if (keylen == 2048) { FILE *in = fopen("control/dh2048.pem", "r"); if (in) { DH *dh = PEM_read_DHparams(in, NULL, NULL, NULL); fclose(in); if (dh) return dh; } } return DH_generate_parameters(keylen, DH_GENERATOR_2, NULL, NULL); } /* don't want to fail handshake if cert isn't verifiable */ int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; } void tls_nogateway() { /* there may be cases when relayclient is set */ if (!ssl || relayclient) return; out("; no valid cert for gatewaying"); if (ssl_verify_err) { out(": "); out(ssl_verify_err); } } void tls_out(const char *s1, const char *s2) { out("454 TLS "); out(s1); if (s2) { out(": "); out(s2); } out(" (#4.3.0)\r\n"); flush(); } void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); } # define CLIENTCA "control/clientca.pem" # define CLIENTCRL "control/clientcrl.pem" # define SERVERCERT "control/servercert.pem" int tls_verify() { stralloc clients = {0}; struct constmap mapclients; if (!ssl || relayclient || ssl_verified) return 0; ssl_verified = 1; /* don't do this twice */ /* request client cert to see if it can be verified by one of our CAs * and the associated email address matches an entry in tlsclients */ switch (control_readfile(&clients, "control/tlsclients", 0)) { case 1: if (constmap_init(&mapclients, clients.s, clients.len, 0)) { /* if CLIENTCA contains all the standard root certificates, a * 0.9.6b client might fail with SSL_R_EXCESSIVE_MESSAGE_SIZE; * it is probably due to 0.9.6b supporting only 8k key exchange * data while the 0.9.6c release increases that limit to 100k */ STACK_OF(X509_NAME) *sk = SSL_load_client_CA_file(CLIENTCA); if (sk) { SSL_set_client_CA_list(ssl, sk); SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL); break; } constmap_free(&mapclients); } case 0: alloc_free(clients.s); return 0; case -1: die_control(); } if (ssl_timeoutrehandshake(timeout, ssl_rfd, ssl_wfd, ssl) <= 0) { const char *err = ssl_error_str(); tls_out("rehandshake failed", err); die_read(); } do { /* one iteration */ X509 *peercert; X509_NAME *subj; stralloc email = {0}; int n = SSL_get_verify_result(ssl); if (n != X509_V_OK) { ssl_verify_err = X509_verify_cert_error_string(n); break; } peercert = SSL_get_peer_certificate(ssl); if (!peercert) break; subj = X509_get_subject_name(peercert); n = X509_NAME_get_index_by_NID(subj, NID_pkcs9_emailAddress, -1); if (n >= 0) { const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value; if (s) { email.len = s->length; email.s = s->data; } } if (email.len <= 0) ssl_verify_err = "contains no email address"; else if (!constmap(&mapclients, email.s, email.len)) ssl_verify_err = "email address not in my list of tlsclients"; else { /* add the cert email to the proto if it helped allow relaying */ --proto.len; if (!stralloc_cats(&proto, "\n (cert ") /* continuation line */ || !stralloc_catb(&proto, email.s, email.len) || !stralloc_cats(&proto, ")") || !stralloc_0(&proto)) die_nomem(); protocol = proto.s; relayclient = ""; /* also inform qmail-queue */ if (!env_put("RELAYCLIENT=")) die_nomem(); } X509_free(peercert); } while (0); constmap_free(&mapclients); alloc_free(clients.s); /* we are not going to need this anymore: free the memory */ SSL_set_client_CA_list(ssl, NULL); SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); return relayclient ? 1 : 0; } void tls_init() { SSL *myssl; SSL_CTX *ctx; const char *ciphers; stralloc saciphers = {0}; X509_STORE *store; X509_LOOKUP *lookup; SSL_library_init(); /* a new SSL context with the bare minimum of options */ ctx = SSL_CTX_new(SSLv23_server_method()); if (!ctx) { tls_err("unable to initialize ctx"); return; } /* POODLE vulnerability */ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); if (!SSL_CTX_use_certificate_chain_file(ctx, SERVERCERT)) { SSL_CTX_free(ctx); tls_err("missing certificate"); return; } SSL_CTX_load_verify_locations(ctx, CLIENTCA, NULL); #if OPENSSL_VERSION_NUMBER >= 0x00907000L /* crl checking */ store = SSL_CTX_get_cert_store(ctx); if ((lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) && (X509_load_crl_file(lookup, CLIENTCRL, X509_FILETYPE_PEM) == 1)) X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); #endif #if OPENSSL_VERSION_NUMBER >= 0x10002000L /* support ECDH */ SSL_CTX_set_ecdh_auto(ctx,1); #endif /* set the callback here; SSL_set_verify didn't work before 0.9.6c */ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_cb); /* a new SSL object, with the rest added to it directly to avoid copying */ myssl = SSL_new(ctx); SSL_CTX_free(ctx); if (!myssl) { tls_err("unable to initialize ssl"); return; } /* this will also check whether public and private keys match */ if (!SSL_use_RSAPrivateKey_file(myssl, SERVERCERT, SSL_FILETYPE_PEM)) { SSL_free(myssl); tls_err("no valid RSA private key"); return; } ciphers = env_get("TLSCIPHERS"); if (!ciphers) { if (control_readfile(&saciphers, "control/tlsserverciphers", 0) == -1) { SSL_free(myssl); die_control(); } if (saciphers.len) { /* convert all '\0's except the last one to ':' */ int i; for (i = 0; i < saciphers.len - 1; ++i) if (!saciphers.s[i]) saciphers.s[i] = ':'; ciphers = saciphers.s; } } if (!ciphers || !*ciphers) ciphers = "DEFAULT"; SSL_set_cipher_list(myssl, ciphers); alloc_free(saciphers.s); SSL_set_tmp_rsa_callback(myssl, tmp_rsa_cb); SSL_set_tmp_dh_callback(myssl, tmp_dh_cb); SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin)); SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout)); if (!smtps) { out("220 ready for tls\r\n"); flush(); } if (ssl_timeoutaccept(timeout, ssl_rfd, ssl_wfd, myssl) <= 0) { /* neither cleartext nor any other response here is part of a standard */ const char *err = ssl_error_str(); ssl_free(myssl); tls_out("connection failed", err); die_read(); } ssl = myssl; /* The following are a kludgey way of indicating that the channel is sufficiently private to allow the sending of passwords: */ if (!hostname) hostname = "0"; if (!childargs) childargs = pwchecker; /* populate the protocol string, used in Received */ if (!stralloc_copys(&proto, "ESMTPS (") || !stralloc_cats(&proto, SSL_get_cipher(ssl)) || !stralloc_cats(&proto, " encrypted)")) die_nomem(); if (!stralloc_0(&proto)) die_nomem(); protocol = proto.s; /* have to discard the pre-STARTTLS HELO/EHLO argument, if any */ dohelo(remotehost); } # undef SERVERCERT # undef CLIENTCA #endif struct commands smtpcommands[] = { { "rcpt", smtp_rcpt, 0 } , { "mail", smtp_mail, 0 } , { "data", smtp_data, flush } , { "auth", smtp_auth, flush } , { "quit", smtp_quit, flush } , { "helo", smtp_helo, flush } , { "ehlo", smtp_ehlo, flush } , { "rset", smtp_rset, 0 } , { "help", smtp_help, flush } #ifdef TLS , { "starttls", smtp_tls, flush_io } #endif , { "noop", err_noop, flush } , { "vrfy", err_vrfy, flush } , { 0, err_unimpl, flush } } ; void main(argc,argv) int argc; char **argv; { /* waitpid() doesn't do what you expect unless we do something nontrivial with SIGCHLD; read the manpage. */ sig_childcatch(dummy); hostname = ""; if (argc > 1) hostname = argv[1]; childargs = 0; if (argc > 2) childargs = argv + 2; sig_pipeignore(); if (chdir(auto_qmail) == -1) die_control(); setup(); if (ipme_init() != 1) die_ipme(); smtp_greet("220 "); out(" ESMTP\r\n"); if (commands(&ssin,&smtpcommands) == 0) die_read(); die_nomem(); }