xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_chat.c (revision a04395531661c5e8d314125d5ae77d4cbedd5d73)
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