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