1 /* $NetBSD: smtp_chat.c,v 1.2 2017/02/14 01:16:48 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* smtp_chat 3 6 /* SUMMARY 7 /* SMTP client request/response support 8 /* SYNOPSIS 9 /* #include "smtp.h" 10 /* 11 /* typedef struct { 12 /* .in +4 13 /* int code; /* SMTP code, not sanitized */ 14 /* char *dsn; /* enhanced status, sanitized */ 15 /* char *str; /* unmodified SMTP reply */ 16 /* VSTRING *dsn_buf; 17 /* VSTRING *str_buf; 18 /* .in -4 19 /* } SMTP_RESP; 20 /* 21 /* void smtp_chat_cmd(session, format, ...) 22 /* SMTP_SESSION *session; 23 /* const char *format; 24 /* 25 /* DICT *smtp_chat_resp_filter; 26 /* 27 /* SMTP_RESP *smtp_chat_resp(session) 28 /* SMTP_SESSION *session; 29 /* 30 /* void smtp_chat_notify(session) 31 /* SMTP_SESSION *session; 32 /* 33 /* void smtp_chat_init(session) 34 /* SMTP_SESSION *session; 35 /* 36 /* void smtp_chat_reset(session) 37 /* SMTP_SESSION *session; 38 /* DESCRIPTION 39 /* This module implements SMTP client support for request/reply 40 /* conversations, and maintains a limited SMTP transaction log. 41 /* 42 /* smtp_chat_cmd() formats a command and sends it to an SMTP server. 43 /* Optionally, the command is logged. 44 /* 45 /* smtp_chat_resp() reads one SMTP server response. It extracts 46 /* the SMTP reply code and enhanced status code from the text, 47 /* and concatenates multi-line responses to one string, using 48 /* a newline as separator. Optionally, the server response 49 /* is logged. 50 /* .IP \(bu 51 /* Postfix never sanitizes the extracted SMTP reply code except 52 /* to ensure that it is a three-digit code. A malformed reply 53 /* results in a null extracted SMTP reply code value. 54 /* .IP \(bu 55 /* Postfix always sanitizes the extracted enhanced status code. 56 /* When the server's SMTP status code is 2xx, 4xx or 5xx, 57 /* Postfix requires that the first digit of the server's 58 /* enhanced status code matches the first digit of the server's 59 /* SMTP status code. In case of a mis-match, or when the 60 /* server specified no status code, the extracted enhanced 61 /* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With 62 /* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted 63 /* enhanced status code is set to a default value of 5.5.0 64 /* (protocol error) for reasons outlined under the next bullet. 65 /* .IP \(bu 66 /* Since the SMTP reply code may violate the protocol even 67 /* when it is correctly formatted, Postfix uses the sanitized 68 /* extracted enhanced status code to decide whether an error 69 /* condition is permanent or transient. This means that the 70 /* caller may have to update the enhanced status code when it 71 /* discovers that a server reply violates the SMTP protocol, 72 /* even though it was correctly formatted. This happens when 73 /* the client and server get out of step due to a broken proxy 74 /* agent. 75 /* .PP 76 /* smtp_chat_resp_filter specifies an optional filter to 77 /* transform one server reply line before it is parsed. The 78 /* filter is invoked once for each line of a multi-line reply. 79 /* 80 /* smtp_chat_notify() sends a copy of the SMTP transaction log 81 /* to the postmaster for review. The postmaster notice is sent only 82 /* when delivery is possible immediately. It is an error to call 83 /* smtp_chat_notify() when no SMTP transaction log exists. 84 /* 85 /* smtp_chat_init() initializes the per-session transaction log. 86 /* This must be done at the beginning of a new SMTP session. 87 /* 88 /* smtp_chat_reset() resets the transaction log. This is 89 /* typically done at the beginning or end of an SMTP session, 90 /* or within a session to discard non-error information. 91 /* DIAGNOSTICS 92 /* Fatal errors: memory allocation problem, server response exceeds 93 /* configurable limit. 94 /* All other exceptions are handled by long jumps (see smtp_stream(3)). 95 /* SEE ALSO 96 /* smtp_stream(3) SMTP session I/O support 97 /* msg(3) generic logging interface 98 /* LICENSE 99 /* .ad 100 /* .fi 101 /* The Secure Mailer license must be distributed with this software. 102 /* AUTHOR(S) 103 /* Wietse Venema 104 /* IBM T.J. Watson Research 105 /* P.O. Box 704 106 /* Yorktown Heights, NY 10598, USA 107 /*--*/ 108 109 /* System library. */ 110 111 #include <sys_defs.h> 112 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */ 113 #include <stdarg.h> 114 #include <ctype.h> 115 #include <stdlib.h> 116 #include <setjmp.h> 117 #include <string.h> 118 #include <limits.h> 119 120 /* Utility library. */ 121 122 #include <msg.h> 123 #include <vstring.h> 124 #include <vstream.h> 125 #include <argv.h> 126 #include <stringops.h> 127 #include <line_wrap.h> 128 #include <mymalloc.h> 129 130 /* Global library. */ 131 132 #include <recipient_list.h> 133 #include <deliver_request.h> 134 #include <smtp_stream.h> 135 #include <mail_params.h> 136 #include <mail_addr.h> 137 #include <post_mail.h> 138 #include <mail_error.h> 139 #include <dsn_util.h> 140 141 /* Application-specific. */ 142 143 #include "smtp.h" 144 145 /* 146 * Server reply transformations. 147 */ 148 DICT *smtp_chat_resp_filter; 149 150 /* smtp_chat_init - initialize SMTP transaction log */ 151 152 void smtp_chat_init(SMTP_SESSION *session) 153 { 154 session->history = 0; 155 } 156 157 /* smtp_chat_reset - reset SMTP transaction log */ 158 159 void smtp_chat_reset(SMTP_SESSION *session) 160 { 161 if (session->history) { 162 argv_free(session->history); 163 session->history = 0; 164 } 165 } 166 167 /* smtp_chat_append - append record to SMTP transaction log */ 168 169 static void smtp_chat_append(SMTP_SESSION *session, const char *direction, 170 const char *data) 171 { 172 char *line; 173 174 if (session->history == 0) 175 session->history = argv_alloc(10); 176 line = concatenate(direction, data, (char *) 0); 177 argv_add(session->history, line, (char *) 0); 178 myfree(line); 179 } 180 181 /* smtp_chat_cmd - send an SMTP command */ 182 183 void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) 184 { 185 va_list ap; 186 187 /* 188 * Format the command, and update the transaction log. 189 */ 190 va_start(ap, fmt); 191 vstring_vsprintf(session->buffer, fmt, ap); 192 va_end(ap); 193 smtp_chat_append(session, "Out: ", STR(session->buffer)); 194 195 /* 196 * Optionally log the command first, so we can see in the log what the 197 * program is trying to do. 198 */ 199 if (msg_verbose) 200 msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); 201 202 /* 203 * Send the command to the SMTP server. 204 */ 205 smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); 206 207 /* 208 * Force flushing of output does not belong here. It is done in the 209 * smtp_loop() main protocol loop when reading the server response, and 210 * in smtp_helo() when reading the EHLO response after sending the EHLO 211 * command. 212 * 213 * If we do forced flush here, then we must longjmp() on error, and a 214 * matching "prepare for disaster" error handler must be set up before 215 * every smtp_chat_cmd() call. 216 */ 217 #if 0 218 219 /* 220 * Flush unsent data to avoid timeouts after slow DNS lookups. 221 */ 222 if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) 223 vstream_fflush(session->stream); 224 225 /* 226 * Abort immediately if the connection is broken. 227 */ 228 if (vstream_ftimeout(session->stream)) 229 vstream_longjmp(session->stream, SMTP_ERR_TIME); 230 if (vstream_ferror(session->stream)) 231 vstream_longjmp(session->stream, SMTP_ERR_EOF); 232 #endif 233 } 234 235 /* smtp_chat_resp - read and process SMTP server response */ 236 237 SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) 238 { 239 static SMTP_RESP rdata; 240 char *cp; 241 int last_char; 242 int three_digs = 0; 243 size_t len; 244 const char *new_reply; 245 int chat_append_flag; 246 int chat_append_skipped = 0; 247 248 /* 249 * Initialize the response data buffer. 250 */ 251 if (rdata.str_buf == 0) { 252 rdata.dsn_buf = vstring_alloc(10); 253 rdata.str_buf = vstring_alloc(100); 254 } 255 256 /* 257 * Censor out non-printable characters in server responses. Concatenate 258 * multi-line server responses. Separate the status code from the text. 259 * Leave further parsing up to the application. 260 * 261 * We can't parse or store input that exceeds var_line_limit, so we just 262 * skip over it to simplify the remainder of the code below. 263 */ 264 VSTRING_RESET(rdata.str_buf); 265 for (;;) { 266 last_char = smtp_get(session->buffer, session->stream, var_line_limit, 267 SMTP_GET_FLAG_SKIP); 268 /* XXX Update the per-line time limit. */ 269 printable(STR(session->buffer), '?'); 270 if (last_char != '\n') 271 msg_warn("%s: response longer than %d: %.30s...", 272 session->namaddrport, var_line_limit, STR(session->buffer)); 273 if (msg_verbose) 274 msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); 275 276 /* 277 * Defend against a denial of service attack by limiting the amount 278 * of multi-line text that we are willing to store. 279 */ 280 chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); 281 if (chat_append_flag) 282 smtp_chat_append(session, "In: ", STR(session->buffer)); 283 else { 284 if (chat_append_skipped == 0) 285 msg_warn("%s: multi-line response longer than %d %.30s...", 286 session->namaddrport, var_line_limit, STR(rdata.str_buf)); 287 if (chat_append_skipped < INT_MAX) 288 chat_append_skipped++; 289 } 290 291 /* 292 * Server reply substitution, for fault-injection testing, or for 293 * working around broken systems. Use with care. 294 */ 295 if (smtp_chat_resp_filter != 0) { 296 new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); 297 if (new_reply != 0) { 298 msg_info("%s: replacing server reply \"%s\" with \"%s\"", 299 session->namaddrport, STR(session->buffer), new_reply); 300 vstring_strcpy(session->buffer, new_reply); 301 if (chat_append_flag) { 302 smtp_chat_append(session, "Replaced-by: ", ""); 303 smtp_chat_append(session, " ", new_reply); 304 } 305 } else if (smtp_chat_resp_filter->error != 0) { 306 msg_warn("%s: table %s:%s lookup error for %s", 307 session->state->request->queue_id, 308 smtp_chat_resp_filter->type, 309 smtp_chat_resp_filter->name, 310 printable(STR(session->buffer), '?')); 311 vstream_longjmp(session->stream, SMTP_ERR_DATA); 312 } 313 } 314 if (chat_append_flag) { 315 if (LEN(rdata.str_buf)) 316 VSTRING_ADDCH(rdata.str_buf, '\n'); 317 vstring_strcat(rdata.str_buf, STR(session->buffer)); 318 } 319 320 /* 321 * Parse into code and text. Do not ignore garbage (see below). 322 */ 323 for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) 324 /* void */ ; 325 if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { 326 if (*cp == '-') 327 continue; 328 if (*cp == ' ' || *cp == 0) 329 break; 330 } 331 332 /* 333 * XXX Do not simply ignore garbage in the server reply when ESMTP 334 * command pipelining is turned on. For example, after sending 335 * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a 336 * legitimate 2XX reply, Postfix recognizes the server's QUIT reply 337 * as the END-OF-DATA reply after garbage, causing mail to be lost. 338 * 339 * Without the ability to store per-domain status information in queue 340 * files, automatic workarounds are problematic: 341 * 342 * - Automatically deferring delivery creates a "repeated delivery" 343 * problem when garbage arrives after the DATA stage. Without the 344 * workaround, Postfix delivers only once. 345 * 346 * - Automatically deferring delivery creates a "no delivery" problem 347 * when the garbage arrives before the DATA stage. Without the 348 * workaround, mail might still get through. 349 * 350 * - Automatically turning off pipelining for delayed mail affects 351 * deliveries to correctly implemented servers, and may also affect 352 * delivery of large mailing lists. 353 * 354 * So we leave the decision with the administrator, but we don't force 355 * them to take action, like we would with automatic deferral. If 356 * loss of mail is not acceptable then they can turn off pipelining 357 * for specific sites, or they can turn off pipelining globally when 358 * they find that there are just too many broken sites. 359 */ 360 session->error_mask |= MAIL_ERROR_PROTOCOL; 361 if (session->features & SMTP_FEATURE_PIPELINING) { 362 msg_warn("%s: non-%s response from %s: %.100s", 363 session->state->request->queue_id, 364 smtp_mode ? "ESMTP" : "LMTP", 365 session->namaddrport, STR(session->buffer)); 366 if (var_helpful_warnings) 367 msg_warn("to prevent loss of mail, turn off command pipelining " 368 "for %s with the %s parameter", 369 STR(session->iterator->addr), 370 VAR_LMTP_SMTP(EHLO_DIS_MAPS)); 371 } 372 } 373 374 /* 375 * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail 376 * code if none was given. 377 * 378 * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX 379 * replies, or codes whose initial digit is out of sync with the reply 380 * code. 381 * 382 * XXX Potential stability problem. In order to save memory, the queue 383 * manager stores DSNs in a compact manner: 384 * 385 * - empty strings are represented by null pointers, 386 * 387 * - the status and reason are required to be non-empty. 388 * 389 * Other Postfix daemons inherit this behavior, because they use the same 390 * DSN support code. This means that everything that receives DSNs must 391 * cope with null pointers for the optional DSN attributes, and that 392 * everything that provides DSN information must provide a non-empty 393 * status and reason, otherwise the DSN support code wil panic(). 394 * 395 * Thus, when the remote server sends a malformed reply (or 3XX out of 396 * context) we should not panic() in DSN_COPY() just because we don't 397 * have a status. Robustness suggests that we supply a status here, and 398 * that we leave it up to the down-stream code to override the 399 * server-supplied status in case of an error we can't detect here, such 400 * as an out-of-order server reply. 401 */ 402 VSTRING_TERMINATE(rdata.str_buf); 403 vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ 404 if (three_digs != 0) { 405 rdata.code = atoi(STR(session->buffer)); 406 if (strchr("245", STR(session->buffer)[0]) != 0) { 407 for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) 408 /* void */ ; 409 if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { 410 vstring_strncpy(rdata.dsn_buf, cp, len); 411 } else { 412 vstring_strcpy(rdata.dsn_buf, "0.0.0"); 413 STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; 414 } 415 } 416 } else { 417 rdata.code = 0; 418 } 419 rdata.dsn = STR(rdata.dsn_buf); 420 rdata.str = STR(rdata.str_buf); 421 return (&rdata); 422 } 423 424 /* print_line - line_wrap callback */ 425 426 static void print_line(const char *str, int len, int indent, void *context) 427 { 428 VSTREAM *notice = (VSTREAM *) context; 429 430 post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); 431 } 432 433 /* smtp_chat_notify - notify postmaster */ 434 435 void smtp_chat_notify(SMTP_SESSION *session) 436 { 437 const char *myname = "smtp_chat_notify"; 438 VSTREAM *notice; 439 char **cpp; 440 441 /* 442 * Sanity checks. 443 */ 444 if (session->history == 0) 445 msg_panic("%s: no conversation history", myname); 446 if (msg_verbose) 447 msg_info("%s: notify postmaster", myname); 448 449 /* 450 * Construct a message for the postmaster, explaining what this is all 451 * about. This is junk mail: don't send it when the mail posting service 452 * is unavailable, and use the double bounce sender address, to prevent 453 * mail bounce wars. Always prepend one space to message content that we 454 * generate from untrusted data. 455 */ 456 #define NULL_TRACE_FLAGS 0 457 #define NO_QUEUE_ID ((VSTRING *) 0) 458 #define LENGTH 78 459 #define INDENT 4 460 461 notice = post_mail_fopen_nowait(mail_addr_double_bounce(), 462 var_error_rcpt, 463 MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, 464 SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); 465 if (notice == 0) { 466 msg_warn("postmaster notify: %m"); 467 return; 468 } 469 post_mail_fprintf(notice, "From: %s (Mail Delivery System)", 470 mail_addr_mail_daemon()); 471 post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); 472 post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", 473 var_mail_name, smtp_mode ? "SMTP" : "LMTP", 474 session->namaddrport); 475 post_mail_fputs(notice, ""); 476 post_mail_fprintf(notice, "Unexpected response from %s.", 477 session->namaddrport); 478 post_mail_fputs(notice, ""); 479 post_mail_fputs(notice, "Transcript of session follows."); 480 post_mail_fputs(notice, ""); 481 argv_terminate(session->history); 482 for (cpp = session->history->argv; *cpp; cpp++) 483 line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, 484 (void *) notice); 485 post_mail_fputs(notice, ""); 486 post_mail_fprintf(notice, "For other details, see the local mail logfile"); 487 (void) post_mail_fclose(notice); 488 } 489