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 */
119 static 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
smtpd_chat_pre_jail_init(void)126 void 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
smtpd_chat_reset(SMTPD_STATE * state)144 void 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
smtp_chat_append(SMTPD_STATE * state,char * direction,const char * text)154 static 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
smtpd_chat_query_limit(SMTPD_STATE * state,int limit)171 int 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
smtpd_chat_reply(SMTPD_STATE * state,const char * format,...)194 void 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
vsmtpd_chat_reply(SMTPD_STATE * state,const char * format,va_list ap)205 void 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
print_line(const char * str,int len,int indent,void * context)286 static 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
smtpd_chat_notify(SMTPD_STATE * state)295 void 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