Postfix3.3.1
smtp_chat.c
[詳解]
1 /*++
2 /* NAME
3 /* smtp_chat 3
4 /* SUMMARY
5 /* SMTP client request/response support
6 /* SYNOPSIS
7 /* #include "smtp.h"
8 /*
9 /* typedef struct {
10 /* .in +4
11 /* int code; /* SMTP code, not sanitized */
12 /* char *dsn; /* enhanced status, sanitized */
13 /* char *str; /* unmodified SMTP reply */
14 /* VSTRING *dsn_buf;
15 /* VSTRING *str_buf;
16 /* .in -4
17 /* } SMTP_RESP;
18 /*
19 /* void smtp_chat_cmd(session, format, ...)
20 /* SMTP_SESSION *session;
21 /* const char *format;
22 /*
23 /* DICT *smtp_chat_resp_filter;
24 /*
25 /* SMTP_RESP *smtp_chat_resp(session)
26 /* SMTP_SESSION *session;
27 /*
28 /* void smtp_chat_notify(session)
29 /* SMTP_SESSION *session;
30 /*
31 /* void smtp_chat_init(session)
32 /* SMTP_SESSION *session;
33 /*
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.
39 /*
40 /* smtp_chat_cmd() formats a command and sends it to an SMTP server.
41 /* Optionally, the command is logged.
42 /*
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_resp_filter specifies an optional filter to
75 /* transform one server reply line before it is parsed. The
76 /* filter is invoked once for each line of a multi-line reply.
77 /*
78 /* smtp_chat_notify() sends a copy of the SMTP transaction log
79 /* to the postmaster for review. The postmaster notice is sent only
80 /* when delivery is possible immediately. It is an error to call
81 /* smtp_chat_notify() when no SMTP transaction log exists.
82 /*
83 /* smtp_chat_init() initializes the per-session transaction log.
84 /* This must be done at the beginning of a new SMTP session.
85 /*
86 /* smtp_chat_reset() resets the transaction log. This is
87 /* typically done at the beginning or end of an SMTP session,
88 /* or within a session to discard non-error information.
89 /* DIAGNOSTICS
90 /* Fatal errors: memory allocation problem, server response exceeds
91 /* configurable limit.
92 /* All other exceptions are handled by long jumps (see smtp_stream(3)).
93 /* SEE ALSO
94 /* smtp_stream(3) SMTP session I/O support
95 /* msg(3) generic logging interface
96 /* LICENSE
97 /* .ad
98 /* .fi
99 /* The Secure Mailer license must be distributed with this software.
100 /* AUTHOR(S)
101 /* Wietse Venema
102 /* IBM T.J. Watson Research
103 /* P.O. Box 704
104 /* Yorktown Heights, NY 10598, USA
105 /*--*/
106 
107 /* System library. */
108 
109 #include <sys_defs.h>
110 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
111 #include <stdarg.h>
112 #include <ctype.h>
113 #include <stdlib.h>
114 #include <setjmp.h>
115 #include <string.h>
116 #include <limits.h>
117 
118 /* Utility library. */
119 
120 #include <msg.h>
121 #include <vstring.h>
122 #include <vstream.h>
123 #include <argv.h>
124 #include <stringops.h>
125 #include <line_wrap.h>
126 #include <mymalloc.h>
127 
128 /* Global library. */
129 
130 #include <recipient_list.h>
131 #include <deliver_request.h>
132 #include <smtp_stream.h>
133 #include <mail_params.h>
134 #include <mail_addr.h>
135 #include <post_mail.h>
136 #include <mail_error.h>
137 #include <dsn_util.h>
138 
139 /* Application-specific. */
140 
141 #include "smtp.h"
142 
143  /*
144  * Server reply transformations.
145  */
147 
148 /* smtp_chat_init - initialize SMTP transaction log */
149 
151 {
152  session->history = 0;
153 }
154 
155 /* smtp_chat_reset - reset SMTP transaction log */
156 
158 {
159  if (session->history) {
160  argv_free(session->history);
161  session->history = 0;
162  }
163 }
164 
165 /* smtp_chat_append - append record to SMTP transaction log */
166 
167 static void smtp_chat_append(SMTP_SESSION *session, const char *direction,
168  const char *data)
169 {
170  char *line;
171 
172  if (session->history == 0)
173  session->history = argv_alloc(10);
174  line = concatenate(direction, data, (char *) 0);
175  argv_add(session->history, line, (char *) 0);
176  myfree(line);
177 }
178 
179 /* smtp_chat_cmd - send an SMTP command */
180 
181 void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...)
182 {
183  va_list ap;
184 
185  /*
186  * Format the command, and update the transaction log.
187  */
188  va_start(ap, fmt);
189  vstring_vsprintf(session->buffer, fmt, ap);
190  va_end(ap);
191  smtp_chat_append(session, "Out: ", STR(session->buffer));
192 
193  /*
194  * Optionally log the command first, so we can see in the log what the
195  * program is trying to do.
196  */
197  if (msg_verbose)
198  msg_info("> %s: %s", session->namaddrport, STR(session->buffer));
199 
200  /*
201  * Send the command to the SMTP server.
202  */
203  smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream);
204 
205  /*
206  * Force flushing of output does not belong here. It is done in the
207  * smtp_loop() main protocol loop when reading the server response, and
208  * in smtp_helo() when reading the EHLO response after sending the EHLO
209  * command.
210  *
211  * If we do forced flush here, then we must longjmp() on error, and a
212  * matching "prepare for disaster" error handler must be set up before
213  * every smtp_chat_cmd() call.
214  */
215 #if 0
216 
217  /*
218  * Flush unsent data to avoid timeouts after slow DNS lookups.
219  */
220  if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
221  vstream_fflush(session->stream);
222 
223  /*
224  * Abort immediately if the connection is broken.
225  */
226  if (vstream_ftimeout(session->stream))
228  if (vstream_ferror(session->stream))
230 #endif
231 }
232 
233 /* smtp_chat_resp - read and process SMTP server response */
234 
236 {
237  static SMTP_RESP rdata;
238  char *cp;
239  int last_char;
240  int three_digs = 0;
241  size_t len;
242  const char *new_reply;
243  int chat_append_flag;
244  int chat_append_skipped = 0;
245 
246  /*
247  * Initialize the response data buffer.
248  */
249  if (rdata.str_buf == 0) {
250  rdata.dsn_buf = vstring_alloc(10);
251  rdata.str_buf = vstring_alloc(100);
252  }
253 
254  /*
255  * Censor out non-printable characters in server responses. Concatenate
256  * multi-line server responses. Separate the status code from the text.
257  * Leave further parsing up to the application.
258  *
259  * We can't parse or store input that exceeds var_line_limit, so we just
260  * skip over it to simplify the remainder of the code below.
261  */
262  VSTRING_RESET(rdata.str_buf);
263  for (;;) {
264  last_char = smtp_get(session->buffer, session->stream, var_line_limit,
266  /* XXX Update the per-line time limit. */
267  printable(STR(session->buffer), '?');
268  if (last_char != '\n')
269  msg_warn("%s: response longer than %d: %.30s...",
270  session->namaddrport, var_line_limit, STR(session->buffer));
271  if (msg_verbose)
272  msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));
273 
274  /*
275  * Defend against a denial of service attack by limiting the amount
276  * of multi-line text that we are willing to store.
277  */
278  chat_append_flag = (LEN(rdata.str_buf) < var_line_limit);
279  if (chat_append_flag)
280  smtp_chat_append(session, "In: ", STR(session->buffer));
281  else {
282  if (chat_append_skipped == 0)
283  msg_warn("%s: multi-line response longer than %d %.30s...",
284  session->namaddrport, var_line_limit, STR(rdata.str_buf));
285  if (chat_append_skipped < INT_MAX)
286  chat_append_skipped++;
287  }
288 
289  /*
290  * Server reply substitution, for fault-injection testing, or for
291  * working around broken systems. Use with care.
292  */
293  if (smtp_chat_resp_filter != 0) {
294  new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer));
295  if (new_reply != 0) {
296  msg_info("%s: replacing server reply \"%s\" with \"%s\"",
297  session->namaddrport, STR(session->buffer), new_reply);
298  vstring_strcpy(session->buffer, new_reply);
299  if (chat_append_flag) {
300  smtp_chat_append(session, "Replaced-by: ", "");
301  smtp_chat_append(session, " ", new_reply);
302  }
303  } else if (smtp_chat_resp_filter->error != 0) {
304  msg_warn("%s: table %s:%s lookup error for %s",
305  session->state->request->queue_id,
306  smtp_chat_resp_filter->type,
307  smtp_chat_resp_filter->name,
308  printable(STR(session->buffer), '?'));
310  }
311  }
312  if (chat_append_flag) {
313  if (LEN(rdata.str_buf))
314  VSTRING_ADDCH(rdata.str_buf, '\n');
315  vstring_strcat(rdata.str_buf, STR(session->buffer));
316  }
317 
318  /*
319  * Parse into code and text. Do not ignore garbage (see below).
320  */
321  for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
322  /* void */ ;
323  if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
324  if (*cp == '-')
325  continue;
326  if (*cp == ' ' || *cp == 0)
327  break;
328  }
329 
330  /*
331  * XXX Do not simply ignore garbage in the server reply when ESMTP
332  * command pipelining is turned on. For example, after sending
333  * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
334  * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
335  * as the END-OF-DATA reply after garbage, causing mail to be lost.
336  *
337  * Without the ability to store per-domain status information in queue
338  * files, automatic workarounds are problematic:
339  *
340  * - Automatically deferring delivery creates a "repeated delivery"
341  * problem when garbage arrives after the DATA stage. Without the
342  * workaround, Postfix delivers only once.
343  *
344  * - Automatically deferring delivery creates a "no delivery" problem
345  * when the garbage arrives before the DATA stage. Without the
346  * workaround, mail might still get through.
347  *
348  * - Automatically turning off pipelining for delayed mail affects
349  * deliveries to correctly implemented servers, and may also affect
350  * delivery of large mailing lists.
351  *
352  * So we leave the decision with the administrator, but we don't force
353  * them to take action, like we would with automatic deferral. If
354  * loss of mail is not acceptable then they can turn off pipelining
355  * for specific sites, or they can turn off pipelining globally when
356  * they find that there are just too many broken sites.
357  */
358  session->error_mask |= MAIL_ERROR_PROTOCOL;
359  if (session->features & SMTP_FEATURE_PIPELINING) {
360  msg_warn("%s: non-%s response from %s: %.100s",
361  session->state->request->queue_id,
362  smtp_mode ? "ESMTP" : "LMTP",
363  session->namaddrport, STR(session->buffer));
365  msg_warn("to prevent loss of mail, turn off command pipelining "
366  "for %s with the %s parameter",
367  STR(session->iterator->addr),
368  VAR_LMTP_SMTP(EHLO_DIS_MAPS));
369  }
370  }
371 
372  /*
373  * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
374  * code if none was given.
375  *
376  * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
377  * replies, or codes whose initial digit is out of sync with the reply
378  * code.
379  *
380  * XXX Potential stability problem. In order to save memory, the queue
381  * manager stores DSNs in a compact manner:
382  *
383  * - empty strings are represented by null pointers,
384  *
385  * - the status and reason are required to be non-empty.
386  *
387  * Other Postfix daemons inherit this behavior, because they use the same
388  * DSN support code. This means that everything that receives DSNs must
389  * cope with null pointers for the optional DSN attributes, and that
390  * everything that provides DSN information must provide a non-empty
391  * status and reason, otherwise the DSN support code wil panic().
392  *
393  * Thus, when the remote server sends a malformed reply (or 3XX out of
394  * context) we should not panic() in DSN_COPY() just because we don't
395  * have a status. Robustness suggests that we supply a status here, and
396  * that we leave it up to the down-stream code to override the
397  * server-supplied status in case of an error we can't detect here, such
398  * as an out-of-order server reply.
399  */
400  VSTRING_TERMINATE(rdata.str_buf);
401  vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */
402  if (three_digs != 0) {
403  rdata.code = atoi(STR(session->buffer));
404  if (strchr("245", STR(session->buffer)[0]) != 0) {
405  for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
406  /* void */ ;
407  if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
408  vstring_strncpy(rdata.dsn_buf, cp, len);
409  } else {
410  vstring_strcpy(rdata.dsn_buf, "0.0.0");
411  STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
412  }
413  }
414  } else {
415  rdata.code = 0;
416  }
417  rdata.dsn = STR(rdata.dsn_buf);
418  rdata.str = STR(rdata.str_buf);
419  return (&rdata);
420 }
421 
422 /* print_line - line_wrap callback */
423 
424 static void print_line(const char *str, int len, int indent, void *context)
425 {
426  VSTREAM *notice = (VSTREAM *) context;
427 
428  post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
429 }
430 
431 /* smtp_chat_notify - notify postmaster */
432 
434 {
435  const char *myname = "smtp_chat_notify";
436  VSTREAM *notice;
437  char **cpp;
438 
439  /*
440  * Sanity checks.
441  */
442  if (session->history == 0)
443  msg_panic("%s: no conversation history", myname);
444  if (msg_verbose)
445  msg_info("%s: notify postmaster", myname);
446 
447  /*
448  * Construct a message for the postmaster, explaining what this is all
449  * about. This is junk mail: don't send it when the mail posting service
450  * is unavailable, and use the double bounce sender address, to prevent
451  * mail bounce wars. Always prepend one space to message content that we
452  * generate from untrusted data.
453  */
454 #define NULL_TRACE_FLAGS 0
455 #define NO_QUEUE_ID ((VSTRING *) 0)
456 #define LENGTH 78
457 #define INDENT 4
458 
463  if (notice == 0) {
464  msg_warn("postmaster notify: %m");
465  return;
466  }
467  post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
469  post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
470  post_mail_fprintf(notice, "Subject: %s %s client: errors from %s",
471  var_mail_name, smtp_mode ? "SMTP" : "LMTP",
472  session->namaddrport);
473  post_mail_fputs(notice, "");
474  post_mail_fprintf(notice, "Unexpected response from %s.",
475  session->namaddrport);
476  post_mail_fputs(notice, "");
477  post_mail_fputs(notice, "Transcript of session follows.");
478  post_mail_fputs(notice, "");
479  argv_terminate(session->history);
480  for (cpp = session->history->argv; *cpp; cpp++)
481  line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
482  (void *) notice);
483  post_mail_fputs(notice, "");
484  post_mail_fprintf(notice, "For other details, see the local mail logfile");
485  (void) post_mail_fclose(notice);
486 }
int msg_verbose
Definition: msg.c:177
VSTRING * dsn_buf
Definition: smtp.h:501
char * var_mail_name
Definition: mail_params.c:230
void myfree(void *ptr)
Definition: mymalloc.c:207
char * str
Definition: smtp.h:500
int post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
Definition: post_mail.c:418
size_t dsn_valid(const char *text)
Definition: dsn_util.c:112
ARGV * argv_free(ARGV *argvp)
Definition: argv.c:136
#define SMTPUTF8_FLAG_NONE
Definition: smtputf8.h:96
void line_wrap(const char *str, int len, int indent, LINE_WRAP_FN output_fn, void *context)
Definition: line_wrap.c:76
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
const char * dsn
Definition: smtp.h:499
int smtp_mode
Definition: smtp.c:963
char * name
Definition: dict.h:80
#define SMTP_ERR_TIME
Definition: smtp_stream.h:31
DELIVER_REQUEST * request
Definition: smtp.h:146
const char * mail_addr_mail_daemon(void)
Definition: mail_addr.c:88
#define vstream_longjmp(stream, val)
Definition: vstream.h:249
char ** argv
Definition: argv.h:20
#define MAIL_SRC_MASK_NOTIFY
Definition: mail_proto.h:82
void argv_add(ARGV *argvp,...)
Definition: argv.c:197
int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
Definition: smtp_stream.c:305
#define LEN
Definition: cleanup_addr.c:106
#define LENGTH
ARGV * argv_alloc(ssize_t len)
Definition: argv.c:149
#define vstream_ftime(vp)
Definition: vstream.h:127
VSTRING * str_buf
Definition: smtp.h:502
int error_mask
Definition: smtp.h:320
VSTRING * vstring_strcpy(VSTRING *vp, const char *src)
Definition: vstring.c:431
#define VSTRING_TERMINATE(vp)
Definition: vstring.h:74
int const char * fmt
Definition: dict.h:78
char * type
Definition: dict.h:79
ARGV * history
Definition: smtp.h:319
#define VSTRING_ADDCH(vp, ch)
Definition: vstring.h:81
VSTRING * vstring_vsprintf(VSTRING *vp, const char *format, va_list ap)
Definition: vstring.c:614
VSTREAM * post_mail_fopen_nowait(const char *sender, const char *recipient, int source_class, int trace_flags, int utf8_flags, VSTRING *queue_id)
Definition: post_mail.c:293
#define SMTP_ERR_EOF
Definition: smtp_stream.h:30
#define ISDIGIT(c)
Definition: sys_defs.h:1748
#define dict_get(dp, key)
Definition: dict.h:236
#define vstream_ftimeout(vp)
Definition: vstream.h:124
#define VSTRING_RESET(vp)
Definition: vstring.h:77
#define STR(x)
Definition: anvil.c:518
VSTRING * addr
Definition: smtp.h:55
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTREAM * stream
Definition: smtp.h:305
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
void smtp_fputs(const char *cp, ssize_t todo, VSTREAM *stream)
Definition: smtp_stream.c:383
int post_mail_fclose(VSTREAM *cleanup)
Definition: post_mail.c:449
#define VAR_LMTP_SMTP(x)
Definition: smtp.h:671
char * var_error_rcpt
Definition: smtp.c:857
int error
Definition: dict.h:94
#define SMTP_FEATURE_PIPELINING
Definition: smtp.h:206
int var_helpful_warnings
Definition: mail_params.c:231
int var_line_limit
Definition: mail_params.c:263
int vstream_fflush(VSTREAM *stream)
Definition: vstream.c:1257
int post_mail_fputs(VSTREAM *cleanup, const char *str)
Definition: post_mail.c:439
char * concatenate(const char *arg0,...)
Definition: concatenate.c:42
const char * mail_addr_double_bounce(void)
Definition: mail_addr.c:64
SMTP_STATE * state
Definition: smtp.h:346
void smtp_chat_reset(SMTP_SESSION *session)
Definition: smtp_chat.c:157
void smtp_chat_init(SMTP_SESSION *session)
Definition: smtp_chat.c:150
#define MAIL_ERROR_PROTOCOL
Definition: mail_error.h:23
#define SMTP_ERR_DATA
Definition: smtp_stream.h:34
void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...)
Definition: smtp_chat.c:181
int code
Definition: smtp.h:498
void smtp_chat_notify(SMTP_SESSION *session)
Definition: smtp_chat.c:433
#define SMTP_GET_FLAG_SKIP
Definition: smtp_stream.h:51
VSTRING * buffer
Definition: smtp.h:312
char * printable(char *string, int replacement)
Definition: printable.c:49
int features
Definition: smtp.h:316
#define NO_QUEUE_ID
#define vstream_ferror(vp)
Definition: vstream.h:120
VSTRING * vstring_strncpy(VSTRING *vp, const char *src, ssize_t len)
Definition: vstring.c:445
#define INDENT
char * namaddrport
Definition: smtp.h:310
VSTRING * vstring_strcat(VSTRING *vp, const char *src)
Definition: vstring.c:459
#define NULL_TRACE_FLAGS
void argv_terminate(ARGV *argvp)
Definition: argv.c:242
DICT * smtp_chat_resp_filter
Definition: smtp_chat.c:146
void msg_info(const char *fmt,...)
Definition: msg.c:199
SMTP_RESP * smtp_chat_resp(SMTP_SESSION *session)
Definition: smtp_chat.c:235
SMTP_ITERATOR * iterator
Definition: smtp.h:306