--- /dev/null
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "istream.h"
+#include "smtp-parser.h"
+
+#include "smtp-reply-parser.h"
+
+#include <ctype.h>
+
+/* From RFC 5321:
+
+ Reply-line = *( Reply-code "-" [ textstring ] CRLF )
+ Reply-code [ SP textstring ] CRLF
+ Reply-code = %x32-35 %x30-35 %x30-39
+ textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
+
+ Greeting = ( "220 " (Domain / address-literal)
+ [ SP textstring ] CRLF ) /
+ ( "220-" (Domain / address-literal)
+ [ SP textstring ] CRLF
+ *( "220-" [ textstring ] CRLF )
+ "220" [ SP textstring ] CRLF )
+
+ ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
+ / ( "250-" Domain [ SP ehlo-greet ] CRLF
+ *( "250-" ehlo-line CRLF )
+ "250" SP ehlo-line CRLF )
+ ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
+ ; string of any characters other than CR or LF
+ ehlo-line = ehlo-keyword *( SP ehlo-param )
+ ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
+ ; additional syntax of ehlo-params depends on
+ ; ehlo-keyword
+ ehlo-param = 1*(%d33-126)
+ ; any CHAR excluding <SP> and all
+ ; control characters (US-ASCII 0-31 and 127
+ ; inclusive)
+
+ From RFC 2034:
+
+ status-code ::= class "." subject "." detail
+ class ::= "2" / "4" / "5"
+ subject ::= 1*3digit
+ detail ::= 1*3digit
+ */
+
+enum smtp_reply_parser_state {
+ SMTP_REPLY_PARSE_STATE_INIT = 0,
+ SMTP_REPLY_PARSE_STATE_CODE,
+ SMTP_REPLY_PARSE_STATE_SEP,
+ SMTP_REPLY_PARSE_STATE_TEXT,
+ SMTP_REPLY_PARSE_STATE_EHLO_SPACE,
+ SMTP_REPLY_PARSE_STATE_EHLO_GREET,
+ SMTP_REPLY_PARSE_STATE_CR,
+ SMTP_REPLY_PARSE_STATE_CRLF,
+ SMTP_REPLY_PARSE_STATE_LF
+};
+
+struct smtp_reply_parser_state_data {
+ enum smtp_reply_parser_state state;
+ unsigned int line;
+
+ struct smtp_reply *reply;
+ ARRAY_TYPE(const_string) reply_lines;
+ size_t reply_size;
+
+ bool last_line:1;
+};
+
+struct smtp_reply_parser {
+ struct istream *input;
+
+ size_t max_reply_size;
+
+ const unsigned char *begin, *cur, *end;
+
+ string_t *strbuf;
+
+ struct smtp_reply_parser_state_data state;
+ pool_t reply_pool;
+
+ char *error;
+
+ bool enhanced_codes:1;
+ bool ehlo:1;
+};
+
+static inline void ATTR_FORMAT(2, 3)
+smtp_reply_parser_error(struct smtp_reply_parser *parser,
+ const char *format, ...)
+{
+ va_list args;
+
+ i_free(parser->error);
+
+ va_start(args, format);
+ parser->error = i_strdup_vprintf(format, args);
+ va_end(args);
+}
+
+struct smtp_reply_parser *
+smtp_reply_parser_init(struct istream *input, size_t max_reply_size)
+{
+ struct smtp_reply_parser *parser;
+
+ parser = i_new(struct smtp_reply_parser, 1);
+ parser->max_reply_size =
+ (max_reply_size > 0 ? max_reply_size : (size_t)-1);
+ parser->input = input;
+ i_stream_ref(input);
+ parser->strbuf = str_new(default_pool, 128);
+ return parser;
+}
+
+void smtp_reply_parser_deinit(struct smtp_reply_parser **_parser)
+{
+ struct smtp_reply_parser *parser = *_parser;
+
+ *_parser = NULL;
+
+ str_free(&parser->strbuf);
+ pool_unref(&parser->reply_pool);
+ i_stream_unref(&parser->input);
+ i_free(parser->error);
+ i_free(parser);
+}
+
+void smtp_reply_parser_set_stream(struct smtp_reply_parser *parser,
+ struct istream *input)
+{
+ i_stream_unref(&parser->input);
+ if (input != NULL) {
+ parser->input = input;
+ i_stream_ref(parser->input);
+ }
+}
+
+static void
+smtp_reply_parser_restart(struct smtp_reply_parser *parser)
+{
+ str_truncate(parser->strbuf, 0);
+ pool_unref(&parser->reply_pool);
+ i_zero(&parser->state);
+
+ parser->reply_pool = pool_alloconly_create("smtp_reply", 256);
+ parser->state.reply = p_new(parser->reply_pool, struct smtp_reply, 1);
+ p_array_init(&parser->state.reply_lines, parser->reply_pool, 8);
+
+}
+
+static int smtp_reply_parse_code
+(struct smtp_reply_parser *parser, unsigned int *code_r)
+{
+ const unsigned char *first = parser->cur;
+ const unsigned char *p;
+
+ /* Reply-code = %x32-35 %x30-35 %x30-39
+ */
+ while (parser->cur < parser->end && i_isdigit(*parser->cur))
+ parser->cur++;
+
+ if (str_len(parser->strbuf) + (parser->cur-first) > 3)
+ return -1;
+
+ str_append_n(parser->strbuf, first, parser->cur - first);
+ if (parser->cur == parser->end)
+ return 0;
+ if (str_len(parser->strbuf) != 3)
+ return -1;
+ p = str_data(parser->strbuf);
+ if (p[0] < '2' || p[0] > '5' || p[1] > '5')
+ return -1;
+ *code_r = (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
+ str_truncate(parser->strbuf, 0);
+ return 1;
+}
+
+static int smtp_reply_parse_textstring(struct smtp_reply_parser *parser)
+{
+ const unsigned char *first = parser->cur;
+
+ /* textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
+ */
+ while (parser->cur < parser->end && smtp_char_is_textstr(*parser->cur))
+ parser->cur++;
+
+ if (((parser->cur-first) + parser->state.reply_size +
+ str_len(parser->strbuf)) > parser->max_reply_size) {
+ smtp_reply_parser_error(parser,
+ "Reply exceeds size limit");
+ return -1;
+ }
+
+ str_append_n(parser->strbuf, first, parser->cur - first);
+ if (parser->cur == parser->end)
+ return 0;
+ return 1;
+}
+
+static int smtp_reply_parse_ehlo_domain(struct smtp_reply_parser *parser)
+{
+ const unsigned char *first = parser->cur;
+
+ /* Domain [ SP ...
+ */
+ while (parser->cur < parser->end && *parser->cur != ' ' &&
+ smtp_char_is_textstr(*parser->cur))
+ parser->cur++;
+
+ if (((parser->cur-first) + parser->state.reply_size +
+ str_len(parser->strbuf)) > parser->max_reply_size) {
+ smtp_reply_parser_error(parser,
+ "Reply exceeds size limit");
+ return -1;
+ }
+ str_append_n(parser->strbuf, first, parser->cur - first);
+ if (parser->cur == parser->end)
+ return 0;
+ return 1;
+}
+
+static int smtp_reply_parse_ehlo_greet(struct smtp_reply_parser *parser)
+{
+ const unsigned char *first = parser->cur;
+
+ /* ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
+ *
+ * The greet is not supposed to be empty, but we don't really care
+ */
+
+ if (parser->cur == parser->end)
+ return 0;
+ if (smtp_char_is_ehlo_greet(*parser->cur)) {
+ for (;;) {
+ while (parser->cur < parser->end &&
+ smtp_char_is_textstr(*parser->cur))
+ parser->cur++;
+
+ if (((parser->cur-first) + parser->state.reply_size +
+ str_len(parser->strbuf)) >
+ parser->max_reply_size) {
+ smtp_reply_parser_error(parser,
+ "Reply exceeds size limit");
+ return -1;
+ }
+
+ /* sanitize bad characters */
+ str_append_n(parser->strbuf,
+ first, parser->cur - first);
+
+ if (parser->cur == parser->end)
+ return 0;
+ if (!smtp_char_is_ehlo_greet(*parser->cur))
+ break;
+ str_append_c(parser->strbuf, ' ');
+ parser->cur++;
+ first = parser->cur;
+ }
+ }
+ return 1;
+}
+
+static inline const char *_chr_sanitize(unsigned char c)
+{
+ if (c >= 0x20 && c < 0x7F)
+ return t_strdup_printf("'%c'", c);
+ return t_strdup_printf("0x%02x", c);
+}
+
+static void
+smtp_reply_parse_enhanced_code(struct smtp_reply_parser *parser,
+ const char **pos)
+{
+ const char *p = *pos;
+ unsigned int digits, x, y, z;
+ unsigned int prevx = parser->state.reply->enhanced_code.x,
+ prevy = parser->state.reply->enhanced_code.y,
+ prevz = parser->state.reply->enhanced_code.z;
+
+ if (prevx == 9)
+ return; /* failed on earlier line */
+
+ parser->state.reply->enhanced_code.x = 9;
+ parser->state.reply->enhanced_code.y = 0;
+ parser->state.reply->enhanced_code.z = 0;
+
+ /* status-code ::= class "." subject "." detail
+ class ::= "2" / "4" / "5"
+ subject ::= 1*3digit
+ detail ::= 1*3digit
+ */
+
+ /* class */
+ if (p[1] != '.' || (p[0] != '2' && p[0] != '4' && p[0] != '5'))
+ return;
+ x = p[0] - '0';
+ p += 2;
+
+ /* subject */
+ digits = 0;
+ y = 0;
+ while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
+ y = y*10 + (*p - '0');
+ p++;
+ }
+ if (digits == 0 || *p != '.')
+ return;
+ p++;
+
+ /* detail */
+ digits = 0;
+ z = 0;
+ while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
+ z = z*10 + (*p - '0');
+ p++;
+ }
+ if (digits == 0 || (*p != ' ' && *p != '\r' && *p != '\n'))
+ return;
+ p++;
+
+ /* code is syntactically valid; strip code from textstring */
+ *pos = p;
+
+ /* check for match with status */
+ if (x != parser->state.reply->status / 100) {
+ /* ignore code */
+ return;
+ }
+
+ /* check for code consistency */
+ if (parser->state.line > 0 &&
+ (prevx != x || prevy != y || prevz != z)) {
+ /* ignore code */
+ return;
+ }
+
+ parser->state.reply->enhanced_code.x = x;
+ parser->state.reply->enhanced_code.y = y;
+ parser->state.reply->enhanced_code.z = z;
+}
+
+static void smtp_reply_parser_finish_line(struct smtp_reply_parser *parser)
+{
+ const char *text = str_c(parser->strbuf);
+
+ if (parser->enhanced_codes && str_len(parser->strbuf) > 5) {
+ smtp_reply_parse_enhanced_code(parser, &text);
+ }
+
+ parser->state.line++;
+ parser->state.reply_size += str_len(parser->strbuf);
+ text = p_strdup(parser->reply_pool, text);
+ array_append(&parser->state.reply_lines, &text, 1);
+ str_truncate(parser->strbuf, 0);
+}
+
+static int smtp_reply_parse_more(struct smtp_reply_parser *parser)
+{
+ unsigned int status;
+ int ret;
+
+ /*
+ Reply-line = *( Reply-code "-" [ textstring ] CRLF )
+ Reply-code [ SP textstring ] CRLF
+ Reply-code = %x32-35 %x30-35 %x30-39
+
+ ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
+ / ( "250-" Domain [ SP ehlo-greet ] CRLF
+ *( "250-" ehlo-line CRLF )
+ "250" SP ehlo-line CRLF )
+ */
+
+ for (;;) {
+ switch (parser->state.state) {
+ case SMTP_REPLY_PARSE_STATE_INIT:
+ smtp_reply_parser_restart(parser);
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
+ /* fall through */
+ /* Reply-code */
+ case SMTP_REPLY_PARSE_STATE_CODE:
+ if ((ret=smtp_reply_parse_code(parser, &status)) <= 0) {
+ if (ret < 0) {
+ smtp_reply_parser_error(parser,
+ "Invalid status code in reply");
+ }
+ return ret;
+ }
+ if (parser->state.line == 0) {
+ parser->state.reply->status = status;
+ } else if (status != parser->state.reply->status) {
+ smtp_reply_parser_error(parser,
+ "Inconsistent status codes in reply");
+ return -1;
+ }
+ parser->state.state = SMTP_REPLY_PARSE_STATE_SEP;
+ if (parser->cur == parser->end)
+ return 0;
+ /* fall through */
+ /* "-" / SP / CRLF */
+ case SMTP_REPLY_PARSE_STATE_SEP:
+ switch (*parser->cur) {
+ /* "-" [ textstring ] CRLF */
+ case '-':
+ parser->cur++;
+ parser->state.last_line = FALSE;
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_TEXT;
+ break;
+ /* SP [ textstring ] CRLF ; allow missing text */
+ case ' ':
+ parser->cur++;
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_TEXT;
+ parser->state.last_line = TRUE;
+ break;
+ /* CRLF */
+ case '\r':
+ case '\n':
+ parser->state.last_line = TRUE;
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
+ break;
+ default:
+ smtp_reply_parser_error(parser,
+ "Encountered unexpected %s after reply status code",
+ _chr_sanitize(*parser->cur));
+ return -1;
+ }
+ if (parser->state.state != SMTP_REPLY_PARSE_STATE_TEXT)
+ break;
+ /* fall through */
+ /* textstring / (Domain [ SP ehlo-greet ]) */
+ case SMTP_REPLY_PARSE_STATE_TEXT:
+ if (parser->ehlo &&
+ parser->state.reply->status == 250 &&
+ parser->state.line == 0) {
+ /* handle first line of EHLO success response
+ differently because it can contain control
+ characters (WHY??!) */
+ if ((ret=smtp_reply_parse_ehlo_domain(parser)) <= 0)
+ return ret;
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_EHLO_SPACE;
+ if (parser->cur == parser->end)
+ return 0;
+ break;
+ }
+ if ((ret=smtp_reply_parse_textstring(parser)) <= 0)
+ return ret;
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
+ if (parser->cur == parser->end)
+ return 0;
+ /* fall through */
+ /* CR */
+ case SMTP_REPLY_PARSE_STATE_CR:
+ if (*parser->cur == '\r') {
+ parser->cur++;
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_CRLF;
+ } else {
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_LF;
+ }
+ if (parser->cur == parser->end)
+ return 0;
+ /* fall through */
+ /* CRLF / LF */
+ case SMTP_REPLY_PARSE_STATE_CRLF:
+ case SMTP_REPLY_PARSE_STATE_LF:
+ if (*parser->cur != '\n') {
+ if (parser->state.state ==
+ SMTP_REPLY_PARSE_STATE_CRLF) {
+ smtp_reply_parser_error(parser,
+ "Encountered stray CR in reply text");
+ } else {
+ smtp_reply_parser_error(parser,
+ "Encountered stray %s in reply text",
+ _chr_sanitize(*parser->cur));
+ }
+ return -1;
+ }
+ parser->cur++;
+ smtp_reply_parser_finish_line(parser);
+ if (parser->state.last_line) {
+ parser->state.state =
+ SMTP_REPLY_PARSE_STATE_INIT;
+ return 1;
+ }
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
+ break;
+ /* SP ehlo-greet */
+ case SMTP_REPLY_PARSE_STATE_EHLO_SPACE:
+ if (*parser->cur != ' ') {
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
+ break;
+ }
+ parser->cur++;
+ str_append_c(parser->strbuf, ' ');
+ parser->state.state = SMTP_REPLY_PARSE_STATE_EHLO_GREET;
+ if (parser->cur == parser->end)
+ return 0;
+ /* fall through */
+ /* ehlo-greet */
+ case SMTP_REPLY_PARSE_STATE_EHLO_GREET:
+ if ((ret=smtp_reply_parse_ehlo_greet(parser)) <= 0)
+ return ret;
+ parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
+ if (parser->cur == parser->end)
+ return 0;
+ break;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+static int smtp_reply_parse(struct smtp_reply_parser *parser)
+{
+ size_t size;
+ int ret;
+
+ while ((ret = i_stream_read_more(parser->input,
+ &parser->begin, &size)) > 0) {
+ parser->cur = parser->begin;
+ parser->end = parser->cur + size;
+
+ if ((ret = smtp_reply_parse_more(parser)) < 0)
+ return -1;
+
+ i_stream_skip(parser->input, parser->cur - parser->begin);
+ if (ret > 0)
+ return 1;
+ }
+
+ i_assert(ret != -2);
+ if (ret < 0) {
+ i_assert(parser->input->eof);
+ if (parser->input->stream_errno == 0) {
+ if (parser->state.state == SMTP_REPLY_PARSE_STATE_INIT)
+ return 0;
+ smtp_reply_parser_error(parser,
+ "Premature end of input");
+ } else {
+ smtp_reply_parser_error(parser,
+ "Stream error: %s",
+ i_stream_get_error(parser->input));
+ }
+ }
+ return ret;
+}
+
+int smtp_reply_parse_next(struct smtp_reply_parser *parser,
+ bool enhanced_codes, struct smtp_reply **reply_r,
+ const char **error_r)
+{
+ int ret;
+
+ i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
+ (parser->enhanced_codes == enhanced_codes && !parser->ehlo));
+
+ parser->enhanced_codes = enhanced_codes;
+ parser->ehlo = FALSE;
+
+ i_free_and_null(parser->error);
+
+ /*
+ Reply-line = *( Reply-code "-" [ textstring ] CRLF )
+ Reply-code [ SP textstring ] CRLF
+ Reply-code = %x32-35 %x30-35 %x30-39
+ textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
+
+ Greeting is not handled specially here.
+ */
+ if ((ret=smtp_reply_parse(parser)) <= 0) {
+ *error_r = parser->error;
+ return ret;
+ }
+
+ parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
+ parser->state.reply->text_lines =
+ array_idx(&parser->state.reply_lines, 0);
+ *reply_r = parser->state.reply;
+ return 1;
+}
+
+int smtp_reply_parse_ehlo(struct smtp_reply_parser *parser,
+ struct smtp_reply **reply_r, const char **error_r)
+{
+ int ret;
+
+ i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
+ (!parser->enhanced_codes && parser->ehlo));
+
+ parser->enhanced_codes = FALSE;
+ parser->ehlo = TRUE;
+
+ i_free_and_null(parser->error);
+
+ /*
+ ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
+ / ( "250-" Domain [ SP ehlo-greet ] CRLF
+ *( "250-" ehlo-line CRLF )
+ "250" SP ehlo-line CRLF )
+ ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
+ ; string of any characters other than CR or LF
+ ehlo-line = ehlo-keyword *( SP ehlo-param )
+ ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
+ ; additional syntax of ehlo-params depends on
+ ; ehlo-keyword
+ ehlo-param = 1*(%d33-126)
+ ; any CHAR excluding <SP> and all
+ ; control characters (US-ASCII 0-31 and 127
+ ; inclusive)
+ */
+ if ((ret=smtp_reply_parse(parser)) <= 0) {
+ *error_r = parser->error;
+ return ret;
+ }
+
+ parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
+ parser->state.reply->text_lines =
+ array_idx(&parser->state.reply_lines, 0);
+ *reply_r = parser->state.reply;
+ return 1;
+}
--- /dev/null
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "smtp-reply.h"
+
+void smtp_reply_init(struct smtp_reply *reply, unsigned int status,
+ const char *text)
+{
+ const char **text_lines = t_new(const char *, 2);
+
+ text_lines[0] = text;
+ text_lines[1] = NULL;
+
+ i_zero(reply);
+ reply->status = status;
+ reply->text_lines = text_lines;
+}
+
+void smtp_reply_printf(struct smtp_reply *reply, unsigned int status,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ smtp_reply_init(reply, status, t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
+const char *
+smtp_reply_get_enh_code(const struct smtp_reply *reply)
+{
+ if (reply->enhanced_code.x < 2)
+ return NULL;
+ if (reply->enhanced_code.x >= 6)
+ return NULL;
+
+ return t_strdup_printf("%u.%u.%u",
+ reply->enhanced_code.x, reply->enhanced_code.y, reply->enhanced_code.z);
+}
+
+void
+smtp_reply_write(string_t *out, const struct smtp_reply *reply)
+{
+ const char *prefix, *enh_code;
+ const char *const *lines;
+
+ i_assert(reply->status < 560);
+ i_assert(reply->enhanced_code.x < 6);
+
+ prefix = t_strdup_printf("%03u", reply->status);
+ enh_code = smtp_reply_get_enh_code(reply);
+
+ if (reply->text_lines == NULL || *reply->text_lines == NULL) {
+ str_append(out, prefix);
+ if (enh_code != NULL) {
+ str_append_c(out, ' ');
+ str_append(out, enh_code);
+ }
+ str_append(out, " \r\n");
+ return;
+ }
+
+ lines = reply->text_lines;
+ while (*lines != NULL) {
+ str_append(out, prefix);
+ if (*(lines+1) == NULL)
+ str_append_c(out, ' ');
+ else
+ str_append_c(out, '-');
+ if (enh_code != NULL) {
+ str_append(out, enh_code);
+ str_append_c(out, ' ');
+ }
+ str_append(out, *lines);
+ str_append(out, "\r\n");
+ lines++;
+ }
+}
+
+const char *smtp_reply_log(const struct smtp_reply *reply)
+{
+ const char *const *lines;
+ string_t *msg = t_str_new(256);
+
+ if (smtp_reply_is_remote(reply)) {
+ const char *enh_code = smtp_reply_get_enh_code(reply);
+
+ str_printfa(msg, "%03u", reply->status);
+ if (enh_code != NULL) {
+ str_append_c(msg, ' ');
+ str_append(msg, enh_code);
+ }
+ }
+
+ lines = reply->text_lines;
+ while (*lines != NULL) {
+ if (str_len(msg) > 0)
+ str_append_c(msg, ' ');
+ str_append(msg, *lines);
+ lines++;
+ }
+ return str_c(msg);
+}
+
+void smtp_reply_copy(pool_t pool, struct smtp_reply *dst,
+ const struct smtp_reply *src)
+{
+ *dst = *src;
+ dst->text_lines = p_strarray_dup(pool, src->text_lines);
+}
+
+struct smtp_reply *smtp_reply_clone(pool_t pool,
+ const struct smtp_reply *src)
+{
+ struct smtp_reply *dst;
+
+ dst = p_new(pool, struct smtp_reply, 1);
+ smtp_reply_copy(pool, dst, src);
+
+ return dst;
+}
--- /dev/null
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "array.h"
+#include "buffer.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+#include "test-common.h"
+#include "smtp-reply-parser.h"
+
+#include <time.h>
+
+struct smtp_reply_parse_valid_test {
+ const char *reply;
+ unsigned int status;
+ bool ehlo;
+ size_t max_size;
+ struct {
+ unsigned int x, y, z;
+ } enhanced_code;
+ const char *const *text_lines;
+};
+
+/* Valid reply tests */
+
+static const struct smtp_reply_parse_valid_test
+valid_reply_parse_tests[] = {
+ {
+ .reply = "220\r\n",
+ .status = 220,
+ .text_lines = (const char *[]){ "", NULL }
+ },{
+ .reply = "220 \r\n",
+ .status = 220,
+ .text_lines = (const char *[]){ "", NULL }
+ },{
+ .reply = "220 OK\r\n",
+ .status = 220,
+ .text_lines = (const char *[]){ "OK", NULL }
+ },{
+ .reply = "550 Requested action not taken: mailbox unavailable\r\n",
+ .status = 550,
+ .text_lines = (const char *[])
+ { "Requested action not taken: mailbox unavailable", NULL }
+ },{
+ .reply =
+ "250-smtp.example.com Hello client.example.org [10.0.0.1]\r\n"
+ "250-SIZE 52428800\r\n"
+ "250-PIPELINING\r\n"
+ "250-STARTTLS\r\n"
+ "250 HELP\r\n",
+ .ehlo = TRUE,
+ .status = 250,
+ .text_lines = (const char *[]) {
+ "smtp.example.com Hello client.example.org [10.0.0.1]",
+ "SIZE 52428800",
+ "PIPELINING",
+ "STARTTLS",
+ "HELP",
+ NULL
+ }
+ },{
+ .reply =
+ "250-smtp.example.com We got some nice '\x03' and '\x04'\r\n"
+ "250 HELP\r\n",
+ .ehlo = TRUE,
+ .status = 250,
+ .text_lines = (const char *[]) {
+ "smtp.example.com We got some nice ' ' and ' '",
+ "HELP",
+ NULL
+ }
+ },{
+ .reply =
+ "250 smtp.example.com We got some nice '\x08'\r\n",
+ .ehlo = TRUE,
+ .status = 250,
+ .text_lines = (const char *[]) {
+ "smtp.example.com We got some nice ' '",
+ NULL
+ }
+ },{
+ .reply = "250 2.1.0 Originator <frop@example.com> ok\r\n",
+ .status = 250,
+ .enhanced_code = { 2, 1, 0 },
+ .text_lines = (const char *[]){
+ "Originator <frop@example.com> ok", NULL
+ }
+ },{
+ .reply =
+ "551-5.7.1 Forwarding to remote hosts disabled\r\n"
+ "551 5.7.1 Select another host to act as your forwarder\r\n",
+ .status = 551,
+ .enhanced_code = { 5, 7, 1 },
+ .text_lines = (const char *[]) {
+ "Forwarding to remote hosts disabled",
+ "Select another host to act as your forwarder",
+ NULL
+ }
+ }
+};
+
+unsigned int valid_reply_parse_test_count =
+ N_ELEMENTS(valid_reply_parse_tests);
+
+static void test_smtp_reply_parse_valid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < valid_reply_parse_test_count; i++) T_BEGIN {
+ struct istream *input;
+ const struct smtp_reply_parse_valid_test *test;
+ struct smtp_reply_parser *parser;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ test = &valid_reply_parse_tests[i];
+ input = i_stream_create_from_data(test->reply, strlen(test->reply));
+ parser = smtp_reply_parser_init(input, test->max_size);
+ i_stream_unref(&input);
+
+ test_begin(t_strdup_printf("smtp reply valid [%d]", i));
+
+ if (test->ehlo) {
+ while ((ret=smtp_reply_parse_ehlo
+ (parser, &reply, &error)) > 0) {
+ }
+ } else {
+ while ((ret=smtp_reply_parse_next
+ (parser, test->enhanced_code.x > 0, &reply, &error)) > 0) {
+ }
+ }
+
+ test_out_reason("parse success", ret == 0, error);
+
+ if (ret == 0) {
+ /* verify last response only */
+ test_out(t_strdup_printf("reply->status = %d", test->status),
+ reply->status == test->status);
+ if (test->enhanced_code.x > 0) {
+ test_out(t_strdup_printf("reply->enhanced_code = %d.%d.%d",
+ test->enhanced_code.x, test->enhanced_code.y, test->enhanced_code.z),
+ (reply->enhanced_code.x == test->enhanced_code.x &&
+ reply->enhanced_code.y == test->enhanced_code.y &&
+ reply->enhanced_code.z == test->enhanced_code.z));
+ }
+ if (test->text_lines != NULL) {
+ const char *const *line = test->text_lines;
+ const char *const *reply_line = reply->text_lines;
+ unsigned int index = 0;
+
+ while (*line != NULL) {
+ if (*reply_line == NULL) {
+ test_out(
+ t_strdup_printf("reply->text_lines[%d] = NULL", index),
+ FALSE);
+ break;
+ }
+ test_out(t_strdup_printf(
+ "reply->text_lines[%d] = \"%s\"", index, *reply_line),
+ strcmp(*line, *reply_line) == 0);
+ line++;
+ reply_line++;
+ index++;
+ }
+ } else {
+ test_out("reply->text_lines = NULL", reply->text_lines == NULL);
+ }
+ }
+ test_end();
+ smtp_reply_parser_deinit(&parser);
+ } T_END;
+}
+
+struct smtp_reply_parse_invalid_test {
+ const char *reply;
+ bool ehlo;
+ size_t max_size;
+};
+
+static const struct smtp_reply_parse_invalid_test
+ invalid_reply_parse_tests[] = {
+ {
+ .reply = "22X OK\r\n"
+ },{
+ .reply = "220OK\r\n"
+ },{
+ .reply =
+ "200-This is\r\n"
+ "250 inconsistent.\r\n"
+ },{
+ .reply = "400 This \r is wrong\r\n"
+ },{
+ .reply = "500 This is \x03 worse\r\n"
+ },{
+ .reply = "699 Obscure\r\n"
+ },{
+ .reply = "100 Invalid\r\n"
+ },{
+ .reply = "400 Interrupted\r"
+ },{
+ .reply = "251 example.com We got '\x04'\r\n",
+ .ehlo = TRUE
+ },{
+ .reply =
+ "250-example.com Hello\r\n"
+ "250 We got some '\x08' for you\r\n",
+ .ehlo = TRUE
+ },{
+ .reply =
+ "556-This is a very long reply\r\n"
+ "556 that exceeds the very low limit.\r\n",
+ .max_size = 50
+ }
+};
+
+unsigned int invalid_reply_parse_test_count =
+ N_ELEMENTS(invalid_reply_parse_tests);
+
+static void test_smtp_reply_parse_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_reply_parse_test_count; i++) T_BEGIN {
+ const struct smtp_reply_parse_invalid_test *test;
+ struct istream *input;
+ struct smtp_reply_parser *parser;
+ struct smtp_reply *reply;
+ const char *reply_text, *error;
+ int ret;
+
+ test = &invalid_reply_parse_tests[i];
+ reply_text = test->reply;
+ input = i_stream_create_from_data(reply_text, strlen(reply_text));
+ parser = smtp_reply_parser_init(input, test->max_size);
+ i_stream_unref(&input);
+
+ test_begin(t_strdup_printf("smtp reply invalid [%d]", i));
+
+ if (test->ehlo)
+ while ((ret=smtp_reply_parse_ehlo(parser, &reply, &error)) > 0);
+ else
+ while ((ret=smtp_reply_parse_next(parser, FALSE, &reply, &error)) > 0);
+ test_out_reason(t_strdup_printf("parse(\"%s\")",
+ str_sanitize(reply_text, 80)), ret < 0, error);
+ test_end();
+ smtp_reply_parser_deinit(&parser);
+ } T_END;
+}
+
+int main(void)
+{
+ static void (*test_functions[])(void) = {
+ test_smtp_reply_parse_valid,
+ test_smtp_reply_parse_invalid,
+ NULL
+ };
+ return test_run(test_functions);
+}