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