1/*	$NetBSD: smtpd_chat.c,v 1.4 2022/10/08 16:12:49 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#include <hfrom_format.h>
109
110/* Application-specific. */
111
112#include "smtpd.h"
113#include "smtpd_expand.h"
114#include "smtpd_chat.h"
115
116 /*
117  * Reject footer.
118  */
119static MAPS *smtpd_rej_ftr_maps;
120
121#define STR	vstring_str
122#define LEN	VSTRING_LEN
123
124/* smtpd_chat_pre_jail_init - initialize */
125
126void    smtpd_chat_pre_jail_init(void)
127{
128    static int init_count = 0;
129
130    if (init_count++ != 0)
131	msg_panic("smtpd_chat_pre_jail_init: multiple calls");
132
133    /*
134     * SMTP server reject footer.
135     */
136    if (*var_smtpd_rej_ftr_maps)
137	smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
138					 var_smtpd_rej_ftr_maps,
139					 DICT_FLAG_LOCK);
140}
141
142/* smtp_chat_reset - reset SMTP transaction log */
143
144void    smtpd_chat_reset(SMTPD_STATE *state)
145{
146    if (state->history) {
147	argv_free(state->history);
148	state->history = 0;
149    }
150}
151
152/* smtp_chat_append - append record to SMTP transaction log */
153
154static void smtp_chat_append(SMTPD_STATE *state, char *direction,
155			             const char *text)
156{
157    char   *line;
158
159    if (state->notify_mask == 0)
160	return;
161
162    if (state->history == 0)
163	state->history = argv_alloc(10);
164    line = concatenate(direction, text, (char *) 0);
165    argv_add(state->history, line, (char *) 0);
166    myfree(line);
167}
168
169/* smtpd_chat_query - receive and record an SMTP request */
170
171int     smtpd_chat_query_limit(SMTPD_STATE *state, int limit)
172{
173    int     last_char;
174
175    /*
176     * We can't parse or store input that exceeds var_line_limit, so we skip
177     * over it to avoid loss of synchronization.
178     */
179    last_char = smtp_get(state->buffer, state->client, limit,
180			 SMTP_GET_FLAG_SKIP);
181    smtp_chat_append(state, "In:  ", STR(state->buffer));
182    if (last_char != '\n')
183	msg_warn("%s: request longer than %d: %.30s...",
184		 state->namaddr, limit,
185		 printable(STR(state->buffer), '?'));
186
187    if (msg_verbose)
188	msg_info("< %s: %s", state->namaddr, STR(state->buffer));
189    return (last_char == '\n');
190}
191
192/* smtpd_chat_reply - format, send and record an SMTP response */
193
194void    smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
195{
196    va_list ap;
197
198    va_start(ap, format);
199    vsmtpd_chat_reply(state, format, ap);
200    va_end(ap);
201}
202
203/* vsmtpd_chat_reply - format, send and record an SMTP response */
204
205void    vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
206{
207    int     delay = 0;
208    char   *cp;
209    char   *next;
210    char   *end;
211    const char *footer;
212
213    /*
214     * Slow down clients that make errors. Sleep-on-anything slows down
215     * clients that make an excessive number of errors within a session.
216     */
217    if (state->error_count >= var_smtpd_soft_erlim)
218	sleep(delay = var_smtpd_err_sleep);
219
220    vstring_vsprintf(state->buffer, format, ap);
221
222    if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
223	&& ((smtpd_rej_ftr_maps != 0
224	     && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)
225	    || *(footer = var_smtpd_rej_footer) != 0))
226	smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter),
227			  smtpd_expand_lookup, (void *) state);
228
229    /* All 5xx replies must have a 5.xx.xx detail code. */
230    for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
231	if (var_soft_bounce) {
232	    if (cp[0] == '5') {
233		cp[0] = '4';
234		if (cp[4] == '5')
235		    cp[4] = '4';
236	    }
237	}
238	/* This is why we use strlen() above instead of VSTRING_LEN(). */
239	if ((next = strstr(cp, "\r\n")) != 0) {
240	    *next = 0;
241	    if (next[2] != 0)
242		cp[3] = '-';			/* contact footer kludge */
243	    else
244		next = end;			/* strip trailing \r\n */
245	} else {
246	    next = end;
247	}
248	smtp_chat_append(state, "Out: ", cp);
249
250	if (msg_verbose)
251	    msg_info("> %s: %s", state->namaddr, cp);
252
253	smtp_fputs(cp, next - cp, state->client);
254	if (next < end)
255	    cp = next + 2;
256	else
257	    break;
258    }
259
260    /*
261     * Flush unsent output if no I/O happened for a while. This avoids
262     * timeouts with pipelined SMTP sessions that have lots of server-side
263     * delays (tarpit delays or DNS lookups for UCE restrictions).
264     */
265    if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
266	vstream_fflush(state->client);
267
268    /*
269     * Abort immediately if the connection is broken.
270     */
271    if (vstream_ftimeout(state->client))
272	vstream_longjmp(state->client, SMTP_ERR_TIME);
273    if (vstream_ferror(state->client))
274	vstream_longjmp(state->client, SMTP_ERR_EOF);
275
276    /*
277     * Orderly disconnect in case of 421 or 521 reply.
278     */
279    if (strncmp(STR(state->buffer), "421", 3) == 0
280	|| strncmp(STR(state->buffer), "521", 3) == 0)
281	state->flags |= SMTPD_FLAG_HANGUP;
282}
283
284/* print_line - line_wrap callback */
285
286static void print_line(const char *str, int len, int indent, void *context)
287{
288    VSTREAM *notice = (VSTREAM *) context;
289
290    post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
291}
292
293/* smtpd_chat_notify - notify postmaster */
294
295void    smtpd_chat_notify(SMTPD_STATE *state)
296{
297    const char *myname = "smtpd_chat_notify";
298    VSTREAM *notice;
299    char  **cpp;
300
301    /*
302     * Sanity checks.
303     */
304    if (state->history == 0)
305	msg_panic("%s: no conversation history", myname);
306    if (msg_verbose)
307	msg_info("%s: notify postmaster", myname);
308
309    /*
310     * Construct a message for the postmaster, explaining what this is all
311     * about. This is junk mail: don't send it when the mail posting service
312     * is unavailable, and use the double bounce sender address to prevent
313     * mail bounce wars. Always prepend one space to message content that we
314     * generate from untrusted data.
315     */
316#define NULL_TRACE_FLAGS	0
317#define NO_QUEUE_ID		((VSTRING *) 0)
318#define LENGTH	78
319#define INDENT	4
320
321    notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
322				    (state->error_mask & MAIL_ERROR_BOUNCE) ?
323				    var_bounce_rcpt : var_error_rcpt,
324				    MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS,
325				    SMTPUTF8_FLAG_NONE, NO_QUEUE_ID);
326    if (notice == 0) {
327	msg_warn("postmaster notify: %m");
328	return;
329    }
330    if (smtpd_hfrom_format == HFROM_FORMAT_CODE_STD) {
331	post_mail_fprintf(notice, "From: Mail Delivery System <%s>",
332			  mail_addr_mail_daemon());
333	post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt);
334    } else {
335	post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
336			  mail_addr_mail_daemon());
337	post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
338    }
339    post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
340		      var_mail_name, state->namaddr);
341    post_mail_fputs(notice, "");
342    post_mail_fputs(notice, "Transcript of session follows.");
343    post_mail_fputs(notice, "");
344    argv_terminate(state->history);
345    for (cpp = state->history->argv; *cpp; cpp++)
346	line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
347		  (void *) notice);
348    post_mail_fputs(notice, "");
349    if (state->reason)
350	post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
351    post_mail_fputs(notice, "");
352    post_mail_fprintf(notice, "For other details, see the local mail logfile");
353    (void) post_mail_fclose(notice);
354}
355