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