summaryrefslogtreecommitdiffstats
path: root/src/global/smtp_reply_footer.c
blob: 6e5bb75d96310aa442e771e18cb19d9655d6fb44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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