smtp_reply_footer.c [plain text]
#include <sys_defs.h>
#include <string.h>
#include <ctype.h>
#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <dsn_util.h>
#include <smtp_reply_footer.h>
#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;
ssize_t dsn_offs = -1;
int crlf_at_end = 0;
ssize_t reply_code_offs = -1;
ssize_t reply_patch_undo_len;
int mac_expand_error = 0;
int line_added;
char *saved_template;
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);
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);
if (next < vstring_end(buffer))
vstring_truncate(buffer, next - STR(buffer));
reply_patch_undo_len = VSTRING_LEN(buffer);
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) {
cp += 2;
} else {
vstring_strcat(buffer, "\r\n");
VSTRING_SPACE(buffer, 3);
vstring_strncat(buffer, STR(buffer) + reply_code_offs, 3);
vstring_strcat(buffer, next != end ? "-" : " ");
if (dsn_len > 0) {
VSTRING_SPACE(buffer, dsn_len);
vstring_strncat(buffer, STR(buffer) + dsn_offs, dsn_len);
vstring_strcat(buffer, " ");
}
line_added = 1;
}
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);
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] = '-';
}
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