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