From 8f9afec35595f7c376876aa04dee08666614103c Mon Sep 17 00:00:00 2001 From: manuel Date: Sat, 4 Jul 2015 19:28:30 +0200 Subject: add SMTP DANE support --- qmail-remote.c | 161 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 51 deletions(-) (limited to 'qmail-remote.c') diff --git a/qmail-remote.c b/qmail-remote.c index b5b93d5..4227718 100644 --- a/qmail-remote.c +++ b/qmail-remote.c @@ -62,16 +62,24 @@ struct ip_address partner; # include "tls.h" # include "ssl_timeoutio.h" # include +# include +# include + # define EHLO 1 +# define CLIENTCERT "control/clientcert.pem" int tls_init(); const char *ssl_err_str = 0; char **myargv; -#endif +val_context_t *dane_context = NULL; +int dane_context_failed = 0; +struct val_danestatus *dane_status = NULL; +#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 zerodie() { zero(); substdio_flush(subfdoutsmall); _exit(0); } +void zeroflush() { zero(); substdio_flush(subfdoutsmall); } +void zerodie() { zeroflush(); _exit(0); } void outsafe(sa) stralloc *sa; { int i; char ch; for (i = 0;i < sa->len;++i) { ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?'; @@ -213,7 +221,6 @@ unsigned long smtpcode() return code; } -#ifdef EHLO saa ehlokw = {0}; /* list of EHLO keywords and parameters */ int maxehlokwlen = 0; @@ -270,7 +277,6 @@ unsigned long ehlo() return 250; } -#endif void outsmtptext() { @@ -284,9 +290,7 @@ void outsmtptext() } } -void quit(prepend,append) -char *prepend; -char *append; +void smtp_quit() { #ifdef TLS /* shouldn't talk to the client unless in an appropriate state */ @@ -295,9 +299,14 @@ char *append; #endif substdio_putsflush(&smtpto,"QUIT\r\n"); /* waiting for remote side is just too ridiculous */ +} + +void quit2(char *prepend, char *append, int flagdie) +{ + smtp_quit(); out(prepend); outhost(); - out(append); + if (append) out(append); out(".\n"); outsmtptext(); @@ -324,7 +333,11 @@ char *append; } #endif - zerodie(); + (flagdie) ? zerodie() : zeroflush(); +} + +void quit(char *prepend, char *append) { + quit2(prepend, append, 1); } void blast() @@ -355,7 +368,7 @@ void blast() #ifdef TLS char *partner_fqdn = 0; -# define TLS_QUIT quit(ssl ? "; connected to " : "; connecting to ", "") +# 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; @@ -376,6 +389,11 @@ int match_partner(const char *s, int len) /* 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 + */ int tls_init() { int i; @@ -402,26 +420,52 @@ int tls_init() return 0; } alloc_free(tmp.s); + if (env_get("NOTLS")) return 0; } } - + + /* DANE starts here */ + int dane_retval = VAL_DANE_INTERNAL_ERROR; + int tls_required = (smtps || servercert != NULL); + + if (partner_fqdn && !servercert && !dane_context_failed) { + if (val_create_context(NULL, &dane_context) != VAL_NO_ERROR) { + dane_context_failed = 1; + out("lUnable to initialize libval context\n"); + zeroflush(); + } + + /* DANE lookup TLSA records */ + if (dane_context) { + if (dane_status != NULL) + val_free_dane(dane_status); + struct val_daneparams dane_params = { + .port = smtp_port, + .proto = DANE_PARAM_PROTO_TCP + }; + dane_retval = val_getdaneinfo(dane_context, partner_fqdn, &dane_params, &dane_status); + if (dane_retval == VAL_DANE_NOERROR) + 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 (!servercert) return 0; - out("ZNo TLS achieved while "); out(servercert); - out(" exists"); smtptext.len = 0; TLS_QUIT; + if (!tls_required) return 0; + out("ZNo TLS achieved while TLS is required by configuration"); + smtptext.len = 0; TLS_QUIT; } } SSL_library_init(); ctx = SSL_CTX_new(SSLv23_client_method()); if (!ctx) { - if (!smtps && !servercert) return 0; + if (!tls_required) return 0; smtptext.len = 0; tls_quit_error("ZTLS error initializing ctx"); } @@ -437,20 +481,19 @@ int tls_init() } /* let the other side complain if it needs a cert and we don't have one */ -# define CLIENTCERT "control/clientcert.pem" if (SSL_CTX_use_certificate_chain_file(ctx, CLIENTCERT)) SSL_CTX_use_RSAPrivateKey_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM); -# undef CLIENTCERT myssl = SSL_new(ctx); SSL_CTX_free(ctx); if (!myssl) { - if (!smtps && !servercert) return 0; + if (!tls_required) return 0; smtptext.len = 0; tls_quit_error("ZTLS error initializing ssl"); } - if (!smtps) substdio_putsflush(&smtpto, "STARTTLS\r\n"); + if (!smtps) + substdio_putsflush(&smtpto, "STARTTLS\r\n"); /* while the server is preparing a responce, do something else */ if (control_readfile(&saciphers, "control/tlsclientciphers", 0) == -1) @@ -460,10 +503,15 @@ int tls_init() if (!saciphers.s[i]) saciphers.s[i] = ':'; ciphers = saciphers.s; } - else ciphers = "DEFAULT"; + else + ciphers = "DEFAULT"; SSL_set_cipher_list(myssl, ciphers); alloc_free(saciphers.s); + /* set SNI hostname */ + if (partner_fqdn) + SSL_set_tlsext_host_name(myssl, partner_fqdn); + /* SSL_set_options(myssl, SSL_OP_NO_TLSv1); */ SSL_set_fd(myssl, smtpfd); @@ -471,31 +519,29 @@ int tls_init() if (!smtps) { if (smtpcode() != 220) { SSL_free(myssl); - if (!servercert) return 0; - out("ZSTARTTLS rejected while "); - out(servercert); out(" exists"); TLS_QUIT; + if (!tls_required) return 0; + out("ZSTARTTLS rejected while TLS is required by configuration"); + TLS_QUIT; } smtptext.len = 0; } ssl = myssl; if (ssl_timeoutconn(timeout, smtpfd, smtpfd, ssl) <= 0) { - if (servercert) + if (tls_required) tls_quit("ZTLS connect failed", ssl_error_str()); else { - /* shouldn't talk to the client unless in an appropriate state */ - int state = ssl ? ssl->state : SSL_ST_BEFORE; - if (state & SSL_ST_OK || (!smtps && state & SSL_ST_BEFORE)) - substdio_putsflush(&smtpto,"QUIT\r\n"); + smtp_quit(); out("lTLS connect failed: "); out(ssl_error_str()); out("; retrying without TLS\n"); - zero(); - substdio_flush(subfdoutsmall); + zeroflush(); env_put("NOTLS=1"); - execvp(*myargv, myargv); + execvp(*myargv, myargv); //FIXME this will cause all mx to be retried + out("DUnable to execute myself\n"); + zerodie(); } } @@ -550,6 +596,17 @@ int tls_init() X509_free(peercert); } + /* DANE verify tls connection */ + else if (dane_retval == VAL_DANE_NOERROR) { + int do_certcheck = 0; // ignored. DANE SMTP doesn't do any PKIX checks + dane_retval = val_dane_check(dane_context, ssl, dane_status, &do_certcheck); + if (dane_retval != VAL_DANE_NOERROR) { + out("lNo TLSA record matched: "); + out(partner_fqdn); + quit2("/", NULL, 0); + return -1; + } + } if (smtps) if (smtpcode() != 220) quit("ZTLS Connected to "," but greeting failed"); @@ -587,44 +644,46 @@ void smtp() if (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"); - -#ifdef EHLO -# ifdef TLS + +#ifdef TLS if (!smtps) -# endif +#endif code = ehlo(); -# ifdef TLS - if (tls_init()) +#ifdef TLS + int tls = tls_init(); + if (tls == -1) { + if (ssl) + ssl_free(ssl); + ssl = NULL; + return; /* try next MX */ + } + else if (tls) /* 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 +#endif if (code == 250) { /* add EHLO response checks here */ /* and if EHLO failed, use HELO */ } else { -#endif - - 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"); - -#ifdef EHLO + 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"); } -#endif + i = 0; if (auth_smtp_user.len && auth_smtp_pass.len) { while((i += str_chr(smtptext.s+i,'\n') + 1) && -- cgit v1.2.3