From: Wietse Venema Date: Sun, 3 Jun 2018 05:00:00 +0000 (-0500) Subject: postfix-3.4-20180603-nonprod X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fdadad4ba9a25a3a4a700a49718a40c2eeb4e198;p=thirdparty%2Fpostfix.git postfix-3.4-20180603-nonprod --- diff --git a/postfix/HISTORY b/postfix/HISTORY index c8412f1dd..b856888d3 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -23399,6 +23399,38 @@ Apologies for any names omitted. TLS handshake error. Found during code maintenance. File: tlsproxy/tlsproxy.c. + Connection reuse for TLS-encrypted SMTP sessions. This is + work-in-progress, #ifdef USE_TLSPROXY, to avoid contamination + of existing code. + + The idea is to have smtp(8) talk plaintext while tlsproxy(8) + converts between local plaintext and remote ciphertext. + Then, smtp(8) can save plaintext connections to the cache, + and scache(8) holds the handles to the tlsproxy(8) processes. + + This preliminary implementation does not yet support proxying + of DANE attributes from smtp(8) to tlsproxy(8). tlsproxy(8) + does not have permissions to read private key files that + smtp(8) can read. And the name of a connection cache entry + does not yet depend on whether the cached connection uses + TLS, nor does it depend on DANE information. + + Files: global/mail_proto.h, postscreen/postscreen_starttls.c, + posttls-finger/posttls-finger.c, smtp/smtp.c, smtp/smtp.h, + smtp/smtp_params.c, smtp/smtp_proto.c, smtp/smtp_session.c, + smtpd/smtpd.c, tls/tls.h, tls/tls_client.c, tls/tls_proxy.h, + tls/tls_proxy_client_init_print.c, + tls/tls_proxy_client_init_scan.c, + tls/tls_proxy_client_start_print.c, + tls/tls_proxy_client_start_scan.c, tls/tls_proxy_clnt.c, + tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c, + tls/tls_proxy_server_init_print.c, + tls/tls_proxy_server_init_scan.c, + tls/tls_proxy_server_start_print.c, + tls/tls_proxy_server_start_scan.c, tlsproxy/tlsproxy.c, + tlsproxy/tlsproxy.h, tlsproxy/tlsproxy_state.c, util/argv_attr.h, + util/argv_attr_print.c, util/argv_attr_scan.c. + 20180425 Cleanup: dnsblog proccesses now retire voluntarily after @@ -23445,3 +23477,26 @@ Apologies for any names omitted. Documentation: bash syntax to eliminate or view default settings in "postconf -n" output. File: postconf/postconf.c. Contributed by various postfix-users list members. + +20180603 + + TLS reuse: serializer/deserializer support for TLS_DANE and + related data structures. Files: tls/tls_proxy_client_print.c, + tls/tls_proxy_client_scan.c, tls/tls_proxy.h, util/argv_attr.h, + util/argv_attr_print.c, util/argv_attr_scan.c. + + TLS reuse: posttls-finger -X test flag for quick tests. + File: posttls-finger/posttls-finger.c. + + TLS reuse: smtp_use_tlsproxy boolean parameter. This is a + preliminary implementation that should support override via + smtp_tls_policy_maps. Files: smtp.c, smtp_connect.c, + smtp_params.c, smtp_proto.c, smtp_session.c. + + TLS reuse: the SMTP client now includes the requested TLS + security level in the scache(8) key. + + TLS reuse: address-based reuse is allowed only for TLS + levels that require no certificate checks. Perhaps it still + makes sense to save such sessions for reuse by less sensitive + deliveries. Files: smtp/smtp.h smtp/smtp_reuse.c. diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 42106f2c3..440aea655 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -1,5 +1,9 @@ Wish list: + With smtpd_reject_footer=$foo in master.cf, and foo defined + in main.cf, postconf complains about an unuse setting in main.cf. + Note that "postconf -Px" will expand the macro. + Things to do before the stable release: Spell-check, double-word check, HTML validator check, diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 093a3c6b5..a631ad157 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -13337,6 +13337,17 @@ is a concern for you, use the smtp_t Postfix 2.3 and later use smtp_tls_security_level instead.

+ + +
smtp_use_tlsproxy +(default: no)
+ +

The LMTP-specific version of the smtp_use_tlsproxy configuration +parameter. See there for details.

+ +

This feature is available in Postfix 3.4 and later.

+ +
smtp_xforward_timeout diff --git a/postfix/html/posttls-finger.1.html b/postfix/html/posttls-finger.1.html index 44dbd9eaa..db83e928d 100644 --- a/postfix/html/posttls-finger.1.html +++ b/postfix/html/posttls-finger.1.html @@ -280,17 +280,20 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) STARTTLS protocol. The destination domain:port should of course provide such a service. + -X Enable tlsproxy(8) mode. This is an unsupported mode, for pro- + gram development only. + [inet:]domain[:port] Connect via TCP to domain domain, port port. The default port is - smtp (or 24 with LMTP). With SMTP an MX lookup is performed to - resolve the domain to a host, unless the domain is enclosed in - []. If you want to connect to a specific MX host, for instance - mx1.example.com, specify [mx1.example.com] as the destination + smtp (or 24 with LMTP). With SMTP an MX lookup is performed to + resolve the domain to a host, unless the domain is enclosed in + []. If you want to connect to a specific MX host, for instance + mx1.example.com, specify [mx1.example.com] as the destination and example.com as a match argument. When using DNS, the desti- - nation domain is assumed fully qualified and no default domain - or search suffixes are applied; you must use fully-qualified - names or also enable native host lookups (these don't support - dane or dane-only as no DNSSEC validation information is avail- + nation domain is assumed fully qualified and no default domain + or search suffixes are applied; you must use fully-qualified + names or also enable native host lookups (these don't support + dane or dane-only as no DNSSEC validation information is avail- able via native lookups). unix:pathname @@ -299,8 +302,8 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) match ... With no match arguments specified, certificate peername matching uses the compiled-in default strategies for each security level. - If you specify one or more arguments, these will be used as the - list of certificate or public-key digests to match for the fin- + If you specify one or more arguments, these will be used as the + list of certificate or public-key digests to match for the fin- gerprint level, or as the list of DNS names to match in the cer- tificate at the verify and secure levels. If the security level is dane, or dane-only the match names are ignored, and hostname, diff --git a/postfix/makedefs b/postfix/makedefs index 5449732b0..26e950d4b 100644 --- a/postfix/makedefs +++ b/postfix/makedefs @@ -878,7 +878,7 @@ CCARGS="$CCARGS -DSNAPSHOT" # Non-production: needs thorough testing, or major changes are still # needed before the code stabilizes. -#CCARGS="$CCARGS -DNONPROD" +CCARGS="$CCARGS -DNONPROD" # Workaround: prepend Postfix include files before other include files. CCARGS="-I. -I../../include $CCARGS" diff --git a/postfix/man/man1/posttls-finger.1 b/postfix/man/man1/posttls-finger.1 index ca773102a..2515089b7 100644 --- a/postfix/man/man1/posttls-finger.1 +++ b/postfix/man/man1/posttls-finger.1 @@ -251,6 +251,9 @@ provided on port 465 by servers that are compatible with the ad\-hoc SMTP in SSL protocol, rather than the standard STARTTLS protocol. The destination \fIdomain\fR:\fIport\fR should of course provide such a service. +.IP "\fB\-X\fR" +Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode, +for program development only. .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]" Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default port is \fBsmtp\fR (or 24 with LMTP). With SMTP an MX lookup is diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index b2dff862e..3ca7bcfc5 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -8853,6 +8853,11 @@ is a concern for you, use the smtp_tls_per_site feature instead. .PP This feature is available in Postfix 2.2 and later. With Postfix 2.3 and later use smtp_tls_security_level instead. +.SH smtp_use_tlsproxy (default: no) +The LMTP\-specific version of the smtp_use_tlsproxy configuration +parameter. See there for details. +.PP +This feature is available in Postfix 3.4 and later. .SH smtp_xforward_timeout (default: 300s) The Postfix SMTP client time limit for sending the XFORWARD command, and for receiving the remote SMTP server response. diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 4f07119a1..4f7b6adaf 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -690,6 +690,8 @@ while (<>) { s;\bsmtp_per_record_deadline\b;$&;g; s;\bsmtp_send_dummy_mail_auth\b;$&;g; s;\bsmtp_balance_inet_protocols\b;$&;g; + s;\bsmtp_use_tlsproxy\b;$&;g; + s;\blmtp_use_tlsproxy\b;$&;g; s;\bsmtpd_enforce_tls\b;$&;g; s;\bsmtpd_sasl_tls_security_options\b;$&;g; s;\bsmtpd_sasl_type\b;$&;g; diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 23f4f63e6..76670568e 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -16582,6 +16582,22 @@ Postfix versions.

This feature is available in Postfix 3.0 and later.

+%PARAM smtp_use_tlsproxy no + +

Try to use a TLS session multiple times, without reconnecting. +This uses the tlsproxy(8) service to make an outbound SMTP-over-TLS +connection, and uses the sache(8) service to save that connection. +

+ +

This feature is available in Postfix 3.4 and later.

+ +%PARAM smtp_use_tlsproxy no + +

The LMTP-specific version of the smtp_use_tlsproxy configuration +parameter. See there for details.

+ +

This feature is available in Postfix 3.4 and later.

+ %PARAM virtual_alias_address_length_limit 1000

diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index dd2589fe0..31843dfca 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -4008,6 +4008,15 @@ extern char *var_smtp_dns_re_filter; #define DEF_SMTPD_DNS_RE_FILTER "" extern char *var_smtpd_dns_re_filter; + /* + * Share TLS sessions through tlproxy(8). + */ +#define VAR_SMTP_USE_TLSPROXY "smtp_use_tlsproxy" +#define DEF_SMTP_USE_TLSPROXY 0 +#define VAR_LMTP_USE_TLSPROXY "lmtp_use_tlsproxy" +#define DEF_LMTP_USE_TLSPROXY 0 +extern bool var_smtp_use_tlsproxy; + /* * Location of shared-library files. * diff --git a/postfix/src/global/mail_proto.h b/postfix/src/global/mail_proto.h index 363bc71ec..9fc9ab68e 100644 --- a/postfix/src/global/mail_proto.h +++ b/postfix/src/global/mail_proto.h @@ -276,25 +276,6 @@ extern char *mail_pathname(const char *, const char *); #define MAIL_ATTR_DSN_ORCPT "dsn_orig_rcpt" /* dsn original recipient */ #define MAIL_ATTR_SMTPUTF8 "smtputf8" /* RFC6531 support */ - /* - * TLSPROXY support. - */ -#define MAIL_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */ -#define MAIL_ATTR_ROLE "role" /* requested role */ -#define MAIL_ATTR_ROLE_SERVER "server" -#define MAIL_ATTR_ROLE_CLIENT "client" -#define MAIL_ATTR_TIMEOUT "timeout" -#define MAIL_ATTR_PEER_CN "peer_CN" -#define MAIL_ATTR_ISSUER_CN "issuer_CN" -#define MAIL_ATTR_PEER_CERT_FPT "peer_fingerprint" -#define MAIL_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint" -#define MAIL_ATTR_PEER_STATUS "peer_status" -#define MAIL_ATTR_CIPHER_PROTOCOL "cipher_protocol" -#define MAIL_ATTR_CIPHER_NAME "cipher_name" -#define MAIL_ATTR_CIPHER_USEBITS "cipher_usebits" -#define MAIL_ATTR_CIPHER_ALGBITS "cipher_algbits" -#define MAIL_ATTR_SERVER_ID "server_id" - /* * SMTP reply footer support. */ diff --git a/postfix/src/postscreen/postscreen_starttls.c b/postfix/src/postscreen/postscreen_starttls.c index a5d6906e6..863ba8c23 100644 --- a/postfix/src/postscreen/postscreen_starttls.c +++ b/postfix/src/postscreen/postscreen_starttls.c @@ -226,10 +226,10 @@ void psc_starttls_open(PSC_STATE *smtp_state, EVENT_NOTIFY_FN resume_event) vstring_sprintf(remote_endpt, "[%s]:%s", smtp_state->smtp_client_addr, smtp_state->smtp_client_port); attr_print(tlsproxy_stream, ATTR_FLAG_NONE, - SEND_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt)), - SEND_ATTR_INT(MAIL_ATTR_FLAGS, TLS_PROXY_FLAG_ROLE_SERVER), - SEND_ATTR_INT(MAIL_ATTR_TIMEOUT, psc_normal_cmd_time_limit), - SEND_ATTR_STR(MAIL_ATTR_SERVER_ID, MAIL_SERVICE_SMTPD), /* XXX */ + SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)), + SEND_ATTR_INT(TLS_ATTR_FLAGS, TLS_PROXY_FLAG_ROLE_SERVER), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit), + SEND_ATTR_STR(TLS_ATTR_SERVERID, MAIL_SERVICE_SMTPD), /* XXX */ ATTR_TYPE_END); if (vstream_fflush(tlsproxy_stream) != 0) { msg_warn("error sending request to %s service: %m", psc_tlsp_service); diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c index 5f559b4fd..d9d128ff1 100644 --- a/postfix/src/posttls-finger/posttls-finger.c +++ b/postfix/src/posttls-finger/posttls-finger.c @@ -245,6 +245,9 @@ /* SMTP in SSL protocol, rather than the standard STARTTLS protocol. /* The destination \fIdomain\fR:\fIport\fR should of course provide such /* a service. +/* .IP "\fB-X\fR" +/* Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode, +/* for program development only. /* .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]" /* Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default /* port is \fBsmtp\fR (or 24 with LMTP). With SMTP an MX lookup is @@ -357,6 +360,7 @@ #include #include #include +#include /* DNS library. */ @@ -374,6 +378,7 @@ #include #ifdef USE_TLS +#include #include #endif @@ -431,6 +436,7 @@ typedef struct STATE { int force_tlsa; /* -f option */ unsigned port; /* TCP port */ char *dest; /* Full destination spec */ + char *paddr; /* XXX printable addr for proxy */ char *addrport; /* [addr]:port */ char *namaddrport; /* name[addr]:port */ char *nexthop; /* Nexthop domain for verification */ @@ -461,6 +467,7 @@ typedef struct STATE { char *grade; /* Minimum cipher grade */ char *protocols; /* Protocol inclusion/exclusion */ int mxinsec_level; /* DANE for insecure MX RRs? */ + int tlsproxy_mode; #endif OPTIONS options; /* JCL */ } STATE; @@ -699,7 +706,11 @@ static int starttls(STATE *state) int except; RESPONSE *resp; VSTREAM *stream = state->stream; - TLS_CLIENT_START_PROPS tls_props; + TLS_CLIENT_START_PROPS start_props; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; + int cwd_fd; if (state->wrapper_mode == 0) { /* SMTP stream with deadline timeouts */ @@ -743,24 +754,132 @@ static int starttls(STATE *state) else ADD_EXCLUDE(cipher_exclusions, "eNULL"); - state->tls_context = - TLS_CLIENT_START(&tls_props, - ctx = state->tls_ctx, - stream = stream, - timeout = smtp_tmout, - tls_level = state->level, - nexthop = state->nexthop, - host = state->hostname, - namaddr = state->namaddrport, - serverid = state->addrport, - helo = state->helo ? state->helo : "", - protocols = state->protocols, - cipher_grade = state->grade, - cipher_exclusions - = vstring_str(cipher_exclusions), - matchargv = state->match, - mdalg = state->mdalg, - dane = state->ddane ? state->ddane : state->dane); + if (state->tlsproxy_mode) { + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = "-L option", + log_level = state->options.logopts, + verifydepth = DEF_SMTP_TLS_SCERT_VD, + cache_type = "memory", + cert_file = state->certfile, + key_file = state->keyfile, + dcert_file = "", + dkey_file = "", + eccert_file = "", + eckey_file = "", + CAfile = state->CAfile, + CApath = state->CApath, + mdalg = state->mdalg); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = smtp_tmout, + tls_level = state->level, + nexthop = state->nexthop, + host = state->hostname, + namaddr = state->namaddrport, + serverid = state->addrport, + helo = state->helo ? state->helo : "", + protocols = state->protocols, + cipher_grade = state->grade, + cipher_exclusions + = vstring_str(cipher_exclusions), + matchargv = state->match, + mdalg = state->mdalg, + dane = state->ddane ? + state->ddane : state->dane); + +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) +#define var_tlsproxy_service + + if ((cwd_fd = open(".", O_RDONLY)) < 0) + msg_fatal("open(\".\", O_RDONLY): %m"); + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(%s): %m", var_queue_dir); + port_buf = vstring_alloc(100); + vstring_sprintf(port_buf, "%d", ntohs(state->port)); + tlsproxy = + tls_proxy_open(DEF_TLSPROXY_SERVICE /* TODO */ , PROXY_OPEN_FLAGS, + state->stream, state->paddr, + STR(port_buf), smtp_tmout, + state->addrport, &init_props, &start_props); + vstring_free(port_buf); + if (fchdir(cwd_fd) < 0) + msg_fatal("fchdir: %m"); + (void) close(cwd_fd); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. + */ + if (tlsproxy == 0) { + state->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(state->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + state->tls_context = tls_proxy_context_receive(state->stream); + msg_info("%s: subject_CN=%s, issuer_CN=%s, " + "fingerprint=%s, pkey_fingerprint=%s", + state->namaddrport, state->tls_context->peer_CN, + state->tls_context->issuer_CN, + state->tls_context->peer_cert_fprint, + state->tls_context->peer_pkey_fprint); + msg_info("%s TLS connection established to %s: %s with cipher %s " + "(%d/%d bits)", + !TLS_CERT_IS_PRESENT(state->tls_context) ? "Anonymous" : + TLS_CERT_IS_SECURED(state->tls_context) ? "Verified" : + TLS_CERT_IS_TRUSTED(state->tls_context) ? "Trusted" : + "Untrusted", state->namaddrport, + state->tls_context->protocol, + state->tls_context->cipher_name, + state->tls_context->cipher_usebits, + state->tls_context->cipher_algbits); + } + } else { /* tls_proxy_mode */ + state->tls_context = + TLS_CLIENT_START(&start_props, + ctx = state->tls_ctx, + stream = stream, + fd = -1, + timeout = smtp_tmout, + tls_level = state->level, + nexthop = state->nexthop, + host = state->hostname, + namaddr = state->namaddrport, + serverid = state->addrport, + helo = state->helo ? state->helo : "", + protocols = state->protocols, + cipher_grade = state->grade, + cipher_exclusions + = vstring_str(cipher_exclusions), + matchargv = state->match, + mdalg = state->mdalg, + dane = state->ddane ? state->ddane : state->dane); + } /* tlsproxy_mode */ vstring_free(cipher_exclusions); if (state->helo) { myfree(state->helo); @@ -780,12 +899,14 @@ static int starttls(STATE *state) ehlo(state); if (!TLS_CERT_IS_PRESENT(state->tls_context)) msg_info("Server is anonymous"); - else if (state->print_trust) - print_trust_info(state); - state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT | - TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED); - state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY; - tls_update_app_logmask(state->tls_ctx, state->log_mask); + else if (state->tlsproxy_mode == 0) { + if (state->print_trust) + print_trust_info(state); + state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT | + TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED); + state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY; + tls_update_app_logmask(state->tls_ctx, state->log_mask); + } } return (0); } @@ -879,6 +1000,8 @@ static VSTREAM *connect_sock(int sock, struct sockaddr *sa, int salen, vstring_sprintf(vstring_alloc(10), "[%s]:%u", addr, ntohs(state->port))); + state->paddr = mystrdup(addr); /* XXX for tlsproxy */ + /* * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ @@ -1426,9 +1549,14 @@ static int connect_dest(STATE *state) static void disconnect_dest(STATE *state) { #ifdef USE_TLS - if (state->tls_context) - tls_client_stop(state->tls_ctx, state->stream, - smtp_tmout, 0, state->tls_context); + if (state->tls_context) { + if (state->tlsproxy_mode) { + tls_proxy_context_free(state->tls_context); + } else { + tls_client_stop(state->tls_ctx, state->stream, + smtp_tmout, 0, state->tls_context); + } + } state->tls_context = 0; if (state->ddane) tls_dane_free(state->ddane); @@ -1447,6 +1575,10 @@ static void disconnect_dest(STATE *state) myfree(state->addrport); state->addrport = 0; + if (state->paddr) + myfree(state->paddr); + state->paddr = 0; + /* Reused on reconnect */ if (state->reconnect <= 0) { if (state->addr) @@ -1491,7 +1623,7 @@ static int finger(STATE *state) return (1); #ifdef USE_TLS - if (state->reconnect > 0) { + if (state->tlsproxy_mode == 0 && state->reconnect > 0) { int cache_enabled; int cache_count; int cache_hits; @@ -1620,6 +1752,7 @@ static void tls_init(STATE *state) if (state->level <= TLS_LEV_NONE) return; + /* Needed for tls_dane_avail() and other DANE-related processing. */ state->tls_ctx = TLS_CLIENT_INIT(&props, log_param = "-L option", @@ -1672,7 +1805,7 @@ static void parse_options(STATE *state, int argc, char *argv[]) #define OPTS "a:ch:o:St:T:v" #ifdef USE_TLS -#define TLSOPTS "A:Cd:fF:g:k:K:l:L:m:M:p:P:r:w" +#define TLSOPTS "A:Cd:fF:g:k:K:l:L:m:M:p:P:r:wX" state->mdalg = mystrdup("sha1"); state->CApath = mystrdup(""); @@ -1683,6 +1816,7 @@ static void parse_options(STATE *state, int argc, char *argv[]) state->options.logopts = 0; state->level = TLS_LEV_DANE; state->mxinsec_level = TLS_LEV_DANE; + state->tlsproxy_mode = 0; #else #define TLSOPTS "" state->level = TLS_LEV_NONE; @@ -1792,6 +1926,8 @@ static void parse_options(STATE *state, int argc, char *argv[]) break; case 'w': state->wrapper_mode = 1; + case 'X': + state->tlsproxy_mode = 1; break; #endif } diff --git a/postfix/src/smtp/Makefile.in b/postfix/src/smtp/Makefile.in index acd830264..7d4556496 100644 --- a/postfix/src/smtp/Makefile.in +++ b/postfix/src/smtp/Makefile.in @@ -101,8 +101,10 @@ smtp.o: ../../include/flush_clnt.h smtp.o: ../../include/header_body_checks.h smtp.o: ../../include/header_opts.h smtp.o: ../../include/htable.h +smtp.o: ../../include/iostuff.h smtp.o: ../../include/mail_conf.h smtp.o: ../../include/mail_params.h +smtp.o: ../../include/mail_proto.h smtp.o: ../../include/mail_server.h smtp.o: ../../include/mail_version.h smtp.o: ../../include/maps.h @@ -124,6 +126,7 @@ smtp.o: ../../include/string_list.h smtp.o: ../../include/stringops.h smtp.o: ../../include/sys_defs.h smtp.o: ../../include/tls.h +smtp.o: ../../include/tls_proxy.h smtp.o: ../../include/tok822.h smtp.o: ../../include/vbuf.h smtp.o: ../../include/vstream.h @@ -168,6 +171,7 @@ smtp_addr.o: ../../include/string_list.h smtp_addr.o: ../../include/stringops.h smtp_addr.o: ../../include/sys_defs.h smtp_addr.o: ../../include/tls.h +smtp_addr.o: ../../include/tls_proxy.h smtp_addr.o: ../../include/tok822.h smtp_addr.o: ../../include/vbuf.h smtp_addr.o: ../../include/vstream.h @@ -217,6 +221,7 @@ smtp_chat.o: ../../include/string_list.h smtp_chat.o: ../../include/stringops.h smtp_chat.o: ../../include/sys_defs.h smtp_chat.o: ../../include/tls.h +smtp_chat.o: ../../include/tls_proxy.h smtp_chat.o: ../../include/tok822.h smtp_chat.o: ../../include/vbuf.h smtp_chat.o: ../../include/vstream.h @@ -266,6 +271,7 @@ smtp_connect.o: ../../include/stringops.h smtp_connect.o: ../../include/sys_defs.h smtp_connect.o: ../../include/timed_connect.h smtp_connect.o: ../../include/tls.h +smtp_connect.o: ../../include/tls_proxy.h smtp_connect.o: ../../include/tok822.h smtp_connect.o: ../../include/vbuf.h smtp_connect.o: ../../include/vstream.h @@ -305,6 +311,7 @@ smtp_key.o: ../../include/sock_addr.h smtp_key.o: ../../include/string_list.h smtp_key.o: ../../include/sys_defs.h smtp_key.o: ../../include/tls.h +smtp_key.o: ../../include/tls_proxy.h smtp_key.o: ../../include/tok822.h smtp_key.o: ../../include/vbuf.h smtp_key.o: ../../include/vstream.h @@ -344,6 +351,7 @@ smtp_map11.o: ../../include/sock_addr.h smtp_map11.o: ../../include/string_list.h smtp_map11.o: ../../include/sys_defs.h smtp_map11.o: ../../include/tls.h +smtp_map11.o: ../../include/tls_proxy.h smtp_map11.o: ../../include/tok822.h smtp_map11.o: ../../include/vbuf.h smtp_map11.o: ../../include/vstream.h @@ -405,6 +413,7 @@ smtp_proto.o: ../../include/string_list.h smtp_proto.o: ../../include/stringops.h smtp_proto.o: ../../include/sys_defs.h smtp_proto.o: ../../include/tls.h +smtp_proto.o: ../../include/tls_proxy.h smtp_proto.o: ../../include/tok822.h smtp_proto.o: ../../include/uxtext.h smtp_proto.o: ../../include/vbuf.h @@ -450,6 +459,7 @@ smtp_rcpt.o: ../../include/string_list.h smtp_rcpt.o: ../../include/stringops.h smtp_rcpt.o: ../../include/sys_defs.h smtp_rcpt.o: ../../include/tls.h +smtp_rcpt.o: ../../include/tls_proxy.h smtp_rcpt.o: ../../include/tok822.h smtp_rcpt.o: ../../include/vbuf.h smtp_rcpt.o: ../../include/vstream.h @@ -487,6 +497,7 @@ smtp_reuse.o: ../../include/string_list.h smtp_reuse.o: ../../include/stringops.h smtp_reuse.o: ../../include/sys_defs.h smtp_reuse.o: ../../include/tls.h +smtp_reuse.o: ../../include/tls_proxy.h smtp_reuse.o: ../../include/tok822.h smtp_reuse.o: ../../include/vbuf.h smtp_reuse.o: ../../include/vstream.h @@ -527,6 +538,7 @@ smtp_sasl_auth_cache.o: ../../include/string_list.h smtp_sasl_auth_cache.o: ../../include/stringops.h smtp_sasl_auth_cache.o: ../../include/sys_defs.h smtp_sasl_auth_cache.o: ../../include/tls.h +smtp_sasl_auth_cache.o: ../../include/tls_proxy.h smtp_sasl_auth_cache.o: ../../include/tok822.h smtp_sasl_auth_cache.o: ../../include/vbuf.h smtp_sasl_auth_cache.o: ../../include/vstream.h @@ -569,6 +581,7 @@ smtp_sasl_glue.o: ../../include/string_list.h smtp_sasl_glue.o: ../../include/stringops.h smtp_sasl_glue.o: ../../include/sys_defs.h smtp_sasl_glue.o: ../../include/tls.h +smtp_sasl_glue.o: ../../include/tls_proxy.h smtp_sasl_glue.o: ../../include/tok822.h smtp_sasl_glue.o: ../../include/vbuf.h smtp_sasl_glue.o: ../../include/vstream.h @@ -609,6 +622,7 @@ smtp_sasl_proto.o: ../../include/string_list.h smtp_sasl_proto.o: ../../include/stringops.h smtp_sasl_proto.o: ../../include/sys_defs.h smtp_sasl_proto.o: ../../include/tls.h +smtp_sasl_proto.o: ../../include/tls_proxy.h smtp_sasl_proto.o: ../../include/tok822.h smtp_sasl_proto.o: ../../include/vbuf.h smtp_sasl_proto.o: ../../include/vstream.h @@ -648,6 +662,7 @@ smtp_session.o: ../../include/string_list.h smtp_session.o: ../../include/stringops.h smtp_session.o: ../../include/sys_defs.h smtp_session.o: ../../include/tls.h +smtp_session.o: ../../include/tls_proxy.h smtp_session.o: ../../include/tok822.h smtp_session.o: ../../include/vbuf.h smtp_session.o: ../../include/vstream.h @@ -685,6 +700,7 @@ smtp_state.o: ../../include/sock_addr.h smtp_state.o: ../../include/string_list.h smtp_state.o: ../../include/sys_defs.h smtp_state.o: ../../include/tls.h +smtp_state.o: ../../include/tls_proxy.h smtp_state.o: ../../include/tok822.h smtp_state.o: ../../include/vbuf.h smtp_state.o: ../../include/vstream.h @@ -724,6 +740,7 @@ smtp_tls_policy.o: ../../include/string_list.h smtp_tls_policy.o: ../../include/stringops.h smtp_tls_policy.o: ../../include/sys_defs.h smtp_tls_policy.o: ../../include/tls.h +smtp_tls_policy.o: ../../include/tls_proxy.h smtp_tls_policy.o: ../../include/tok822.h smtp_tls_policy.o: ../../include/valid_hostname.h smtp_tls_policy.o: ../../include/valid_utf8_hostname.h @@ -768,6 +785,7 @@ smtp_trouble.o: ../../include/string_list.h smtp_trouble.o: ../../include/stringops.h smtp_trouble.o: ../../include/sys_defs.h smtp_trouble.o: ../../include/tls.h +smtp_trouble.o: ../../include/tls_proxy.h smtp_trouble.o: ../../include/tok822.h smtp_trouble.o: ../../include/vbuf.h smtp_trouble.o: ../../include/vstream.h @@ -804,6 +822,7 @@ smtp_unalias.o: ../../include/sock_addr.h smtp_unalias.o: ../../include/string_list.h smtp_unalias.o: ../../include/sys_defs.h smtp_unalias.o: ../../include/tls.h +smtp_unalias.o: ../../include/tls_proxy.h smtp_unalias.o: ../../include/tok822.h smtp_unalias.o: ../../include/vbuf.h smtp_unalias.o: ../../include/vstream.h diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c index 68d611e26..d057195d4 100644 --- a/postfix/src/smtp/lmtp_params.c +++ b/postfix/src/smtp/lmtp_params.c @@ -61,6 +61,7 @@ VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, 0, }; static const CONFIG_TIME_TABLE lmtp_time_table[] = { @@ -108,6 +109,7 @@ VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand, VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls, VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_LMTP_USE_TLSPROXY, DEF_LMTP_USE_TLSPROXY, &var_smtp_use_tlsproxy, #ifdef USE_TLS VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index ed27be96a..bbd039972 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -810,6 +810,7 @@ /* Global library. */ #include +#include #include #include #include @@ -895,6 +896,8 @@ bool var_smtp_enforce_tls; char *var_smtp_tls_per_site; char *var_smtp_tls_policy; bool var_smtp_tls_wrappermode; +bool var_smtp_use_tlsproxy; +char *var_tlsproxy_service; #ifdef USE_TLS char *var_smtp_sasl_tls_opts; @@ -1219,6 +1222,9 @@ static void pre_init(char *unused_name, char **unused_argv) * * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. + * + * With tlsproxy(8) turned on, this is still needed for DANE-related + * initializations. */ smtp_tls_ctx = TLS_CLIENT_INIT(&props, diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h index 11beb4406..27e7a8612 100644 --- a/postfix/src/smtp/smtp.h +++ b/postfix/src/smtp/smtp.h @@ -38,6 +38,11 @@ */ #include + /* + * tlsproxy client. + */ +#include + /* * Global iterator support. This is updated by the connection-management * loop, and contains dynamic context that appears in lookup keys for SASL @@ -614,12 +619,13 @@ char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); #define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */ #define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */ #define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */ +#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */ #define SMTP_KEY_MASK_ALL \ (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \ SMTP_KEY_FLAG_REQ_NEXTHOP | \ SMTP_KEY_FLAG_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \ - SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT) + SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) /* * Conditional lookup-key flags for cached connections that may be @@ -647,7 +653,7 @@ char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); */ #define SMTP_KEY_MASK_SCACHE_DEST_LABEL \ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ - | SMTP_KEY_FLAG_REQ_NEXTHOP) + | SMTP_KEY_FLAG_REQ_NEXTHOP | SMTP_KEY_FLAG_TLS_LEVEL) /* * Connection-cache endpoint lookup key. The SENDER, NEXTHOP, and HOSTNAME @@ -658,7 +664,7 @@ char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); #define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \ (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ | COND_SASL_SMTP_KEY_FLAG_NEXTHOP | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ - | SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT) + | SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) /* * Silly little macros. @@ -686,6 +692,11 @@ extern int smtp_mode; /* 111 8th Avenue /* New York, NY 10011, USA /* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* /* TLS support originally by: /* Lutz Jaenicke /* BTU Cottbus diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index 2bf209d92..7fb8e168a 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -667,13 +667,19 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list, * and restore it here, so that subsequent connections will use the * proper nexthop information. * - * We request a dummy "TLS disabled" policy for connection-cache lookup by - * request nexthop only. If we find a saved connection, then we know that - * plaintext was permitted, because we never save a connection after - * turning on TLS. + * If TLS is proxied, lookup the TLS policy now so that we reuse only + * matching sessions. Otherwise, request a dummy "TLS disabled" policy + * for connection-cache lookup by request nexthop only. */ #ifdef USE_TLS - smtp_tls_policy_dummy(state->tls); + if (var_smtp_use_tlsproxy) { + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + return (0); /* XXX */ + } + } else + smtp_tls_policy_dummy(state->tls); #endif SMTP_ITER_SAVE_DEST(state->iterator); if (*addr_list && SMTP_RCPT_LEFT(state) > 0 diff --git a/postfix/src/smtp/smtp_key.c b/postfix/src/smtp/smtp_key.c index 78d795923..56cff253b 100644 --- a/postfix/src/smtp/smtp_key.c +++ b/postfix/src/smtp/smtp_key.c @@ -76,6 +76,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* @@ -191,7 +196,13 @@ char *smtp_key_prefix(VSTRING *buffer, const char *delim_na, if (flags & SMTP_KEY_FLAG_PORT) smtp_key_append_uint(buffer, ntohs(iter->port), delim_na); - /* Similarly, provide unique TLS fingerprint when applicable. */ + /* + * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup + * engine also try the requested TLS level and 'stronger', in case a + * server hosts multiple domains with different TLS requirements? + */ + if (flags & SMTP_KEY_FLAG_TLS_LEVEL) + smtp_key_append_uint(buffer, state->tls->level, delim_na); VSTRING_TERMINATE(buffer); diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c index ed5ea455b..7fef9e707 100644 --- a/postfix/src/smtp/smtp_params.c +++ b/postfix/src/smtp/smtp_params.c @@ -62,6 +62,7 @@ VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, 0, }; static const CONFIG_TIME_TABLE smtp_time_table[] = { @@ -112,6 +113,7 @@ VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand, VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_USE_TLSPROXY, DEF_SMTP_USE_TLSPROXY, &var_smtp_use_tlsproxy, #ifdef USE_TLS VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 15a15db17..44c9a4d2e 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -848,9 +848,12 @@ static int smtp_start_tls(SMTP_STATE *state) { SMTP_SESSION *session = state->session; SMTP_ITERATOR *iter = state->iterator; - TLS_CLIENT_START_PROPS tls_props; + TLS_CLIENT_START_PROPS start_props; VSTRING *serverid; SMTP_RESP fake; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; /* * Turn off SMTP connection caching. When the TLS handshake succeeds, we @@ -861,7 +864,8 @@ static int smtp_start_tls(SMTP_STATE *state) * SMTP connection either, because the conversation is in an unknown * state. */ - DONT_CACHE_THIS_SESSION; + if (var_smtp_use_tlsproxy == 0) + DONT_CACHE_THIS_SESSION; /* * The following assumes sites that use TLS in a perverse configuration: @@ -894,37 +898,122 @@ static int smtp_start_tls(SMTP_STATE *state) | SMTP_KEY_FLAG_HOSTNAME | SMTP_KEY_FLAG_ADDR); - /* - * As of Postfix 2.5, tls_client_start() tries hard to always complete - * the TLS handshake. It records the verification and match status in the - * resulting TLScontext. It is now up to the application to abort the TLS - * connection if it chooses. - * - * XXX When tls_client_start() fails then we don't know what state the SMTP - * connection is in, so we give up on this connection even if we are not - * required to use TLS. - * - * Large parameter lists are error-prone, so we emulate a language feature - * that C does not have natively: named parameter lists. - */ - session->tls_context = - TLS_CLIENT_START(&tls_props, - ctx = smtp_tls_ctx, - stream = session->stream, - timeout = var_smtp_starttls_tmout, - tls_level = state->tls->level, - nexthop = session->tls_nexthop, - host = STR(iter->host), - namaddr = session->namaddrport, - serverid = vstring_str(serverid), - helo = session->helo, - protocols = state->tls->protocols, - cipher_grade = state->tls->grade, - cipher_exclusions - = vstring_str(state->tls->exclusions), - matchargv = state->tls->matchargv, - mdalg = var_smtp_tls_fpt_dgst, - dane = state->tls->dane); + if (var_smtp_use_tlsproxy) { + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) + + port_buf = vstring_alloc(100); + vstring_sprintf(port_buf, "%d", ntohs(iter->port)); + tlsproxy = + tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + session->stream, STR(iter->addr), + STR(port_buf), var_smtp_starttls_tmout, + state->service, &init_props, &start_props); + vstring_free(port_buf); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. + */ + if (tlsproxy == 0) { + session->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(session->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + session->tls_context = tls_proxy_context_receive(session->stream); + } + } else { /* var_smtp_tls_proxy */ + + /* + * As of Postfix 2.5, tls_client_start() tries hard to always + * complete the TLS handshake. It records the verification and match + * status in the resulting TLScontext. It is now up to the + * application to abort the TLS connection if it chooses. + * + * XXX When tls_client_start() fails then we don't know what state the + * SMTP connection is in, so we give up on this connection even if we + * are not required to use TLS. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + session->tls_context = + TLS_CLIENT_START(&start_props, + ctx = smtp_tls_ctx, + stream = session->stream, + fd = -1, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + } /* var_smtp_use_tlsproxy */ + vstring_free(serverid); if (session->tls_context == 0) { diff --git a/postfix/src/smtp/smtp_reuse.c b/postfix/src/smtp/smtp_reuse.c index da23692fd..2bc5c5ed0 100644 --- a/postfix/src/smtp/smtp_reuse.c +++ b/postfix/src/smtp/smtp_reuse.c @@ -41,6 +41,8 @@ /* The restored session information does not include the "best /* MX" bit, and does not override the iterator dest, host and /* addr fields. +/* This function is a NOOP for TLS levels stronger than "encrypt", +/* because stronger levels require certificate checks, /* The result is null in case of failure. /* /* Arguments: @@ -64,6 +66,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* System library. */ @@ -159,10 +166,9 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd, SMTP_SESSION *session; /* - * Can't happen. Both smtp_reuse_nexthop() and smtp_reuse_addr() decline - * the request when the TLS policy is not TLS_LEV_NONE. + * Obsolete. */ -#ifdef USE_TLS +#ifdef notdef if (state->tls->level > TLS_LEV_NONE) msg_panic("%s: unexpected plain-text cached session to %s", myname, label); @@ -211,10 +217,10 @@ SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags) int fd; /* - * Don't look up an existing plaintext connection when a new connection - * would (try to) use TLS. + * Obsolete: the TLS level and nexthop are part of the connection cache + * key. TODO(tlsproxy) is the port included in the nexthop? */ -#ifdef USE_TLS +#ifdef notdef if (state->tls->level > TLS_LEV_NONE) return (0); #endif @@ -244,11 +250,11 @@ SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags) int fd; /* - * Don't look up an existing plaintext connection when a new connection - * would (try to) use TLS. + * Allow address-based reuse only for security levels that don't require + * certificate checks. */ #ifdef USE_TLS - if (state->tls->level > TLS_LEV_NONE) + if (state->tls->level > TLS_LEV_ENCRYPT) return (0); #endif diff --git a/postfix/src/smtp/smtp_session.c b/postfix/src/smtp/smtp_session.c index 806d8de06..594567f78 100644 --- a/postfix/src/smtp/smtp_session.c +++ b/postfix/src/smtp/smtp_session.c @@ -84,6 +84,11 @@ /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* /* Viktor Dukhovni /*--*/ @@ -176,7 +181,11 @@ void smtp_session_free(SMTP_SESSION *session) #ifdef USE_TLS if (session->stream) { vstream_fflush(session->stream); - if (session->tls_context) + } + if (session->tls_context) { + if (var_smtp_use_tlsproxy) + tls_proxy_context_free(session->tls_context); + else tls_client_stop(smtp_tls_ctx, session->stream, var_smtp_starttls_tmout, 0, session->tls_context); } @@ -223,9 +232,11 @@ int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop, * serialize the properties with attr_print() instead of using ad-hoc, * non-reusable, code and hard-coded format strings. * + * TODO(tlsproxy): save TLS_SESS_STATE information so that we can + * restore TLS session properties. + * * TODO: save SASL username and password information so that we can * correctly save a reused authenticated connection. - * */ vstring_sprintf(dest_prop, "%s\n%s\n%s\n%u", STR(iter->dest), STR(iter->host), STR(iter->addr), diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 20598992b..5006e7449 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -1496,9 +1496,11 @@ static void tls_reset(SMTPD_STATE *); /* * TLS initialization status. */ +#ifndef USE_TLSPROXY static TLS_APPL_STATE *smtpd_tls_ctx; static int ask_client_cert; +#endif /* USE_TLSPROXY */ #endif /* @@ -4704,9 +4706,11 @@ static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) #define PROXY_OPEN_FLAGS \ (TLS_PROXY_FLAG_ROLE_SERVER | TLS_PROXY_FLAG_SEND_CONTEXT) - state->tlsproxy = tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, - state->client, state->addr, - state->port, var_smtpd_tmout); + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); if (state->tlsproxy == 0) { state->error_mask |= MAIL_ERROR_SOFTWARE; /* RFC 3207 Section 4. */ @@ -4963,10 +4967,12 @@ static void smtpd_proto(SMTPD_STATE *state) if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode) { #ifdef USE_TLSPROXY /* We garbage-collect the VSTREAM in smtpd_state_reset() */ - state->tlsproxy = tls_proxy_open(var_tlsproxy_service, - PROXY_OPEN_FLAGS, - state->client, state->addr, - state->port, var_smtpd_tmout); + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, + PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); if (state->tlsproxy == 0) { msg_warn("Wrapper-mode request dropped from %s for service %s." " TLS context initialization failed. For details see" diff --git a/postfix/src/tls/Makefile.in b/postfix/src/tls/Makefile.in index 55dfd1ecc..16efff4dc 100644 --- a/postfix/src/tls/Makefile.in +++ b/postfix/src/tls/Makefile.in @@ -4,13 +4,19 @@ SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \ tls_rsa.c tls_verify.c tls_dane.c tls_certkey.c tls_session.c \ tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \ tls_level.c \ - tls_proxy_clnt.c tls_proxy_print.c tls_proxy_scan.c + tls_proxy_clnt.c tls_proxy_context_print.c tls_proxy_context_scan.c \ + tls_proxy_client_init_print.c tls_proxy_client_init_scan.c \ + tls_proxy_server_init_print.c tls_proxy_server_init_scan.c \ + tls_proxy_client_start_print.c tls_proxy_client_start_scan.c \ + tls_proxy_server_start_print.c tls_proxy_server_start_scan.c OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \ tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \ tls_rsa.o tls_verify.o tls_dane.o tls_certkey.o tls_session.o \ tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \ tls_level.o \ - tls_proxy_clnt.o tls_proxy_print.o tls_proxy_scan.o + tls_proxy_clnt.o tls_proxy_context_print.o tls_proxy_context_scan.o \ + tls_proxy_client_print.o tls_proxy_client_scan.o \ + tls_proxy_server_print.o tls_proxy_server_scan.o HDRS = tls.h tls_prng.h tls_scache.h tls_mgr.h tls_proxy.h TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) @@ -294,6 +300,46 @@ tls_prng_file.o: ../../include/mymalloc.h tls_prng_file.o: ../../include/sys_defs.h tls_prng_file.o: tls_prng.h tls_prng_file.o: tls_prng_file.c +tls_proxy_client_print.o: ../../include/argv.h +tls_proxy_client_print.o: ../../include/argv_attr.h +tls_proxy_client_print.o: ../../include/attr.h +tls_proxy_client_print.o: ../../include/check_arg.h +tls_proxy_client_print.o: ../../include/dns.h +tls_proxy_client_print.o: ../../include/htable.h +tls_proxy_client_print.o: ../../include/msg.h +tls_proxy_client_print.o: ../../include/myaddrinfo.h +tls_proxy_client_print.o: ../../include/mymalloc.h +tls_proxy_client_print.o: ../../include/name_code.h +tls_proxy_client_print.o: ../../include/name_mask.h +tls_proxy_client_print.o: ../../include/nvtable.h +tls_proxy_client_print.o: ../../include/sock_addr.h +tls_proxy_client_print.o: ../../include/sys_defs.h +tls_proxy_client_print.o: ../../include/vbuf.h +tls_proxy_client_print.o: ../../include/vstream.h +tls_proxy_client_print.o: ../../include/vstring.h +tls_proxy_client_print.o: tls.h +tls_proxy_client_print.o: tls_proxy.h +tls_proxy_client_print.o: tls_proxy_client_print.c +tls_proxy_client_scan.o: ../../include/argv.h +tls_proxy_client_scan.o: ../../include/argv_attr.h +tls_proxy_client_scan.o: ../../include/attr.h +tls_proxy_client_scan.o: ../../include/check_arg.h +tls_proxy_client_scan.o: ../../include/dns.h +tls_proxy_client_scan.o: ../../include/htable.h +tls_proxy_client_scan.o: ../../include/msg.h +tls_proxy_client_scan.o: ../../include/myaddrinfo.h +tls_proxy_client_scan.o: ../../include/mymalloc.h +tls_proxy_client_scan.o: ../../include/name_code.h +tls_proxy_client_scan.o: ../../include/name_mask.h +tls_proxy_client_scan.o: ../../include/nvtable.h +tls_proxy_client_scan.o: ../../include/sock_addr.h +tls_proxy_client_scan.o: ../../include/sys_defs.h +tls_proxy_client_scan.o: ../../include/vbuf.h +tls_proxy_client_scan.o: ../../include/vstream.h +tls_proxy_client_scan.o: ../../include/vstring.h +tls_proxy_client_scan.o: tls.h +tls_proxy_client_scan.o: tls_proxy.h +tls_proxy_client_scan.o: tls_proxy_client_scan.c tls_proxy_clnt.o: ../../include/argv.h tls_proxy_clnt.o: ../../include/attr.h tls_proxy_clnt.o: ../../include/check_arg.h @@ -318,46 +364,79 @@ tls_proxy_clnt.o: ../../include/vstring.h tls_proxy_clnt.o: tls.h tls_proxy_clnt.o: tls_proxy.h tls_proxy_clnt.o: tls_proxy_clnt.c -tls_proxy_print.o: ../../include/argv.h -tls_proxy_print.o: ../../include/attr.h -tls_proxy_print.o: ../../include/check_arg.h -tls_proxy_print.o: ../../include/dns.h -tls_proxy_print.o: ../../include/htable.h -tls_proxy_print.o: ../../include/iostuff.h -tls_proxy_print.o: ../../include/mail_proto.h -tls_proxy_print.o: ../../include/myaddrinfo.h -tls_proxy_print.o: ../../include/mymalloc.h -tls_proxy_print.o: ../../include/name_code.h -tls_proxy_print.o: ../../include/name_mask.h -tls_proxy_print.o: ../../include/nvtable.h -tls_proxy_print.o: ../../include/sock_addr.h -tls_proxy_print.o: ../../include/sys_defs.h -tls_proxy_print.o: ../../include/vbuf.h -tls_proxy_print.o: ../../include/vstream.h -tls_proxy_print.o: ../../include/vstring.h -tls_proxy_print.o: tls.h -tls_proxy_print.o: tls_proxy.h -tls_proxy_print.o: tls_proxy_print.c -tls_proxy_scan.o: ../../include/argv.h -tls_proxy_scan.o: ../../include/attr.h -tls_proxy_scan.o: ../../include/check_arg.h -tls_proxy_scan.o: ../../include/dns.h -tls_proxy_scan.o: ../../include/htable.h -tls_proxy_scan.o: ../../include/iostuff.h -tls_proxy_scan.o: ../../include/mail_proto.h -tls_proxy_scan.o: ../../include/myaddrinfo.h -tls_proxy_scan.o: ../../include/mymalloc.h -tls_proxy_scan.o: ../../include/name_code.h -tls_proxy_scan.o: ../../include/name_mask.h -tls_proxy_scan.o: ../../include/nvtable.h -tls_proxy_scan.o: ../../include/sock_addr.h -tls_proxy_scan.o: ../../include/sys_defs.h -tls_proxy_scan.o: ../../include/vbuf.h -tls_proxy_scan.o: ../../include/vstream.h -tls_proxy_scan.o: ../../include/vstring.h -tls_proxy_scan.o: tls.h -tls_proxy_scan.o: tls_proxy.h -tls_proxy_scan.o: tls_proxy_scan.c +tls_proxy_context_print.o: ../../include/argv.h +tls_proxy_context_print.o: ../../include/attr.h +tls_proxy_context_print.o: ../../include/check_arg.h +tls_proxy_context_print.o: ../../include/dns.h +tls_proxy_context_print.o: ../../include/htable.h +tls_proxy_context_print.o: ../../include/myaddrinfo.h +tls_proxy_context_print.o: ../../include/mymalloc.h +tls_proxy_context_print.o: ../../include/name_code.h +tls_proxy_context_print.o: ../../include/name_mask.h +tls_proxy_context_print.o: ../../include/nvtable.h +tls_proxy_context_print.o: ../../include/sock_addr.h +tls_proxy_context_print.o: ../../include/sys_defs.h +tls_proxy_context_print.o: ../../include/vbuf.h +tls_proxy_context_print.o: ../../include/vstream.h +tls_proxy_context_print.o: ../../include/vstring.h +tls_proxy_context_print.o: tls.h +tls_proxy_context_print.o: tls_proxy.h +tls_proxy_context_print.o: tls_proxy_context_print.c +tls_proxy_context_scan.o: ../../include/argv.h +tls_proxy_context_scan.o: ../../include/attr.h +tls_proxy_context_scan.o: ../../include/check_arg.h +tls_proxy_context_scan.o: ../../include/dns.h +tls_proxy_context_scan.o: ../../include/htable.h +tls_proxy_context_scan.o: ../../include/msg.h +tls_proxy_context_scan.o: ../../include/myaddrinfo.h +tls_proxy_context_scan.o: ../../include/mymalloc.h +tls_proxy_context_scan.o: ../../include/name_code.h +tls_proxy_context_scan.o: ../../include/name_mask.h +tls_proxy_context_scan.o: ../../include/nvtable.h +tls_proxy_context_scan.o: ../../include/sock_addr.h +tls_proxy_context_scan.o: ../../include/sys_defs.h +tls_proxy_context_scan.o: ../../include/vbuf.h +tls_proxy_context_scan.o: ../../include/vstream.h +tls_proxy_context_scan.o: ../../include/vstring.h +tls_proxy_context_scan.o: tls.h +tls_proxy_context_scan.o: tls_proxy.h +tls_proxy_context_scan.o: tls_proxy_context_scan.c +tls_proxy_server_print.o: ../../include/argv.h +tls_proxy_server_print.o: ../../include/attr.h +tls_proxy_server_print.o: ../../include/check_arg.h +tls_proxy_server_print.o: ../../include/dns.h +tls_proxy_server_print.o: ../../include/htable.h +tls_proxy_server_print.o: ../../include/myaddrinfo.h +tls_proxy_server_print.o: ../../include/mymalloc.h +tls_proxy_server_print.o: ../../include/name_code.h +tls_proxy_server_print.o: ../../include/name_mask.h +tls_proxy_server_print.o: ../../include/nvtable.h +tls_proxy_server_print.o: ../../include/sock_addr.h +tls_proxy_server_print.o: ../../include/sys_defs.h +tls_proxy_server_print.o: ../../include/vbuf.h +tls_proxy_server_print.o: ../../include/vstream.h +tls_proxy_server_print.o: ../../include/vstring.h +tls_proxy_server_print.o: tls.h +tls_proxy_server_print.o: tls_proxy.h +tls_proxy_server_print.o: tls_proxy_server_print.c +tls_proxy_server_scan.o: ../../include/argv.h +tls_proxy_server_scan.o: ../../include/attr.h +tls_proxy_server_scan.o: ../../include/check_arg.h +tls_proxy_server_scan.o: ../../include/dns.h +tls_proxy_server_scan.o: ../../include/htable.h +tls_proxy_server_scan.o: ../../include/myaddrinfo.h +tls_proxy_server_scan.o: ../../include/mymalloc.h +tls_proxy_server_scan.o: ../../include/name_code.h +tls_proxy_server_scan.o: ../../include/name_mask.h +tls_proxy_server_scan.o: ../../include/nvtable.h +tls_proxy_server_scan.o: ../../include/sock_addr.h +tls_proxy_server_scan.o: ../../include/sys_defs.h +tls_proxy_server_scan.o: ../../include/vbuf.h +tls_proxy_server_scan.o: ../../include/vstream.h +tls_proxy_server_scan.o: ../../include/vstring.h +tls_proxy_server_scan.o: tls.h +tls_proxy_server_scan.o: tls_proxy.h +tls_proxy_server_scan.o: tls_proxy_server_scan.c tls_rsa.o: ../../include/argv.h tls_rsa.o: ../../include/check_arg.h tls_rsa.o: ../../include/dns.h diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index 79b8d73a3..01419cb56 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -448,6 +448,7 @@ typedef struct { typedef struct { TLS_APPL_STATE *ctx; VSTREAM *stream; + int fd; /* Event-driven file descriptor */ int timeout; int tls_level; /* Security level */ const char *nexthop; /* destination domain */ @@ -465,6 +466,8 @@ typedef struct { extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *); extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *); +extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *, + const TLS_CLIENT_START_PROPS *); #define tls_client_stop(ctx, stream, timeout, failure, TLScontext) \ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext)) @@ -477,11 +480,12 @@ extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *); ((props)->a12), ((props)->a13), (props))) #define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ - a10, a11, a12, a13, a14, a15) \ + a10, a11, a12, a13, a14, a15, a16) \ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ - ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), (props))) + ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ + ((props)->a16), (props))) /* * tls_server.c diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index bbe18f905..09e523364 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -12,6 +12,10 @@ /* TLS_SESS_STATE *tls_client_start(start_props) /* const TLS_CLIENT_START_PROPS *start_props; /* +/* TLS_SESS_STATE *tls_client_post_connect(TLScontext, start_props) +/* TLS_SESS_STATE *TLScontext; +/* const TLS_CLIENT_START_PROPS *start_props; +/* /* void tls_client_stop(app_ctx, stream, failure, TLScontext) /* TLS_APPL_STATE *app_ctx; /* VSTREAM *stream; @@ -96,6 +100,31 @@ /* the fingerprint of the certificate. /* .PP /* If no peer certificate is presented the peer_status is set to 0. +/* EVENT_DRIVEN APPLICATIONS +/* .ad +/* .fi +/* Event-driven programs manage multiple I/O channels. Such +/* programs cannot use the synchronous VSTREAM-over-TLS +/* implementation that the current TLS library provides, +/* including tls_client_stop() and the underlying tls_stream(3) +/* and tls_bio_ops(3) routines. +/* +/* With the current TLS library implementation, this means +/* that an event-driven application is responsible for calling +/* and retrying SSL_connect(), SSL_read(), SSL_write() and +/* SSL_shutdown(). +/* +/* To maintain control over TLS I/O, an event-driven client +/* invokes tls_client_start() with a null VSTREAM argument and +/* with an fd argument that specifies the I/O file descriptor. +/* Then, tls_client_start() performs all the necessary +/* preparations before the TLS handshake and returns a partially +/* populated TLS context. The event-driven application is then +/* responsible for invoking SSL_connect(), and if successful, +/* for invoking tls_client_post_connect() to finish the work +/* that was started by tls_client_start(). In case of unrecoverable +/* failure, tls_client_post_connect() destroys the TLS context +/* and returns a null pointer value. /* LICENSE /* .ad /* .fi @@ -837,8 +866,6 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) int protomask; const char *cipher_list; SSL_SESSION *session = 0; - SSL_CIPHER_const SSL_CIPHER *cipher; - X509 *peercert; TLS_SESS_STATE *TLScontext; TLS_APPL_STATE *app_ctx = props->ctx; char *myserverid; @@ -1018,7 +1045,8 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) /* * Connect the SSL connection with the network socket. */ - if (SSL_set_fd(TLScontext->con, vstream_fileno(props->stream)) != 1) { + if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd : + vstream_fileno(props->stream)) != 1) { msg_info("SSL_set_fd error to %s", props->namaddr); tls_print_errors(); uncache_session(app_ctx->ssl_ctx, TLScontext); @@ -1026,12 +1054,6 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) return (0); } - /* - * Turn on non-blocking I/O so that we can enforce timeouts on network - * I/O. - */ - non_blocking(vstream_fileno(props->stream), NON_BLOCKING); - /* * If the debug level selected is high enough, all of the data is dumped: * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will @@ -1046,6 +1068,19 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) tls_dane_set_callback(app_ctx->ssl_ctx, TLScontext); + /* + * If we don't trigger the handshake in the library, leave control over + * SSL_connect/read/write/etc with the application. + */ + if (props->stream == 0) + return (TLScontext); + + /* + * Turn on non-blocking I/O so that we can enforce timeouts on network + * I/O. + */ + non_blocking(vstream_fileno(props->stream), NON_BLOCKING); + /* * Start TLS negotiations. This process is a black box that invokes our * call-backs for certificate verification. @@ -1069,8 +1104,19 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) tls_free_context(TLScontext); return (0); } + return (tls_client_post_connect(TLScontext, props)); +} + +/* tls_client_post_connect - post-handshake processing */ + +TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext, + const TLS_CLIENT_START_PROPS *props) +{ + SSL_CIPHER_const SSL_CIPHER *cipher; + X509 *peercert; + /* Turn off packet dump if only dumping the handshake */ - if ((log_mask & TLS_LOG_ALLPKTS) == 0) + if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0) BIO_set_callback(SSL_get_rbio(TLScontext->con), 0); /* @@ -1078,7 +1124,7 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) * session was negotiated. */ TLScontext->session_reused = SSL_session_reused(TLScontext->con); - if ((log_mask & TLS_LOG_CACHE) && TLScontext->session_reused) + if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused) msg_info("%s: Reusing old session", TLScontext->namaddr); /* @@ -1125,7 +1171,8 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) * The TLS engine is active. Switch to the tls_timed_read/write() * functions and make the TLScontext available to those functions. */ - tls_stream_start(props->stream, TLScontext); + if (TLScontext->stream != 0) + tls_stream_start(props->stream, TLScontext); /* * Fully secured only if trusted, matched and not insecure like halfdane. @@ -1142,7 +1189,7 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) /* * All the key facts in a single log entry. */ - if (log_mask & TLS_LOG_SUMMARY) + if (TLScontext->log_mask & TLS_LOG_SUMMARY) msg_info("%s TLS connection established to %s: %s with cipher %s " "(%d/%d bits)", !TLS_CERT_IS_PRESENT(TLScontext) ? "Anonymous" : diff --git a/postfix/src/tls/tls_proxy.h b/postfix/src/tls/tls_proxy.h index e677c67ab..99f1afe1a 100644 --- a/postfix/src/tls/tls_proxy.h +++ b/postfix/src/tls/tls_proxy.h @@ -31,13 +31,173 @@ #ifdef USE_TLS +#define tls_proxy_legacy_open(service, flags, peer_stream, peer_addr, \ + peer_port, timeout, serverid) \ + tls_proxy_open((service), (flags), (peer_stream), (peer_addr), \ + (peer_port), (timeout), (serverid), (void *) 0, (void *) 0) + extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *, - const char *, int); + const char *, int, const char *, + void *, void *); + +#define TLS_PROXY_CLIENT_INIT_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13) \ + (((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13)) + +#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13) \ + (((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13)) + extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *); extern void tls_proxy_context_free(TLS_SESS_STATE *); extern int tls_proxy_context_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); extern int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); +extern int tls_proxy_client_init_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); +extern int tls_proxy_client_init_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); +extern void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *); +extern char *tls_proxy_client_init_to_string(VSTRING *, TLS_CLIENT_INIT_PROPS *); + +extern int tls_proxy_client_start_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); +extern int tls_proxy_client_start_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); +extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *); + +extern int tls_proxy_server_init_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); +extern int tls_proxy_server_init_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); +extern void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *); + +extern int tls_proxy_server_start_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); +extern int tls_proxy_server_start_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); +extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *); + + /* + * TLSPROXY attributes + */ +#define TLS_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */ +#define TLS_ATTR_FLAGS "flags" +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_SERVERID "serverid" + + /* + * Misc attributes. + */ +#define TLS_ATTR_COUNT "count" + + /* + * TLS_SESS_STATE attributes. + */ +#define TLS_ATTR_PEER_CN "peer_CN" +#define TLS_ATTR_ISSUER_CN "issuer_CN" +#define TLS_ATTR_PEER_CERT_FPT "peer_fingerprint" +#define TLS_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint" +#define TLS_ATTR_PEER_STATUS "peer_status" +#define TLS_ATTR_CIPHER_PROTOCOL "cipher_protocol" +#define TLS_ATTR_CIPHER_NAME "cipher_name" +#define TLS_ATTR_CIPHER_USEBITS "cipher_usebits" +#define TLS_ATTR_CIPHER_ALGBITS "cipher_algbits" + + /* + * TLS_SERVER_INIT_PROPS attributes. + */ +#define TLS_ATTR_LOG_PARAM "log_param" +#define TLS_ATTR_LOG_LEVEL "log_level" +#define TLS_ATTR_VERIFYDEPTH "verifydepth" +#define TLS_ATTR_CACHE_TYPE "cache_type" +#define TLS_ATTR_SET_SESSID "set_sessid" +#define TLS_ATTR_CERT_FILE "cert_file" +#define TLS_ATTR_KEY_FILE "key_file" +#define TLS_ATTR_DCERT_FILE "dcert_file" +#define TLS_ATTR_DKEY_FILE "dkey_file" +#define TLS_ATTR_ECCERT_FILE "eccert_file" +#define TLS_ATTR_ECKEY_FILE "eckey_file" +#define TLS_ATTR_CAFILE "CAfile" +#define TLS_ATTR_CAPATH "CApath" +#define TLS_ATTR_PROTOCOLS "protocols" +#define TLS_ATTR_EECDH_GRADE "eecdh_grade" +#define TLS_ATTR_DH1K_PARAM_FILE "dh1024_param_file" +#define TLS_ATTR_DH512_PARAM_FILE "dh512_param_file" +#define TLS_ATTR_ASK_CCERT "ask_ccert" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_SERVER_START_PROPS attributes. + */ +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_REQUIRECERT "requirecert" +#define TLS_ATTR_SERVERID "serverid" +#define TLS_ATTR_NAMADDR "namaddr" +#define TLS_ATTR_CIPHER_GRADE "cipher_grade" +#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_CLIENT_INIT_PROPS attributes. + */ +#define TLS_ATTR_LOG_PARAM "log_param" +#define TLS_ATTR_LOG_LEVEL "log_level" +#define TLS_ATTR_VERIFYDEPTH "verifydepth" +#define TLS_ATTR_CACHE_TYPE "cache_type" +#define TLS_ATTR_CERT_FILE "cert_file" +#define TLS_ATTR_KEY_FILE "key_file" +#define TLS_ATTR_DCERT_FILE "dcert_file" +#define TLS_ATTR_DKEY_FILE "dkey_file" +#define TLS_ATTR_ECCERT_FILE "eccert_file" +#define TLS_ATTR_ECKEY_FILE "eckey_file" +#define TLS_ATTR_CAFILE "CAfile" +#define TLS_ATTR_CAPATH "CApath" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_CLIENT_START_PROPS attributes. + */ +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_TLS_LEVEL "tls_level" +#define TLS_ATTR_NEXTHOP "nexthop" +#define TLS_ATTR_HOST "host" +#define TLS_ATTR_NAMADDR "namaddr" +#define TLS_ATTR_SERVERID "serverid" +#define TLS_ATTR_HELO "helo" +#define TLS_ATTR_PROTOCOLS "protocols" +#define TLS_ATTR_CIPHER_GRADE "cipher_grade" +#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions" +#define TLS_ATTR_MATCHARGV "matchargv" +#define TLS_ATTR_MDALG "mdalg" +#define TLS_ATTR_DANE "dane" + + /* + * TLS_TLSA attributes. + */ +#define TLS_ATTR_MDALG "mdalg" +#define TLS_ATTR_CERTS "certs" +#define TLS_ATTR_PKEYS "pkeys" + + /* + * TLS_CERTS attributes. + */ +#define TLS_ATTR_CERT "cert" + + /* + * TLS_PKEYS attributes. + */ +#define TLS_ATTR_PKEY "pkey" + + /* + * TLS_DANE attributes. + */ +#define TLS_ATTR_TA "ta" +#define TLS_ATTR_EE "ee" +#define TLS_ATTR_CERTS "certs" +#define TLS_ATTR_PKEYS "pkeys" +#define TLS_ATTR_DOMAIN "domain" +#define TLS_ATTR_FLAGS "flags" +#define TLS_ATTR_EXP "exp" + #endif /* LICENSE @@ -49,6 +209,11 @@ extern int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ #endif diff --git a/postfix/src/tls/tls_proxy_client_print.c b/postfix/src/tls/tls_proxy_client_print.c new file mode 100644 index 000000000..7ed66b9de --- /dev/null +++ b/postfix/src/tls/tls_proxy_client_print.c @@ -0,0 +1,324 @@ +/*++ +/* NAME +/* tls_proxy_client_print 3 +/* SUMMARY +/* write TLS_CLIENT_XXX structures to stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_client_init_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* int tls_proxy_client_start_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* tls_proxy_client_init_print() writes a full TLS_CLIENT_INIT_PROPS +/* structure to the named stream using the specified attribute +/* print routine. tls_proxy_client_init_print() is meant to +/* be passed as a call-back to attr_print(), thusly: +/* +/* SEND_ATTR_FUNC(tls_proxy_client_init_print, (void *) init_props), ... +/* +/* tls_proxy_client_start_print() writes a TLS_CLIENT_START_PROPS +/* structure, without stream or file descriptor members, to +/* the named stream using the specified attribute print routine. +/* tls_proxy_client_start_print() is meant to be passed as a +/* call-back to attr_print(), thusly: +/* +/* SEND_ATTR_FUNC(tls_proxy_client_start_print, (void *) start_props), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include +#include + +/* TLS library. */ + +#include +#include + + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* tls_proxy_client_init_print - send TLS_CLIENT_INIT_PROPS over stream */ + +int tls_proxy_client_init_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_INIT_PROPS *props = (TLS_CLIENT_INIT_PROPS *) ptr; + int ret; + + if (msg_verbose) + msg_info("begin tls_proxy_client_init_print"); + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_LOG_PARAM, + STRING_OR_EMPTY(props->log_param)), + SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL, + STRING_OR_EMPTY(props->log_level)), + SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth), + SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE, + STRING_OR_EMPTY(props->cache_type)), + SEND_ATTR_STR(TLS_ATTR_CERT_FILE, + STRING_OR_EMPTY(props->cert_file)), + SEND_ATTR_STR(TLS_ATTR_KEY_FILE, + STRING_OR_EMPTY(props->key_file)), + SEND_ATTR_STR(TLS_ATTR_DCERT_FILE, + STRING_OR_EMPTY(props->dcert_file)), + SEND_ATTR_STR(TLS_ATTR_DKEY_FILE, + STRING_OR_EMPTY(props->dkey_file)), + SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE, + STRING_OR_EMPTY(props->eccert_file)), + SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE, + STRING_OR_EMPTY(props->eckey_file)), + SEND_ATTR_STR(TLS_ATTR_CAFILE, + STRING_OR_EMPTY(props->CAfile)), + SEND_ATTR_STR(TLS_ATTR_CAPATH, + STRING_OR_EMPTY(props->CApath)), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_init_print ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_certs_print - send x509 certificates over stream */ + +static int tls_proxy_client_certs_print(ATTR_PRINT_MASTER_FN print_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_CERTS *tls_certs = (TLS_CERTS *) ptr; + TLS_CERTS *tp; + int count; + int ret; + + for (tp = tls_certs, count = 0; tp != 0; tp = tp->next, count++) + /* void */ ; + if (msg_verbose) + msg_info("tls_proxy_client_certs_print count=%d", count); + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_COUNT, count), + ATTR_TYPE_END); + + if (ret == 0 && count > 0) { + VSTRING *buf = vstring_alloc(100); + int n; + + for (tp = tls_certs, n = 0; ret == 0 && n < count; tp = tp->next, n++) { + size_t len = i2d_X509(tp->cert, (unsigned char **) 0); + unsigned char *bp; + + VSTRING_RESET(buf); + VSTRING_SPACE(buf, len); + bp = (unsigned char *) STR(buf); + i2d_X509(tp->cert, &bp); + if ((char *) bp - STR(buf) != len) + msg_panic("i2d_X509 failed to encode certificate"); + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_DATA(TLS_ATTR_CERT, LEN(buf), STR(buf)), + ATTR_TYPE_END); + } + vstring_free(buf); + } + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_certs_print ret=%d", count); + return (ret); +} + +/* tls_proxy_client_pkeys_print - send public keys over stream */ + +static int tls_proxy_client_pkeys_print(ATTR_PRINT_MASTER_FN print_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_PKEYS *tls_pkeys = (TLS_PKEYS *) ptr; + TLS_PKEYS *tp; + int count; + int ret; + + for (tp = tls_pkeys, count = 0; tp != 0; tp = tp->next, count++) + /* void */ ; + if (msg_verbose) + msg_info("tls_proxy_client_pkeys_print count=%d", count); + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_COUNT, count), + ATTR_TYPE_END); + + if (ret == 0 && count > 0) { + VSTRING *buf = vstring_alloc(100); + int n; + + for (tp = tls_pkeys, n = 0; ret == 0 && n < count; tp = tp->next, n++) { + size_t len = i2d_PUBKEY(tp->pkey, (unsigned char **) 0); + unsigned char *bp; + + VSTRING_RESET(buf); + VSTRING_SPACE(buf, len); + bp = (unsigned char *) STR(buf); + i2d_PUBKEY(tp->pkey, &bp); + if ((char *) bp - STR(buf) != len) + msg_panic("i2d_PUBKEY failed to encode public key"); + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_DATA(TLS_ATTR_PKEY, LEN(buf), STR(buf)), + ATTR_TYPE_END); + } + vstring_free(buf); + } + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_pkeys_print ret=%d", count); + return (ret); +} + +/* tls_proxy_client_tlsa_print - send TLS_TLSA over stream */ + +static int tls_proxy_client_tlsa_print(ATTR_PRINT_MASTER_FN print_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_TLSA *tls_tlsa = (TLS_TLSA *) ptr; + TLS_TLSA *tp; + int count; + int ret; + + for (tp = tls_tlsa, count = 0; tp != 0; tp = tp->next, count++) + /* void */ ; + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_print count=%d", count); + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_COUNT, count), + ATTR_TYPE_END); + + if (ret == 0 && count > 0) { + int n; + + for (tp = tls_tlsa, n = 0; ret == 0 && n < count; tp = tp->next, n++) { + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_MDALG, tp->mdalg), + SEND_ATTR_FUNC(argv_attr_print, + (void *) tp->certs), + SEND_ATTR_FUNC(argv_attr_print, + (void *) tp->pkeys), + ATTR_TYPE_END); + } + } + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_print ret=%d", count); + return (ret); +} + +/* tls_proxy_client_dane_print - send TLS_DANE over stream */ + +static int tls_proxy_client_dane_print(ATTR_PRINT_MASTER_FN print_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_DANE *dane = (TLS_DANE *) ptr; + int ret; + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_DANE, dane != 0), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_dane_print dane=%d", dane != 0); + + if (ret == 0 && dane != 0) { + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_FUNC(tls_proxy_client_tlsa_print, + (void *) dane->ta), + SEND_ATTR_FUNC(tls_proxy_client_tlsa_print, + (void *) dane->ee), + SEND_ATTR_FUNC(tls_proxy_client_certs_print, + (void *) dane->certs), + SEND_ATTR_FUNC(tls_proxy_client_pkeys_print, + (void *) dane->pkeys), + SEND_ATTR_STR(TLS_ATTR_DOMAIN, + STRING_OR_EMPTY(dane->base_domain)), + SEND_ATTR_INT(TLS_ATTR_FLAGS, dane->flags), + SEND_ATTR_LONG(TLS_ATTR_EXP, dane->expires), + ATTR_TYPE_END); + } + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_dane_print ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_start_print - send TLS_CLIENT_START_PROPS over stream */ + +int tls_proxy_client_start_print(ATTR_PRINT_MASTER_FN print_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_CLIENT_START_PROPS *props = (TLS_CLIENT_START_PROPS *) ptr; + int ret; + + if (msg_verbose) + msg_info("begin tls_proxy_client_start_print"); + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout), + SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level), + SEND_ATTR_STR(TLS_ATTR_NEXTHOP, + STRING_OR_EMPTY(props->nexthop)), + SEND_ATTR_STR(TLS_ATTR_HOST, + STRING_OR_EMPTY(props->host)), + SEND_ATTR_STR(TLS_ATTR_NAMADDR, + STRING_OR_EMPTY(props->namaddr)), + SEND_ATTR_STR(TLS_ATTR_SERVERID, + STRING_OR_EMPTY(props->serverid)), + SEND_ATTR_STR(TLS_ATTR_HELO, + STRING_OR_EMPTY(props->helo)), + SEND_ATTR_STR(TLS_ATTR_PROTOCOLS, + STRING_OR_EMPTY(props->protocols)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE, + STRING_OR_EMPTY(props->cipher_grade)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + STRING_OR_EMPTY(props->cipher_exclusions)), + SEND_ATTR_FUNC(argv_attr_print, + (void *) props->matchargv), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + SEND_ATTR_FUNC(tls_proxy_client_dane_print, + (void *) props->dane), + ATTR_TYPE_END); + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_start_print ret=%d", ret); + return (ret); +} + +#endif diff --git a/postfix/src/tls/tls_proxy_client_scan.c b/postfix/src/tls/tls_proxy_client_scan.c new file mode 100644 index 000000000..19ca7c286 --- /dev/null +++ b/postfix/src/tls/tls_proxy_client_scan.c @@ -0,0 +1,566 @@ +/*++ +/* NAME +/* tls_proxy_client_scan 3 +/* SUMMARY +/* read TLS_CLIENT_XXX structures from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_client_init_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* char *tls_proxy_client_init_to_string(buf, init_props) +/* VSTRING *buf; +/* TLS_CLIENT_INIT_PROPS *init_props; +/* +/* void tls_proxy_client_init_free(init_props) +/* TLS_CLIENT_INIT_PROPS *init_props; +/* +/* int tls_proxy_client_start_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_client_start_free(start_props) +/* TLS_CLIENT_START_PROPS *start_props; +/* DESCRIPTION +/* tls_proxy_client_init_scan() reads a full TLS_CLIENT_INIT_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_client_init_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_client_init_to_string() produces a lookup key +/* that is unique for the properties received by +/* tls_proxy_client_init_scan(). +/* +/* tls_proxy_client_init_free() destroys a TLS_CLIENT_INIT_PROPS +/* structure that was created by tls_proxy_client_init_scan(). +/* +/* TLS_CLIENT_INIT_PROPS *init_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_client_init_scan, (void *) &init_props) +/* ... +/* if (init_props != 0) +/* tls_proxy_client_init_free(init_props); +/* +/* tls_proxy_client_start_scan() reads a TLS_CLIENT_START_PROPS +/* structure, without the stream of file descriptor members, +/* from the named stream using the specified attribute scan +/* routine. tls_proxy_client_start_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_client_start_free() destroys a TLS_CLIENT_START_PROPS +/* structure that was created by tls_proxy_client_start_scan(). +/* +/* TLS_CLIENT_START_PROPS *start_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_client_start_scan, (void *) &start_props) +/* ... +/* if (start_props != 0) +/* tls_proxy_client_start_free(start_props); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include +#include +#include + +/* TLS library. */ + +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* tls_proxy_client_init_free - destroy TLS_CLIENT_INIT_PROPS structure */ + +void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *props) +{ + myfree((void *) props->log_param); + myfree((void *) props->log_level); + myfree((void *) props->cache_type); + myfree((void *) props->cert_file); + myfree((void *) props->key_file); + myfree((void *) props->dcert_file); + myfree((void *) props->dkey_file); + myfree((void *) props->eccert_file); + myfree((void *) props->eckey_file); + myfree((void *) props->CAfile); + myfree((void *) props->CApath); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +/* tls_proxy_client_init_scan - receive TLS_CLIENT_INIT_PROPS from stream */ + +int tls_proxy_client_init_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_INIT_PROPS *props + = (TLS_CLIENT_INIT_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *log_param = vstring_alloc(25); + VSTRING *log_level = vstring_alloc(25); + VSTRING *cache_type = vstring_alloc(25); + VSTRING *cert_file = vstring_alloc(25); + VSTRING *key_file = vstring_alloc(25); + VSTRING *dcert_file = vstring_alloc(25); + VSTRING *dkey_file = vstring_alloc(25); + VSTRING *eccert_file = vstring_alloc(25); + VSTRING *eckey_file = vstring_alloc(25); + VSTRING *CAfile = vstring_alloc(25); + VSTRING *CApath = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_client_init_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param), + RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level), + RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth), + RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file), + RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file), + RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file), + RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file), + RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file), + RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file), + RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile), + RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->log_param = vstring_export(log_param); + props->log_level = vstring_export(log_level); + props->cache_type = vstring_export(cache_type); + props->cert_file = vstring_export(cert_file); + props->key_file = vstring_export(key_file); + props->dcert_file = vstring_export(dcert_file); + props->dkey_file = vstring_export(dkey_file); + props->eccert_file = vstring_export(eccert_file); + props->eckey_file = vstring_export(eckey_file); + props->CAfile = vstring_export(CAfile); + props->CApath = vstring_export(CApath); + props->mdalg = vstring_export(mdalg); + ret = (ret == 13 ? 1 : -1); + if (ret != 1) { + tls_proxy_client_init_free(props); + props = 0; + } + *(TLS_CLIENT_INIT_PROPS **) ptr = props; + if (msg_verbose) + msg_info("tls_proxy_client_init_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_init_to_string - serialize to string */ + +char *tls_proxy_client_init_to_string(VSTRING *buf, + TLS_CLIENT_INIT_PROPS *props) +{ + vstring_sprintf(buf, "%s\n%s\n%d\n%s\n%s\n%s\n%s\n%s\n" + "%s\n%s\n%s\n%s\n%s\n", props->log_param, + props->log_level, props->verifydepth, + props->cache_type, props->cert_file, props->key_file, + props->dcert_file, props->dkey_file, + props->eccert_file, props->eckey_file, + props->CAfile, props->CApath, props->mdalg); + return (vstring_str(buf)); +} + +/* tls_proxy_client_certs_free - destroy TLS_PKEYS from stream */ + +static void tls_proxy_client_certs_free(TLS_CERTS *tp) +{ + if (tp->next) + tls_proxy_client_certs_free(tp->next); + X509_free(tp->cert); + myfree((void *) tp); +} + +/* tls_proxy_client_pkeys_free - destroy TLS_PKEYS from stream */ + +static void tls_proxy_client_pkeys_free(TLS_PKEYS *tp) +{ + if (tp->next) + tls_proxy_client_pkeys_free(tp->next); + EVP_PKEY_free(tp->pkey); + myfree((void *) tp); +} + +/* tls_proxy_client_tlsa_free - destroy TLS_TLSA from stream */ + +static void tls_proxy_client_tlsa_free(TLS_TLSA *tp) +{ + if (tp->next) + tls_proxy_client_tlsa_free(tp->next); + myfree(tp->mdalg); + if (tp->certs) + argv_free(tp->certs); + if (tp->pkeys) + argv_free(tp->pkeys); + myfree((void *) tp); +} + +/* tls_proxy_client_dane_free - destroy TLS_DANE from stream */ + +static void tls_proxy_client_dane_free(TLS_DANE *dane) +{ + if (dane->ta) + tls_proxy_client_tlsa_free(dane->ta); + if (dane->ee) + tls_proxy_client_tlsa_free(dane->ee); + if (dane->certs) + tls_proxy_client_certs_free(dane->certs); + if (dane->pkeys) + tls_proxy_client_pkeys_free(dane->pkeys); + myfree(dane->base_domain); + if (dane->refs-- == 1) + myfree((void *) dane); +} + +/* tls_proxy_client_start_free - destroy TLS_CLIENT_START_PROPS structure */ + +void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props) +{ + myfree((void *) props->nexthop); + myfree((void *) props->host); + myfree((void *) props->namaddr); + myfree((void *) props->serverid); + myfree((void *) props->helo); + myfree((void *) props->protocols); + myfree((void *) props->cipher_grade); + myfree((void *) props->cipher_exclusions); + if (props->matchargv) + argv_free((ARGV *) props->matchargv); + myfree((void *) props->mdalg); + if (props->dane) + tls_proxy_client_dane_free((TLS_DANE *) props->dane); + myfree((void *) props); +} + +/* tls_proxy_client_certs_scan - receive TLS_CERTS from stream */ + +static int tls_proxy_client_certs_scan(ATTR_SCAN_MASTER_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + int ret; + int count; + VSTRING *buf = 0; + TLS_CERTS **tpp; + TLS_CERTS *head = 0; + TLS_CERTS *tp; + int n; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_COUNT, &count), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_certs_scan count=%d", count); + + for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) { + *tpp = tp = (TLS_CERTS *) mymalloc(sizeof(*tp)); + D2I_const unsigned char *bp; + + if (buf == 0) + buf = vstring_alloc(100); + + /* + * Note: memset() is not a portable way to initialize non-integer + * types. + */ + memset(tp, 0, sizeof(*tp)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_DATA(TLS_ATTR_CERT, buf), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + if (ret == 1) { + bp = (D2I_const unsigned char *) STR(buf); + if (d2i_X509(&tp->cert, &bp, LEN(buf)) == 0 + || LEN(buf) != ((char *) bp) - STR(buf)) { + msg_warn("malformed certificate in TLS_CERTS"); + ret = -1; + } + } else { + tp->cert = 0; + } + tp->next = 0; + } + if (buf) + vstring_free(buf); + if (ret != 1) { + tls_proxy_client_certs_free(head); + head = 0; + } + *(TLS_CERTS **) ptr = head; + if (msg_verbose) + msg_info("tls_proxy_client_certs_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_pkeys_scan - receive TLS_PKEYS from stream */ + +static int tls_proxy_client_pkeys_scan(ATTR_SCAN_MASTER_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + int ret; + int count; + VSTRING *buf = vstring_alloc(100); + TLS_PKEYS **tpp; + TLS_PKEYS *head = 0; + TLS_PKEYS *tp; + int n; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_COUNT, &count), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_pkeys_scan count=%d", count); + + for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) { + *tpp = tp = (TLS_PKEYS *) mymalloc(sizeof(*tp)); + D2I_const unsigned char *bp; + + if (buf == 0) + buf = vstring_alloc(100); + + /* + * Note: memset() is not a portable way to initialize non-integer + * types. + */ + memset(tp, 0, sizeof(*tp)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_DATA(TLS_ATTR_PKEY, buf), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + if (ret == 1) { + bp = (D2I_const unsigned char *) STR(buf); + if (d2i_PUBKEY(&tp->pkey, &bp, LEN(buf)) == 0 + || LEN(buf) != (char *) bp - STR(buf)) { + msg_warn("malformed public key in TLS_PKEYS"); + ret = -1; + } + } else { + tp->pkey = 0; + } + tp->next = 0; + } + if (buf) + vstring_free(buf); + if (ret != 1) { + tls_proxy_client_pkeys_free(head); + head = 0; + } + *(TLS_PKEYS **) ptr = head; + if (msg_verbose) + msg_info("tls_proxy_client_pkeys_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_tlsa_scan - receive TLS_TLSA from stream */ + +static int tls_proxy_client_tlsa_scan(ATTR_SCAN_MASTER_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + int ret; + int count; + TLS_TLSA **tpp; + TLS_TLSA *head = 0; + TLS_TLSA *tp; + int n; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_COUNT, &count), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_scan count=%d", count); + + for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) { + *tpp = tp = (TLS_TLSA *) mymalloc(sizeof(*tp)); + VSTRING *mdalg = vstring_alloc(25); + + /* + * Note: memset() is not a portable way to initialize non-integer + * types. + */ + memset(tp, 0, sizeof(*tp)); + /* Always construct a well-formed structure. */ + tp->certs = 0; /* scan_fn may return early */ + tp->pkeys = 0; + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + RECV_ATTR_FUNC(argv_attr_scan, &tp->certs), + RECV_ATTR_FUNC(argv_attr_scan, &tp->pkeys), + ATTR_TYPE_END); + tp->mdalg = vstring_export(mdalg); + tp->next = 0; + ret = (ret == 3 ? 1 : -1); + } + if (ret != 1) { + tls_proxy_client_tlsa_free(head); + head = 0; + } + *(TLS_TLSA **) ptr = head; + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_dane_scan - receive TLS_DANE from stream */ + +static int tls_proxy_client_dane_scan(ATTR_SCAN_MASTER_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_DANE *dane = 0; + int ret; + int have_dane = 0; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_DANE, &have_dane), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_dane_scan have_dane=%d", have_dane); + + if (ret == 1 && have_dane) { + VSTRING *base_domain = vstring_alloc(25); + long expires; + + dane = (TLS_DANE *) mymalloc(sizeof(*dane)); + + /* + * Note: memset() is not a portable way to initialize non-integer + * types. + */ + memset(dane, 0, sizeof(*dane)); + /* Always construct a well-formed structure. */ + dane->ta = 0; /* scan_fn may return early */ + dane->ee = 0; + dane->certs = 0; + dane->pkeys = 0; + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan, + &dane->ta), + RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan, + &dane->ee), + RECV_ATTR_FUNC(tls_proxy_client_certs_scan, + &dane->certs), + RECV_ATTR_FUNC(tls_proxy_client_pkeys_scan, + &dane->pkeys), + RECV_ATTR_STR(TLS_ATTR_DOMAIN, base_domain), + RECV_ATTR_INT(TLS_ATTR_FLAGS, &dane->flags), + RECV_ATTR_LONG(TLS_ATTR_EXP, &expires), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + dane->base_domain = vstring_export(base_domain); + dane->expires = expires; + dane->refs = 1; + ret = (ret == 7 ? 1 : -1); + /* XXX if scan_fn() completed normally, sanity check dane->flags. */ + if (ret != 1) { + tls_proxy_client_dane_free(dane); + dane = 0; + } + } + *(TLS_DANE **) ptr = dane; + if (msg_verbose) + msg_info("tls_proxy_client_dane_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_start_scan - receive TLS_CLIENT_START_PROPS from stream */ + +int tls_proxy_client_start_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_START_PROPS *props + = (TLS_CLIENT_START_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *nexthop = vstring_alloc(25); + VSTRING *host = vstring_alloc(25); + VSTRING *namaddr = vstring_alloc(25); + VSTRING *serverid = vstring_alloc(25); + VSTRING *helo = vstring_alloc(25); + VSTRING *protocols = vstring_alloc(25); + VSTRING *cipher_grade = vstring_alloc(25); + VSTRING *cipher_exclusions = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_client_start_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + props->ctx = 0; + props->stream = 0; + props->fd = -1; + props->dane = 0; /* scan_fn may return early */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout), + RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level), + RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop), + RECV_ATTR_STR(TLS_ATTR_HOST, host), + RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), + RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid), + RECV_ATTR_STR(TLS_ATTR_HELO, helo), + RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols), + RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade), + RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + cipher_exclusions), + RECV_ATTR_FUNC(argv_attr_scan, &props->matchargv), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + RECV_ATTR_FUNC(tls_proxy_client_dane_scan, + &props->dane), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->nexthop = vstring_export(nexthop); + props->host = vstring_export(host); + props->namaddr = vstring_export(namaddr); + props->serverid = vstring_export(serverid); + props->helo = vstring_export(helo); + props->protocols = vstring_export(protocols); + props->cipher_grade = vstring_export(cipher_grade); + props->cipher_exclusions = vstring_export(cipher_exclusions); + props->mdalg = vstring_export(mdalg); + ret = (ret == 13 ? 1 : -1); + if (ret != 1) { + tls_proxy_client_start_free(props); + props = 0; + } + *(TLS_CLIENT_START_PROPS **) ptr = props; + if (msg_verbose) + msg_info("tls_proxy_client_start_scan ret=%d", ret); + return (ret); +} + +#endif diff --git a/postfix/src/tls/tls_proxy_clnt.c b/postfix/src/tls/tls_proxy_clnt.c index 97d096264..90b21cd50 100644 --- a/postfix/src/tls/tls_proxy_clnt.c +++ b/postfix/src/tls/tls_proxy_clnt.c @@ -2,24 +2,36 @@ /* NAME /* tlsproxy_clnt 3 /* SUMMARY -/* postscreen TLS proxy support +/* tlsproxy(8) client support /* SYNOPSIS /* #include /* /* VSTREAM *tls_proxy_open(service, flags, peer_stream, peer_addr, -/* peer_port, timeout) +/* peer_port, timeout, serverid, init_props, +/* start_props) /* const char *service; /* int flags; /* VSTREAM *peer_stream; /* const char *peer_addr; /* const char *peer_port; /* int timeout; +/* const char *serverid; +/* void *init_props; +/* void *start_props; /* /* TLS_SESS_STATE *tls_proxy_context_receive(proxy_stream) /* VSTREAM *proxy_stream; -/* -/* void tls_proxy_context_free(tls_context) -/* TLS_SESS_STATE *tls_context; +/* AUXILIARY FUNCTIONS +/* VSTREAM *tls_proxy_legacy_open(service, flags, peer_stream, +/* peer_addr, peer_port, +/* timeout, serverid) +/* const char *service; +/* int flags; +/* VSTREAM *peer_stream; +/* const char *peer_addr; +/* const char *peer_port; +/* int timeout; +/* const char *serverid; /* DESCRIPTION /* tls_proxy_open() prepares for inserting the tlsproxy(8) /* daemon between the current process and a remote peer (the @@ -44,8 +56,8 @@ /* /* After this, the proxy_stream is ready for plain-text I/O. /* -/* tls_proxy_context_free() destroys a TLS context object that -/* was received with tls_proxy_context_receive(). +/* tls_proxy_legacy_open() is a backwards-compatibility feature +/* that provides a historical interface. /* /* Arguments: /* .IP service @@ -68,6 +80,12 @@ /* Printable TCP port of the remote peer_stream endpoint. /* .IP timeout /* Time limit that the tlsproxy(8) daemon should use. +/* .IP serverid +/* Unique service identifier. +/* .IP init_props +/* Pointer to TLS_CLIENT_INIT_PROPS or TLS_SERVER_INIT_PROPS. +/* .IP start_props +/* Pointer to TLS_CLIENT_START_PROPS or TLS_SERVER_START_PROPS. /* .IP proxy_stream /* Stream from tls_proxy_open(). /* .IP tls_context @@ -81,6 +99,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ #ifdef USE_TLS @@ -119,8 +142,12 @@ VSTREAM *tls_proxy_open(const char *service, int flags, VSTREAM *peer_stream, const char *peer_addr, const char *peer_port, - int timeout) + int timeout, + const char *serverid, + void *init_props, + void *start_props) { + const char myname[] = "tls_proxy_open"; VSTREAM *tlsproxy_stream; int status; int fd; @@ -146,19 +173,42 @@ VSTREAM *tls_proxy_open(const char *service, int flags, } /* - * Initial handshake. Send the data attributes now, and send the client - * file descriptor in a later transaction. - * - * XXX The formatted endpoint should be a state member. Then, we can - * simplify all the format strings throughout the program. + * Initial handshake. Send common data attributes now, and send the + * remote peer file descriptor in a later transaction. */ tlsproxy_stream = vstream_fdopen(fd, O_RDWR); vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port); attr_print(tlsproxy_stream, ATTR_FLAG_NONE, - SEND_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt)), - SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), - SEND_ATTR_INT(MAIL_ATTR_TIMEOUT, timeout), + SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)), + SEND_ATTR_INT(TLS_ATTR_FLAGS, flags), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, timeout), + SEND_ATTR_STR(TLS_ATTR_SERVERID, serverid), ATTR_TYPE_END); + /* Do not flush the stream yet. */ + if (vstream_ferror(tlsproxy_stream) != 0) { + msg_warn("error sending request to %s service: %m", + STR(tlsproxy_service)); + vstream_fclose(tlsproxy_stream); + return (0); + } + switch (flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_client_init_print, init_props), + SEND_ATTR_FUNC(tls_proxy_client_start_print, start_props), + ATTR_TYPE_END); + break; + case TLS_PROXY_FLAG_ROLE_SERVER: +#if 0 + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_server_init_print, init_props), + SEND_ATTR_FUNC(tls_proxy_server_start_print, start_props), + ATTR_TYPE_END); +#endif + break; + default: + msg_panic("%s: bad flags: 0x%x", myname, flags); + } if (vstream_fflush(tlsproxy_stream) != 0) { msg_warn("error sending request to %s service: %m", STR(tlsproxy_service)); @@ -170,11 +220,12 @@ VSTREAM *tls_proxy_open(const char *service, int flags, * Receive the "TLS is available" indication. * * This may seem out of order, but we must have a read transaction between - * sending the request attributes and sending the SMTP client file + * sending the request attributes and sending the plaintext file * descriptor. We can't assume UNIX-domain socket semantics here. */ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + /* TODO: informative message. */ ATTR_TYPE_END) != 1 || status == 0) { /* @@ -191,7 +242,7 @@ VSTREAM *tls_proxy_open(const char *service, int flags, } /* - * Send the remote SMTP client file descriptor. + * Send the remote peer file descriptor. */ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), vstream_fileno(peer_stream)) < 0) { @@ -207,41 +258,22 @@ VSTREAM *tls_proxy_open(const char *service, int flags, return (tlsproxy_stream); } + /* tls_proxy_context_receive - receive TLS session object from tlsproxy(8) */ TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream) { - TLS_SESS_STATE *tls_context; - - tls_context = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context)); + TLS_SESS_STATE *tls_context = 0; if (attr_scan(proxy_stream, ATTR_FLAG_STRICT, - RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) tls_context), + RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ATTR_TYPE_END) != 1) { - tls_proxy_context_free(tls_context); + if (tls_context) + tls_proxy_context_free(tls_context); return (0); } else { return (tls_context); } } -/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */ - -void tls_proxy_context_free(TLS_SESS_STATE *tls_context) -{ - if (tls_context->peer_CN) - myfree(tls_context->peer_CN); - if (tls_context->issuer_CN) - myfree(tls_context->issuer_CN); - if (tls_context->peer_cert_fprint) - myfree(tls_context->peer_cert_fprint); - if (tls_context->peer_pkey_fprint) - myfree(tls_context->peer_pkey_fprint); - if (tls_context->protocol) - myfree((void *) tls_context->protocol); - if (tls_context->cipher_name) - myfree((void *) tls_context->cipher_name); - myfree((void *) tls_context); -} - #endif diff --git a/postfix/src/tls/tls_proxy_print.c b/postfix/src/tls/tls_proxy_context_print.c similarity index 63% rename from postfix/src/tls/tls_proxy_print.c rename to postfix/src/tls/tls_proxy_context_print.c index e30e8be88..a597024b2 100644 --- a/postfix/src/tls/tls_proxy_print.c +++ b/postfix/src/tls/tls_proxy_context_print.c @@ -1,8 +1,8 @@ /*++ /* NAME -/* tls_proxy_print +/* tls_proxy_context_print /* SUMMARY -/* write DSN structure to stream +/* write TLS_ATTR_STATE structure to stream /* SYNOPSIS /* #include /* @@ -12,10 +12,10 @@ /* int flags; /* void *ptr; /* DESCRIPTION -/* tls_proxy_context_print() writes a TLS_SESS_STATE structure -/* to the named stream using the specified attribute print -/* routine. TLS_SESS_STATE() is meant to be passed as a call-back -/* to attr_print(), thusly: +/* tls_proxy_context_print() writes the public members of a +/* TLS_ATTR_STATE structure to the named stream using the +/* specified attribute print routine. tls_proxy_context_print() +/* is meant to be passed as a call-back to attr_print(), thusly: /* /* ... SEND_ATTR_FUNC(tls_proxy_context_print, (void *) tls_context), ... /* DIAGNOSTICS @@ -29,6 +29,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ #ifdef USE_TLS @@ -41,10 +46,6 @@ #include -/* Global library. */ - -#include - /* TLS library. */ #include @@ -61,25 +62,26 @@ int tls_proxy_context_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp, #define STRING_OR_EMPTY(s) ((s) ? (s) : "") ret = print_fn(fp, flags | ATTR_FLAG_MORE, - SEND_ATTR_STR(MAIL_ATTR_PEER_CN, + SEND_ATTR_STR(TLS_ATTR_PEER_CN, STRING_OR_EMPTY(tp->peer_CN)), - SEND_ATTR_STR(MAIL_ATTR_ISSUER_CN, + SEND_ATTR_STR(TLS_ATTR_ISSUER_CN, STRING_OR_EMPTY(tp->issuer_CN)), - SEND_ATTR_STR(MAIL_ATTR_PEER_CERT_FPT, + SEND_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, STRING_OR_EMPTY(tp->peer_cert_fprint)), - SEND_ATTR_STR(MAIL_ATTR_PEER_PKEY_FPT, + SEND_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, STRING_OR_EMPTY(tp->peer_pkey_fprint)), - SEND_ATTR_INT(MAIL_ATTR_PEER_STATUS, + SEND_ATTR_INT(TLS_ATTR_PEER_STATUS, tp->peer_status), - SEND_ATTR_STR(MAIL_ATTR_CIPHER_PROTOCOL, + SEND_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, STRING_OR_EMPTY(tp->protocol)), - SEND_ATTR_STR(MAIL_ATTR_CIPHER_NAME, + SEND_ATTR_STR(TLS_ATTR_CIPHER_NAME, STRING_OR_EMPTY(tp->cipher_name)), - SEND_ATTR_INT(MAIL_ATTR_CIPHER_USEBITS, + SEND_ATTR_INT(TLS_ATTR_CIPHER_USEBITS, tp->cipher_usebits), - SEND_ATTR_INT(MAIL_ATTR_CIPHER_ALGBITS, + SEND_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS, tp->cipher_algbits), ATTR_TYPE_END); + /* Do not flush the stream. */ return (ret); } diff --git a/postfix/src/tls/tls_proxy_context_scan.c b/postfix/src/tls/tls_proxy_context_scan.c new file mode 100644 index 000000000..74f109b31 --- /dev/null +++ b/postfix/src/tls/tls_proxy_context_scan.c @@ -0,0 +1,140 @@ +/*++ +/* NAME +/* tls_proxy_context_scan +/* SUMMARY +/* read TLS session state from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_context_free(tls_context) +/* TLS_SESS_STATE *tls_context; +/* DESCRIPTION +/* tls_proxy_context_scan() reads the public members of a +/* TLS_ATTR_STATE structure from the named stream using the +/* specified attribute scan routine. tls_proxy_context_scan() +/* is meant to be passed as a call-back to attr_scan() as shown +/* below. +/* +/* tls_proxy_context_free() destroys a TLS context object that +/* was received with tls_proxy_context_scan(). +/* +/* TLS_ATTR_STATE *tls_context = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ... +/* ... +/* if (tls_context) +/* tls_proxy_context_free(tls_context); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_context_scan - receive TLS session state from stream */ + +int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SESS_STATE *tls_context + = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));; + int ret; + VSTRING *peer_CN = vstring_alloc(25); + VSTRING *issuer_CN = vstring_alloc(25); + VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */ + VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */ + VSTRING *protocol = vstring_alloc(25); + VSTRING *cipher_name = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_context_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(tls_context, 0, sizeof(*tls_context)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_PEER_CN, peer_CN), + RECV_ATTR_STR(TLS_ATTR_ISSUER_CN, issuer_CN), + RECV_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, peer_cert_fprint), + RECV_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, peer_pkey_fprint), + RECV_ATTR_INT(TLS_ATTR_PEER_STATUS, + &tls_context->peer_status), + RECV_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, protocol), + RECV_ATTR_STR(TLS_ATTR_CIPHER_NAME, cipher_name), + RECV_ATTR_INT(TLS_ATTR_CIPHER_USEBITS, + &tls_context->cipher_usebits), + RECV_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS, + &tls_context->cipher_algbits), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + tls_context->peer_CN = vstring_export(peer_CN); + tls_context->issuer_CN = vstring_export(issuer_CN); + tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint); + tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint); + tls_context->protocol = vstring_export(protocol); + tls_context->cipher_name = vstring_export(cipher_name); + ret = (ret == 9 ? 1 : -1); + if (ret != 1) { + tls_proxy_context_free(tls_context); + tls_context = 0; + } + *(TLS_SESS_STATE **) ptr = tls_context; + if (msg_verbose) + msg_info("tls_proxy_context_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */ + +void tls_proxy_context_free(TLS_SESS_STATE *tls_context) +{ + if (tls_context->peer_CN) + myfree(tls_context->peer_CN); + if (tls_context->issuer_CN) + myfree(tls_context->issuer_CN); + if (tls_context->peer_cert_fprint) + myfree(tls_context->peer_cert_fprint); + if (tls_context->peer_pkey_fprint) + myfree(tls_context->peer_pkey_fprint); + if (tls_context->protocol) + myfree((void *) tls_context->protocol); + if (tls_context->cipher_name) + myfree((void *) tls_context->cipher_name); + myfree((void *) tls_context); +} + +#endif diff --git a/postfix/src/tls/tls_proxy_scan.c b/postfix/src/tls/tls_proxy_scan.c deleted file mode 100644 index 29a0cd9f9..000000000 --- a/postfix/src/tls/tls_proxy_scan.c +++ /dev/null @@ -1,94 +0,0 @@ -/*++ -/* NAME -/* tls_proxy_scan -/* SUMMARY -/* read TLS session state from stream -/* SYNOPSIS -/* #include -/* -/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr) -/* ATTR_SCAN_MASTER_FN scan_fn; -/* VSTREAM *stream; -/* int flags; -/* void *ptr; -/* DESCRIPTION -/* tls_proxy_context_scan() reads a TLS_SESS_STATE structure -/* from the named stream using the specified attribute scan -/* routine. tls_proxy_context_scan() is meant to be passed as -/* a call-back to attr_scan(), thusly: -/* -/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) tls_context), ... -/* DIAGNOSTICS -/* Fatal: out of memory. -/* LICENSE -/* .ad -/* .fi -/* The Secure Mailer license must be distributed with this software. -/* AUTHOR(S) -/* Wietse Venema -/* IBM T.J. Watson Research -/* P.O. Box 704 -/* Yorktown Heights, NY 10598, USA -/*--*/ - -#ifdef USE_TLS - -/* System library. */ - -#include - -/* Utility library */ - -#include - -/* Global library. */ - -#include - -/* TLS library. */ - -#include -#include - -/* tls_proxy_context_scan - receive TLS session state from stream */ - -int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, - int flags, void *ptr) -{ - TLS_SESS_STATE *tls_context = (TLS_SESS_STATE *) ptr; - int ret; - VSTRING *peer_CN = vstring_alloc(25); - VSTRING *issuer_CN = vstring_alloc(25); - VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */ - VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */ - VSTRING *protocol = vstring_alloc(25); - VSTRING *cipher_name = vstring_alloc(25); - - /* - * Note: memset() is not a portable way to initialize non-integer types. - */ - memset(ptr, 0, sizeof(TLS_SESS_STATE)); - ret = scan_fn(fp, flags | ATTR_FLAG_MORE, - RECV_ATTR_STR(MAIL_ATTR_PEER_CN, peer_CN), - RECV_ATTR_STR(MAIL_ATTR_ISSUER_CN, issuer_CN), - RECV_ATTR_STR(MAIL_ATTR_PEER_CERT_FPT, peer_cert_fprint), - RECV_ATTR_STR(MAIL_ATTR_PEER_PKEY_FPT, peer_pkey_fprint), - RECV_ATTR_INT(MAIL_ATTR_PEER_STATUS, - &tls_context->peer_status), - RECV_ATTR_STR(MAIL_ATTR_CIPHER_PROTOCOL, protocol), - RECV_ATTR_STR(MAIL_ATTR_CIPHER_NAME, cipher_name), - RECV_ATTR_INT(MAIL_ATTR_CIPHER_USEBITS, - &tls_context->cipher_usebits), - RECV_ATTR_INT(MAIL_ATTR_CIPHER_ALGBITS, - &tls_context->cipher_algbits), - ATTR_TYPE_END); - tls_context->peer_CN = vstring_export(peer_CN); - tls_context->issuer_CN = vstring_export(issuer_CN); - tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint); - tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint); - tls_context->protocol = vstring_export(protocol); - tls_context->cipher_name = vstring_export(cipher_name); - return (ret == 9 ? 1 : -1); -} - -#endif diff --git a/postfix/src/tls/tls_proxy_server_print.c b/postfix/src/tls/tls_proxy_server_print.c new file mode 100644 index 000000000..670f84119 --- /dev/null +++ b/postfix/src/tls/tls_proxy_server_print.c @@ -0,0 +1,141 @@ +/*++ +/* NAME +/* tls_proxy_server_print 3 +/* SUMMARY +/* write TLS_SERVER_XXX structures to stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_server_init_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* int tls_proxy_server_start_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* tls_proxy_server_init_print() writes a TLS_SERVER_INIT_PROPS +/* structure to the named stream using the specified attribute print +/* routine. tls_proxy_server_init_print() is meant to be passed as +/* a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(tls_proxy_server_init_print, (void *) init_props), ... +/* +/* tls_proxy_server_start_print() writes a TLS_SERVER_START_PROPS +/* structure to the named stream using the specified attribute print +/* routine. tls_proxy_server_start_print() is meant to be passed as +/* a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(tls_proxy_server_start_print, (void *) start_props), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_server_init_print - send TLS_SERVER_INIT_PROPS over stream */ + +int tls_proxy_server_init_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_INIT_PROPS *props = (TLS_SERVER_INIT_PROPS *) ptr; + int ret; + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_LOG_PARAM, + STRING_OR_EMPTY(props->log_param)), + SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL, + STRING_OR_EMPTY(props->log_level)), + SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth), + SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE, + STRING_OR_EMPTY(props->cache_type)), + SEND_ATTR_INT(TLS_ATTR_SET_SESSID, props->set_sessid), + SEND_ATTR_STR(TLS_ATTR_CERT_FILE, + STRING_OR_EMPTY(props->cert_file)), + SEND_ATTR_STR(TLS_ATTR_KEY_FILE, + STRING_OR_EMPTY(props->key_file)), + SEND_ATTR_STR(TLS_ATTR_DCERT_FILE, + STRING_OR_EMPTY(props->dcert_file)), + SEND_ATTR_STR(TLS_ATTR_DKEY_FILE, + STRING_OR_EMPTY(props->dkey_file)), + SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE, + STRING_OR_EMPTY(props->eccert_file)), + SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE, + STRING_OR_EMPTY(props->eckey_file)), + SEND_ATTR_STR(TLS_ATTR_CAFILE, + STRING_OR_EMPTY(props->CAfile)), + SEND_ATTR_STR(TLS_ATTR_CAPATH, + STRING_OR_EMPTY(props->CApath)), + SEND_ATTR_STR(TLS_ATTR_PROTOCOLS, + STRING_OR_EMPTY(props->protocols)), + SEND_ATTR_STR(TLS_ATTR_EECDH_GRADE, + STRING_OR_EMPTY(props->eecdh_grade)), + SEND_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, + STRING_OR_EMPTY(props->dh1024_param_file)), + SEND_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, + STRING_OR_EMPTY(props->dh512_param_file)), + SEND_ATTR_INT(TLS_ATTR_ASK_CCERT, props->ask_ccert), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + return (ret); +} + +/* tls_proxy_server_start_print - send TLS_SERVER_START_PROPS over stream */ + +int tls_proxy_server_start_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_START_PROPS *props = (TLS_SERVER_START_PROPS *) ptr; + int ret; + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout), + SEND_ATTR_INT(TLS_ATTR_REQUIRECERT, props->requirecert), + SEND_ATTR_STR(TLS_ATTR_SERVERID, + STRING_OR_EMPTY(props->serverid)), + SEND_ATTR_STR(TLS_ATTR_NAMADDR, + STRING_OR_EMPTY(props->namaddr)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE, + STRING_OR_EMPTY(props->cipher_grade)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + STRING_OR_EMPTY(props->cipher_exclusions)), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + return (ret); +} + +#endif diff --git a/postfix/src/tls/tls_proxy_server_scan.c b/postfix/src/tls/tls_proxy_server_scan.c new file mode 100644 index 000000000..62487d8ce --- /dev/null +++ b/postfix/src/tls/tls_proxy_server_scan.c @@ -0,0 +1,241 @@ +/*++ +/* NAME +/* tls_proxy_server_scan 3 +/* SUMMARY +/* read TLS_SERVER_XXX structures from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_server_init_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* tls_proxy_server_init_free(init_props) +/* TLS_SERVER_INIT_PROPS *init_props; +/* +/* int tls_proxy_server_start_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_server_start_free(start_props) +/* TLS_SERVER_START_PROPS *start_props; +/* DESCRIPTION +/* tls_proxy_server_init_scan() reads a TLS_SERVER_INIT_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_server_init_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_server_init_free() destroys a TLS_SERVER_INIT_PROPS +/* structure that was created by tls_proxy_server_init_scan(). +/* +/* TLS_SERVER_INIT_PROPS *init_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_server_init_scan, (void *) &init_props) +/* ... +/* if (init_props) +/* tls_proxy_client_init_free(init_props); +/* +/* tls_proxy_server_start_scan() reads a TLS_SERVER_START_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_server_start_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_server_start_free() destroys a TLS_SERVER_START_PROPS +/* structure that was created by tls_proxy_server_start_scan(). +/* +/* TLS_SERVER_START_PROPS *start_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_server_start_scan, (void *) &start_props) +/* ... +/* if (start_props) +/* tls_proxy_server_start_free(start_props); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_server_init_scan - receive TLS_SERVER_INIT_PROPS from stream */ + +int tls_proxy_server_init_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_INIT_PROPS *props + = (TLS_SERVER_INIT_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *log_param = vstring_alloc(25); + VSTRING *log_level = vstring_alloc(25); + VSTRING *cache_type = vstring_alloc(25); + VSTRING *cert_file = vstring_alloc(25); + VSTRING *key_file = vstring_alloc(25); + VSTRING *dcert_file = vstring_alloc(25); + VSTRING *dkey_file = vstring_alloc(25); + VSTRING *eccert_file = vstring_alloc(25); + VSTRING *eckey_file = vstring_alloc(25); + VSTRING *CAfile = vstring_alloc(25); + VSTRING *CApath = vstring_alloc(25); + VSTRING *protocols = vstring_alloc(25); + VSTRING *eecdh_grade = vstring_alloc(25); + VSTRING *dh1024_param_file = vstring_alloc(25); + VSTRING *dh512_param_file = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param), + RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level), + RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth), + RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_INT(TLS_ATTR_SET_SESSID, &props->set_sessid), + RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file), + RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file), + RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file), + RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file), + RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file), + RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file), + RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile), + RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath), + RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols), + RECV_ATTR_STR(TLS_ATTR_EECDH_GRADE, eecdh_grade), + RECV_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, dh1024_param_file), + RECV_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, dh512_param_file), + RECV_ATTR_INT(TLS_ATTR_ASK_CCERT, &props->ask_ccert), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->log_param = vstring_export(log_param); + props->log_level = vstring_export(log_level); + props->cache_type = vstring_export(cache_type); + props->cert_file = vstring_export(cert_file); + props->key_file = vstring_export(key_file); + props->dcert_file = vstring_export(dcert_file); + props->dkey_file = vstring_export(dkey_file); + props->eccert_file = vstring_export(eccert_file); + props->eckey_file = vstring_export(eckey_file); + props->CAfile = vstring_export(CAfile); + props->CApath = vstring_export(CApath); + props->protocols = vstring_export(protocols); + props->eecdh_grade = vstring_export(eecdh_grade); + props->dh1024_param_file = vstring_export(dh1024_param_file); + props->dh512_param_file = vstring_export(dh512_param_file); + props->mdalg = vstring_export(mdalg); + ret = (ret == 19 ? 1 : -1); + if (ret != 1) { + tls_proxy_server_init_free(props); + props = 0; + } + *(TLS_SERVER_INIT_PROPS **) ptr = props; + return (ret); +} + +/* tls_proxy_server_init_free - destroy TLS_SERVER_INIT_PROPS structure */ + +void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *props) +{ + myfree((void *) props->log_param); + myfree((void *) props->log_level); + myfree((void *) props->cache_type); + myfree((void *) props->cert_file); + myfree((void *) props->key_file); + myfree((void *) props->dcert_file); + myfree((void *) props->dkey_file); + myfree((void *) props->eccert_file); + myfree((void *) props->eckey_file); + myfree((void *) props->CAfile); + myfree((void *) props->CApath); + myfree((void *) props->protocols); + myfree((void *) props->eecdh_grade); + myfree((void *) props->dh1024_param_file); + myfree((void *) props->dh512_param_file); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +/* tls_proxy_server_start_scan - receive TLS_SERVER_START_PROPS from stream */ + +int tls_proxy_server_start_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_START_PROPS *props + = (TLS_SERVER_START_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *serverid = vstring_alloc(25); + VSTRING *namaddr = vstring_alloc(25); + VSTRING *cipher_grade = vstring_alloc(25); + VSTRING *cipher_exclusions = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + props->ctx = 0; + props->stream = 0; + /* XXX Caller sets fd. */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout), + RECV_ATTR_INT(TLS_ATTR_REQUIRECERT, &props->requirecert), + RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid), + RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), + RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade), + RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + cipher_exclusions), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + props->serverid = vstring_export(serverid); + props->namaddr = vstring_export(namaddr); + props->cipher_grade = vstring_export(cipher_grade); + props->cipher_exclusions = vstring_export(cipher_exclusions); + props->mdalg = vstring_export(mdalg); + ret = (ret == 7 ? 1 : -1); + if (ret != 1) { + tls_proxy_server_start_free(props); + props = 0; + } + *(TLS_SERVER_START_PROPS **) ptr = props; + return (ret); +} + +/* tls_proxy_server_start_free - destroy TLS_SERVER_START_PROPS structure */ + +void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props) +{ + /* XXX Caller closes fd. */ + myfree((void *) props->serverid); + myfree((void *) props->namaddr); + myfree((void *) props->cipher_grade); + myfree((void *) props->cipher_exclusions); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +#endif diff --git a/postfix/src/tlsproxy/Makefile.in b/postfix/src/tlsproxy/Makefile.in index 087721bf7..e248f8501 100644 --- a/postfix/src/tlsproxy/Makefile.in +++ b/postfix/src/tlsproxy/Makefile.in @@ -61,6 +61,7 @@ depend: $(MAKES) @$(EXPORT) make -f Makefile.in Makefile 1>&2 # do not edit below this line - it is generated by 'make depend' +tls_client.o: tls_client.c tlsproxy.o: ../../include/argv.h tlsproxy.o: ../../include/attr.h tlsproxy.o: ../../include/check_arg.h @@ -90,9 +91,11 @@ tlsproxy.o: ../../include/vstring.h tlsproxy.o: tlsproxy.c tlsproxy.o: tlsproxy.h tlsproxy_state.o: ../../include/argv.h +tlsproxy_state.o: ../../include/attr.h tlsproxy_state.o: ../../include/check_arg.h tlsproxy_state.o: ../../include/dns.h tlsproxy_state.o: ../../include/events.h +tlsproxy_state.o: ../../include/htable.h tlsproxy_state.o: ../../include/mail_conf.h tlsproxy_state.o: ../../include/mail_server.h tlsproxy_state.o: ../../include/msg.h @@ -101,9 +104,11 @@ tlsproxy_state.o: ../../include/mymalloc.h tlsproxy_state.o: ../../include/name_code.h tlsproxy_state.o: ../../include/name_mask.h tlsproxy_state.o: ../../include/nbbio.h +tlsproxy_state.o: ../../include/nvtable.h tlsproxy_state.o: ../../include/sock_addr.h tlsproxy_state.o: ../../include/sys_defs.h tlsproxy_state.o: ../../include/tls.h +tlsproxy_state.o: ../../include/tls_proxy.h tlsproxy_state.o: ../../include/vbuf.h tlsproxy_state.o: ../../include/vstream.h tlsproxy_state.o: ../../include/vstring.h diff --git a/postfix/src/tlsproxy/tlsproxy.c b/postfix/src/tlsproxy/tlsproxy.c index 2c855ffcc..ecfb149df 100644 --- a/postfix/src/tlsproxy/tlsproxy.c +++ b/postfix/src/tlsproxy/tlsproxy.c @@ -309,14 +309,33 @@ int var_tlsp_watchdog; static TLS_APPL_STATE *tlsp_server_ctx; static int ask_client_cert; + /* + * TLS per-client status. + */ +static HTABLE *tlsp_client_app_cache; + + /* + * Error handling: if a function detects an error, then that function is + * responsible for destroying TLSP_STATE. Exceptions to this principle are + * indicated in the code. + */ + + /* + * Internal status API. + */ +#define TLSP_STAT_OK 0 +#define TLSP_STAT_ERR (-1) + /* * SLMs. */ #define STR(x) vstring_str(x) /* - * This code looks simpler than expected. That is the result of a great deal - * of effort, mainly in design and analysis. + * The code that implements the TLS engine looks simpler than expected. That + * is the result of a great deal of effort, mainly in design and analysis. + * + * The initial use case was to provide TLS support for postscreen(8). * * By design, postscreen(8) is an event-driven server that must scale up to a * large number of clients. This means that postscreen(8) must avoid doing @@ -343,12 +362,17 @@ static int ask_client_cert; * public mailing lists. After some field experience with this code, we may * be able to factor it out as a library module, like nbbio(3), that can * become part of the TLS library. + * + * Later in the life cycle, tlsproxy(8) has also become an enabler for TLS + * session reuse across different SMTP client processes. */ static void tlsp_ciphertext_event(int, void *); #define TLSP_INIT_TIMEOUT 100 +static void tlsp_plaintext_event(int event, void *context); + /* tlsp_drain - delayed exit after "postfix reload" */ static void tlsp_drain(char *unused_service, char **unused_argv) @@ -406,7 +430,7 @@ static int tlsp_eval_tls_error(TLSP_STATE *state, int err) state->timeout); state->ssl_last_err = SSL_ERROR_NONE; } - return (0); + return (TLSP_STAT_OK); /* * The TLS engine wants to write to the network. Turn on @@ -422,7 +446,7 @@ static int tlsp_eval_tls_error(TLSP_STATE *state, int err) } event_request_timer(tlsp_ciphertext_event, (void *) state, state->timeout); - return (0); + return (TLSP_STAT_OK); /* * The TLS engine wants to read from the network. Turn on @@ -438,7 +462,7 @@ static int tlsp_eval_tls_error(TLSP_STATE *state, int err) } event_request_timer(tlsp_ciphertext_event, (void *) state, state->timeout); - return (0); + return (TLSP_STAT_OK); /* * Some error. Self-destruct. This automagically cleans up all @@ -450,10 +474,63 @@ static int tlsp_eval_tls_error(TLSP_STATE *state, int err) /* FALLTHROUGH */ default: tlsp_state_free(state); - return (-1); + return (TLSP_STAT_ERR); } } +/* tlsp_post_handshake - post-handshake processing */ + +static int tlsp_post_handshake(TLSP_STATE *state) +{ + + /* + * Currently, tls_server_post_accept() and tls_client_post_connect() + * always succeed. + */ + if (state->is_server_role) + state->tls_context = tls_server_post_accept(state->tls_context); + else + state->tls_context = tls_client_post_connect(state->tls_context, + state->client_start_props); + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Report TLS handshake results to the tlsproxy client. + * + * Security: this sends internal data over the same local plaintext stream + * that will also be used for sending decrypted remote content from an + * arbitrary remote peer. For this reason we enable decrypted I/O only + * after reporting the TLS handshake results. The Postfix attribute + * protocol is robust enough that an attacker cannot append content. + */ + if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 + && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) state->tls_context), + ATTR_TYPE_END) != 0 + || vstream_fflush(state->plaintext_stream) != 0)) { + msg_warn("cannot send TLS context: %m"); + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Initialize plaintext-related session state. Once we have this behind + * us, the TLSP_STATE destructor will automagically clean up requests for + * plaintext read/write/timeout events, which makes error recovery + * easier. + */ + state->plaintext_buf = + nbbio_create(vstream_fileno(state->plaintext_stream), + VSTREAM_BUFSIZE, state->server_id, + tlsp_plaintext_event, + (void *) state); + return (TLSP_STAT_OK); +} + /* tlsp_strategy - decide what to read or write next. */ static void tlsp_strategy(TLSP_STATE *state) @@ -466,33 +543,25 @@ static void tlsp_strategy(TLSP_STATE *state) int handshake_err; /* - * Be sure to complete the TLS handshake before enabling plain-text I/O. - * In case of an unrecoverable error, this automagically cleans up all - * pending read/write and timeout event requests. + * Do not enable plain-text I/O before completing the TLS handshake. + * Otherwise the remote peer can prepend plaintext to the optional + * TLS_SESS_STATE object. */ if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { - ssl_stat = SSL_accept(tls_context->con); + if (state->is_server_role) + ssl_stat = SSL_accept(tls_context->con); + else + ssl_stat = SSL_connect(tls_context->con); if (ssl_stat != 1) { handshake_err = SSL_get_error(tls_context->con, ssl_stat); tlsp_eval_tls_error(state, handshake_err); /* At this point, state could be a dangling pointer. */ return; } - if ((state->tls_context = tls_server_post_accept(tls_context)) == 0) { - tlsp_state_free(state); - return; - } - if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 - && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, - SEND_ATTR_FUNC(tls_proxy_context_print, - (void *) state->tls_context), - ATTR_TYPE_END) != 0 - || vstream_fflush(state->plaintext_stream) != 0)) { - msg_warn("cannot send TLS context: %m"); - tlsp_state_free(state); + state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; + if (tlsp_post_handshake(state) != TLSP_STAT_OK) { return; } - state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; } /* @@ -585,17 +654,19 @@ static void tlsp_strategy(TLSP_STATE *state) /* * Try to enable/disable plaintext read/write events. Basically, if we - * have nothing to write to the postscreen(8) server, see if there is + * have nothing to write to the plaintext stream, see if there is * something to read. If the write buffer is empty and the read buffer is * full, suspend plaintext I/O until conditions change (but keep the * timer active, as a safety mechanism in case ciphertext I/O gets * stuck). * - * XXX In theory, if the client keeps writing fast enough then we would - * never read from postscreen(8), and cause postscreen(8) to block. In - * practice, postscreen(8) limits the number of client commands, and thus - * postscreen(8)'s output will fit in a kernel buffer. This may not be - * true in other scenarios where the tlsproxy(8) server could be used. + * XXX In theory, if the ciphertext peer keeps writing fast enough then we + * would never read from the plaintext stream and cause the latter to + * block. In practice, postscreen(8) limits the number of client + * commands, and thus postscreen(8)'s output will fit in a kernel buffer. + * A remote SMTP server is not supposed to flood the local SMTP client + * with massive replies; it it does, then the local SMTP client should + * deal with it. */ if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) @@ -661,9 +732,23 @@ static void tlsp_ciphertext_event(int event, void *context) } } -/* tlsp_start_tls - turn on TLS or force disconnect */ +/* tlsp_client_start_pre_handshake - turn on TLS or force disconnect */ -static int tlsp_start_tls(TLSP_STATE *state) +static int tlsp_client_start_pre_handshake(TLSP_STATE *state) +{ + state->client_start_props->ctx = state->appl_state; + state->client_start_props->fd = state->ciphertext_fd; + state->tls_context = tls_client_start(state->client_start_props); + if (state->tls_context != 0) + return (TLSP_STAT_OK); + + tlsp_state_free(state); + return (TLSP_STAT_ERR); +} + +/* tlsp_server_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_server_start_pre_handshake(TLSP_STATE *state) { TLS_SERVER_START_PROPS props; static char *cipher_grade; @@ -676,8 +761,8 @@ static int tlsp_start_tls(TLSP_STATE *state) */ /* - * Perform the before-handshake portion of the per-session initialization. - * Pass a null VSTREAM to indicate that this program, will do the + * Perform the before-handshake portion of per-session initialization. + * Pass a null VSTREAM to indicate that this program will do the * ciphertext I/O, not libtls. * * The cipher grade and exclusions don't change between sessions. Compute @@ -716,7 +801,7 @@ static int tlsp_start_tls(TLSP_STATE *state) if (state->tls_context == 0) { tlsp_state_free(state); - return (-1); + return (TLSP_STAT_ERR); } /* @@ -725,59 +810,75 @@ static int tlsp_start_tls(TLSP_STATE *state) * whitelist status, but bad clients hammering the server can suck up * lots of CPU cycles. Per-client concurrency limits in postscreen(8) * will divert only naive security "researchers". - * - * XXX Do we care about certificate verification results? Not as long as - * postscreen(8) doesn't actually receive email. */ - return (0); + return (TLSP_STAT_OK); } -/* tlsp_get_fd_event - receive final postscreen(8) hand-off information */ + /* + * From here on down is low-level code that sets up the plumbing before + * passing control to the TLS engine above. + */ + +/* tlsp_request_read_event - pre-handshake event boiler plate */ + +static void tlsp_request_read_event(int fd, EVENT_NOTIFY_FN handler, + int timeout, void *context) +{ + event_enable_read(fd, handler, context); + event_request_timer(handler, context, timeout); +} + +/* tlsp_accept_event - pre-handshake event boiler plate */ + +static void tlsp_accept_event(int event, EVENT_NOTIFY_FN handler, + void *context) +{ + if (event != EVENT_TIME) + event_cancel_timer(handler, context); + else + errno = ETIMEDOUT; + /* tlsp_state_free() disables pre-handshake I/O events. */ +} + +/* tlsp_get_fd_event - receive final connection hand-off information */ static void tlsp_get_fd_event(int event, void *context) { const char *myname = "tlsp_get_fd_event"; TLSP_STATE *state = (TLSP_STATE *) context; int plaintext_fd = vstream_fileno(state->plaintext_stream); + int status; /* - * At this point we still manually manage plaintext read/write/timeout - * events. Disable I/O and timer events. Don't assume that the first - * plaintext request will be a read. + * Disable I/O events on the plaintext stream until the TLS handshake is + * completed. */ + tlsp_accept_event(event, tlsp_get_fd_event, (void *) state); event_disable_readwrite(plaintext_fd); - if (event != EVENT_TIME) - event_cancel_timer(tlsp_get_fd_event, (void *) state); - else - errno = ETIMEDOUT; - /* - * Initialize plaintext-related session state. Once we have this behind - * us, the TLSP_STATE destructor will automagically clean up requests for - * read/write/timeout events, which makes error recovery easier. - * - * Register the plaintext event handler for timer cleanup in the TLSP_STATE - * destructor. Insert the NBBIO event-driven I/O layer between the - * postscreen(8) server and the TLS engine. - */ if (event != EVENT_READ || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) { - msg_warn("%s: receive SMTP client file descriptor: %m", myname); + msg_warn("%s: receive remote SMTP peer file descriptor: %m", myname); tlsp_state_free(state); return; } - non_blocking(state->ciphertext_fd, NON_BLOCKING); + + /* + * This is a bit early, to ensure that timer events for this file handle + * are guaranteed to be turned off by the TLSP_STATE destructor. + */ state->ciphertext_timer = tlsp_ciphertext_event; - state->plaintext_buf = nbbio_create(plaintext_fd, - VSTREAM_BUFSIZE, "postscreen", - tlsp_plaintext_event, - (void *) state); + non_blocking(state->ciphertext_fd, NON_BLOCKING); /* * Perform the TLS layer before-handshake initialization. We perform the - * remainder after the TLS handshake completes. + * remainder after the actual TLS handshake completes. */ - if (tlsp_start_tls(state) < 0) + if (state->is_server_role) + status = tlsp_server_start_pre_handshake(state); + else + status = tlsp_client_start_pre_handshake(state); + if (status != TLSP_STAT_OK) return; /* @@ -786,7 +887,46 @@ static void tlsp_get_fd_event(int event, void *context) tlsp_strategy(state); } -/* tlsp_get_request_event - receive initial postscreen(8) hand-off info */ + /* + * This function does not destroy TLSP_STATE in case of error, because that + * would complicate the caller. + */ + +/* tlsp_client_init_no_tlsp_state_free - initialize a TLS client engine */ + +static int tlsp_client_init_no_tlsp_state_free(TLSP_STATE *state) +{ + VSTRING *buf; + char *key; + + /* + * Share a TLS_APPL_STATE object among multiple requests that specify the + * same TLS_CLIENT_INIT_PROPS. TLS_APPL_STATE owns an SSL_CTX which is + * expensive. + */ + buf = vstring_alloc(100); + key = tls_proxy_client_init_to_string(buf, state->client_init_props); + if ((state->appl_state = (TLS_APPL_STATE *) + htable_find(tlsp_client_app_cache, key)) == 0 + && (state->appl_state = + tls_client_init(state->client_init_props)) != 0) + (void) htable_enter(tlsp_client_app_cache, key, + (void *) state->appl_state); + vstring_free(buf); + return (state->appl_state != 0); +} + +/* tlsp_close_event - pre-handshake plaintext-client close event */ + +static void tlsp_close_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + tlsp_accept_event(event, tlsp_close_event, (void *) state); + tlsp_state_free(state); +} + +/* tlsp_get_request_event - receive initial hand-off info */ static void tlsp_get_request_event(int event, void *context) { @@ -798,7 +938,13 @@ static void tlsp_get_request_event(int event, void *context) static VSTRING *server_id; int req_flags; int timeout; - int ready; + int ready = 0; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. + */ + tlsp_accept_event(event, tlsp_get_request_event, (void *) state); /* * One-time initialization. @@ -808,16 +954,6 @@ static void tlsp_get_request_event(int event, void *context) server_id = vstring_alloc(10); } - /* - * At this point we still manually manage plaintext read/write/timeout - * events. Turn off timer events. Below we disable read events on error, - * and redefine read events on success. - */ - if (event != EVENT_TIME) - event_cancel_timer(tlsp_get_request_event, (void *) state); - else - errno = ETIMEDOUT; - /* * We must send some data, after receiving the request attributes and * before receiving the remote file descriptor. We can't assume @@ -825,52 +961,77 @@ static void tlsp_get_request_event(int event, void *context) */ if (event != EVENT_READ || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, - RECV_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, remote_endpt), - RECV_ATTR_INT(MAIL_ATTR_FLAGS, &req_flags), - RECV_ATTR_INT(MAIL_ATTR_TIMEOUT, &timeout), - RECV_ATTR_STR(MAIL_ATTR_SERVER_ID, server_id), + RECV_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, remote_endpt), + RECV_ATTR_INT(TLS_ATTR_FLAGS, &req_flags), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &timeout), + RECV_ATTR_STR(TLS_ATTR_SERVERID, server_id), ATTR_TYPE_END) != 4) { msg_warn("%s: receive request attributes: %m", myname); - event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } + /* + * XXX We use the same fixed timeout throughout the entire session for + * both plaintext and ciphertext communication. This timeout is just a + * safety feature; the real timeout will be enforced by our plaintext + * peer. + */ + state->remote_endpt = mystrdup(STR(remote_endpt)); + state->server_id = mystrdup(STR(server_id)); + msg_info("CONNECT %s %s", + (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : + (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : + "(bogus_direction)", state->remote_endpt); + state->req_flags = req_flags; + /* state->is_server_role is set below. */ + state->timeout = timeout + 10; /* XXX */ + + /* + * Receive the TLS preferences now, to reduce the number of protocol + * roundtrips. To call the pre-handshake tls_*_start() before receiving + * the ciphertext FD, pass in the FD through some other interface. + */ + switch (req_flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + state->is_server_role = 0; + if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(tls_proxy_client_init_scan, + (void *) &state->client_init_props), + RECV_ATTR_FUNC(tls_proxy_client_start_scan, + (void *) &state->client_start_props), + ATTR_TYPE_END) != 2) { + msg_warn("%s: receive client TLS settings: %m", myname); + tlsp_state_free(state); + return; + } + ready = tlsp_client_init_no_tlsp_state_free(state); + break; + case TLS_PROXY_FLAG_ROLE_SERVER: + state->is_server_role = 1; + ready = (tlsp_server_ctx != 0); + break; + default: + state->is_server_role = 0; + msg_warn("%s: bad request flags: 0x%x", myname, req_flags); + ready = 0; + } + /* * If the requested TLS engine is unavailable, hang up after making sure * that the plaintext peer has received our "sorry" indication. */ - ready = ((req_flags & TLS_PROXY_FLAG_ROLE_SERVER) != 0 - && tlsp_server_ctx != 0); if (attr_print(plaintext_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), ATTR_TYPE_END) != 0 || vstream_fflush(plaintext_stream) != 0 || ready == 0) { - read_wait(plaintext_fd, TLSP_INIT_TIMEOUT); /* XXX */ - event_disable_readwrite(plaintext_fd); - tlsp_state_free(state); + tlsp_request_read_event(plaintext_fd, tlsp_close_event, + TLSP_INIT_TIMEOUT, (void *) state); return; - } - - /* - * XXX We use the same fixed timeout throughout the entire session for - * both plaintext and ciphertext communication. This timeout is just a - * safety feature; the real timeout will be enforced by our plaintext - * peer. - */ - else { - state->remote_endpt = mystrdup(STR(remote_endpt)); - state->server_id = mystrdup(STR(server_id)); - msg_info("CONNECT %s %s", - (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : - (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : - "(bogus_direction)", state->remote_endpt); - state->req_flags = req_flags; - state->timeout = timeout + 10; /* XXX */ - event_enable_read(plaintext_fd, tlsp_get_fd_event, (void *) state); - event_request_timer(tlsp_get_fd_event, (void *) state, - TLSP_INIT_TIMEOUT); + } else { + tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event, + TLSP_INIT_TIMEOUT, (void *) state); return; } } @@ -906,9 +1067,8 @@ static void tlsp_service(VSTREAM *plaintext_stream, * Receive postscreen's remote SMTP client address/port and socket. */ state = tlsp_state_create(service, plaintext_stream); - event_enable_read(plaintext_fd, tlsp_get_request_event, (void *) state); - event_request_timer(tlsp_get_request_event, (void *) state, - TLSP_INIT_TIMEOUT); + tlsp_request_read_event(plaintext_fd, tlsp_get_request_event, + TLSP_INIT_TIMEOUT, (void *) state); } /* pre_jail_init - pre-jail initialization */ @@ -1033,7 +1193,7 @@ static void pre_jail_init(char *unused_name, char **unused_argv) static void post_jail_init(char *unused_name, char **unused_argv) { - /* void */ ; + tlsp_client_app_cache = htable_create(10); } MAIL_VERSION_STAMP_DECLARE; diff --git a/postfix/src/tlsproxy/tlsproxy.h b/postfix/src/tlsproxy/tlsproxy.h index e3e1d11fa..336c58c69 100644 --- a/postfix/src/tlsproxy/tlsproxy.h +++ b/postfix/src/tlsproxy/tlsproxy.h @@ -25,6 +25,7 @@ typedef struct { int flags; /* see below */ int req_flags; /* request flags, see tls_proxy.h */ + int is_server_role; /* avoid clumsy handler code */ char *service; /* argv[0] */ VSTREAM *plaintext_stream; /* local peer: postscreen(8), etc. */ NBBIO *plaintext_buf; /* plaintext buffer */ @@ -33,8 +34,13 @@ typedef struct { int timeout; /* read/write time limit */ char *remote_endpt; /* printable remote endpoint */ char *server_id; /* cache management */ - TLS_SESS_STATE *tls_context; /* llibtls state */ + TLS_APPL_STATE *appl_state; /* libtls state */ + TLS_SESS_STATE *tls_context; /* libtls state */ int ssl_last_err; /* TLS I/O state */ + TLS_SERVER_INIT_PROPS *server_init_props; + TLS_SERVER_START_PROPS *server_start_props; + TLS_CLIENT_INIT_PROPS *client_init_props; + TLS_CLIENT_START_PROPS *client_start_props; } TLSP_STATE; #define TLSP_FLAG_DO_HANDSHAKE (1<<0) @@ -51,4 +57,9 @@ extern void tlsp_state_free(TLSP_STATE *); /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ diff --git a/postfix/src/tlsproxy/tlsproxy_state.c b/postfix/src/tlsproxy/tlsproxy_state.c index e32764920..91dcfca7c 100644 --- a/postfix/src/tlsproxy/tlsproxy_state.c +++ b/postfix/src/tlsproxy/tlsproxy_state.c @@ -18,7 +18,9 @@ /* /* tlsp_state_create() initializes session context. /* -/* tlsp_state_free() destroys session context. +/* tlsp_state_free() destroys session context. If the handshake +/* was in progress, it sends a 'handshake failed' message to the +/* plaintext peer. /* /* Arguments: /* .IP service @@ -60,6 +62,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* @@ -85,6 +92,7 @@ #ifdef USE_TLS #define TLS_INTERNAL /* XXX */ #include +#include /* * Application-specific. @@ -108,6 +116,10 @@ TLSP_STATE *tlsp_state_create(const char *service, state->remote_endpt = 0; state->server_id = 0; state->tls_context = 0; + state->server_init_props = 0; + state->server_start_props = 0; + state->client_init_props = 0; + state->client_start_props = 0; return (state); } @@ -116,9 +128,16 @@ TLSP_STATE *tlsp_state_create(const char *service, void tlsp_state_free(TLSP_STATE *state) { + /* Don't log failure after plaintext EOF. */ + if (state->remote_endpt && state->server_id + && (state->flags & TLSP_FLAG_DO_HANDSHAKE)) + msg_info("TLS handshake failed for service=%s peer=%s", + state->server_id, state->remote_endpt); myfree(state->service); if (state->plaintext_buf) /* turns off plaintext events */ nbbio_free(state->plaintext_buf); + else + event_disable_readwrite(vstream_fileno(state->plaintext_stream)); event_server_disconnect(state->plaintext_stream); if (state->ciphertext_fd >= 0) { event_disable_readwrite(state->ciphertext_fd); @@ -134,6 +153,14 @@ void tlsp_state_free(TLSP_STATE *state) myfree(state->server_id); if (state->tls_context) tls_free_context(state->tls_context); + if (state->server_init_props) + tls_proxy_server_init_free(state->server_init_props); + if (state->server_start_props) + tls_proxy_server_start_free(state->server_start_props); + if (state->client_init_props) + tls_proxy_client_init_free(state->client_init_props); + if (state->client_start_props) + tls_proxy_client_start_free(state->client_start_props); myfree((void *) state); } diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 32cefbc07..d419cb397 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -40,7 +40,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \ valid_utf8_hostname.c midna_domain.c argv_splitq.c balpar.c dict_union.c \ extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \ - split_qnameval.c + split_qnameval.c argv_attr_print.c argv_attr_scan.c OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ @@ -82,7 +82,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \ valid_utf8_hostname.o midna_domain.o argv_splitq.o balpar.o dict_union.o \ extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \ - split_qnameval.o + split_qnameval.o argv_attr_print.o argv_attr_scan.o # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), # otherwise it sets the PLUGIN_* macros. @@ -111,7 +111,8 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \ dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \ slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \ - valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h check_arg.h + valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \ + check_arg.h argv_attr.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c DEFS = -I. -D$(SYSTYPE) @@ -879,6 +880,32 @@ argv.o: argv.h argv.o: msg.h argv.o: mymalloc.h argv.o: sys_defs.h +argv_attr_print.o: argv.h +argv_attr_print.o: argv_attr.h +argv_attr_print.o: argv_attr_print.c +argv_attr_print.o: attr.h +argv_attr_print.o: check_arg.h +argv_attr_print.o: htable.h +argv_attr_print.o: msg.h +argv_attr_print.o: mymalloc.h +argv_attr_print.o: nvtable.h +argv_attr_print.o: sys_defs.h +argv_attr_print.o: vbuf.h +argv_attr_print.o: vstream.h +argv_attr_print.o: vstring.h +argv_attr_scan.o: argv.h +argv_attr_scan.o: argv_attr.h +argv_attr_scan.o: argv_attr_scan.c +argv_attr_scan.o: attr.h +argv_attr_scan.o: check_arg.h +argv_attr_scan.o: htable.h +argv_attr_scan.o: msg.h +argv_attr_scan.o: mymalloc.h +argv_attr_scan.o: nvtable.h +argv_attr_scan.o: sys_defs.h +argv_attr_scan.o: vbuf.h +argv_attr_scan.o: vstream.h +argv_attr_scan.o: vstring.h argv_split.o: argv.h argv_split.o: argv_split.c argv_split.o: check_arg.h @@ -1785,6 +1812,8 @@ load_file.o: vbuf.h load_file.o: vstream.h load_file.o: warn_stat.h load_lib.o: load_lib.c +load_lib.o: load_lib.h +load_lib.o: msg.h load_lib.o: sys_defs.h lowercase.o: check_arg.h lowercase.o: lowercase.c @@ -2363,6 +2392,7 @@ vbuf.o: vbuf.c vbuf.o: vbuf.h vbuf_print.o: check_arg.h vbuf_print.o: msg.h +vbuf_print.o: mymalloc.h vbuf_print.o: sys_defs.h vbuf_print.o: vbuf.h vbuf_print.o: vbuf_print.c diff --git a/postfix/src/util/argv_attr.h b/postfix/src/util/argv_attr.h new file mode 100644 index 000000000..65cfa5541 --- /dev/null +++ b/postfix/src/util/argv_attr.h @@ -0,0 +1,43 @@ +#ifndef _ARGV_ATTR_H_INCLUDED_ +#define _ARGV_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* argv_attr 3h +/* SUMMARY +/* argv serialization/deserialization +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * External API. + */ +#define ARGV_ATTR_SIZE "argv_size" +#define ARGV_ATTR_VALUE "argv_value" +#define ARGV_ATTR_MAX 1024 + +extern int argv_attr_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *); +extern int argv_attr_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/util/argv_attr_print.c b/postfix/src/util/argv_attr_print.c new file mode 100644 index 000000000..87b086a9b --- /dev/null +++ b/postfix/src/util/argv_attr_print.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* argv_attr_print +/* SUMMARY +/* write ARGV to stream +/* SYNOPSIS +/* #include +/* +/* int argv_attr_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_print() writes an ARGV to the named stream using +/* the specified attribute print routine. argv_attr_print() is meant +/* to be passed as a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(argv_attr_print, (void *) argv), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* The result value is zero in case of success, non-zero +/* otherwise. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + +/* argv_attr_print - write ARGV to stream */ + +int argv_attr_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp, + int flags, void *ptr) +{ + ARGV *argv = (ARGV *) ptr; + int n; + int ret; + int argc = argv ? argv->argc : 0; + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(ARGV_ATTR_SIZE, argc), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print count=%d", argc); + for (n = 0; ret == 0 && n < argc; n++) + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(ARGV_ATTR_VALUE, argv->argv[n]), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print ret=%d", ret); + /* Do not flush the stream. */ + return (ret); +} diff --git a/postfix/src/util/argv_attr_scan.c b/postfix/src/util/argv_attr_scan.c new file mode 100644 index 000000000..3005cb2dd --- /dev/null +++ b/postfix/src/util/argv_attr_scan.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* argv_attr_scan +/* SUMMARY +/* read ARGV from stream +/* SYNOPSIS +/* #include +/* +/* int argv_attr_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_MASTER_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_scan() creates an ARGV and reads its contents +/* from the named stream using the specified attribute scan +/* routine. argv_attr_scan() is meant to be passed as a call-back +/* to attr_scan(), thusly: +/* +/* ARGV *argv = 0; +/* ... +/* ... RECV_ATTR_FUNC(argv_attr_scan, (void *) &argv), ... +/* ... +/* if (argv) +/* argv_free(argv); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* In case of error, this function returns non-zero and creates +/* an ARGV null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + +/* argv_attr_scan - write ARGV to stream */ + +int argv_attr_scan(ATTR_PRINT_MASTER_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + ARGV *argv = 0; + int size; + int ret; + + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(ARGV_ATTR_SIZE, &size), + ATTR_TYPE_END)) == 1) { + if (msg_verbose) + msg_info("argv_attr_scan count=%d", size); + if (size < 0 || size > ARGV_ATTR_MAX) { + msg_warn("invalid size %d from %s while reading ARGV", + size, VSTREAM_PATH(fp)); + ret = -1; + } else if (size > 0) { + VSTRING *buffer = vstring_alloc(100); + + argv = argv_alloc(size); + while (ret == 1 && size-- > 0) { + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(ARGV_ATTR_VALUE, buffer), + ATTR_TYPE_END)) == 1) + argv_add(argv, vstring_str(buffer), ARGV_END); + } + argv_terminate(argv); + vstring_free(buffer); + } + } + *(ARGV **) ptr = argv; + if (msg_verbose) + msg_info("argv_attr_scan ret=%d", ret); + return (ret); +}