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