/*++ /* NAME /* postscreen_starttls 3 /* SUMMARY /* postscreen TLS proxy support /* SYNOPSIS /* #include /* /* int psc_starttls_open(state, resume_event) /* PSC_STATE *state; /* void (*resume_event)(int unused_event, char *context); /* DESCRIPTION /* This module inserts the tlsproxy(8) proxy between the /* postscreen(8) server and the remote SMTP client. The entire /* process happens in the background, including notification /* of completion to the remote SMTP client and to the calling /* application. /* /* Before calling psc_starttls_open() the caller must turn off /* all pending timer and I/O event requests on the SMTP client /* stream. /* /* psc_starttls_open() starts the first transaction in the /* tlsproxy(8) hand-off protocol, and sets up event handlers /* for the successive protocol stages. /* /* Upon completion, the event handlers call resume_event() /* which must reset the SMTP helo/sender/etc. state when the /* PSC_STATE_FLAG_USING_TLS is set, and set up timer and read /* event requests to receive the next SMTP command. /* 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 /*--*/ /* System library. */ #include /* Utility library. */ #include #include #include #include /* concatenate() */ #include /* Global library. */ #include #include /* TLS library. */ #include /* Application-specific. */ #include /* * For now, this code is built into the postscreen(8) daemon. In the future * it may be abstracted into a reusable library module for use by other * event-driven programs (perhaps smtp-source and smtp-sink). */ /* * Transient state for the postscreen(8)-to-tlsproxy(8) hand-off protocol. */ typedef struct { VSTREAM *tlsproxy_stream; /* hand-off negotiation */ EVENT_NOTIFY_FN resume_event; /* call-back handler */ PSC_STATE *smtp_state; /* SMTP session state */ } PSC_STARTTLS; #define TLSPROXY_INIT_TIMEOUT 10 static char *psc_tlsp_service = 0; /* Resume the dummy SMTP engine after an event handling error */ #define PSC_STARTTLS_EVENT_ERR_RESUME_RETURN() do { \ event_disable_readwrite(vstream_fileno(tlsproxy_stream)); \ PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); \ } while (0); /* Resume the dummy SMTP engine, possibly after swapping streams */ #define PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state) do { \ vstream_fclose(tlsproxy_stream); \ starttls_state->resume_event(event, (void *) smtp_state); \ myfree((void *) starttls_state); \ return; \ } while (0) /* psc_starttls_finish - complete negotiation with TLS proxy */ static void psc_starttls_finish(int event, void *context) { const char *myname = "psc_starttls_finish"; PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context; PSC_STATE *smtp_state = starttls_state->smtp_state; VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream; int status; if (msg_verbose) msg_info("%s: send client handle on proxy socket %d" " for smtp socket %d from [%s]:%s flags=%s", myname, vstream_fileno(tlsproxy_stream), vstream_fileno(smtp_state->smtp_client_stream), smtp_state->smtp_client_addr, smtp_state->smtp_client_port, psc_print_state_flags(smtp_state->flags, myname)); /* * We leave read-event notification enabled on the postscreen to TLS * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here, * and one when resuming the dummy SMTP engine. */ if (event != EVENT_TIME) event_cancel_timer(psc_starttls_finish, (void *) starttls_state); /* * 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 * descriptor. We can't assume UNIX-domain socket semantics here. */ if (event != EVENT_READ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), ATTR_TYPE_END) != 1 || status == 0) { /* * The TLS proxy reports that the TLS engine is not available (due to * configuration error, or other causes). */ msg_warn("%s receiving status from %s service", event == EVENT_TIME ? "timeout" : "problem", psc_tlsp_service); PSC_SEND_REPLY(smtp_state, "454 4.7.0 TLS not available due to local problem\r\n"); PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); } /* * Send the remote SMTP client file descriptor. */ else if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), vstream_fileno(smtp_state->smtp_client_stream)) < 0) { /* * Some error: drop the TLS proxy stream. */ msg_warn("problem sending file handle to %s service", psc_tlsp_service); PSC_SEND_REPLY(smtp_state, "454 4.7.0 TLS not available due to local problem\r\n"); PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); } /* * After we send the plaintext 220 greeting, the client-side TLS engine * is supposed to talk first, then the server-side TLS engine. However, * postscreen(8) will not participate in that conversation. */ else { PSC_SEND_REPLY(smtp_state, "220 2.0.0 Ready to start TLS\r\n"); /* * Swap the SMTP client stream and the TLS proxy stream, and close * the direct connection to the SMTP client. The TLS proxy will talk * directly to the SMTP client, and once the TLS handshake is * completed, the TLS proxy will talk plaintext to postscreen(8). * * Swap the file descriptors from under the VSTREAM so that we don't * have to worry about loss of user-configurable VSTREAM attributes. */ vstream_fpurge(smtp_state->smtp_client_stream, VSTREAM_PURGE_BOTH); vstream_control(smtp_state->smtp_client_stream, CA_VSTREAM_CTL_SWAP_FD(tlsproxy_stream), CA_VSTREAM_CTL_END); smtp_state->flags |= PSC_STATE_FLAG_USING_TLS; PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); } } /* psc_starttls_first - start negotiation with TLS proxy */ static void psc_starttls_first(int event, void *context) { const char *myname = "psc_starttls_first"; PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context; PSC_STATE *smtp_state = starttls_state->smtp_state; VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream; static VSTRING *remote_endpt = 0; if (msg_verbose) msg_info("%s: receive server protocol on proxy socket %d" " for smtp socket %d from [%s]:%s flags=%s", myname, vstream_fileno(tlsproxy_stream), vstream_fileno(smtp_state->smtp_client_stream), smtp_state->smtp_client_addr, smtp_state->smtp_client_port, psc_print_state_flags(smtp_state->flags, myname)); /* * We leave read-event notification enabled on the postscreen to TLS * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here, * and one when resuming the dummy SMTP engine. */ if (event != EVENT_TIME) event_cancel_timer(psc_starttls_first, (void *) starttls_state); /* * Receive and verify the server protocol. */ if (event != EVENT_READ || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY), ATTR_TYPE_END) != 0) { msg_warn("%s receiving %s attribute from %s service: %m", event == EVENT_TIME ? "timeout" : "problem", MAIL_ATTR_PROTO, psc_tlsp_service); PSC_SEND_REPLY(smtp_state, "454 4.7.0 TLS not available due to local problem\r\n"); PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); } /* * Send the data attributes now, and send the client file descriptor in a * later transaction. We report all errors asynchronously, to avoid * having to maintain multiple error delivery paths. * * XXX The formatted endpoint should be a state member. Then, we can * simplify all the format strings throughout the program. */ if (remote_endpt == 0) remote_endpt = vstring_alloc(20); 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(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_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); PSC_SEND_REPLY(smtp_state, "454 4.7.0 TLS not available due to local problem\r\n"); PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); } /* * Set up a read event for the next phase of the TLS proxy handshake. */ PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_finish, (void *) starttls_state, TLSPROXY_INIT_TIMEOUT); } /* psc_starttls_open - open negotiations with TLS proxy */ void psc_starttls_open(PSC_STATE *smtp_state, EVENT_NOTIFY_FN resume_event) { const char *myname = "psc_starttls_open"; PSC_STARTTLS *starttls_state; VSTREAM *tlsproxy_stream; int fd; if (psc_tlsp_service == 0) { psc_tlsp_service = concatenate(MAIL_CLASS_PRIVATE "/", var_tlsproxy_service, (char *) 0); } /* * Connect to the tlsproxy(8) daemon. We report all errors * asynchronously, to avoid having to maintain multiple delivery paths. */ if ((fd = LOCAL_CONNECT(psc_tlsp_service, NON_BLOCKING, 1)) < 0) { msg_warn("connect to %s service: %m", psc_tlsp_service); PSC_SEND_REPLY(smtp_state, "454 4.7.0 TLS not available due to local problem\r\n"); event_request_timer(resume_event, (void *) smtp_state, 0); return; } if (msg_verbose) msg_info("%s: connecting to proxy socket %d" " for smtp socket %d from [%s]:%s flags=%s", myname, fd, vstream_fileno(smtp_state->smtp_client_stream), smtp_state->smtp_client_addr, smtp_state->smtp_client_port, psc_print_state_flags(smtp_state->flags, myname)); tlsproxy_stream = vstream_fdopen(fd, O_RDWR); vstream_control(tlsproxy_stream, VSTREAM_CTL_PATH, psc_tlsp_service, VSTREAM_CTL_END); /* * Set up a read event for the next phase of the TLS proxy handshake. */ starttls_state = (PSC_STARTTLS *) mymalloc(sizeof(*starttls_state)); starttls_state->tlsproxy_stream = tlsproxy_stream; starttls_state->resume_event = resume_event; starttls_state->smtp_state = smtp_state; PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_first, (void *) starttls_state, TLSPROXY_INIT_TIMEOUT); }