summaryrefslogtreecommitdiffstats
path: root/src/global/smtp_reply_footer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/smtp_reply_footer.c')
-rw-r--r--src/global/smtp_reply_footer.c288
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