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