#include #include #include #include "sig.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 "qregex.h" #include "strerr.h" #include "cdb.h" #include "qmail-spp.h" #include "dns.h" #include "base64.h" #include "open.h" #include "fd.h" #include "fork.h" int spp_val; #define BMCHECK_BMF 0 #define BMCHECK_BMFNR 1 #define BMCHECK_BMT 2 #define BMCHECK_BMTNR 3 #define BMCHECK_BHELO 4 //#define CRAM_MD5 #define AUTHSLEEP 5 #define MAXHOPS 100 unsigned int databytes = 0; unsigned int mfchk = 0; unsigned int greetdelay = 0; unsigned int drop_pre_greet = 0; int timeout = 1200; int recipcount = 0; unsigned int max_recipcount = 100; const char *protocol = "SMTP"; char *remoteip; char *remotehost; char *remoteinfo; char *local; char *relayclient; #ifdef TLS # include # include # include # include "tls.h" # include "ssl_timeoutio.h" # define SERVERCERT "control/servercert.pem" void tls_init(); void tls_nogateway(); int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ stralloc proto = {0}; int tls_before_auth = 0; #endif #ifdef SMTPUTF8 # include int smtputf8 = 0; #endif /* SMTP AUTH */ int flagauth = 0; char **childargs; ssize_t safewrite(fd,buf,len) int fd; char *buf; int len; { ssize_t 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); /* write errors to stderr */ char erroutbuf[512]; substdio errout = SUBSTDIO_FDBUF(safewrite,2,erroutbuf,sizeof erroutbuf); void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } void eflush() { substdio_flush(&errout); } void eout(s) char *s; { substdio_puts(&errout,s); } void enew() { substdio_puts(&errout,"qmail-smtpd: "); } void die_read() { _exit(1); } void die_alarm() { enew(); eout("Connection to "); eout(remoteip); eout(" timed out.\n"); out("451 timeout (#4.4.2)\r\n"); flush(); eflush(); _exit(1); } void die_nomem() { enew(); eout("Out of memory while connected to "); eout(remoteip); eout("!\n"); out("421 out of memory (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } void die_control() { enew(); eout("Unable to read controls!\n"); out("421 unable to read controls (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } void die_ipme() { enew(); eout("Unable to figure out my IP addresses!\n"); out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } void die_cdb() { enew(); eout("Unable to read cdb user database!\n"); out("421 unable to read cdb user database (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } void die_sys() { enew(); eout("Unable to read system user database!\n"); out("421 unable to read system user database (#4.3.0)\r\n"); flush(); eflush(); _exit(1); } void straynewline() { enew(); eout("Stray newline from "); eout(remoteip); eout(".\n"); out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); eflush(); _exit(1); } void die_pre_greet() { out("554 SMTP protocol violation\r\n"); flush(); _exit(1); } int hard_errors = 0; unsigned int max_hard_errors = 20; void do_hard_errors() { if (++hard_errors < max_hard_errors) return; enew(); eout("Maximum errors for "); eout(remoteip); eout(" reached. Closing connection\n"); out("421 too many errors (#4.7.0)\r\n"); flush(); eflush(); _exit(1); } void err_size() { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); do_hard_errors(); } void err_bmf() { out("553 sorry, your envelope sender has been denied (#5.7.1)\r\n"); do_hard_errors(); } void err_bmt() { out("553 sorry, your envelope recipient has been denied (#5.7.1)\r\n"); do_hard_errors(); } void err_hmf() { out("553 sorry, your envelope sender domain must exist (#5.7.1)\r\n"); do_hard_errors(); } void err_smf() { out("451 DNS temporary failure (#4.3.0)\r\n"); } void err_brt() { out("550 sorry, this message is not deliverable (#5.7.1)\r\n"); do_hard_errors(); } void err_bhelo() { out("553 sorry, your HELO host name has been denied (#5.7.1)\r\n"); do_hard_errors(); } void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts"); #ifdef TLS tls_nogateway(); #endif out(" (#5.7.1)\r\n"); do_hard_errors(); } void err_badbounce() { out("550 sorry, bounce messages should have a single envelope recipient (#5.7.1)\r\n"); do_hard_errors(); } void err_unimpl(arg) char *arg; { out("502 unimplemented (#5.5.1)\r\n"); do_hard_errors(); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); do_hard_errors(); } void err_relay() { out("553 we don't relay (#5.7.1)\r\n"); do_hard_errors(); } void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); do_hard_errors(); } void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); do_hard_errors(); } 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"); do_hard_errors(); } int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); do_hard_errors(); return -1; } int err_noauth2() { out("503 auth not available (#5.3.3)\r\n"); do_hard_errors(); } int err_authabrt() { out("501 auth exchange canceled (#5.0.0)\r\n"); return -1; } int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; } int err_wantstarttls() { out("530 Must issue a STARTTLS command first (#5.7.0)\r\n"); return -1; }; void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); do_hard_errors(); } void err_nomailbox() { out("554 sorry, no mailbox here by that name (#5.1.1)\r\n"); do_hard_errors(); } void err_maxrcpt() { out("450 too many recipients (#4.7.1)\r\n"); do_hard_errors(); } extern void realrcptto_init(); extern void realrcptto_start(); extern int realrcptto(); extern int realrcptto_deny(); 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 qmail home page: http://pobox.com/~djb/qmail.html\r\n"); } void smtp_quit(arg) char *arg; { smtp_greet("221 "); out("\r\n"); flush(); _exit(0); } 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}; int bmfnrok = 0; stralloc bmfnr = {0}; int bmtok = 0; stralloc bmt = {0}; int bmtnrok = 0; stralloc bmtnr = {0}; int brtok = 0; stralloc brt = {0}; struct constmap mapbrt; int fdmbrt; int bhelook = 0; stralloc bhelo = {0}; int logregex = 0; stralloc matchedregex = {0}; 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(); if (spp_init() == -1) die_control(); if (control_readint(&mfchk,"control/mfcheck") == -1) die_control(); x = env_get("MFCHECK"); if (x) { scan_ulong(x,&u); mfchk = u; } bmfok = control_readfile(&bmf,"control/badmailfrom",0); if (bmfok == -1) die_control(); bmfnrok = control_readfile(&bmfnr,"control/badmailfromnorelay",0); if (bmfnrok == -1) die_control(); bmtok = control_readfile(&bmt,"control/badmailto",0); if (bmtok == -1) die_control(); bmtnrok = control_readfile(&bmtnr,"control/badmailtonorelay",0); if (bmtnrok == -1) die_control(); bhelook = control_readfile(&bhelo, "control/badhelo",0); if (bhelook == -1) die_control(); if (env_get("NOBADHELO")) bhelook = 0; if (env_get("LOGREGEX")) logregex = 1; brtok = control_readfile(&brt,"control/badrcptto",0); if (brtok == -1) die_control(); if (brtok) if (!constmap_init(&mapbrt,brt.s,brt.len,0)) die_nomem(); fdmbrt = open_read("control/morebadrcptto.cdb"); if (fdmbrt == -1) if (errno != error_noent) die_control(); realrcptto_init(); 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; x = env_get("GREETDELAY"); if (x) { scan_ulong(x, &u); greetdelay = u; } x = env_get("DROP_PRE_GREET"); if (x) { scan_ulong(x, &u); drop_pre_greet = u; } x = env_get("MAXRCPT"); if (x) { scan_ulong(x,&u); max_recipcount = u; } 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("TLSBEFOREAUTH")) tls_before_auth = 1; 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 = ' '; 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(); } } if (addr.len > 900) return 0; return 1; } #ifdef SMTPUTF8 static int idn_addr_convert(stralloc *addr) { int i = byte_rchr(addr->s, addr->len, '@') + 1; if (i < addr->len) { char *asciihost = NULL; switch (idn2_lookup_u8(addr->s + i, (uint8_t **)&asciihost, IDN2_NFC_INPUT)) { case IDN2_OK: break; case IDN2_MALLOC: die_nomem(); default: err_smf(); return 0; } addr->len = i; if (!stralloc_cats(addr, asciihost)) die_nomem(); if (!stralloc_0(addr)) die_nomem(); } return 1; } #endif static void log_deny(m,f,t) char *m,*f,*t; { enew(); eout(m); eout(" check failed ("); eout(f); eout(") -> ("); eout(t); eout(") ["); eout(remoteip); eout("] (HELO "); eout(helohost.s); eout(")\n"); eflush(); } int bmcheck(which) int which; { int i = 0; int j = 0; int x = 0; int negate = 0; static stralloc bmb = {0}; static stralloc curregex = {0}; if (which == BMCHECK_BMF) { if (!stralloc_copy(&bmb,&bmf)) die_nomem(); } else if (which == BMCHECK_BMFNR) { if (!stralloc_copy(&bmb,&bmfnr)) die_nomem(); } else if (which == BMCHECK_BMT) { if (!stralloc_copy(&bmb,&bmt)) die_nomem(); } else if (which == BMCHECK_BMTNR) { if (!stralloc_copy(&bmb,&bmtnr)) die_nomem(); } else if (which == BMCHECK_BHELO) { if (!stralloc_copy(&bmb,&bhelo)) die_nomem(); } else { die_control(); } while (j < bmb.len) { i = j; while ((bmb.s[i] != '\0') && (i < bmb.len)) i++; if (bmb.s[j] == '!') { negate = 1; j++; } if (!stralloc_copyb(&curregex,bmb.s + j,(i - j))) die_nomem(); if (!stralloc_0(&curregex)) die_nomem(); if (which == BMCHECK_BHELO) { x = matchregex(helohost.s, curregex.s); } else { x = matchregex(addr.s, curregex.s); } if ((negate) && (x == 0)) { if (!stralloc_copyb(&matchedregex,bmb.s + j - 1,(i - j + 1))) die_nomem(); if (!stralloc_0(&matchedregex)) die_nomem(); return 1; } if (!(negate) && (x > 0)) { if (!stralloc_copyb(&matchedregex,bmb.s + j,(i - j))) die_nomem(); if (!stralloc_0(&matchedregex)) die_nomem(); return 1; } j = i + 1; negate = 0; } return 0; } int brtcheck() { int j; if (brtok) if (constmap(&mapbrt,addr.s,addr.len - 1)) return 1; if (fdmbrt != -1) { uint32 dlen; j = cdb_seek(fdmbrt, addr.s, addr.len - 1, &dlen); if (j == -1) die_control(); if (j) return j; } return 0; } int mfcheck() { stralloc sa = {0}; ipalloc ia = {0}; unsigned int random; int j; if (!mfchk) return 0; random = now() + (getpid() << 16); j = byte_rchr(addr.s,addr.len,'@') + 1; if (j < addr.len) { stralloc_copys(&sa, addr.s + j); dns_init(0); j = dns_mxip(&ia,&sa,random); if (j < 0) return j; } return 0; } int addrallowed() { int r; r = rcpthosts(addr.s,str_len(addr.s)); if (r == -1) die_control(); 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 flagbarfbmf; /* defined if seenmail */ int flagbarfbmt; int flagbrt; /* defined if any bad rcpts */ int flagbarfbhelo; int allowed; int flagsize; stralloc mailfrom = {0}; stralloc rcptto = {0}; stralloc fuser = {0}; stralloc mfparms = {0}; int mailfrom_size(arg) char *arg; { long r; unsigned long sizebytes = 0; scan_ulong(arg,&r); sizebytes = r; if (databytes) if (sizebytes > databytes) return 1; return 0; } void mailfrom_auth(arg,len) char *arg; int len; { int j; if (!stralloc_copys(&fuser,"")) die_nomem(); if (case_starts(arg,"<>")) { if (!stralloc_cats(&fuser,"unknown")) die_nomem(); } else while (len) { if (*arg == '+') { if (case_starts(arg,"+3D")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"=")) die_nomem(); } if (case_starts(arg,"+2B")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"+")) die_nomem(); } } else if (!stralloc_catb(&fuser,arg,1)) die_nomem(); arg++; len--; } if(!stralloc_0(&fuser)) die_nomem(); if (!remoteinfo) { remoteinfo = fuser.s; if (!env_unset("TCPREMOTEINFO")) die_read(); if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem(); } } void mailfrom_parms(arg) char *arg; { int i; int len; len = str_len(arg); if (!stralloc_copys(&mfparms,"")) die_nomem; i = byte_chr(arg,len,'>'); if (i > 4 && i < len) { while (len) { arg++; len--; if (*arg == ' ' || *arg == '\0' ) { #ifdef SMTPUTF8 if (case_starts(mfparms.s,"SMTPUTF8")) smtputf8 = 1; #endif if (case_starts(mfparms.s,"SIZE=")) if (mailfrom_size(mfparms.s+5)) { flagsize = 1; return; } /* we do not support this */ //if (case_starts(mfparms.s,"AUTH=")) mailfrom_auth(mfparms.s+5,mfparms.len-5); if (!stralloc_copys(&mfparms,"")) die_nomem; } else if (!stralloc_catb(&mfparms,arg,1)) die_nomem; } } } #define V4MAPPREFIX "::ffff:" static const stralloc *client_get_session_id() { static stralloc buf = {0}, base64_buf = {0}; const char *tmp; struct timeval tv; uint64_t timestamp; unsigned int i; unsigned long port; int family = AF_INET; if (base64_buf.s) return &base64_buf; if (!stralloc_ready(&buf, 24)) die_nomem(); /* add lowest 48 bits of the timestamp. this gives us a bit less than 9 years until it wraps */ gettimeofday(&tv, NULL); timestamp = tv.tv_usec + (long long)tv.tv_sec * 1000ULL*1000ULL; for (i = 0; i < 48; i += 8) buf.s[buf.len++] = (timestamp >> i) & 0xff; if ((tmp = getenv("TCPREMOTEPORT")) != NULL && scan_ulong(tmp, &port)) { buf.s[buf.len] = port & 0xff; buf.s[buf.len+1] = (port >> 8) & 0xff; } buf.len += 2; family = ((tmp = getenv("PROTO")) && strcmp(tmp, "TCP6") == 0) ? AF_INET6 : AF_INET; if ((tmp = getenv("TCPREMOTEIP"))) { if (family == AF_INET6 && !strncmp(tmp, V4MAPPREFIX, strlen(V4MAPPREFIX))) { tmp += strlen(V4MAPPREFIX); family = AF_INET; } (void)inet_pton(family, tmp, buf.s + buf.len); buf.len += (family == AF_INET) ? 4 : 16; } if (b64encode(&buf, &base64_buf) < 0) die_nomem(); if (!stralloc_0(&base64_buf)) die_nomem(); if (!env_put2("SMTPSESSION", base64_buf.s)) die_nomem(); return &base64_buf; } void smtp_helo(arg) char *arg; { if(!spp_helo(arg)) return; smtp_greet("250 "); out("\r\n"); seenmail = 0; dohelo(arg); if (bhelook) flagbarfbhelo = bmcheck(BMCHECK_BHELO); } void smtp_authout(); /* ESMTP extensions are published here */ void smtp_ehlo(arg) char *arg; { #ifdef TLS struct stat st; #endif char size[FMT_ULONG]; size[fmt_ulong(size,(unsigned int) databytes)] = 0; if(!spp_helo(arg)) return; smtp_greet("250-"); #ifdef TLS const char *servercert = env_get("SMTP_SERVERCERT"); if (!servercert) servercert = SERVERCERT; if (!ssl && (stat(servercert, &st) == 0)) out("\r\n250-STARTTLS"); #endif #ifdef SMTPUTF8 out("\r\n250-PIPELINING\r\n250-SMTPUTF8\r\n250-8BITMIME\r\n"); #else out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n"); #endif #if defined(TLS) if(!tls_before_auth || (tls_before_auth && ssl)) smtp_authout(); #else smtp_authout(); #endif out("250 SIZE "); out(size); out("\r\n"); seenmail = 0; dohelo(arg); if (bhelook) flagbarfbhelo = bmcheck(BMCHECK_BHELO); } void smtp_rset(arg) char *arg; { spp_rset(); seenmail = 0; out("250 flushed\r\n"); } void smtp_mail(arg) char *arg; { if (!addrparse(arg)) { err_syntax(); return; } flagsize = 0; mailfrom_parms(arg); if (flagsize) { err_size(); return; } #ifdef SMTPUTF8 if (smtputf8) { if (!idn_addr_convert(&addr)) return; } #endif flagbarfbmf = 0; /* bmcheck is skipped for empty envelope senders */ if ((bmfok) && (addr.len != 1)) flagbarfbmf = bmcheck(BMCHECK_BMF); if ((!flagbarfbmf) && (bmfnrok) && (addr.len != 1) && (!relayclient)) { flagbarfbmf = bmcheck(BMCHECK_BMFNR); } switch(mfcheck()) { case DNS_HARD: strerr_warn4("qmail-smtpd: invalid envelope sender: <",addr.s,"> at ",remoteip,0); err_hmf(); return; case DNS_SOFT: err_smf(); return; case DNS_MEM: die_nomem(); } if (!(spp_val = spp_mail())) return; seenmail = 1; if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); realrcptto_start(); recipcount = 0; out("250 ok\r\n"); } void smtp_rcpt(arg) char *arg; { if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } if (!relayclient && addrrelay()) { err_relay(); return; } if (recipcount >= max_recipcount) { enew(); eout("Too many recipients for "); eout(remoteip); eout(".\n"); err_maxrcpt(); return; } if (flagbarfbhelo) { if (logregex) { strerr_warn6("qmail-smtpd: badhelo: <",helohost.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0); } else { strerr_warn4("qmail-smtpd: badhelo: <",helohost.s,"> at ",remoteip,0); } err_bhelo(); return; } if (flagbarfbmf) { if (logregex) { strerr_warn6("qmail-smtpd: badmailfrom: <",mailfrom.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0); } else { strerr_warn4("qmail-smtpd: badmailfrom: <",mailfrom.s,"> at ",remoteip,0); } err_bmf(); return; } #ifdef SMTPUTF8 if (smtputf8) { if (!idn_addr_convert(&addr)) return; } #endif if (bmtok) flagbarfbmt = bmcheck(BMCHECK_BMT); if ((!flagbarfbmt) && (bmtnrok) && (!relayclient)) { flagbarfbmt = bmcheck(BMCHECK_BMTNR); } if (flagbarfbmt) { if (logregex) { strerr_warn6("qmail-smtpd: badmailto: <",addr.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0); } else { strerr_warn4("qmail-smtpd: badmailto: <",addr.s,"> at ",remoteip,0); } err_bmt(); return; } if (!relayclient) allowed = addrallowed(); else allowed = 1; if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } if (!env_get("RELAYCLIENT") && brtcheck()) { flagbrt = 1; log_deny("BAD RCPT TO", mailfrom.s,addr.s); } if (!flagauth && !relayclient && !realrcptto(addr.s,1)) { err_nomailbox(); return; } if (!(spp_val = spp_rcpt(allowed))) return; if (!relayclient && spp_val == 1) { if (!allowed) { strerr_warn6("qmail-smtpd: Attempted relay from ",mailfrom.s," at ",remoteip," to ",addr.s,0); err_nogateway(); return; } } spp_rcpt_accepted(); 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"); } ssize_t saferead(fd,buf,len) int fd; char *buf; int len; { ssize_t 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; } if (flagbrt) { err_brt(); return; } if (!spp_data()) return; if (!relayclient && realrcptto_deny()) { err_nomailbox(); 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"); if (smtputf8) { stralloc utf8proto = {0}; if (*protocol == 'E') ++protocol; if (!stralloc_copys(&utf8proto, "UTF8")) die_nomem(); if (!stralloc_cats(&utf8proto, protocol)) die_nomem(); if (!stralloc_0(&utf8proto)) die_nomem(); protocol = utf8proto.s; } if (flagauth) received_authed(&qqt,protocol,local,remoteinfo,client_get_session_id()->s); else received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo); qmail_put(&qqt,sppheaders.s,sppheaders.len); /* set in qmail-spp.c */ spp_rset(); 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) { err_size(); return; } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n"); } /* this file is too long ----------------------------------------- SMTP AUTH */ char unique[FMT_ULONG + FMT_ULONG + 3]; static stralloc authin = {0}; /* input from SMTP client */ static stralloc user = {0}; /* authorization user-id */ static stralloc pass = {0}; /* plain passwd or digest */ static stralloc resp = {0}; /* b64 response */ #ifdef CRAM_MD5 static stralloc chal = {0}; /* plain challenge */ static stralloc slop = {0}; /* b64 challenge */ #endif char ssauthbuf[512]; char ssauth2buf[512]; substdio ssauth = SUBSTDIO_FDBUF(safewrite,3,ssauthbuf,sizeof(ssauthbuf)); substdio ssauth2 = SUBSTDIO_FDBUF(saferead,4,ssauth2buf,sizeof(ssauth2buf)); void smtp_authout() { if (!*childargs) return; #ifdef CRAM_MD5 out("250-AUTH LOGIN PLAIN CRAM-MD5\r\n"); #else out("250-AUTH LOGIN PLAIN\r\n"); #endif } 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]; int piauth[2], i, len; char *arg; static stralloc authout = {0}, authparams = {0}; if (!stralloc_0(&user)) die_nomem(); if (!stralloc_0(&pass)) die_nomem(); #ifdef CRAM_MD5 if (!stralloc_0(&chal)) die_nomem(); #endif if (pipe(pi) == -1) return err_pipe(); if (pipe(piauth) == -1) return err_pipe(); switch(child = fork()) { case -1: return err_fork(); case 0: close(pi[1]); close(piauth[0]); if(fd_copy(3,pi[0]) == -1) return err_pipe(); if(fd_copy(4,piauth[1]) == -1) return err_pipe(); sig_pipedefault(); execvp(*childargs, childargs); _exit(1); } close(pi[0]); close(piauth[1]); substdio_fdbuf(&ssauth,write,pi[1],ssauthbuf,sizeof ssauthbuf); substdio_fdbuf(&ssauth2,read,piauth[0],ssauth2buf,sizeof ssauth2buf); if (substdio_put(&ssauth,user.s,user.len) == -1) return err_write(); if (substdio_put(&ssauth,pass.s,pass.len) == -1) return err_write(); #ifdef CRAM_MD5 if (substdio_put(&ssauth,chal.s,chal.len) == -1) return err_write(); #endif if (substdio_flush(&ssauth) == -1) return err_write(); close(pi[1]); if (!stralloc_copys(&authout, "")) die_nomem(); for (;;) { if (!stralloc_readyplus(&authout, 1)) die_nomem(); i = substdio_get(&ssauth2, authout.s + authout.len, 1); if (i == 0) break; ++authout.len; } authout.s[authout.len] = 0; if (substdio_flush(&ssauth2) == -1) return err_write(); close(piauth[0]); if (authout.len > 0) { len = authout.len; arg = authout.s; if (!stralloc_copys(&authparams, "")) die_nomem; while (len) { if (*arg == '\0') { if (case_starts(authparams.s, "USER=") && (authparams.len - 5) > 0) { if (!stralloc_copyb(&user, authparams.s + 5, authparams.len - 5)) die_nomem(); if (!stralloc_0(&user)) die_nomem(); } if (!stralloc_copys(&authparams, "")) die_nomem; } else if (!stralloc_catb(&authparams, arg, 1)) die_nomem; arg++; len--; } } #ifdef CRAM_MD5 if (!stralloc_copys(&chal,"")) die_nomem(); if (!stralloc_copys(&slop,"")) die_nomem(); #endif byte_zero(ssauthbuf,sizeof ssauthbuf); byte_zero(ssauth2buf,sizeof ssauth2buf); if (wait_pid(&wstat,child) == -1) return err_child(); if (wait_crashed(wstat)) return err_child(); if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); return 1; } /* no */ return 0; /* yes */ } int auth_login(arg) char *arg; { int r; #if defined(TLS) if (tls_before_auth && !ssl) return err_wantstarttls(); #endif 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 defined(TLS) if (tls_before_auth && !ssl) return err_wantstarttls(); #endif if (*arg) { if (r = b64decode(arg,str_len(arg),&resp) == 1) return err_input(); } else { out("334 \r\n"); flush(); if (authgetl() < 0) return -1; if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input(); } if (r == -1 || !stralloc_0(&resp)) die_nomem(); while (resp.s[id]) id++; /* "authorize-id\0userid\0passwd\0" */ if (resp.len > id + 1) if (!stralloc_copys(&user,resp.s + id + 1)) die_nomem(); if (resp.len > id + user.len + 2) if (!stralloc_copys(&pass,resp.s + id + user.len + 2)) die_nomem(); if (!user.len || !pass.len) return err_input(); return authenticate(); } #ifdef CRAM_MD5 int auth_cram() { int i, r; char *s; #if defined(TLS) if (tls_before_auth && !ssl) return err_wantstarttls(); #endif s = unique; /* generate challenge */ s += fmt_uint(s,getpid()); *s++ = '.'; s += fmt_ulong(s,(unsigned long) now()); *s++ = '@'; *s++ = 0; if (!stralloc_copys(&chal,"<")) die_nomem(); if (!stralloc_cats(&chal,unique)) die_nomem(); if (!stralloc_cats(&chal,local)) die_nomem(); if (!stralloc_cats(&chal,">")) die_nomem(); if (b64encode(&chal,&slop) < 0) die_nomem(); if (!stralloc_0(&slop)) die_nomem(); out("334 "); /* "334 base64_challenge \r\n" */ out(slop.s); out("\r\n"); flush(); if (authgetl() < 0) return -1; /* got response */ if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input(); if (r == -1 || !stralloc_0(&resp)) die_nomem(); i = str_chr(resp.s,' '); s = resp.s + i; while (*s == ' ') ++s; resp.s[i] = 0; if (!stralloc_copys(&user,resp.s)) die_nomem(); /* userid */ if (!stralloc_copys(&pass,s)) die_nomem(); /* digest */ if (!user.len || !pass.len) return err_input(); return authenticate(); } #endif struct authcmd { char *text; int (*fun)(); } authcmds[] = { { "login",auth_login } , { "plain",auth_plain } #ifdef CRAM_MD5 , { "cram-md5",auth_cram } #endif , { 0,err_noauth } }; void smtp_auth(arg) char *arg; { int i; char *cmd = arg; if (!*childargs) { err_noauth2(); return; } if (flagauth) { 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(); #ifdef CRAM_MD5 if (!stralloc_copys(&chal,"")) die_nomem(); #endif 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: (void)client_get_session_id(); /* generate session */ if (!spp_auth(authcmds[i].text, user.s)) return; flagauth = 1; if (ssl) { if (!stralloc_copys(&proto, "ESMTPSA (") || !stralloc_cats(&proto, SSL_get_cipher(ssl)) || !stralloc_cats(&proto, " encrypted, authenticated)")) die_nomem(); if (!stralloc_0(&proto)) die_nomem(); protocol = proto.s; } else { protocol = "ESMTPA"; } relayclient = ""; remoteinfo = user.s; if (!env_unset("TCPREMOTEINFO")) die_read(); if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem(); if (!env_put2("RELAYCLIENT",relayclient)) die_nomem(); out("235 ok, go ahead (#2.0.0)\r\n"); break; case 1: err_authfail(user.s,authcmds[i].text); } } /* this file is too long --------------------------------------------- GO ON */ #ifdef TLS 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(); } /* * Grab well-defined DH parameters from OpenSSL, see the get_rfc* * functions in for all available primes. */ #if OPENSSL_VERSION_NUMBER < 0x10100005L static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) { /* q is optional */ if (p == NULL || g == NULL) return 0; BN_free(dh->p); BN_free(dh->q); BN_free(dh->g); dh->p = p; dh->q = q; dh->g = g; if (q != NULL) dh->length = BN_num_bits(q); return 1; } #endif static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *)) { BIGNUM *p, *g; DH *dh = DH_new(); if (!dh) return NULL; p = prime(NULL); g = BN_new(); if (g != NULL) BN_set_word(g, 2); if (!p || !g || !DH_set0_pqg(dh, p, NULL, g)) { DH_free(dh); BN_free(p); BN_free(g); return NULL; } return dh; } /* Storage and initialization for DH parameters. */ static struct dhparam { BIGNUM *(*const prime)(BIGNUM *); /* function to generate... */ DH *dh; /* ...this, used for keys.... */ const unsigned int min; /* ...of length >= this. */ } dhparams[] = { { get_rfc3526_prime_8192, NULL, 6145 }, { get_rfc3526_prime_6144, NULL, 4097 }, { get_rfc3526_prime_4096, NULL, 3073 }, { get_rfc3526_prime_3072, NULL, 2049 }, { get_rfc3526_prime_2048, NULL, 1025 }, { get_rfc2409_prime_1024, NULL, 0 } }; /* Hand out the same DH structure though once generated as we leak * memory otherwise and freeing the structure up after use would be * hard to track and in fact is not needed at all as it is safe to * use the same parameters over and over again security wise (in * contrast to the keys itself) and code safe as the returned structure * is duplicated by OpenSSL anyway. Hence no modification happens * to our copy. */ static DH *tmp_dh_cb(SSL *ssl, int export, int keylen) { EVP_PKEY *pkey = SSL_get_privatekey(ssl); int type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE; unsigned n; /* * OpenSSL will call us with either keylen == 512 or keylen == 1024 * (see the definition of SSL_EXPORT_PKEYLENGTH in ssl_locl.h). * Adjust the DH parameter length according to the size of the * RSA/DSA private key used for the current connection, and always * use at least 1024-bit parameters. * Note: This may cause interoperability issues with implementations * which limit their DH support to 1024 bit - e.g. Java 7 and earlier. * In this case, SSLCertificateFile can be used to specify fixed * 1024-bit DH parameters (with the effect that OpenSSL skips this * callback). */ if (type == EVP_PKEY_RSA || type == EVP_PKEY_DSA) keylen = EVP_PKEY_bits(pkey); for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++) { if (keylen >= dhparams[n].min) { if (dhparams[n].dh == NULL) dhparams[n].dh = make_dh_params(dhparams[n].prime); return dhparams[n].dh; } } return NULL; /* impossible to reach. */ } static DH *ssl_dh_GetParamFromFile(const char *file) { DH *dh = NULL; BIO *bio; if ((bio = BIO_new_file(file, "r")) == NULL) return NULL; dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if (!dh) (void)ERR_get_error(); return dh; } #ifdef HAVE_ECC static EC_GROUP *ssl_ec_GetParamFromFile(const char *file) { EC_GROUP *group = NULL; BIO *bio; if ((bio = BIO_new_file(file, "r")) == NULL) return NULL; group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL); BIO_free(bio); if (!group) (void)ERR_get_error(); return group; } #if !defined(SSL_set_ecdh_auto) static EC_KEY *tmp_ecdh_cb(SSL *ssl, int export, int keylen) { return EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); } #endif #endif /* 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(); } void tls_init() { SSL *myssl; SSL_CTX *ctx; const char *ciphers; stralloc saciphers = {0}; X509_STORE *store; X509_LOOKUP *lookup; const char *servercert; DH *dhparams; #ifdef HAVE_ECC EC_GROUP *ecparams; int nid; EC_KEY *eckey = NULL; #endif /* if set, use servercert selected through SMTP_SERVERCERT env var */ servercert = env_get("SMTP_SERVERCERT"); if (!servercert) servercert = SERVERCERT; 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; } SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_PRIORITIZE_CHACHA); if (*childargs) SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); if (!SSL_CTX_use_certificate_chain_file(ctx, servercert)) { SSL_CTX_free(ctx); tls_err("missing certificate"); return; } /* 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_PrivateKey_file(myssl, servercert, SSL_FILETYPE_PEM)) { SSL_free(myssl); tls_err("no valid 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); //TODO: we shouldn't use hardcoded DH: see https://weakdh.org/ SSL_set_tmp_dh_callback(myssl, tmp_dh_cb); /* try to read DH parameters from certificate */ if ((dhparams = ssl_dh_GetParamFromFile(servercert))) SSL_set_tmp_dh(myssl, dhparams); #ifdef HAVE_ECC /* similarly, try to read the ECDH curve name from certificate... */ if ((ecparams = ssl_ec_GetParamFromFile(servercert)) && (nid = EC_GROUP_get_curve_name(ecparams)) && (eckey = EC_KEY_new_by_curve_name(nid))) { SSL_set_tmp_ecdh(myssl, eckey); } else { #if defined(SSL_set_ecdh_auto) /* ...otherwise, enable auto curve selection (OpenSSL 1.0.2 and later) */ SSL_set_ecdh_auto(myssl, 1); #else /* or set callback, which will configure NIST P-256 */ SSL_set_tmp_ecdh_callback(myssl, tmp_ecdh_cb); #endif } EC_KEY_free(eckey); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100005L stralloc opensslconf = {0}; if (control_readfile(&opensslconf, "control/opensslconf", 0) == -1) { SSL_free(myssl); die_control(); } if (opensslconf.len) { SSL_CONF_CTX *cctx = SSL_CONF_CTX_new(); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS); SSL_CONF_CTX_set_ssl(cctx, myssl); int i, j, next = 0; char *cmd, * arg; for (i = 0; i < opensslconf.len; i += next) { cmd = opensslconf.s + i; next = str_len(cmd) + 1; j = str_chr(cmd, ' '); arg = cmd + j; while (*arg == ' ') ++arg; cmd[j] = 0; if (SSL_CONF_cmd(cctx, cmd, arg) <= 0) { enew(); eout("opensslconf \""); eout(cmd); eout(" "); eout(arg); eout("\" failed: "); eout(ssl_error()); eout("\n"); eflush(); tls_out("OpenSSL", "unable to initialize ssl"); SSL_free(myssl); die_read(); } } (void)SSL_CONF_CTX_finish(cctx); } #endif 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; if (!env_put("SMTPSECURED=1")) die_nomem(); /* 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); } #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; { int n, m; childargs = argv + 1; sig_pipeignore(); if (chdir(auto_qmail) == -1) die_control(); setup(); if (ipme_init() != 1) die_ipme(); if (spp_connect()) { if (!relayclient && greetdelay) { if (drop_pre_greet) { n = timeoutread(greetdelay ? greetdelay : 1, 0, ssinbuf, sizeof(ssinbuf)); if(n == -1) { if (errno != error_timeout) strerr_die3sys(1, "GREETDELAY from ", remoteip, ": "); } else if (n == 0) { strerr_die3x(1, "GREETDELAY from ", remoteip, ": client disconnected"); } else { strerr_warn3("GREETDELAY from ", remoteip, ": client sent data before greeting", 0); die_pre_greet(); } } else { sleep(greetdelay); m = 0; for (;;) { n = timeoutread(0, 0, ssinbuf, sizeof(ssinbuf)); if (n <= 0) break; if (n > 0 && m == 0) { strerr_warn3("GREETDELAY from ", remoteip, ": client sent data before greeting. ignoring", 0); m = 1; } } } } smtp_greet("220 "); out(" ESMTP\r\n"); } if (commands(&ssin,&smtpcommands) == 0) die_read(); die_nomem(); }