Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / smtp / smtp_chat.c
blob4710cc509c548033b30a2fe6f4b414e6850efc85
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* smtp_chat 3
6 /* SUMMARY
7 /* SMTP client request/response support
8 /* SYNOPSIS
9 /* #include "smtp.h"
11 /* typedef struct {
12 /* .in +4
13 /* int code; /* SMTP code, not sanitized */
14 /* char *dsn; /* enhanced status, sanitized */
15 /* char *str; /* unmodified SMTP reply */
16 /* VSTRING *dsn_buf;
17 /* VSTRING *str_buf;
18 /* .in -4
19 /* } SMTP_RESP;
21 /* void smtp_chat_cmd(session, format, ...)
22 /* SMTP_SESSION *session;
23 /* char *format;
25 /* SMTP_RESP *smtp_chat_resp(session)
26 /* SMTP_SESSION *session;
28 /* void smtp_chat_notify(session)
29 /* SMTP_SESSION *session;
31 /* void smtp_chat_init(session)
32 /* SMTP_SESSION *session;
34 /* void smtp_chat_reset(session)
35 /* SMTP_SESSION *session;
36 /* DESCRIPTION
37 /* This module implements SMTP client support for request/reply
38 /* conversations, and maintains a limited SMTP transaction log.
40 /* smtp_chat_cmd() formats a command and sends it to an SMTP server.
41 /* Optionally, the command is logged.
43 /* smtp_chat_resp() reads one SMTP server response. It extracts
44 /* the SMTP reply code and enhanced status code from the text,
45 /* and concatenates multi-line responses to one string, using
46 /* a newline as separator. Optionally, the server response
47 /* is logged.
48 /* .IP \(bu
49 /* Postfix never sanitizes the extracted SMTP reply code except
50 /* to ensure that it is a three-digit code. A malformed reply
51 /* results in a null extracted SMTP reply code value.
52 /* .IP \(bu
53 /* Postfix always sanitizes the extracted enhanced status code.
54 /* When the server's SMTP status code is 2xx, 4xx or 5xx,
55 /* Postfix requires that the first digit of the server's
56 /* enhanced status code matches the first digit of the server's
57 /* SMTP status code. In case of a mis-match, or when the
58 /* server specified no status code, the extracted enhanced
59 /* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With
60 /* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted
61 /* enhanced status code is set to a default value of 5.5.0
62 /* (protocol error) for reasons outlined under the next bullet.
63 /* .IP \(bu
64 /* Since the SMTP reply code may violate the protocol even
65 /* when it is correctly formatted, Postfix uses the sanitized
66 /* extracted enhanced status code to decide whether an error
67 /* condition is permanent or transient. This means that the
68 /* caller may have to update the enhanced status code when it
69 /* discovers that a server reply violates the SMTP protocol,
70 /* even though it was correctly formatted. This happens when
71 /* the client and server get out of step due to a broken proxy
72 /* agent.
73 /* .PP
74 /* smtp_chat_notify() sends a copy of the SMTP transaction log
75 /* to the postmaster for review. The postmaster notice is sent only
76 /* when delivery is possible immediately. It is an error to call
77 /* smtp_chat_notify() when no SMTP transaction log exists.
79 /* smtp_chat_init() initializes the per-session transaction log.
80 /* This must be done at the beginning of a new SMTP session.
82 /* smtp_chat_reset() resets the transaction log. This is
83 /* typically done at the beginning or end of an SMTP session,
84 /* or within a session to discard non-error information.
85 /* DIAGNOSTICS
86 /* Fatal errors: memory allocation problem, server response exceeds
87 /* configurable limit.
88 /* All other exceptions are handled by long jumps (see smtp_stream(3)).
89 /* SEE ALSO
90 /* smtp_stream(3) SMTP session I/O support
91 /* msg(3) generic logging interface
92 /* LICENSE
93 /* .ad
94 /* .fi
95 /* The Secure Mailer license must be distributed with this software.
96 /* AUTHOR(S)
97 /* Wietse Venema
98 /* IBM T.J. Watson Research
99 /* P.O. Box 704
100 /* Yorktown Heights, NY 10598, USA
101 /*--*/
103 /* System library. */
105 #include <sys_defs.h>
106 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
107 #include <stdarg.h>
108 #include <ctype.h>
109 #include <stdlib.h>
110 #include <setjmp.h>
111 #include <string.h>
113 /* Utility library. */
115 #include <msg.h>
116 #include <vstring.h>
117 #include <vstream.h>
118 #include <argv.h>
119 #include <stringops.h>
120 #include <line_wrap.h>
121 #include <mymalloc.h>
123 /* Global library. */
125 #include <recipient_list.h>
126 #include <deliver_request.h>
127 #include <smtp_stream.h>
128 #include <mail_params.h>
129 #include <mail_addr.h>
130 #include <post_mail.h>
131 #include <mail_error.h>
132 #include <dsn_util.h>
134 /* Application-specific. */
136 #include "smtp.h"
138 /* smtp_chat_init - initialize SMTP transaction log */
140 void smtp_chat_init(SMTP_SESSION *session)
142 session->history = 0;
145 /* smtp_chat_reset - reset SMTP transaction log */
147 void smtp_chat_reset(SMTP_SESSION *session)
149 if (session->history) {
150 argv_free(session->history);
151 session->history = 0;
155 /* smtp_chat_append - append record to SMTP transaction log */
157 static void smtp_chat_append(SMTP_SESSION *session, char *direction, char *data)
159 char *line;
161 if (session->history == 0)
162 session->history = argv_alloc(10);
163 line = concatenate(direction, data, (char *) 0);
164 argv_add(session->history, line, (char *) 0);
165 myfree(line);
168 /* smtp_chat_cmd - send an SMTP command */
170 void smtp_chat_cmd(SMTP_SESSION *session, char *fmt,...)
172 va_list ap;
175 * Format the command, and update the transaction log.
177 va_start(ap, fmt);
178 vstring_vsprintf(session->buffer, fmt, ap);
179 va_end(ap);
180 smtp_chat_append(session, "Out: ", STR(session->buffer));
183 * Optionally log the command first, so we can see in the log what the
184 * program is trying to do.
186 if (msg_verbose)
187 msg_info("> %s: %s", session->namaddrport, STR(session->buffer));
190 * Send the command to the SMTP server.
192 smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream);
195 * Force flushing of output does not belong here. It is done in the
196 * smtp_loop() main protocol loop when reading the server response, and
197 * in smtp_helo() when reading the EHLO response after sending the EHLO
198 * command.
200 * If we do forced flush here, then we must longjmp() on error, and a
201 * matching "prepare for disaster" error handler must be set up before
202 * every smtp_chat_cmd() call.
204 #if 0
207 * Flush unsent data to avoid timeouts after slow DNS lookups.
209 if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
210 vstream_fflush(session->stream);
213 * Abort immediately if the connection is broken.
215 if (vstream_ftimeout(session->stream))
216 vstream_longjmp(session->stream, SMTP_ERR_TIME);
217 if (vstream_ferror(session->stream))
218 vstream_longjmp(session->stream, SMTP_ERR_EOF);
219 #endif
222 /* smtp_chat_resp - read and process SMTP server response */
224 SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
226 static SMTP_RESP rdata;
227 char *cp;
228 int last_char;
229 int three_digs = 0;
230 size_t len;
233 * Initialize the response data buffer.
235 if (rdata.str_buf == 0) {
236 rdata.dsn_buf = vstring_alloc(10);
237 rdata.str_buf = vstring_alloc(100);
241 * Censor out non-printable characters in server responses. Concatenate
242 * multi-line server responses. Separate the status code from the text.
243 * Leave further parsing up to the application.
245 VSTRING_RESET(rdata.str_buf);
246 for (;;) {
247 last_char = smtp_get(session->buffer, session->stream, var_line_limit);
248 printable(STR(session->buffer), '?');
249 if (last_char != '\n')
250 msg_warn("%s: response longer than %d: %.30s...",
251 session->namaddrport, var_line_limit, STR(session->buffer));
252 if (msg_verbose)
253 msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));
256 * Defend against a denial of service attack by limiting the amount
257 * of multi-line text that we are willing to store.
259 if (LEN(rdata.str_buf) < var_line_limit) {
260 if (LEN(rdata.str_buf))
261 VSTRING_ADDCH(rdata.str_buf, '\n');
262 vstring_strcat(rdata.str_buf, STR(session->buffer));
263 smtp_chat_append(session, "In: ", STR(session->buffer));
267 * Parse into code and text. Ignore unrecognized garbage. This means
268 * that any character except space (or end of line) will have the
269 * same effect as the '-' line continuation character.
271 for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
272 /* void */ ;
273 if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
274 if (*cp == '-')
275 continue;
276 if (*cp == ' ' || *cp == 0)
277 break;
281 * XXX Do not simply ignore garbage in the server reply when ESMTP
282 * command pipelining is turned on. For example, after sending
283 * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
284 * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
285 * as the END-OF-DATA reply after garbage, causing mail to be lost.
287 * Without the ability to store per-domain status information in queue
288 * files, automatic workarounds are problematic:
290 * - Automatically deferring delivery creates a "repeated delivery"
291 * problem when garbage arrives after the DATA stage. Without the
292 * workaround, Postfix delivers only once.
294 * - Automatically deferring delivery creates a "no delivery" problem
295 * when the garbage arrives before the DATA stage. Without the
296 * workaround, mail might still get through.
298 * - Automatically turning off pipelining for delayed mail affects
299 * deliveries to correctly implemented servers, and may also affect
300 * delivery of large mailing lists.
302 * So we leave the decision with the administrator, but we don't force
303 * them to take action, like we would with automatic deferral. If
304 * loss of mail is not acceptable then they can turn off pipelining
305 * for specific sites, or they can turn off pipelining globally when
306 * they find that there are just too many broken sites.
308 session->error_mask |= MAIL_ERROR_PROTOCOL;
309 if (session->features & SMTP_FEATURE_PIPELINING) {
310 msg_warn("%s: non-%s response from %s: %.100s",
311 session->state->request->queue_id,
312 (session->state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) ?
313 "LMTP" : "ESMTP", session->namaddrport,
314 STR(session->buffer));
315 if (var_helpful_warnings)
316 msg_warn("to prevent loss of mail, turn off command pipelining "
317 "for %s with the %s parameter", session->addr,
318 (session->state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) ?
319 VAR_LMTP_EHLO_DIS_MAPS : VAR_SMTP_EHLO_DIS_MAPS);
324 * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
325 * code if none was given.
327 * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
328 * replies, or codes whose initial digit is out of sync with the reply
329 * code.
331 * XXX Potential stability problem. In order to save memory, the queue
332 * manager stores DSNs in a compact manner:
334 * - empty strings are represented by null pointers,
336 * - the status and reason are required to be non-empty.
338 * Other Postfix daemons inherit this behavior, because they use the same
339 * DSN support code. This means that everything that receives DSNs must
340 * cope with null pointers for the optional DSN attributes, and that
341 * everything that provides DSN information must provide a non-empty
342 * status and reason, otherwise the DSN support code wil panic().
344 * Thus, when the remote server sends a malformed reply (or 3XX out of
345 * context) we should not panic() in DSN_COPY() just because we don't
346 * have a status. Robustness suggests that we supply a status here, and
347 * that we leave it up to the down-stream code to override the
348 * server-supplied status in case of an error we can't detect here, such
349 * as an out-of-order server reply.
351 VSTRING_TERMINATE(rdata.str_buf);
352 vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */
353 if (three_digs != 0) {
354 rdata.code = atoi(STR(session->buffer));
355 if (strchr("245", STR(session->buffer)[0]) != 0) {
356 for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
357 /* void */ ;
358 if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
359 vstring_strncpy(rdata.dsn_buf, cp, len);
360 } else {
361 vstring_strcpy(rdata.dsn_buf, "0.0.0");
362 STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
365 } else {
366 rdata.code = 0;
368 rdata.dsn = STR(rdata.dsn_buf);
369 rdata.str = STR(rdata.str_buf);
370 return (&rdata);
373 /* print_line - line_wrap callback */
375 static void print_line(const char *str, int len, int indent, char *context)
377 VSTREAM *notice = (VSTREAM *) context;
379 post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
382 /* smtp_chat_notify - notify postmaster */
384 void smtp_chat_notify(SMTP_SESSION *session)
386 const char *myname = "smtp_chat_notify";
387 VSTREAM *notice;
388 char **cpp;
391 * Sanity checks.
393 if (session->history == 0)
394 msg_panic("%s: no conversation history", myname);
395 if (msg_verbose)
396 msg_info("%s: notify postmaster", myname);
399 * Construct a message for the postmaster, explaining what this is all
400 * about. This is junk mail: don't send it when the mail posting service
401 * is unavailable, and use the double bounce sender address, to prevent
402 * mail bounce wars. Always prepend one space to message content that we
403 * generate from untrusted data.
405 #define NULL_TRACE_FLAGS 0
406 #define NO_QUEUE_ID ((VSTRING *) 0)
407 #define LENGTH 78
408 #define INDENT 4
410 notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
411 var_error_rcpt,
412 INT_FILT_MASK_NOTIFY,
413 NULL_TRACE_FLAGS, NO_QUEUE_ID);
414 if (notice == 0) {
415 msg_warn("postmaster notify: %m");
416 return;
418 post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
419 mail_addr_mail_daemon());
420 post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
421 post_mail_fprintf(notice, "Subject: %s %s client: errors from %s",
422 var_mail_name,
423 (session->state->misc_flags &
424 SMTP_MISC_FLAG_USE_LMTP) ? "LMTP" : "SMTP",
425 session->namaddrport);
426 post_mail_fputs(notice, "");
427 post_mail_fprintf(notice, "Unexpected response from %s.",
428 session->namaddrport);
429 post_mail_fputs(notice, "");
430 post_mail_fputs(notice, "Transcript of session follows.");
431 post_mail_fputs(notice, "");
432 argv_terminate(session->history);
433 for (cpp = session->history->argv; *cpp; cpp++)
434 line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
435 (char *) notice);
436 post_mail_fputs(notice, "");
437 post_mail_fprintf(notice, "For other details, see the local mail logfile");
438 (void) post_mail_fclose(notice);