xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_chat.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
1 /*	$NetBSD: smtp_chat.c,v 1.1.1.3 2013/01/02 18:59:07 tron 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 		     (session->state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) ?
365 		     "LMTP" : "ESMTP", session->namaddrport,
366 		     STR(session->buffer));
367 	    if (var_helpful_warnings)
368 		msg_warn("to prevent loss of mail, turn off command pipelining "
369 			 "for %s with the %s parameter", session->addr,
370 		    (session->state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) ?
371 			 VAR_LMTP_EHLO_DIS_MAPS : VAR_SMTP_EHLO_DIS_MAPS);
372 	}
373     }
374 
375     /*
376      * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
377      * code if none was given.
378      *
379      * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
380      * replies, or codes whose initial digit is out of sync with the reply
381      * code.
382      *
383      * XXX Potential stability problem. In order to save memory, the queue
384      * manager stores DSNs in a compact manner:
385      *
386      * - empty strings are represented by null pointers,
387      *
388      * - the status and reason are required to be non-empty.
389      *
390      * Other Postfix daemons inherit this behavior, because they use the same
391      * DSN support code. This means that everything that receives DSNs must
392      * cope with null pointers for the optional DSN attributes, and that
393      * everything that provides DSN information must provide a non-empty
394      * status and reason, otherwise the DSN support code wil panic().
395      *
396      * Thus, when the remote server sends a malformed reply (or 3XX out of
397      * context) we should not panic() in DSN_COPY() just because we don't
398      * have a status. Robustness suggests that we supply a status here, and
399      * that we leave it up to the down-stream code to override the
400      * server-supplied status in case of an error we can't detect here, such
401      * as an out-of-order server reply.
402      */
403     VSTRING_TERMINATE(rdata.str_buf);
404     vstring_strcpy(rdata.dsn_buf, "5.5.0");	/* SAFETY! protocol error */
405     if (three_digs != 0) {
406 	rdata.code = atoi(STR(session->buffer));
407 	if (strchr("245", STR(session->buffer)[0]) != 0) {
408 	    for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
409 		 /* void */ ;
410 	    if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
411 		vstring_strncpy(rdata.dsn_buf, cp, len);
412 	    } else {
413 		vstring_strcpy(rdata.dsn_buf, "0.0.0");
414 		STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
415 	    }
416 	}
417     } else {
418 	rdata.code = 0;
419     }
420     rdata.dsn = STR(rdata.dsn_buf);
421     rdata.str = STR(rdata.str_buf);
422     return (&rdata);
423 }
424 
425 /* print_line - line_wrap callback */
426 
427 static void print_line(const char *str, int len, int indent, char *context)
428 {
429     VSTREAM *notice = (VSTREAM *) context;
430 
431     post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
432 }
433 
434 /* smtp_chat_notify - notify postmaster */
435 
436 void    smtp_chat_notify(SMTP_SESSION *session)
437 {
438     const char *myname = "smtp_chat_notify";
439     VSTREAM *notice;
440     char  **cpp;
441 
442     /*
443      * Sanity checks.
444      */
445     if (session->history == 0)
446 	msg_panic("%s: no conversation history", myname);
447     if (msg_verbose)
448 	msg_info("%s: notify postmaster", myname);
449 
450     /*
451      * Construct a message for the postmaster, explaining what this is all
452      * about. This is junk mail: don't send it when the mail posting service
453      * is unavailable, and use the double bounce sender address, to prevent
454      * mail bounce wars. Always prepend one space to message content that we
455      * generate from untrusted data.
456      */
457 #define NULL_TRACE_FLAGS	0
458 #define NO_QUEUE_ID		((VSTRING *) 0)
459 #define LENGTH	78
460 #define INDENT	4
461 
462     notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
463 				    var_error_rcpt,
464 				    INT_FILT_MASK_NOTIFY,
465 				    NULL_TRACE_FLAGS, NO_QUEUE_ID);
466     if (notice == 0) {
467 	msg_warn("postmaster notify: %m");
468 	return;
469     }
470     post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
471 		      mail_addr_mail_daemon());
472     post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
473     post_mail_fprintf(notice, "Subject: %s %s client: errors from %s",
474 		      var_mail_name,
475 		      (session->state->misc_flags &
476 		       SMTP_MISC_FLAG_USE_LMTP) ? "LMTP" : "SMTP",
477 		      session->namaddrport);
478     post_mail_fputs(notice, "");
479     post_mail_fprintf(notice, "Unexpected response from %s.",
480 		      session->namaddrport);
481     post_mail_fputs(notice, "");
482     post_mail_fputs(notice, "Transcript of session follows.");
483     post_mail_fputs(notice, "");
484     argv_terminate(session->history);
485     for (cpp = session->history->argv; *cpp; cpp++)
486 	line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
487 		  (char *) notice);
488     post_mail_fputs(notice, "");
489     post_mail_fprintf(notice, "For other details, see the local mail logfile");
490     (void) post_mail_fclose(notice);
491 }
492