Postfix3.3.1
smtp_reply_footer.c
[詳解]
1 /*++
2 /* NAME
3 /* smtp_reply_footer 3
4 /* SUMMARY
5 /* SMTP reply footer text support
6 /* SYNOPSIS
7 /* #include <smtp_reply_footer.h>
8 /*
9 /* int smtp_reply_footer(buffer, start, template, filter,
10 /* lookup, context)
11 /* VSTRING *buffer;
12 /* ssize_t start;
13 /* const char *template;
14 /* const char *filter;
15 /* const char *(*lookup) (const char *name, void *context);
16 /* void *context;
17 /* DESCRIPTION
18 /* smtp_reply_footer() expands a reply template, and appends
19 /* the result to an existing reply text.
20 /*
21 /* Arguments:
22 /* .IP buffer
23 /* Result buffer. This should contain a properly formatted
24 /* one-line or multi-line SMTP reply, with or without the final
25 /* <CR><LF>. The reply code and optional enhanced status code
26 /* will be replicated in the footer text. One space character
27 /* after the SMTP reply code is replaced by '-'. If the existing
28 /* reply ends in <CR><LF>, the result text will also end in
29 /* <CR><LF>.
30 /* .IP start
31 /* The beginning of the SMTP reply that the footer will be
32 /* appended to. This supports applications that buffer up
33 /* multiple responses in one buffer.
34 /* .IP template
35 /* Template text, with optional $name attributes that will be
36 /* expanded. The two-character sequence "\n" is replaced by a
37 /* line break followed by a copy of the original SMTP reply
38 /* code and optional enhanced status code.
39 /* The two-character sequence "\c" at the start of the template
40 /* suppresses the line break between the reply text and the
41 /* template text.
42 /* .IP filter
43 /* The set of characters that are allowed in attribute expansion.
44 /* .IP lookup
45 /* Attribute name/value lookup function. The result value must
46 /* be a null for a name that is not found, otherwise a pointer
47 /* to null-terminated string.
48 /* .IP context
49 /* Call-back context for the lookup function.
50 /* SEE ALSO
51 /* mac_expand(3) macro expansion
52 /* DIAGNOSTICS
53 /* smtp_reply_footer() returns 0 upon success, -1 if the existing
54 /* reply text is malformed, -2 in the case of a template macro
55 /* parsing error (an undefined macro value is not an error).
56 /*
57 /* Fatal errors: memory allocation problem.
58 /* LICENSE
59 /* .ad
60 /* .fi
61 /* The Secure Mailer license must be distributed with this software.
62 /* AUTHOR(S)
63 /* Wietse Venema
64 /* IBM T.J. Watson Research
65 /* P.O. Box 704
66 /* Yorktown Heights, NY 10598, USA
67 /*
68 /* Wietse Venema
69 /* Google, Inc.
70 /* 111 8th Avenue
71 /* New York, NY 10011, USA
72 /*--*/
73 
74 /* System library. */
75 
76 #include <sys_defs.h>
77 #include <string.h>
78 #include <ctype.h>
79 
80 /* Utility library. */
81 
82 #include <msg.h>
83 #include <mymalloc.h>
84 #include <vstring.h>
85 
86 /* Global library. */
87 
88 #include <dsn_util.h>
89 #include <smtp_reply_footer.h>
90 
91 /* SLMs. */
92 
93 #define STR vstring_str
94 
95 int smtp_reply_footer(VSTRING *buffer, ssize_t start,
96  const char *template,
97  const char *filter,
98  MAC_EXP_LOOKUP_FN lookup,
99  void *context)
100 {
101  const char *myname = "smtp_reply_footer";
102  char *cp;
103  char *next;
104  char *end;
105  ssize_t dsn_len; /* last status code length */
106  ssize_t dsn_offs = -1; /* last status code offset */
107  int crlf_at_end = 0;
108  ssize_t reply_code_offs = -1; /* last SMTP reply code offset */
109  ssize_t reply_patch_undo_len; /* length without final CRLF */
110  int mac_expand_error = 0;
111  int line_added;
112  char *saved_template;
113 
114  /*
115  * Sanity check.
116  */
117  if (start < 0 || start > VSTRING_LEN(buffer))
118  msg_panic("%s: bad start: %ld", myname, (long) start);
119  if (*template == 0)
120  msg_panic("%s: empty template", myname);
121 
122  /*
123  * Scan the original response without making changes. If the response is
124  * not what we expect, report an error. Otherwise, remember the offset of
125  * the last SMTP reply code.
126  */
127  for (cp = STR(buffer) + start, end = cp + strlen(cp);;) {
128  if (!ISDIGIT(cp[0]) || !ISDIGIT(cp[1]) || !ISDIGIT(cp[2])
129  || (cp[3] != ' ' && cp[3] != '-'))
130  return (-1);
131  reply_code_offs = cp - STR(buffer);
132  if ((next = strstr(cp, "\r\n")) == 0) {
133  next = end;
134  break;
135  }
136  cp = next + 2;
137  if (cp == end) {
138  crlf_at_end = 1;
139  break;
140  }
141  }
142  if (reply_code_offs < 0)
143  return (-1);
144 
145  /*
146  * Truncate text after the first null, and truncate the trailing CRLF.
147  */
148  if (next < vstring_end(buffer))
149  vstring_truncate(buffer, next - STR(buffer));
150  reply_patch_undo_len = VSTRING_LEN(buffer);
151 
152  /*
153  * Append the footer text one line at a time. Caution: before we append
154  * parts from the buffer to itself, we must extend the buffer first,
155  * otherwise we would have a dangling pointer "read" bug.
156  *
157  * XXX mac_expand() has no template length argument, so we must
158  * null-terminate the template in the middle.
159  */
160  dsn_offs = reply_code_offs + 4;
161  dsn_len = dsn_valid(STR(buffer) + dsn_offs);
162  line_added = 0;
163  saved_template = mystrdup(template);
164  for (cp = saved_template, end = cp + strlen(cp);;) {
165  if ((next = strstr(cp, "\\n")) != 0) {
166  *next = 0;
167  } else {
168  next = end;
169  }
170  if (cp == saved_template && strncmp(cp, "\\c", 2) == 0) {
171  /* Handle \c at start of template. */
172  cp += 2;
173  } else {
174  /* Append a clone of the SMTP reply code. */
175  vstring_strcat(buffer, "\r\n");
176  VSTRING_SPACE(buffer, 3);
177  vstring_strncat(buffer, STR(buffer) + reply_code_offs, 3);
178  vstring_strcat(buffer, next != end ? "-" : " ");
179  /* Append a clone of the optional enhanced status code. */
180  if (dsn_len > 0) {
181  VSTRING_SPACE(buffer, dsn_len);
182  vstring_strncat(buffer, STR(buffer) + dsn_offs, dsn_len);
183  vstring_strcat(buffer, " ");
184  }
185  line_added = 1;
186  }
187  /* Append one line of footer text. */
188  mac_expand_error = (mac_expand(buffer, cp, MAC_EXP_FLAG_APPEND, filter,
189  lookup, context) & MAC_PARSE_ERROR);
190  if (mac_expand_error)
191  break;
192  if (next < end) {
193  cp = next + 2;
194  } else
195  break;
196  }
197  myfree(saved_template);
198  /* Discard appended text after error, or finalize the result. */
199  if (mac_expand_error) {
200  vstring_truncate(buffer, reply_patch_undo_len);
201  VSTRING_TERMINATE(buffer);
202  } else if (line_added > 0) {
203  STR(buffer)[reply_code_offs + 3] = '-';
204  }
205  /* Restore CRLF at end. */
206  if (crlf_at_end)
207  vstring_strcat(buffer, "\r\n");
208  return (mac_expand_error ? -2 : 0);
209 }
210 
211 #ifdef TEST
212 
213 #include <stdlib.h>
214 #include <unistd.h>
215 #include <string.h>
216 #include <msg.h>
217 #include <vstream.h>
218 #include <vstring_vstream.h>
219 #include <msg_vstream.h>
220 
221 struct test_case {
222  const char *title;
223  const char *orig_reply;
224  const char *template;
225  const char *filter;
226  int expected_status;
227  const char *expected_reply;
228 };
229 
230 #define NO_FILTER ((char *) 0)
231 #define NO_TEMPLATE "NO_TEMPLATE"
232 #define NO_ERROR (0)
233 #define BAD_SMTP (-1)
234 #define BAD_MACRO (-2)
235 
236 static const struct test_case test_cases[] = {
237  {"missing reply", "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
238  {"long smtp_code", "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
239  {"short smtp_code", "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
240  {"good+bad smtp_code", "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
241  {"1-line no dsn", "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"},
242  {"1-line no dsn", "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"},
243  {"2-line no dsn", "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"},
244  {"1-line with dsn", "550 5.1.1 Foo", "Bar", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n550 5.1.1 Bar"},
245  {"2-line with dsn", "550-5.1.1 Foo\r\n450 4.1.1 Bar", "Baz", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n450-4.1.1 Bar\r\n450 4.1.1 Baz"},
246  {"bad macro", "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
247  {"bad macroCRLF", "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
248  {"good macro", "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"},
249  {"good macroCRLF", "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"},
250  0,
251 };
252 
253 static const char *lookup(const char *name, int unused_mode, void *context)
254 {
255  return "DUMMY";
256 }
257 
258 int main(int argc, char **argv)
259 {
260  const struct test_case *tp;
261  int status;
262  VSTRING *buf = vstring_alloc(10);
263  void *context = 0;
264 
265  msg_vstream_init(argv[0], VSTREAM_ERR);
266 
267  for (tp = test_cases; tp->title != 0; tp++) {
268  vstring_strcpy(buf, tp->orig_reply);
269  status = smtp_reply_footer(buf, 0, tp->template, tp->filter,
270  lookup, context);
271  if (status != tp->expected_status) {
272  msg_warn("test \"%s\": status %d, expected %d",
273  tp->title, status, tp->expected_status);
274  } else if (status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) {
275  msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
276  tp->title, STR(buf), tp->orig_reply);
277  } else if (status == 0 && strcmp(STR(buf), tp->expected_reply) != 0) {
278  msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
279  tp->title, STR(buf), tp->expected_reply);
280  } else {
281  msg_info("test \"%s\": pass", tp->title);
282  }
283  }
284  vstring_free(buf);
285  exit(0);
286 }
287 
288 #endif
void myfree(void *ptr)
Definition: mymalloc.c:207
size_t dsn_valid(const char *text)
Definition: dsn_util.c:112
char * mystrdup(const char *str)
Definition: mymalloc.c:225
int mac_expand(VSTRING *result, const char *pattern, int flags, const char *filter, MAC_EXP_LOOKUP_FN lookup, void *context)
Definition: mac_expand.c:593
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
int main(int argc, char **argv)
Definition: anvil.c:1010
VSTRING * vstring_strncat(VSTRING *vp, const char *src, ssize_t len)
Definition: vstring.c:471
VSTRING * vstring_truncate(VSTRING *vp, ssize_t len)
Definition: vstring.c:415
#define VSTRING_LEN(vp)
Definition: vstring.h:72
VSTRING * vstring_strcpy(VSTRING *vp, const char *src)
Definition: vstring.c:431
#define VSTRING_TERMINATE(vp)
Definition: vstring.h:74
#define vstring_end(vp)
Definition: vstring.h:73
const char *(* MAC_EXP_LOOKUP_FN)(const char *, int, void *)
Definition: mac_expand.h:35
#define ISDIGIT(c)
Definition: sys_defs.h:1748
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
#define VSTRING_SPACE(vp, len)
Definition: vstring.h:70
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
void msg_vstream_init(const char *name, VSTREAM *vp)
Definition: msg_vstream.c:77
#define MAC_EXP_FLAG_APPEND
Definition: mac_expand.h:25
VSTRING * vstring_strcat(VSTRING *vp, const char *src)
Definition: vstring.c:459
#define VSTREAM_ERR
Definition: vstream.h:68
#define MAC_PARSE_ERROR
Definition: mac_parse.h:27
void msg_info(const char *fmt,...)
Definition: msg.c:199