1 /* $NetBSD: smtp_chat.c,v 1.3 2020/03/18 19:05:20 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 /* Wietse Venema 109 /* Google, Inc. 110 /* 111 8th Avenue 111 /* New York, NY 10011, USA 112 /*--*/ 113 114 /* System library. */ 115 116 #include <sys_defs.h> 117 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */ 118 #include <stdarg.h> 119 #include <ctype.h> 120 #include <stdlib.h> 121 #include <setjmp.h> 122 #include <string.h> 123 #include <limits.h> 124 125 /* Utility library. */ 126 127 #include <msg.h> 128 #include <vstring.h> 129 #include <vstream.h> 130 #include <argv.h> 131 #include <stringops.h> 132 #include <line_wrap.h> 133 #include <mymalloc.h> 134 135 /* Global library. */ 136 137 #include <recipient_list.h> 138 #include <deliver_request.h> 139 #include <smtp_stream.h> 140 #include <mail_params.h> 141 #include <mail_addr.h> 142 #include <post_mail.h> 143 #include <mail_error.h> 144 #include <dsn_util.h> 145 146 /* Application-specific. */ 147 148 #include "smtp.h" 149 150 /* 151 * Server reply transformations. 152 */ 153 DICT *smtp_chat_resp_filter; 154 155 /* smtp_chat_init - initialize SMTP transaction log */ 156 157 void smtp_chat_init(SMTP_SESSION *session) 158 { 159 session->history = 0; 160 } 161 162 /* smtp_chat_reset - reset SMTP transaction log */ 163 164 void smtp_chat_reset(SMTP_SESSION *session) 165 { 166 if (session->history) { 167 argv_free(session->history); 168 session->history = 0; 169 } 170 } 171 172 /* smtp_chat_append - append record to SMTP transaction log */ 173 174 static void smtp_chat_append(SMTP_SESSION *session, const char *direction, 175 const char *data) 176 { 177 char *line; 178 179 if (session->history == 0) 180 session->history = argv_alloc(10); 181 line = concatenate(direction, data, (char *) 0); 182 argv_add(session->history, line, (char *) 0); 183 myfree(line); 184 } 185 186 /* smtp_chat_cmd - send an SMTP command */ 187 188 void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) 189 { 190 va_list ap; 191 192 /* 193 * Format the command, and update the transaction log. 194 */ 195 va_start(ap, fmt); 196 vstring_vsprintf(session->buffer, fmt, ap); 197 va_end(ap); 198 smtp_chat_append(session, "Out: ", STR(session->buffer)); 199 200 /* 201 * Optionally log the command first, so we can see in the log what the 202 * program is trying to do. 203 */ 204 if (msg_verbose) 205 msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); 206 207 /* 208 * Send the command to the SMTP server. 209 */ 210 smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); 211 212 /* 213 * Force flushing of output does not belong here. It is done in the 214 * smtp_loop() main protocol loop when reading the server response, and 215 * in smtp_helo() when reading the EHLO response after sending the EHLO 216 * command. 217 * 218 * If we do forced flush here, then we must longjmp() on error, and a 219 * matching "prepare for disaster" error handler must be set up before 220 * every smtp_chat_cmd() call. 221 */ 222 #if 0 223 224 /* 225 * Flush unsent data to avoid timeouts after slow DNS lookups. 226 */ 227 if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) 228 vstream_fflush(session->stream); 229 230 /* 231 * Abort immediately if the connection is broken. 232 */ 233 if (vstream_ftimeout(session->stream)) 234 vstream_longjmp(session->stream, SMTP_ERR_TIME); 235 if (vstream_ferror(session->stream)) 236 vstream_longjmp(session->stream, SMTP_ERR_EOF); 237 #endif 238 } 239 240 /* smtp_chat_resp - read and process SMTP server response */ 241 242 SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) 243 { 244 static SMTP_RESP rdata; 245 char *cp; 246 int last_char; 247 int three_digs = 0; 248 size_t len; 249 const char *new_reply; 250 int chat_append_flag; 251 int chat_append_skipped = 0; 252 253 /* 254 * Initialize the response data buffer. 255 */ 256 if (rdata.str_buf == 0) { 257 rdata.dsn_buf = vstring_alloc(10); 258 rdata.str_buf = vstring_alloc(100); 259 } 260 261 /* 262 * Censor out non-printable characters in server responses. Concatenate 263 * multi-line server responses. Separate the status code from the text. 264 * Leave further parsing up to the application. 265 * 266 * We can't parse or store input that exceeds var_line_limit, so we just 267 * skip over it to simplify the remainder of the code below. 268 */ 269 VSTRING_RESET(rdata.str_buf); 270 for (;;) { 271 last_char = smtp_get(session->buffer, session->stream, var_line_limit, 272 SMTP_GET_FLAG_SKIP); 273 /* XXX Update the per-line time limit. */ 274 printable(STR(session->buffer), '?'); 275 if (last_char != '\n') 276 msg_warn("%s: response longer than %d: %.30s...", 277 session->namaddrport, var_line_limit, STR(session->buffer)); 278 if (msg_verbose) 279 msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); 280 281 /* 282 * Defend against a denial of service attack by limiting the amount 283 * of multi-line text that we are willing to store. 284 */ 285 chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); 286 if (chat_append_flag) 287 smtp_chat_append(session, "In: ", STR(session->buffer)); 288 else { 289 if (chat_append_skipped == 0) 290 msg_warn("%s: multi-line response longer than %d %.30s...", 291 session->namaddrport, var_line_limit, STR(rdata.str_buf)); 292 if (chat_append_skipped < INT_MAX) 293 chat_append_skipped++; 294 } 295 296 /* 297 * Server reply substitution, for fault-injection testing, or for 298 * working around broken systems. Use with care. 299 */ 300 if (smtp_chat_resp_filter != 0) { 301 new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); 302 if (new_reply != 0) { 303 msg_info("%s: replacing server reply \"%s\" with \"%s\"", 304 session->namaddrport, STR(session->buffer), new_reply); 305 vstring_strcpy(session->buffer, new_reply); 306 if (chat_append_flag) { 307 smtp_chat_append(session, "Replaced-by: ", ""); 308 smtp_chat_append(session, " ", new_reply); 309 } 310 } else if (smtp_chat_resp_filter->error != 0) { 311 msg_warn("%s: table %s:%s lookup error for %s", 312 session->state->request->queue_id, 313 smtp_chat_resp_filter->type, 314 smtp_chat_resp_filter->name, 315 printable(STR(session->buffer), '?')); 316 vstream_longjmp(session->stream, SMTP_ERR_DATA); 317 } 318 } 319 if (chat_append_flag) { 320 if (LEN(rdata.str_buf)) 321 VSTRING_ADDCH(rdata.str_buf, '\n'); 322 vstring_strcat(rdata.str_buf, STR(session->buffer)); 323 } 324 325 /* 326 * Parse into code and text. Do not ignore garbage (see below). 327 */ 328 for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) 329 /* void */ ; 330 if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { 331 if (*cp == '-') 332 continue; 333 if (*cp == ' ' || *cp == 0) 334 break; 335 } 336 337 /* 338 * XXX Do not simply ignore garbage in the server reply when ESMTP 339 * command pipelining is turned on. For example, after sending 340 * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a 341 * legitimate 2XX reply, Postfix recognizes the server's QUIT reply 342 * as the END-OF-DATA reply after garbage, causing mail to be lost. 343 * 344 * Without the ability to store per-domain status information in queue 345 * files, automatic workarounds are problematic: 346 * 347 * - Automatically deferring delivery creates a "repeated delivery" 348 * problem when garbage arrives after the DATA stage. Without the 349 * workaround, Postfix delivers only once. 350 * 351 * - Automatically deferring delivery creates a "no delivery" problem 352 * when the garbage arrives before the DATA stage. Without the 353 * workaround, mail might still get through. 354 * 355 * - Automatically turning off pipelining for delayed mail affects 356 * deliveries to correctly implemented servers, and may also affect 357 * delivery of large mailing lists. 358 * 359 * So we leave the decision with the administrator, but we don't force 360 * them to take action, like we would with automatic deferral. If 361 * loss of mail is not acceptable then they can turn off pipelining 362 * for specific sites, or they can turn off pipelining globally when 363 * they find that there are just too many broken sites. 364 * 365 * Fix 20190621: don't cache an SMTP session after an SMTP protocol 366 * error. The protocol may be in a bad state. Disable caching here so 367 * that the protocol engine will send QUIT. 368 */ 369 session->error_mask |= MAIL_ERROR_PROTOCOL; 370 DONT_CACHE_THIS_SESSION; 371 if (session->features & SMTP_FEATURE_PIPELINING) { 372 msg_warn("%s: non-%s response from %s: %.100s", 373 session->state->request->queue_id, 374 smtp_mode ? "ESMTP" : "LMTP", 375 session->namaddrport, STR(session->buffer)); 376 if (var_helpful_warnings) 377 msg_warn("to prevent loss of mail, turn off command pipelining " 378 "for %s with the %s parameter", 379 STR(session->iterator->addr), 380 VAR_LMTP_SMTP(EHLO_DIS_MAPS)); 381 } 382 } 383 384 /* 385 * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail 386 * code if none was given. 387 * 388 * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX 389 * replies, or codes whose initial digit is out of sync with the reply 390 * code. 391 * 392 * XXX Potential stability problem. In order to save memory, the queue 393 * manager stores DSNs in a compact manner: 394 * 395 * - empty strings are represented by null pointers, 396 * 397 * - the status and reason are required to be non-empty. 398 * 399 * Other Postfix daemons inherit this behavior, because they use the same 400 * DSN support code. This means that everything that receives DSNs must 401 * cope with null pointers for the optional DSN attributes, and that 402 * everything that provides DSN information must provide a non-empty 403 * status and reason, otherwise the DSN support code wil panic(). 404 * 405 * Thus, when the remote server sends a malformed reply (or 3XX out of 406 * context) we should not panic() in DSN_COPY() just because we don't 407 * have a status. Robustness suggests that we supply a status here, and 408 * that we leave it up to the down-stream code to override the 409 * server-supplied status in case of an error we can't detect here, such 410 * as an out-of-order server reply. 411 */ 412 VSTRING_TERMINATE(rdata.str_buf); 413 vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ 414 if (three_digs != 0) { 415 rdata.code = atoi(STR(session->buffer)); 416 if (strchr("245", STR(session->buffer)[0]) != 0) { 417 for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) 418 /* void */ ; 419 if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { 420 vstring_strncpy(rdata.dsn_buf, cp, len); 421 } else { 422 vstring_strcpy(rdata.dsn_buf, "0.0.0"); 423 STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; 424 } 425 } 426 } else { 427 rdata.code = 0; 428 } 429 rdata.dsn = STR(rdata.dsn_buf); 430 rdata.str = STR(rdata.str_buf); 431 return (&rdata); 432 } 433 434 /* print_line - line_wrap callback */ 435 436 static void print_line(const char *str, int len, int indent, void *context) 437 { 438 VSTREAM *notice = (VSTREAM *) context; 439 440 post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); 441 } 442 443 /* smtp_chat_notify - notify postmaster */ 444 445 void smtp_chat_notify(SMTP_SESSION *session) 446 { 447 const char *myname = "smtp_chat_notify"; 448 VSTREAM *notice; 449 char **cpp; 450 451 /* 452 * Sanity checks. 453 */ 454 if (session->history == 0) 455 msg_panic("%s: no conversation history", myname); 456 if (msg_verbose) 457 msg_info("%s: notify postmaster", myname); 458 459 /* 460 * Construct a message for the postmaster, explaining what this is all 461 * about. This is junk mail: don't send it when the mail posting service 462 * is unavailable, and use the double bounce sender address, to prevent 463 * mail bounce wars. Always prepend one space to message content that we 464 * generate from untrusted data. 465 */ 466 #define NULL_TRACE_FLAGS 0 467 #define NO_QUEUE_ID ((VSTRING *) 0) 468 #define LENGTH 78 469 #define INDENT 4 470 471 notice = post_mail_fopen_nowait(mail_addr_double_bounce(), 472 var_error_rcpt, 473 MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, 474 SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); 475 if (notice == 0) { 476 msg_warn("postmaster notify: %m"); 477 return; 478 } 479 post_mail_fprintf(notice, "From: %s (Mail Delivery System)", 480 mail_addr_mail_daemon()); 481 post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); 482 post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", 483 var_mail_name, smtp_mode ? "SMTP" : "LMTP", 484 session->namaddrport); 485 post_mail_fputs(notice, ""); 486 post_mail_fprintf(notice, "Unexpected response from %s.", 487 session->namaddrport); 488 post_mail_fputs(notice, ""); 489 post_mail_fputs(notice, "Transcript of session follows."); 490 post_mail_fputs(notice, ""); 491 argv_terminate(session->history); 492 for (cpp = session->history->argv; *cpp; cpp++) 493 line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, 494 (void *) notice); 495 post_mail_fputs(notice, ""); 496 post_mail_fprintf(notice, "For other details, see the local mail logfile"); 497 (void) post_mail_fclose(notice); 498 } 499