xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtpd/smtpd_chat.c (revision 48fb7bfab72acd4281a53bbee5ccf3f809019e75)
1 /*	$NetBSD: smtpd_chat.c,v 1.1.1.3 2013/01/02 18:59:09 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	smtpd_chat 3
6 /* SUMMARY
7 /*	SMTP server request/response support
8 /* SYNOPSIS
9 /*	#include <smtpd.h>
10 /*	#include <smtpd_chat.h>
11 /*
12 /*	void	smtpd_chat_query(state)
13 /*	SMTPD_STATE *state;
14 /*
15 /*	void	smtpd_chat_reply(state, format, ...)
16 /*	SMTPD_STATE *state;
17 /*	char	*format;
18 /*
19 /*	void	smtpd_chat_notify(state)
20 /*	SMTPD_STATE *state;
21 /*
22 /*	void	smtpd_chat_reset(state)
23 /*	SMTPD_STATE *state;
24 /* DESCRIPTION
25 /*	This module implements SMTP server support for request/reply
26 /*	conversations, and maintains a limited SMTP transaction log.
27 /*
28 /*	smtpd_chat_query() receives a client request and appends a copy
29 /*	to the SMTP transaction log.
30 /*
31 /*	smtpd_chat_reply() formats a server reply, sends it to the
32 /*	client, and appends a copy to the SMTP transaction log.
33 /*	When soft_bounce is enabled, all 5xx (reject) reponses are
34 /*	replaced by 4xx (try again). In case of a 421 reply the
35 /*	SMTPD_FLAG_HANGUP flag is set for orderly disconnect.
36 /*
37 /*	smtpd_chat_notify() sends a copy of the SMTP transaction log
38 /*	to the postmaster for review. The postmaster notice is sent only
39 /*	when delivery is possible immediately. It is an error to call
40 /*	smtpd_chat_notify() when no SMTP transaction log exists.
41 /*
42 /*	smtpd_chat_reset() resets the transaction log. This is
43 /*	typically done at the beginning of an SMTP session, or
44 /*	within a session to discard non-error information.
45 /* DIAGNOSTICS
46 /*	Panic: interface violations. Fatal errors: out of memory.
47 /*	internal protocol errors.
48 /* LICENSE
49 /* .ad
50 /* .fi
51 /*	The Secure Mailer license must be distributed with this software.
52 /* AUTHOR(S)
53 /*	Wietse Venema
54 /*	IBM T.J. Watson Research
55 /*	P.O. Box 704
56 /*	Yorktown Heights, NY 10598, USA
57 /*--*/
58 
59 /* System library. */
60 
61 #include <sys_defs.h>
62 #include <setjmp.h>
63 #include <unistd.h>
64 #include <time.h>
65 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
66 #include <stdarg.h>
67 
68 /* Utility library. */
69 
70 #include <msg.h>
71 #include <argv.h>
72 #include <vstring.h>
73 #include <vstream.h>
74 #include <stringops.h>
75 #include <line_wrap.h>
76 #include <mymalloc.h>
77 
78 /* Global library. */
79 
80 #include <smtp_stream.h>
81 #include <record.h>
82 #include <rec_type.h>
83 #include <mail_proto.h>
84 #include <mail_params.h>
85 #include <mail_addr.h>
86 #include <post_mail.h>
87 #include <mail_error.h>
88 #include <smtp_reply_footer.h>
89 
90 /* Application-specific. */
91 
92 #include "smtpd.h"
93 #include "smtpd_expand.h"
94 #include "smtpd_chat.h"
95 
96 #define STR	vstring_str
97 #define LEN	VSTRING_LEN
98 
99 /* smtp_chat_reset - reset SMTP transaction log */
100 
101 void    smtpd_chat_reset(SMTPD_STATE *state)
102 {
103     if (state->history) {
104 	argv_free(state->history);
105 	state->history = 0;
106     }
107 }
108 
109 /* smtp_chat_append - append record to SMTP transaction log */
110 
111 static void smtp_chat_append(SMTPD_STATE *state, char *direction,
112 			             const char *text)
113 {
114     char   *line;
115 
116     if (state->notify_mask == 0)
117 	return;
118 
119     if (state->history == 0)
120 	state->history = argv_alloc(10);
121     line = concatenate(direction, text, (char *) 0);
122     argv_add(state->history, line, (char *) 0);
123     myfree(line);
124 }
125 
126 /* smtpd_chat_query - receive and record an SMTP request */
127 
128 void    smtpd_chat_query(SMTPD_STATE *state)
129 {
130     int     last_char;
131 
132     /*
133      * We can't parse or store input that exceeds var_line_limit, so we skip
134      * over it to avoid loss of synchronization.
135      */
136     last_char = smtp_get(state->buffer, state->client, var_line_limit,
137 			 SMTP_GET_FLAG_SKIP);
138     smtp_chat_append(state, "In:  ", STR(state->buffer));
139     if (last_char != '\n')
140 	msg_warn("%s: request longer than %d: %.30s...",
141 		 state->namaddr, var_line_limit,
142 		 printable(STR(state->buffer), '?'));
143 
144     if (msg_verbose)
145 	msg_info("< %s: %s", state->namaddr, STR(state->buffer));
146 }
147 
148 /* smtpd_chat_reply - format, send and record an SMTP response */
149 
150 void    smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
151 {
152     va_list ap;
153     int     delay = 0;
154     char   *cp;
155     char   *next;
156     char   *end;
157 
158     /*
159      * Slow down clients that make errors. Sleep-on-anything slows down
160      * clients that make an excessive number of errors within a session.
161      */
162     if (state->error_count >= var_smtpd_soft_erlim)
163 	sleep(delay = var_smtpd_err_sleep);
164 
165     va_start(ap, format);
166     vstring_vsprintf(state->buffer, format, ap);
167     va_end(ap);
168 
169     if (*var_smtpd_rej_footer
170 	&& (*(cp = STR(state->buffer)) == '4' || *cp == '5'))
171 	smtp_reply_footer(state->buffer, 0, var_smtpd_rej_footer,
172 			  STR(smtpd_expand_filter), smtpd_expand_lookup,
173 			  (char *) state);
174 
175     /* All 5xx replies must have a 5.xx.xx detail code. */
176     for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
177 	if (var_soft_bounce) {
178 	    if (cp[0] == '5') {
179 		cp[0] = '4';
180 		if (cp[4] == '5')
181 		    cp[4] = '4';
182 	    }
183 	}
184 	/* This is why we use strlen() above instead of VSTRING_LEN(). */
185 	if ((next = strstr(cp, "\r\n")) != 0) {
186 	    *next = 0;
187 	    if (next[2] != 0)
188 		cp[3] = '-';			/* contact footer kludge */
189 	    else
190 		next = end;			/* strip trailing \r\n */
191 	} else {
192 	    next = end;
193 	}
194 	smtp_chat_append(state, "Out: ", cp);
195 
196 	if (msg_verbose)
197 	    msg_info("> %s: %s", state->namaddr, cp);
198 
199 	smtp_fputs(cp, next - cp, state->client);
200 	if (next < end)
201 	    cp = next + 2;
202 	else
203 	    break;
204     }
205 
206     /*
207      * Flush unsent output if no I/O happened for a while. This avoids
208      * timeouts with pipelined SMTP sessions that have lots of server-side
209      * delays (tarpit delays or DNS lookups for UCE restrictions).
210      */
211     if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
212 	vstream_fflush(state->client);
213 
214     /*
215      * Abort immediately if the connection is broken.
216      */
217     if (vstream_ftimeout(state->client))
218 	vstream_longjmp(state->client, SMTP_ERR_TIME);
219     if (vstream_ferror(state->client))
220 	vstream_longjmp(state->client, SMTP_ERR_EOF);
221 
222     /*
223      * Orderly disconnect in case of 421 or 521 reply.
224      */
225     if (strncmp(STR(state->buffer), "421", 3) == 0
226 	|| strncmp(STR(state->buffer), "521", 3) == 0)
227 	state->flags |= SMTPD_FLAG_HANGUP;
228 }
229 
230 /* print_line - line_wrap callback */
231 
232 static void print_line(const char *str, int len, int indent, char *context)
233 {
234     VSTREAM *notice = (VSTREAM *) context;
235 
236     post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
237 }
238 
239 /* smtpd_chat_notify - notify postmaster */
240 
241 void    smtpd_chat_notify(SMTPD_STATE *state)
242 {
243     const char *myname = "smtpd_chat_notify";
244     VSTREAM *notice;
245     char  **cpp;
246 
247     /*
248      * Sanity checks.
249      */
250     if (state->history == 0)
251 	msg_panic("%s: no conversation history", myname);
252     if (msg_verbose)
253 	msg_info("%s: notify postmaster", myname);
254 
255     /*
256      * Construct a message for the postmaster, explaining what this is all
257      * about. This is junk mail: don't send it when the mail posting service
258      * is unavailable, and use the double bounce sender address to prevent
259      * mail bounce wars. Always prepend one space to message content that we
260      * generate from untrusted data.
261      */
262 #define NULL_TRACE_FLAGS	0
263 #define NO_QUEUE_ID		((VSTRING *) 0)
264 #define LENGTH	78
265 #define INDENT	4
266 
267     notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
268 				    var_error_rcpt,
269 				    INT_FILT_MASK_NOTIFY,
270 				    NULL_TRACE_FLAGS, NO_QUEUE_ID);
271     if (notice == 0) {
272 	msg_warn("postmaster notify: %m");
273 	return;
274     }
275     post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
276 		      mail_addr_mail_daemon());
277     post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
278     post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
279 		      var_mail_name, state->namaddr);
280     post_mail_fputs(notice, "");
281     post_mail_fputs(notice, "Transcript of session follows.");
282     post_mail_fputs(notice, "");
283     argv_terminate(state->history);
284     for (cpp = state->history->argv; *cpp; cpp++)
285 	line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
286 		  (char *) notice);
287     post_mail_fputs(notice, "");
288     if (state->reason)
289 	post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
290     post_mail_fputs(notice, "");
291     post_mail_fprintf(notice, "For other details, see the local mail logfile");
292     (void) post_mail_fclose(notice);
293 }
294