#include #include #include #include #include #include #include "sig.h" #include "stralloc.h" #include "substdio.h" #include "subfd.h" #include "scan.h" #include "byte.h" #include "case.h" #include "error.h" #include "auto_qmail.h" #include "control.h" #include "dns.h" #include "alloc.h" #include "quote.h" #include "fmt.h" #include "ip.h" #include "ipalloc.h" #include "ipme.h" #include "gen_alloc.h" #include "gen_allocdefs.h" #include "str.h" #include "now.h" #include "exit.h" #include "constmap.h" #include "tcpto.h" #include "timeoutconn.h" #include "timeoutread.h" #include "timeoutwrite.h" #include "base64.h" #include "env.h" #define HUGESMTPTEXT 5000 unsigned long smtp_port = 25; /* silly rabbit, /etc/services is for users */ unsigned long qmtp_port = 209; GEN_ALLOC_typedef(saa,stralloc,sa,len,a) GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) static stralloc sauninit = {0}; stralloc helohost = {0}; stralloc routes = {0}; struct constmap maproutes; stralloc host = {0}; stralloc idnhost = {0}; stralloc sender = {0}; stralloc auth_smtp_user = {0}; stralloc auth_smtp_pass = {0}; stralloc auth_b64_user = {0}; stralloc auth_b64_pass = {0}; stralloc auth_status = {0}; saa reciplist = {0}; struct ip_address partner; #ifdef TLS # include # include "tls.h" # include "ssl_timeoutio.h" # include # define EHLO 1 # define CLIENTCERT "control/clientcert.pem" const char *ssl_err_str = 0; char **myargv; #endif #ifdef SMTPUTF8 # include int flagutf8 = 0; stralloc header = {0}; #endif void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); } void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); } void zeroflush() { zero(); substdio_flush(subfdoutsmall); } void zerodie() { zeroflush(); _exit(0); } void outsafe(sa) stralloc *sa; { int i; unsigned char ch; for (i = 0;i < sa->len;++i) { ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126 && ch <= 127) ch = '?'; if (substdio_put(subfdoutsmall,&ch,1) == -1) _exit(0); } } void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } void temp_oserr() { out("Z\ System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); } void temp_noconn() { out("Z\ Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); } void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } void temp_dnscanon() { out("Z\ CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); } void temp_dns() { out("Z\ Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); } void temp_chdir() { out("Z\ Unable to switch to home directory. (#4.3.0)\n"); zerodie(); } void temp_control() { out("Z\ Unable to read control files. (#4.3.0)\n"); zerodie(); } void temp_proto() { out("Z\ recipient did not talk proper QMTP (#4.3.0)\n"); zerodie(); } void perm_partialline() { out("D\ SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); } void perm_usage() { out("D\ I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); } void perm_dns() { out("D\ Sorry, I couldn't find any host named "); outsafe(&host); out(". (#5.1.2)\n"); zerodie(); } void perm_nomx() { out("D\ Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n"); zerodie(); } void perm_ambigmx() { out("D\ Sorry. Although I'm listed as a best-preference MX or A for that host,\n\ it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n"); zerodie(); } void auth_user_not_set() { //if(!stralloc_copys(&auth_status, // "User and password not set, continuing without authentication.\n")) // temp_nomem(); if(!stralloc_0(&auth_status)) temp_nomem(); } void no_supported_auth() { if(!stralloc_copys(&auth_status, "No supported AUTH method found, continuing without authentication.\n")) temp_nomem(); if(!stralloc_0(&auth_status)) temp_nomem(); } void outhost() { char x[IPFMT]; if (substdio_put(subfdoutsmall,x,ip_fmt(x,&partner)) == -1) _exit(0); } int flagcritical = 0; void dropped() { out("ZConnected to "); outhost(); out(" but connection died. "); if (flagcritical) out("Possible duplicate! "); #ifdef TLS if (ssl_err_str) { out(ssl_err_str); out(" "); } #endif out("(#4.4.2)\n"); zerodie(); } int timeoutconnect = 60; int smtpfd; int timeout = 1200; ssize_t saferead(fd,buf,len) int fd; char *buf; int len; { ssize_t r; #ifdef TLS if (ssl) { r = ssl_timeoutread(timeout, smtpfd, smtpfd, ssl, buf, len); if (r < 0) ssl_err_str = ssl_error_str(); } else #endif r = timeoutread(timeout,smtpfd,buf,len); if (r <= 0) dropped(); return r; } ssize_t safewrite(fd,buf,len) int fd; char *buf; int len; { ssize_t r; #ifdef TLS if (ssl) { r = ssl_timeoutwrite(timeout, smtpfd, smtpfd, ssl, buf, len); if (r < 0) ssl_err_str = ssl_error_str(); } else #endif r = timeoutwrite(timeout,smtpfd,buf,len); if (r <= 0) dropped(); return r; } char inbuf[1500]; substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof inbuf); char smtptobuf[1500]; substdio smtpto = SUBSTDIO_FDBUF(safewrite,-1,smtptobuf,sizeof smtptobuf); char smtpfrombuf[128]; substdio smtpfrom = SUBSTDIO_FDBUF(saferead,-1,smtpfrombuf,sizeof smtpfrombuf); stralloc smtptext = {0}; void get(ch) char *ch; { substdio_get(&smtpfrom,ch,1); if (*ch != '\r') if (smtptext.len < HUGESMTPTEXT) if (!stralloc_append(&smtptext,ch)) temp_nomem(); } unsigned long smtpcode() { unsigned char ch; unsigned long code; if (!stralloc_copys(&smtptext,"")) temp_nomem(); get(&ch); code = ch - '0'; get(&ch); code = code * 10 + (ch - '0'); get(&ch); code = code * 10 + (ch - '0'); for (;;) { get(&ch); if (ch != '-') break; while (ch != '\n') get(&ch); get(&ch); get(&ch); get(&ch); } while (ch != '\n') get(&ch); return code; } saa ehlokw = {0}; /* list of EHLO keywords and parameters */ int maxehlokwlen = 0; unsigned long ehlo() { stralloc *sa; char *s, *e, *p; unsigned long code; if (ehlokw.len > maxehlokwlen) maxehlokwlen = ehlokw.len; ehlokw.len = 0; substdio_puts(&smtpto, "EHLO "); substdio_put(&smtpto, helohost.s, helohost.len); substdio_puts(&smtpto, "\r\n"); substdio_flush(&smtpto); code = smtpcode(); if (code != 250) return code; s = smtptext.s; while (*s++ != '\n') ; /* skip the first line: contains the domain */ e = smtptext.s + smtptext.len - 6; /* 250-?\n */ while (s <= e) { int wasspace = 0; if (!saa_readyplus(&ehlokw, 1)) temp_nomem(); sa = ehlokw.sa + ehlokw.len++; if (ehlokw.len > maxehlokwlen) *sa = sauninit; else sa->len = 0; /* smtptext is known to end in a '\n' */ for (p = (s += 4); ; ++p) if (*p == '\n' || *p == ' ' || *p == '\t') { if (!wasspace) if (!stralloc_catb(sa, s, p - s) || !stralloc_0(sa)) temp_nomem(); if (*p == '\n') break; wasspace = 1; } else if (wasspace == 1) { wasspace = 0; s = p; } s = ++p; /* keyword should consist of alpha-num and '-' * broken AUTH might use '=' instead of space */ for (p = sa->s; *p; ++p) if (*p == '=') { *p = 0; break; } } return 250; } void outsmtptext() { int i; if (smtptext.s) if (smtptext.len) { out("Remote host said: "); for (i = 0;i < smtptext.len;++i) if (!smtptext.s[i]) smtptext.s[i] = '?'; if (substdio_put(subfdoutsmall,smtptext.s,smtptext.len) == -1) _exit(0); smtptext.len = 0; } } void smtp_quit() { #ifdef TLS /* shouldn't talk to the client unless in an appropriate state */ if ((!smtps && !ssl) || (ssl && SSL_is_init_finished(ssl)) || (!smtps && ssl && SSL_in_before(ssl))) #endif substdio_putsflush(&smtpto,"QUIT\r\n"); /* waiting for remote side is just too ridiculous */ } void quit2(const char *prepend, const char *append, int flagdie) { smtp_quit(); out(prepend); outhost(); if (append) out(append); out(".\n"); outsmtptext(); #if defined(TLS) && defined(DEBUG) if (ssl) { X509 *peercert; out("STARTTLS proto="); out(SSL_get_version(ssl)); out("; cipher="); out(SSL_get_cipher(ssl)); /* we want certificate details */ if (peercert = SSL_get_peer_certificate(ssl)) { char *str; str = X509_NAME_oneline(X509_get_subject_name(peercert), NULL, 0); out("; subject="); out(str); OPENSSL_free(str); str = X509_NAME_oneline(X509_get_issuer_name(peercert), NULL, 0); out("; issuer="); out(str); OPENSSL_free(str); X509_free(peercert); } out(";\n"); } #endif (flagdie) ? zerodie() : zeroflush(); } void quit(char *prepend, char *append) { quit2(prepend, append, 1); } void blast() { int r; char ch; #ifdef SMTPUTF8 substdio_put(&smtpto,header.s,header.len); #endif for (;;) { r = substdio_get(&ssin,&ch,1); if (r == 0) break; if (r == -1) temp_read(); if (ch == '.') substdio_put(&smtpto,".",1); while (ch != '\n') { substdio_put(&smtpto,&ch,1); r = substdio_get(&ssin,&ch,1); if (r == 0) perm_partialline(); if (r == -1) temp_read(); } substdio_put(&smtpto,"\r\n",2); } flagcritical = 1; substdio_put(&smtpto,".\r\n",3); substdio_flush(&smtpto); } #ifdef TLS # define TLS_QUIT quit(ssl ? "; connected to " : "; connecting to ", NULL) void tls_quit(const char *s1, const char *s2) { out(s1); if (s2) { out(": "); out(s2); } TLS_QUIT; } # define tls_quit_error(s) tls_quit(s, ssl_error()) int match_mx_host(const char *mx_host, const char *s, int len) { if (!case_diffb(mx_host, len, s) && !mx_host[len]) return 1; /* we also match if the name is *.domainname */ if (*s == '*') { const char *domain = mx_host + str_chr(mx_host, '.'); if (!case_diffb(domain, --len, ++s) && !domain[len]) return 1; } return 0; } /* don't want to fail handshake if certificate can't be verified */ int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; } /* * @return 1 ...on success * @return 0 ...TLS failed, continue plaintext * @return -1 ...skip to next MX */ static int tls_init(struct ip_mx *current_mx) { int i; SSL *myssl; SSL_CTX *ctx; stralloc saciphers = {0}; const char *ciphers, *servercert = 0; const char *mx_host = current_mx->fqdn; const char *failure_class = "Z"; // per default we do temp failures if (mx_host) { struct stat st; stralloc tmp = {0}; if (!stralloc_copys(&tmp, "control/tlshosts/") || !stralloc_catb(&tmp, mx_host, str_len(mx_host)) || !stralloc_catb(&tmp, ".pem", 5)) temp_nomem(); if (stat(tmp.s, &st) == 0) servercert = tmp.s; else { if (!stralloc_copys(&tmp, "control/notlshosts/") || !stralloc_catb(&tmp, mx_host, str_len(mx_host)+1)) temp_nomem(); if ((stat("control/tlshosts/exhaustivelist", &st) == 0) || (stat(tmp.s, &st) == 0)) { alloc_free(tmp.s); return 0; } alloc_free(tmp.s); if (env_get("NOTLS")) return 0; } } int tls_required = (smtps || servercert != NULL); /* per-domain "require TLS"-settings */ if (!tls_required) { int at = byte_rchr(sender.s, sender.len, '@') + 1; if (at < sender.len) { stralloc tmp = { 0 }; if (!stralloc_copys(&tmp, "control/tlsrequire/") || !stralloc_catb(&tmp, sender.s + at, sender.len - at) || !stralloc_0(&tmp)) // sender is not 0-terminated temp_nomem(); if (control_readint(&tls_required, tmp.s) == -1) temp_control(); tls_required = (tls_required & 0x02) ? 1 : 0; // 2nd bit is SMTP outgoing if (tls_required) failure_class = "D"; // do perm failures for faster user feedback } } /* DANE: lookup TLSA records */ stralloc tlsa_rr = { 0 }; if (mx_host && !servercert && current_mx->validated) { stralloc tlsa_label = { 0 }; char port[FMT_ULONG]; if (!stralloc_copyb(&tlsa_label, "_", 1)) temp_nomem(); port[fmt_ulong(port, smtp_port)] = 0; if (!stralloc_cats(&tlsa_label, port)) temp_nomem(); if (!stralloc_cats(&tlsa_label, "._tcp.")) temp_nomem(); if (!stralloc_cats(&tlsa_label, mx_host)) temp_nomem(); switch (dns_tlsa(&tlsa_rr, &tlsa_label)) { case DNS_MEM: temp_nomem(); case DNS_SOFT: temp_dns(); case DNS_HARD: tlsa_rr.len = 0; // no record found } if (tlsa_rr.len && dns_last_query_validated()) tls_required = 1; } if (!smtps) { stralloc *sa = ehlokw.sa; unsigned int len = ehlokw.len; /* look for STARTTLS among EHLO keywords */ for ( ; len && case_diffs(sa->s, "STARTTLS"); ++sa, --len) ; if (!len) { if (!tls_required) return 0; out(failure_class); out("TLS is required, but was not offered by host"); smtptext.len = 0; TLS_QUIT; } } SSL_library_init(); ctx = SSL_CTX_new(TLS_client_method()); if (!ctx) { smtptext.len = 0; tls_quit_error("ZTLS error initializing ctx"); } /* TLS renegotiation is possible cpu resource attack */ SSL_CTX_set_options(ctx, SSL_OP_NO_RENEGOTIATION); /* SMTP does not suffer from truncation attacks due to its application framing */ SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF); /* we verify ourself below. see SSL_get_verify_result */ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); if (SSL_CTX_dane_enable(ctx) <= 0) { smtptext.len = 0; tls_quit_error("ZTLS error initializing dane ctx"); } if (servercert) { if (!SSL_CTX_load_verify_locations(ctx, servercert, NULL)) { SSL_CTX_free(ctx); smtptext.len = 0; out("ZTLS unable to load "); tls_quit_error(servercert); } /* set the callback here; SSL_set_verify didn't work before 0.9.6c */ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb); } /* let the other side complain if it needs a cert and we don't have one */ if (SSL_CTX_use_certificate_chain_file(ctx, CLIENTCERT)) SSL_CTX_use_PrivateKey_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM); myssl = SSL_new(ctx); SSL_CTX_free(ctx); if (!myssl) { smtptext.len = 0; tls_quit_error("ZTLS error initializing ssl"); } /* while the server is preparing a responce, do something else */ if (control_readfile(&saciphers, "control/tlsclientciphers", 0) == -1) { SSL_free(myssl); temp_control(); } if (saciphers.len) { for (i = 0; i < saciphers.len - 1; ++i) if (!saciphers.s[i]) saciphers.s[i] = ':'; ciphers = saciphers.s; } else ciphers = "DEFAULT"; SSL_set_cipher_list(myssl, ciphers); alloc_free(saciphers.s); stralloc opensslconf = {0}; if (control_readfile(&opensslconf, "control/opensslconf", 0) == -1) { SSL_free(myssl); temp_control(); } if (opensslconf.len) { SSL_CONF_CTX *cctx = SSL_CONF_CTX_new(); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); /* client + server so we can share one single file */ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT); 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) { SSL_free(myssl); out("Zopensslconf \""); out(cmd); out(" "); out(arg); out("\" failed: "); out(ssl_error()); TLS_QUIT; } } (void)SSL_CONF_CTX_finish(cctx); } /* set SNI hostname */ if (mx_host) SSL_set_tlsext_host_name(myssl, mx_host); /* DANE: enable + add records */ if (tlsa_rr.len) { if (SSL_dane_enable(myssl, mx_host) <= 0) { smtptext.len = 0; tls_quit_error("ZTLS error enabling dane"); } SSL_dane_set_flags(myssl, DANE_FLAG_NO_DANE_EE_NAMECHECKS); // loop through TLSA rr int pos = 0, num_usable = 0; unsigned char *rr_data = (unsigned char *)tlsa_rr.s; while (pos < tlsa_rr.len && tlsa_rr.len - pos > 2 + 4) // sizeof(rrlen) + min(rrdata) { unsigned short rrlen = (rr_data[pos] << 8) + rr_data[pos + 1]; pos += 2; uint8_t usage = rr_data[pos]; uint8_t selector = rr_data[pos + 1]; uint8_t mtype = rr_data[pos + 2]; /* * Opportunistic DANE TLS clients support only DANE-TA(2) or DANE-EE(3). * They treat all other certificate usages, and in particular PKIX-TA(0) * and PKIX-EE(1), as unusable. */ switch (usage) { default: case 0: /* PKIX-TA(0) */ case 1: /* PKIX-EE(1) */ pos += rrlen; continue; case 2: /* DANE-TA(2) */ case 3: /* DANE-EE(3) */ break; } // rrlen includes usage+selector+mtype (3) byte. remove unsigned const char *cdata = rr_data + pos + 3; size_t cdlen = rrlen - 3; int tlsa_added = SSL_dane_tlsa_add(myssl, usage, selector, mtype, cdata, cdlen); if (tlsa_added < 0) { smtptext.len = 0; tls_quit_error("ZTLS error adding DANE TLSA record"); } else if (tlsa_added > 0) ++num_usable; pos += rrlen; } // no records usable. fallback to unauthenticated TLS if (num_usable == 0) tlsa_rr.len = 0; } if (!smtps) substdio_putsflush(&smtpto, "STARTTLS\r\n"); SSL_set_fd(myssl, smtpfd); /* read the responce to STARTTLS */ if (!smtps) { unsigned int code = smtpcode(); if (code != 220) { SSL_free(myssl); if (!tls_required) return 0; out((code >= 500) ? "D" : failure_class); out("TLS is required, but host refused to start TLS"); TLS_QUIT; } smtptext.len = 0; } ssl = myssl; if (ssl_timeoutconn(timeout, smtpfd, smtpfd, myssl) <= 0) { if (tls_required) { out(failure_class); tls_quit("TLS connect failed", ssl_error_str()); } else { out("lTLS connect failed"); const char *err = ssl_error_str(); if (err) { out(": "); out(err); } out("; retrying without TLS\n"); zeroflush(); env_put("NOTLS=1"); execvp(*myargv, myargv); //FIXME this will cause all mx to be retried out("DUnable to execute myself\n"); zerodie(); } } if (servercert) { X509 *peercert; STACK_OF(GENERAL_NAME) *gens; int r = SSL_get_verify_result(myssl); if (r != X509_V_OK) { out("ZTLS unable to verify server with "); tls_quit(servercert, X509_verify_cert_error_string(r)); } alloc_free(servercert); peercert = SSL_get_peer_certificate(myssl); if (!peercert) { out("ZTLS unable to verify server "); tls_quit(mx_host, "no certificate provided"); } /* RFC 2595 section 2.4: find a matching name * first find a match among alternative names */ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0); if (gens) { for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) { const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); if (gn->type == GEN_DNS) if (match_mx_host(mx_host, ASN1_STRING_get0_data(gn->d.dNSName), ASN1_STRING_length(gn->d.dNSName))) break; } sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } /* no alternative name matched, look up commonName */ if (!gens || i >= r) { stralloc peer = {0}; X509_NAME *subj = X509_get_subject_name(peercert); i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1); if (i >= 0) { X509_NAME_ENTRY *entry = X509_NAME_get_entry(subj, i); ASN1_STRING *s = X509_NAME_ENTRY_get_data(entry); if (s) { peer.len = ASN1_STRING_length(s); peer.s = (char *)ASN1_STRING_get0_data(s); } } if (peer.len <= 0) { out("ZTLS unable to verify server "); tls_quit(mx_host, "certificate contains no valid commonName"); } if (!match_mx_host(mx_host, peer.s, peer.len)) { out("ZTLS unable to verify server "); out(mx_host); out(": received certificate for "); outsafe(&peer); TLS_QUIT; } } X509_free(peercert); } /* DANE: verify result */ else if (tlsa_rr.len) { if (!SSL_get_peer_certificate(myssl) || SSL_get_verify_result(myssl) != X509_V_OK) { out("lNo TLSA record matched: "); out(mx_host); quit2("/", NULL, 0); return -1; } } if (smtps) { unsigned int code = smtpcode(); if (code >= 500 && code < 600) quit("DTLS connected to "," but greeting failed"); if (code >= 400 && code < 500) return -1; /* try next MX, see RFC-2821 */ if (code != 220) quit("ZTLS connected to "," but greeting failed"); } return 1; } #endif stralloc recip = {0}; #ifdef SMTPUTF8 int utf8string(unsigned char *ch, int len) { int i = 0; while (i < len) if (ch[i++] > 127) return 1; return 0; } int utf8received() { int r, i, received = 0; char ch; stralloc receivedline = {0}; for (;;) { /* we consider only our own last written header */ r = substdio_get(&ssin,&ch,1); if (r == 0) break; if (r == -1) temp_read(); if (ch == '\n' && receivedline.len) { if (!stralloc_append(&header,"\r")) temp_nomem(); /* received.c does not add '\r' */ if (!stralloc_append(&header,"\n")) temp_nomem(); if (case_startb(receivedline.s,5,"Date:")) return 0; /* header to quit asap */ if (case_startb(receivedline.s,14,"Received: from")) received++; /* found Received header */ if (received) { if (case_startb(receivedline.s,5," by ")) { for (i = 6; i < receivedline.len-6; ++i) if (*(receivedline.s+i) == ' ') if (case_startb(receivedline.s+i+1,9,"with UTF8")) return 1; return 0; } } if (!stralloc_copys(&receivedline,"")) temp_nomem(); } else if (ch == '\n' && !receivedline.len) { /* we got an empty newline. probably body start */ if (!stralloc_append(&header,"\r")) temp_nomem(); if (!stralloc_append(&header,"\n")) temp_nomem(); return 0; } else { if (!stralloc_append(&header,&ch)) temp_nomem(); if (!stralloc_catb(&receivedline,&ch,1)) temp_nomem(); } } return 0; } #endif void mail_without_auth() { substdio_puts(&smtpto,"MAIL FROM:<"); substdio_put(&smtpto,sender.s,sender.len); substdio_put(&smtpto,">",1); #ifdef SMTPUTF8 if (flagutf8 || utf8received()) substdio_puts(&smtpto, " SMTPUTF8"); #endif substdio_puts(&smtpto,"\r\n"); substdio_flush(&smtpto); } static void smtp(struct ip_mx *current_mx) { unsigned long code = 0; int flagbother; #ifdef TLS if (smtp_port == 465) smtps = 1; if (!smtps) { #endif code = smtpcode(); if (code >= 500 && code < 600) quit("DConnected to "," but greeting failed"); if (code >= 400 && code < 500) return; /* try next MX, see RFC-2821 */ if (code != 220) quit("ZConnected to "," but greeting failed"); code = ehlo(); #ifdef TLS } #endif #ifdef TLS int tls = tls_init(current_mx); if (tls == -1) { if (ssl) ssl_free(ssl); ssl = NULL; return; /* try next MX */ } else if (tls > 0) /* RFC2487 says we should issue EHLO (even if we might not need * extensions); at the same time, it does not prohibit a server * to reject the EHLO and make us fallback to HELO */ code = ehlo(); #endif if (code == 250) { /* add EHLO response checks here */ /* and if EHLO failed, use HELO */ } else { substdio_puts(&smtpto,"HELO "); substdio_put(&smtpto,helohost.s,helohost.len); substdio_puts(&smtpto,"\r\n"); substdio_flush(&smtpto); code = smtpcode(); if (code >= 500) quit("DConnected to "," but my name was rejected"); if (code != 250) quit("ZConnected to "," but my name was rejected"); } int i = 0; if (auth_smtp_user.len && auth_smtp_pass.len) { while((i += str_chr(smtptext.s+i,'\n') + 1) && (i+8 < smtptext.len) && str_diffn(smtptext.s+i+4,"AUTH",4)); if (((i+9 < smtptext.len) && (str_diffn(smtptext.s+i+9," ",1) || str_diffn(smtptext.s+i+9,"=",1))) && ( i += str_chr(smtptext.s+i,'L') + 1 ) && str_diffn(smtptext.s+i+1,"OGIN",4)) { if (b64encode(&auth_smtp_user,&auth_b64_user)) quit("ZConnected to "," but unable to base64encode user"); if (b64encode(&auth_smtp_pass,&auth_b64_pass)) quit("ZConnected to "," but unable to base64encode pass"); substdio_puts(&smtpto,"AUTH LOGIN\r\n"); substdio_flush(&smtpto); if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (AUTH LOGIN)"); substdio_put(&smtpto,auth_b64_user.s,auth_b64_user.len); substdio_puts(&smtpto,"\r\n"); substdio_flush(&smtpto); if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (username)"); substdio_put(&smtpto,auth_b64_pass.s,auth_b64_pass.len); substdio_puts(&smtpto,"\r\n"); substdio_flush(&smtpto); if (smtpcode() != 235) quit("ZConnected to "," but authentication was rejected (password)"); substdio_puts(&smtpto,"MAIL FROM:<"); substdio_put(&smtpto,sender.s,sender.len); substdio_puts(&smtpto,"> AUTH=<"); substdio_put(&smtpto,sender.s,sender.len); substdio_put(&smtpto,">",1); #ifdef SMTPUTF8 if (flagutf8 || utf8received()) substdio_puts(&smtpto, " SMTPUTF8"); #endif substdio_puts(&smtpto,"\r\n"); substdio_flush(&smtpto); if(!stralloc_copys(&auth_status, "Delivered with authenticated connection to \n")) temp_nomem(); if(!stralloc_0(&auth_status)) temp_nomem(); } else { no_supported_auth(); mail_without_auth(); } } else { auth_user_not_set(); mail_without_auth(); } code = smtpcode(); if (code >= 500) quit("DConnected to "," but sender was rejected"); if (code >= 400) quit("ZConnected to "," but sender was rejected"); flagbother = 0; for (int i = 0;i < reciplist.len;++i) { substdio_puts(&smtpto,"RCPT TO:<"); substdio_put(&smtpto,reciplist.sa[i].s,reciplist.sa[i].len); substdio_puts(&smtpto,">\r\n"); substdio_flush(&smtpto); code = smtpcode(); if (code >= 500) { out("h"); out(auth_status.s); outhost(); out(" does not like recipient.\n"); outsmtptext(); zero(); } else if (code >= 400) { out("s"); out(auth_status.s); outhost(); out(" does not like recipient.\n"); outsmtptext(); zero(); } else { /* * James Raftery * Log _real_ envelope recipient, post canonicalisation. */ out("r"); out(auth_status.s); out("<"); outsafe(&reciplist.sa[i]); out("> "); zero(); flagbother = 1; } } if (!flagbother) quit("DGiving up on ",""); substdio_putsflush(&smtpto,"DATA\r\n"); code = smtpcode(); if (code >= 500) quit("D"," failed on DATA command"); if (code >= 400) quit("Z"," failed on DATA command"); blast(); code = smtpcode(); flagcritical = 0; if (code >= 500) quit("D"," failed after I sent the message"); if (code >= 400) quit("Z"," failed after I sent the message"); quit("K"," accepted message"); } int qmtp_priority(int pref) { if (pref < 12800) return 0; if (pref > 13055) return 0; if (pref % 16 == 1) return 1; return 0; } void qmtp() { struct stat st; unsigned long len; char *x; int i; int n; unsigned char ch; char num[FMT_ULONG]; int flagallok; if (fstat(0,&st) == -1) quit("Z", " unable to fstat stdin"); len = st.st_size; /* the following code was substantially taken from serialmail'ss serialqmtp.c */ substdio_put(&smtpto,num,fmt_ulong(num,len+1)); substdio_put(&smtpto,":\n",2); while (len > 0) { n = substdio_feed(&ssin); if (n <= 0) _exit(32); /* wise guy again */ x = substdio_PEEK(&ssin); substdio_put(&smtpto,x,n); substdio_SEEK(&ssin,n); len -= n; } substdio_put(&smtpto,",",1); len = sender.len; substdio_put(&smtpto,num,fmt_ulong(num,len)); substdio_put(&smtpto,":",1); substdio_put(&smtpto,sender.s,sender.len); substdio_put(&smtpto,",",1); len = 0; for (i = 0;i < reciplist.len;++i) len += fmt_ulong(num,reciplist.sa[i].len) + 1 + reciplist.sa[i].len + 1; substdio_put(&smtpto,num,fmt_ulong(num,len)); substdio_put(&smtpto,":",1); for (i = 0;i < reciplist.len;++i) { substdio_put(&smtpto,num,fmt_ulong(num,reciplist.sa[i].len)); substdio_put(&smtpto,":",1); substdio_put(&smtpto,reciplist.sa[i].s,reciplist.sa[i].len); substdio_put(&smtpto,",",1); } substdio_put(&smtpto,",",1); substdio_flush(&smtpto); flagallok = 1; for (i = 0;i < reciplist.len;++i) { len = 0; for (;;) { get(&ch); if (ch == ':') break; if (len > 200000000) temp_proto(); if (ch - '0' > 9) temp_proto(); len = 10 * len + (ch - '0'); } if (!len) temp_proto(); get(&ch); --len; if ((ch != 'Z') && (ch != 'D') && (ch != 'K')) temp_proto(); if (!stralloc_copyb(&smtptext,&ch,1)) temp_proto(); if (!stralloc_cats(&smtptext,"qmtp: ")) temp_nomem(); while (len > 0) { get(&ch); --len; } for (len = 0;len < smtptext.len;++len) { ch = smtptext.s[len]; if ((ch < 32) || (ch > 126)) smtptext.s[len] = '?'; } get(&ch); if (ch != ',') temp_proto(); smtptext.s[smtptext.len-1] = '\n'; if (smtptext.s[0] == 'K') out("r"); else if (smtptext.s[0] == 'D') { out("h"); flagallok = 0; } else { /* if (smtptext.s[0] == 'Z') */ out("s"); flagallok = 0; } if (substdio_put(subfdoutsmall,smtptext.s+1,smtptext.len-1) == -1) temp_noconn(); zero(); } if (!flagallok) { out("DGiving up on ");outhost();out("\n"); } else { out("KAll received okay by ");outhost();out("\n"); } zerodie(); } stralloc canonbox = {0}; void addrmangle(saout,s) stralloc *saout; /* host has to be canonical, box has to be quoted */ char *s; { int j; #ifdef SMTPUTF8 if (!flagutf8) flagutf8 = utf8string(s, str_len(s)); #endif j = str_rchr(s,'@'); if (!s[j]) { if (!stralloc_copys(saout,s)) temp_nomem(); return; } if (!stralloc_copys(&canonbox,s)) temp_nomem(); canonbox.len = j; if (!quote(saout,&canonbox)) temp_nomem(); if (!stralloc_cats(saout,"@")) temp_nomem(); if (!stralloc_cats(saout,s + j + 1)) temp_nomem(); } void getcontrols() { if (control_init() == -1) temp_control(); if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control(); if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1) temp_control(); if (control_rldef(&helohost,"control/helohost",1,(char *) 0) != 1) temp_control(); switch(control_readfile(&routes,"control/smtproutes",0)) { case -1: temp_control(); case 0: if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break; case 1: if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break; } } void main(argc,argv) int argc; char **argv; { static ipalloc ip = {0}; int i,j; unsigned long random; char **recips; unsigned long prefme; char *relayhost; #ifdef TLS myargv = argv; #endif sig_pipeignore(); if (argc < 4) perm_usage(); if (chdir(auto_qmail) == -1) temp_chdir(); getcontrols(); if (!stralloc_copys(&host,argv[1])) temp_nomem(); if (!stralloc_copys(&auth_smtp_user,"")) temp_nomem(); if (!stralloc_copys(&auth_smtp_pass,"")) temp_nomem(); relayhost = 0; for (i = 0;i <= host.len;++i) if ((i == 0) || (i == host.len) || (host.s[i] == '.')) if ((relayhost = constmap(&maproutes,host.s + i,host.len - i))) break; if (relayhost && !*relayhost) relayhost = 0; if (relayhost) { i = str_chr(relayhost,' '); if (relayhost[i]) { j = str_chr(relayhost + i + 1,' '); if (relayhost[j]) { relayhost[i] = 0; relayhost[i + j + 1] = 0; if (!stralloc_copys(&auth_smtp_user,relayhost + i + 1)) temp_nomem(); if (!stralloc_copys(&auth_smtp_pass,relayhost + i + j + 2)) temp_nomem(); } } i = str_chr(relayhost,':'); if (relayhost[i]) { scan_ulong(relayhost + i + 1,&smtp_port); relayhost[i] = 0; } if (!stralloc_copys(&host,relayhost)) temp_nomem(); } else { #ifdef SMTPUTF8 char *asciihost = 0; if (!stralloc_0(&host)) temp_nomem(); --host.len; switch (idn2_lookup_u8(host.s,(uint8_t**)&asciihost,IDN2_NFC_INPUT)) { case IDN2_OK: break; case IDN2_MALLOC: temp_nomem(); default: perm_dns(); } if (!stralloc_copys(&idnhost,asciihost)) temp_nomem(); if (!stralloc_0(&idnhost)) temp_nomem(); #endif } addrmangle(&sender,argv[2]); if (!saa_readyplus(&reciplist,0)) temp_nomem(); if (ipme_init() != 1) temp_oserr(); recips = argv + 3; while (*recips) { if (!saa_readyplus(&reciplist,1)) temp_nomem(); reciplist.sa[reciplist.len] = sauninit; addrmangle(reciplist.sa + reciplist.len,*recips); ++reciplist.len; ++recips; } random = now() + (getpid() << 16); #ifdef SMTPUTF8 switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&idnhost,random)) { #else switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random)) { #endif case DNS_MEM: temp_nomem(); case DNS_SOFT: temp_dns(); case DNS_HARD: perm_dns(); case 1: if (ip.len <= 0) temp_dns(); } if (ip.len <= 0) perm_nomx(); prefme = 100000; for (i = 0;i < ip.len;++i) if (ipme_is(&ip.ix[i].ip)) if (ip.ix[i].pref < prefme) prefme = ip.ix[i].pref; if (relayhost) prefme = 300000; for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme) break; if (i >= ip.len) perm_ambigmx(); for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme) { if (tcpto(&ip.ix[i].ip)) continue; smtpfd = socket(AF_INET,SOCK_STREAM,0); if (smtpfd == -1) temp_oserr(); #if 0 if (qmtp_priority(ip.ix[i].pref)) { if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) qmtp_port,timeoutconnect) == 0) { tcpto_err(&ip.ix[i].ip,0); partner = ip.ix[i].ip; qmtp(); /* does not return */ } close(smtpfd); smtpfd = socket(AF_INET,SOCK_STREAM,0); if (smtpfd == -1) temp_oserr(); } #endif if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) smtp_port,timeoutconnect) == 0) { tcpto_err(&ip.ix[i].ip,0); partner = ip.ix[i].ip; smtp(&ip.ix[i]); /* only returns when the next MX is to be tried */ } tcpto_err(&ip.ix[i].ip,errno == error_timeout || errno == error_refused); close(smtpfd); } temp_noconn(); }