1/*	$NetBSD$	*/
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
101void    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
111static 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
128void    smtpd_chat_query(SMTPD_STATE *state)
129{
130    int     last_char;
131
132    last_char = smtp_get(state->buffer, state->client, var_line_limit);
133    smtp_chat_append(state, "In:  ", STR(state->buffer));
134    if (last_char != '\n')
135	msg_warn("%s: request longer than %d: %.30s...",
136		 state->namaddr, var_line_limit,
137		 printable(STR(state->buffer), '?'));
138
139    if (msg_verbose)
140	msg_info("< %s: %s", state->namaddr, STR(state->buffer));
141}
142
143/* smtpd_chat_reply - format, send and record an SMTP response */
144
145void    smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
146{
147    va_list ap;
148    int     delay = 0;
149    char   *cp;
150    char   *next;
151    char   *end;
152
153    /*
154     * Slow down clients that make errors. Sleep-on-anything slows down
155     * clients that make an excessive number of errors within a session.
156     */
157    if (state->error_count >= var_smtpd_soft_erlim)
158	sleep(delay = var_smtpd_err_sleep);
159
160    va_start(ap, format);
161    vstring_vsprintf(state->buffer, format, ap);
162    va_end(ap);
163
164    if (*var_smtpd_rej_footer
165	&& (*(cp = STR(state->buffer)) == '4' || *cp == '5'))
166	smtp_reply_footer(state->buffer, 0, var_smtpd_rej_footer,
167			  STR(smtpd_expand_filter), smtpd_expand_lookup,
168			  (char *) state);
169
170    /* All 5xx replies must have a 5.xx.xx detail code. */
171    for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
172	if (var_soft_bounce) {
173	    if (cp[0] == '5') {
174		cp[0] = '4';
175		if (cp[4] == '5')
176		    cp[4] = '4';
177	    }
178	}
179	/* This is why we use strlen() above instead of VSTRING_LEN(). */
180	if ((next = strstr(cp, "\r\n")) != 0) {
181	    *next = 0;
182	    cp[3] = '-';			/* contact footer kludge */
183	} else {
184	    next = end;
185	}
186	smtp_chat_append(state, "Out: ", cp);
187
188	if (msg_verbose)
189	    msg_info("> %s: %s", state->namaddr, cp);
190
191	smtp_fputs(cp, next - cp, state->client);
192	if (next < end)
193	    cp = next + 2;
194	else
195	    break;
196    }
197
198    /*
199     * Flush unsent output if no I/O happened for a while. This avoids
200     * timeouts with pipelined SMTP sessions that have lots of server-side
201     * delays (tarpit delays or DNS lookups for UCE restrictions).
202     */
203    if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
204	vstream_fflush(state->client);
205
206    /*
207     * Abort immediately if the connection is broken.
208     */
209    if (vstream_ftimeout(state->client))
210	vstream_longjmp(state->client, SMTP_ERR_TIME);
211    if (vstream_ferror(state->client))
212	vstream_longjmp(state->client, SMTP_ERR_EOF);
213
214    /*
215     * Orderly disconnect in case of 421 or 521 reply.
216     */
217    if (strncmp(STR(state->buffer), "421", 3) == 0
218	|| strncmp(STR(state->buffer), "521", 3) == 0)
219	state->flags |= SMTPD_FLAG_HANGUP;
220}
221
222/* print_line - line_wrap callback */
223
224static void print_line(const char *str, int len, int indent, char *context)
225{
226    VSTREAM *notice = (VSTREAM *) context;
227
228    post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
229}
230
231/* smtpd_chat_notify - notify postmaster */
232
233void    smtpd_chat_notify(SMTPD_STATE *state)
234{
235    const char *myname = "smtpd_chat_notify";
236    VSTREAM *notice;
237    char  **cpp;
238
239    /*
240     * Sanity checks.
241     */
242    if (state->history == 0)
243	msg_panic("%s: no conversation history", myname);
244    if (msg_verbose)
245	msg_info("%s: notify postmaster", myname);
246
247    /*
248     * Construct a message for the postmaster, explaining what this is all
249     * about. This is junk mail: don't send it when the mail posting service
250     * is unavailable, and use the double bounce sender address to prevent
251     * mail bounce wars. Always prepend one space to message content that we
252     * generate from untrusted data.
253     */
254#define NULL_TRACE_FLAGS	0
255#define NO_QUEUE_ID		((VSTRING *) 0)
256#define LENGTH	78
257#define INDENT	4
258
259    notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
260				    var_error_rcpt,
261				    INT_FILT_MASK_NOTIFY,
262				    NULL_TRACE_FLAGS, NO_QUEUE_ID);
263    if (notice == 0) {
264	msg_warn("postmaster notify: %m");
265	return;
266    }
267    post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
268		      mail_addr_mail_daemon());
269    post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
270    post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
271		      var_mail_name, state->namaddr);
272    post_mail_fputs(notice, "");
273    post_mail_fputs(notice, "Transcript of session follows.");
274    post_mail_fputs(notice, "");
275    argv_terminate(state->history);
276    for (cpp = state->history->argv; *cpp; cpp++)
277	line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
278		  (char *) notice);
279    post_mail_fputs(notice, "");
280    if (state->reason)
281	post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
282    post_mail_fputs(notice, "");
283    post_mail_fprintf(notice, "For other details, see the local mail logfile");
284    (void) post_mail_fclose(notice);
285}
286