Current File : //root/postfix-3.2.0/src/global/smtp_reply_footer.c |
/*++
/* 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