Home | History | Annotate | Line # | Download | only in postscreen
      1 /*	$NetBSD: postscreen_send.c,v 1.3 2020/03/18 19:05:19 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	postscreen_send 3
      6 /* SUMMARY
      7 /*	postscreen low-level output
      8 /* SYNOPSIS
      9 /*	#include <postscreen.h>
     10 /*
     11 /*	void	pcs_send_pre_jail_init(void)
     12 /*
     13 /*	int	psc_send_reply(state, text)
     14 /*	PSC_STATE *state;
     15 /*	const char *text;
     16 /*
     17 /*	int	PSC_SEND_REPLY(state, text)
     18 /*	PSC_STATE *state;
     19 /*	const char *text;
     20 /*
     21 /*	void	psc_send_socket(state)
     22 /*	PSC_STATE *state;
     23 /* DESCRIPTION
     24 /*	pcs_send_pre_jail_init() performs one-time initialization.
     25 /*
     26 /*	psc_send_reply() sends the specified text to the specified
     27 /*	remote SMTP client.  In case of an immediate error, it logs
     28 /*	a warning (except EPIPE) with the client address and port,
     29 /*	and returns a non-zero result (all errors including EPIPE).
     30 /*
     31 /*	psc_send_reply() does a best effort to send the reply, but
     32 /*	it won't block when the output is throttled by a hostile
     33 /*	peer.
     34 /*
     35 /*	PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply().
     36 /*	It will eventually be replaced by its expansion.
     37 /*
     38 /*	psc_send_socket() sends the specified socket to the real
     39 /*	Postfix SMTP server. The socket is delivered in the background.
     40 /*	This function must be called after all other session-related
     41 /*	work is finished including postscreen cache updates.
     42 /*
     43 /*	In case of an immediate error, psc_send_socket() sends a 421
     44 /*	reply to the remote SMTP client and closes the connection.
     45 /*	If the 220- greeting was sent, sending 421 would be invalid;
     46 /*	instead, the client is redirected to the dummy SMTP engine
     47 /*	which sends the 421 reply at the first legitimate opportunity.
     48 /* LICENSE
     49 /* .ad
     50 /* .fi
     51 /*	The Secure Mailer license must be distributed with this software.
     52 /* AUTHOR(S)
     53 /*	Wietse Venema
     54 /*	IBM T.J. Watson Research
     55 /*	P.O. Box 704
     56 /*	Yorktown Heights, NY 10598, USA
     57 /*
     58 /*	Wietse Venema
     59 /*	Google, Inc.
     60 /*	111 8th Avenue
     61 /*	New York, NY 10011, USA
     62 /*--*/
     63 
     64 /* System library. */
     65 
     66 #include <sys_defs.h>
     67 #include <string.h>
     68 #include <errno.h>
     69 
     70 /* Utility library. */
     71 
     72 #include <msg.h>
     73 #include <iostuff.h>
     74 #include <connect.h>
     75 #include <attr.h>
     76 #include <vstream.h>
     77 
     78 /* Global library. */
     79 
     80 #include <mail_params.h>
     81 #include <smtp_reply_footer.h>
     82 #include <mail_proto.h>
     83 #include <maps.h>
     84 
     85 /* Application-specific. */
     86 
     87 #include <postscreen.h>
     88 
     89 static MAPS *psc_rej_ftr_maps;
     90 
     91  /*
     92   * This program screens all inbound SMTP connections, so it better not waste
     93   * time.
     94   */
     95 #define PSC_SEND_SOCK_CONNECT_TIMEOUT	1
     96 #define PSC_SEND_SOCK_NOTIFY_TIMEOUT	100
     97 
     98 /* pcs_send_pre_jail_init - initialize */
     99 
    100 void    pcs_send_pre_jail_init(void)
    101 {
    102     static int init_count = 0;
    103 
    104     if (init_count++ != 0)
    105 	msg_panic("pcs_send_pre_jail_init: multiple calls");
    106 
    107     /*
    108      * SMTP server reject footer.
    109      */
    110     if (*var_psc_rej_ftr_maps)
    111 	psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
    112 				       var_psc_rej_ftr_maps,
    113 				       DICT_FLAG_LOCK);
    114 }
    115 
    116 /* psc_get_footer - find that footer */
    117 
    118 static const char *psc_get_footer(const char *text, ssize_t text_len)
    119 {
    120     static VSTRING *footer_buf = 0;
    121 
    122     if (footer_buf == 0)
    123 	footer_buf = vstring_alloc(100);
    124     /* Strip the \r\n for consistency with smtpd. */
    125     vstring_strncpy(footer_buf, text, text_len);
    126     return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0));
    127 }
    128 
    129 /* psc_send_reply - send reply to remote SMTP client */
    130 
    131 int     psc_send_reply(PSC_STATE *state, const char *text)
    132 {
    133     ssize_t start;
    134     int     ret;
    135     const char *footer;
    136     ssize_t text_len = strlen(text) - 2;
    137 
    138     if (msg_verbose)
    139 	msg_info("> [%s]:%s: %.*s", state->smtp_client_addr,
    140 		 state->smtp_client_port, (int) text_len, text);
    141 
    142     /*
    143      * Append the new text to earlier text that could not be sent because the
    144      * output was throttled.
    145      */
    146     start = VSTRING_LEN(state->send_buf);
    147     vstring_strcat(state->send_buf, text);
    148 
    149     /*
    150      * For soft_bounce support, we also fix the REJECT logging before the
    151      * dummy SMTP engine calls the psc_send_reply() output routine. We do
    152      * some double work, but it is for debugging only.
    153      */
    154     if (var_soft_bounce) {
    155 	if (text[0] == '5')
    156 	    STR(state->send_buf)[start + 0] = '4';
    157 	if (text[4] == '5')
    158 	    STR(state->send_buf)[start + 4] = '4';
    159     }
    160 
    161     /*
    162      * Append the optional reply footer.
    163      */
    164     if ((*text == '4' || *text == '5')
    165 	&& ((psc_rej_ftr_maps != 0
    166 	     && (footer = psc_get_footer(text, text_len)) != 0)
    167 	    || *(footer = var_psc_rej_footer) != 0))
    168 	smtp_reply_footer(state->send_buf, start, footer,
    169 			  STR(psc_expand_filter), psc_expand_lookup,
    170 			  (void *) state);
    171 
    172     /*
    173      * Do a best effort sending text, but don't block when the output is
    174      * throttled by a hostile peer.
    175      */
    176     ret = write(vstream_fileno(state->smtp_client_stream),
    177 		STR(state->send_buf), LEN(state->send_buf));
    178     if (ret > 0)
    179 	vstring_truncate(state->send_buf, ret - LEN(state->send_buf));
    180     if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET)
    181 	msg_warn("write [%s]:%s: %m", state->smtp_client_addr,
    182 		 state->smtp_client_port);
    183     return (ret < 0 && errno != EAGAIN);
    184 }
    185 
    186 /* psc_send_socket_close_event - file descriptor has arrived or timeout */
    187 
    188 static void psc_send_socket_close_event(int event, void *context)
    189 {
    190     const char *myname = "psc_send_socket_close_event";
    191     PSC_STATE *state = (PSC_STATE *) context;
    192 
    193     if (msg_verbose > 1)
    194 	msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s",
    195 		 myname, psc_post_queue_length, psc_check_queue_length,
    196 		 event, state->smtp_server_fd, state->smtp_client_addr,
    197 		 state->smtp_client_port);
    198 
    199     /*
    200      * The real SMTP server has closed the local IPC channel, or we have
    201      * reached the limit of our patience. In the latter case it is still
    202      * possible that the real SMTP server will receive the socket so we
    203      * should not interfere.
    204      */
    205     PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
    206 			    context);
    207     if (event == EVENT_TIME)
    208 	msg_warn("timeout sending connection to service %s",
    209 		 psc_smtpd_service_name);
    210     psc_free_session_state(state);
    211 }
    212 
    213 /* psc_send_socket - send socket to real SMTP server process */
    214 
    215 void    psc_send_socket(PSC_STATE *state)
    216 {
    217     const char *myname = "psc_send_socket";
    218     int     server_fd;
    219     int     pass_err;
    220     VSTREAM *fp;
    221 
    222     if (msg_verbose > 1)
    223 	msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s",
    224 		 myname, psc_post_queue_length, psc_check_queue_length,
    225 		 vstream_fileno(state->smtp_client_stream),
    226 		 state->smtp_client_addr, state->smtp_client_port);
    227 
    228     /*
    229      * Connect to the real SMTP service over a local IPC channel, send the
    230      * file descriptor, and close the file descriptor to save resources.
    231      * Experience has shown that some systems will discard information when
    232      * we close a channel immediately after writing. Thus, we waste resources
    233      * waiting for the remote side to close the local IPC channel first. The
    234      * good side of waiting is that we learn when the real SMTP server is
    235      * falling behind.
    236      *
    237      * This is where we would forward the connection to an SMTP server that
    238      * provides an appropriate level of service for this client class. For
    239      * example, a server that is more forgiving, or one that is more
    240      * suspicious. Alternatively, we could send attributes along with the
    241      * socket with client reputation information, making everything even more
    242      * Postfix-specific.
    243      */
    244     if ((server_fd =
    245 	 LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING,
    246 		       PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) {
    247 	msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name);
    248 	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
    249 	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
    250 	} else {
    251 	    PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n");
    252 	    psc_free_session_state(state);
    253 	}
    254 	return;
    255     }
    256     /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */
    257     fp = vstream_fdopen(server_fd, O_RDWR);
    258     pass_err =
    259 	(LOCAL_SEND_FD(server_fd,
    260 		       vstream_fileno(state->smtp_client_stream)) < 0
    261 	 || (attr_print(fp, ATTR_FLAG_NONE,
    262 	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr),
    263 	  SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port),
    264 	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr),
    265 	  SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port),
    266 			ATTR_TYPE_END) || vstream_fflush(fp)));
    267     /* XXX Note: no read between attr_print() and vstream_fdclose(). */
    268     (void) vstream_fdclose(fp);
    269     if (pass_err != 0) {
    270 	msg_warn("cannot pass connection to service %s: %m",
    271 		 psc_smtpd_service_name);
    272 	(void) close(server_fd);
    273 	if (state->flags & PSC_STATE_FLAG_PREGR_TODO) {
    274 	    PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n");
    275 	} else {
    276 	    PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n");
    277 	    psc_free_session_state(state);
    278 	}
    279 	return;
    280     } else {
    281 
    282 	/*
    283 	 * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug
    284 	 * where smtp-source sometimes sees the connection being closed after
    285 	 * it has already received the real SMTP server's 220 greeting!
    286 	 */
    287 #if 0
    288 	PSC_DEL_CLIENT_STATE(state);
    289 #endif
    290 	PSC_ADD_SERVER_STATE(state, server_fd);
    291 	PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event,
    292 			       (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT);
    293 	return;
    294     }
    295 }
    296