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