diff options
Diffstat (limited to 'src/global/smtp_reply_footer.c')
-rw-r--r-- | src/global/smtp_reply_footer.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/src/global/smtp_reply_footer.c b/src/global/smtp_reply_footer.c new file mode 100644 index 0000000..6e5bb75 --- /dev/null +++ b/src/global/smtp_reply_footer.c @@ -0,0 +1,288 @@ +/*++ +/* NAME +/* smtp_reply_footer 3 +/* SUMMARY +/* SMTP reply footer text support +/* SYNOPSIS +/* #include <smtp_reply_footer.h> +/* +/* int smtp_reply_footer(buffer, start, template, filter, +/* lookup, context) +/* VSTRING *buffer; +/* ssize_t start; +/* const char *template; +/* const char *filter; +/* const char *(*lookup) (const char *name, void *context); +/* void *context; +/* DESCRIPTION +/* smtp_reply_footer() expands a reply template, and appends +/* the result to an existing reply text. +/* +/* Arguments: +/* .IP buffer +/* Result buffer. This should contain a properly formatted +/* one-line or multi-line SMTP reply, with or without the final +/* <CR><LF>. The reply code and optional enhanced status code +/* will be replicated in the footer text. One space character +/* after the SMTP reply code is replaced by '-'. If the existing +/* reply ends in <CR><LF>, the result text will also end in +/* <CR><LF>. +/* .IP start +/* The beginning of the SMTP reply that the footer will be +/* appended to. This supports applications that buffer up +/* multiple responses in one buffer. +/* .IP template +/* Template text, with optional $name attributes that will be +/* expanded. The two-character sequence "\n" is replaced by a +/* line break followed by a copy of the original SMTP reply +/* code and optional enhanced status code. +/* The two-character sequence "\c" at the start of the template +/* suppresses the line break between the reply text and the +/* template text. +/* .IP filter +/* The set of characters that are allowed in attribute expansion. +/* .IP lookup +/* Attribute name/value lookup function. The result value must +/* be a null for a name that is not found, otherwise a pointer +/* to null-terminated string. +/* .IP context +/* Call-back context for the lookup function. +/* SEE ALSO +/* mac_expand(3) macro expansion +/* DIAGNOSTICS +/* smtp_reply_footer() returns 0 upon success, -1 if the existing +/* reply text is malformed, -2 in the case of a template macro +/* parsing error (an undefined macro value is not an error). +/* +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> + +/* Global library. */ + +#include <dsn_util.h> +#include <smtp_reply_footer.h> + +/* SLMs. */ + +#define STR vstring_str + +int smtp_reply_footer(VSTRING *buffer, ssize_t start, + const char *template, + const char *filter, + MAC_EXP_LOOKUP_FN lookup, + void *context) +{ + const char *myname = "smtp_reply_footer"; + char *cp; + char *next; + char *end; + ssize_t dsn_len; /* last status code length */ + ssize_t dsn_offs = -1; /* last status code offset */ + int crlf_at_end = 0; + ssize_t reply_code_offs = -1; /* last SMTP reply code offset */ + ssize_t reply_patch_undo_len; /* length without final CRLF */ + int mac_expand_error = 0; + int line_added; + char *saved_template; + + /* + * Sanity check. + */ + if (start < 0 || start > VSTRING_LEN(buffer)) + msg_panic("%s: bad start: %ld", myname, (long) start); + if (*template == 0) + msg_panic("%s: empty template", myname); + + /* + * Scan the original response without making changes. If the response is + * not what we expect, report an error. Otherwise, remember the offset of + * the last SMTP reply code. + */ + for (cp = STR(buffer) + start, end = cp + strlen(cp);;) { + if (!ISDIGIT(cp[0]) || !ISDIGIT(cp[1]) || !ISDIGIT(cp[2]) + || (cp[3] != ' ' && cp[3] != '-')) + return (-1); + reply_code_offs = cp - STR(buffer); + if ((next = strstr(cp, "\r\n")) == 0) { + next = end; + break; + } + cp = next + 2; + if (cp == end) { + crlf_at_end = 1; + break; + } + } + if (reply_code_offs < 0) + return (-1); + + /* + * Truncate text after the first null, and truncate the trailing CRLF. + */ + if (next < vstring_end(buffer)) + vstring_truncate(buffer, next - STR(buffer)); + reply_patch_undo_len = VSTRING_LEN(buffer); + + /* + * Append the footer text one line at a time. Caution: before we append + * parts from the buffer to itself, we must extend the buffer first, + * otherwise we would have a dangling pointer "read" bug. + * + * XXX mac_expand() has no template length argument, so we must + * null-terminate the template in the middle. + */ + dsn_offs = reply_code_offs + 4; + dsn_len = dsn_valid(STR(buffer) + dsn_offs); + line_added = 0; + saved_template = mystrdup(template); + for (cp = saved_template, end = cp + strlen(cp);;) { + if ((next = strstr(cp, "\\n")) != 0) { + *next = 0; + } else { + next = end; + } + if (cp == saved_template && strncmp(cp, "\\c", 2) == 0) { + /* Handle \c at start of template. */ + cp += 2; + } else { + /* Append a clone of the SMTP reply code. */ + vstring_strcat(buffer, "\r\n"); + VSTRING_SPACE(buffer, 3); + vstring_strncat(buffer, STR(buffer) + reply_code_offs, 3); + vstring_strcat(buffer, next != end ? "-" : " "); + /* Append a clone of the optional enhanced status code. */ + if (dsn_len > 0) { + VSTRING_SPACE(buffer, dsn_len); + vstring_strncat(buffer, STR(buffer) + dsn_offs, dsn_len); + vstring_strcat(buffer, " "); + } + line_added = 1; + } + /* Append one line of footer text. */ + mac_expand_error = (mac_expand(buffer, cp, MAC_EXP_FLAG_APPEND, filter, + lookup, context) & MAC_PARSE_ERROR); + if (mac_expand_error) + break; + if (next < end) { + cp = next + 2; + } else + break; + } + myfree(saved_template); + /* Discard appended text after error, or finalize the result. */ + if (mac_expand_error) { + vstring_truncate(buffer, reply_patch_undo_len); + VSTRING_TERMINATE(buffer); + } else if (line_added > 0) { + STR(buffer)[reply_code_offs + 3] = '-'; + } + /* Restore CRLF at end. */ + if (crlf_at_end) + vstring_strcat(buffer, "\r\n"); + return (mac_expand_error ? -2 : 0); +} + +#ifdef TEST + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <msg.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <msg_vstream.h> + +struct test_case { + const char *title; + const char *orig_reply; + const char *template; + const char *filter; + int expected_status; + const char *expected_reply; +}; + +#define NO_FILTER ((char *) 0) +#define NO_TEMPLATE "NO_TEMPLATE" +#define NO_ERROR (0) +#define BAD_SMTP (-1) +#define BAD_MACRO (-2) + +static const struct test_case test_cases[] = { + {"missing reply", "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, + {"long smtp_code", "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, + {"short smtp_code", "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, + {"good+bad smtp_code", "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, + {"1-line no dsn", "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"}, + {"1-line no dsn", "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"}, + {"2-line no dsn", "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"}, + {"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"}, + {"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"}, + {"bad macro", "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0}, + {"bad macroCRLF", "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0}, + {"good macro", "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"}, + {"good macroCRLF", "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"}, + 0, +}; + +static const char *lookup(const char *name, int unused_mode, void *context) +{ + return "DUMMY"; +} + +int main(int argc, char **argv) +{ + const struct test_case *tp; + int status; + VSTRING *buf = vstring_alloc(10); + void *context = 0; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + for (tp = test_cases; tp->title != 0; tp++) { + vstring_strcpy(buf, tp->orig_reply); + status = smtp_reply_footer(buf, 0, tp->template, tp->filter, + lookup, context); + if (status != tp->expected_status) { + msg_warn("test \"%s\": status %d, expected %d", + tp->title, status, tp->expected_status); + } else if (status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) { + msg_warn("test \"%s\": result \"%s\", expected \"%s\"", + tp->title, STR(buf), tp->orig_reply); + } else if (status == 0 && strcmp(STR(buf), tp->expected_reply) != 0) { + msg_warn("test \"%s\": result \"%s\", expected \"%s\"", + tp->title, STR(buf), tp->expected_reply); + } else { + msg_info("test \"%s\": pass", tp->title); + } + } + vstring_free(buf); + exit(0); +} + +#endif |