bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch/* From RFC 5321:
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-line = *( Reply-code "-" [ textstring ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code [ SP textstring ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code = %x32-35 %x30-35 %x30-39
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Greeting = ( "220 " (Domain / address-literal)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch [ SP textstring ] CRLF ) /
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ( "220-" (Domain / address-literal)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch [ SP textstring ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch *( "220-" [ textstring ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "220" [ SP textstring ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch / ( "250-" Domain [ SP ehlo-greet ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch *( "250-" ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "250" SP ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; string of any characters other than CR or LF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-line = ehlo-keyword *( SP ehlo-param )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; additional syntax of ehlo-params depends on
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; ehlo-keyword
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-param = 1*(%d33-126)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; any CHAR excluding <SP> and all
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; control characters (US-ASCII 0-31 and 127
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch From RFC 2034:
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch status-code ::= class "." subject "." detail
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch class ::= "2" / "4" / "5"
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch subject ::= 1*3digit
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch detail ::= 1*3digit
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschsmtp_reply_parser_error(struct smtp_reply_parser *parser,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch const char *format, ...)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->error = i_strdup_vprintf(format, args);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschsmtp_reply_parser_init(struct istream *input, size_t max_reply_size)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch (max_reply_size > 0 ? max_reply_size : (size_t)-1);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschvoid smtp_reply_parser_deinit(struct smtp_reply_parser **_parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschvoid smtp_reply_parser_set_stream(struct smtp_reply_parser *parser,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschsmtp_reply_parser_restart(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->reply_pool = pool_alloconly_create("smtp_reply", 256);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.reply = p_new(parser->reply_pool, struct smtp_reply, 1);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch p_array_init(&parser->state.reply_lines, parser->reply_pool, 8);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch(struct smtp_reply_parser *parser, unsigned int *code_r)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch const unsigned char *p;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* Reply-code = %x32-35 %x30-35 %x30-39
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while (parser->cur < parser->end && i_isdigit(*parser->cur))
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (str_len(parser->strbuf) + (parser->cur-first) > 3)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch str_append_n(parser->strbuf, first, parser->cur - first);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch *code_r = (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic int smtp_reply_parse_textstring(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while (parser->cur < parser->end && smtp_char_is_textstr(*parser->cur))
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (((parser->cur-first) + parser->state.reply_size +
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch str_len(parser->strbuf)) > parser->max_reply_size) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Reply exceeds size limit");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch str_append_n(parser->strbuf, first, parser->cur - first);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic int smtp_reply_parse_ehlo_domain(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* Domain [ SP ...
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while (parser->cur < parser->end && *parser->cur != ' ' &&
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (((parser->cur-first) + parser->state.reply_size +
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch str_len(parser->strbuf)) > parser->max_reply_size) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Reply exceeds size limit");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch str_append_n(parser->strbuf, first, parser->cur - first);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic int smtp_reply_parse_ehlo_greet(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch * The greet is not supposed to be empty, but we don't really care
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (((parser->cur-first) + parser->state.reply_size +
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Reply exceeds size limit");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* sanitize bad characters */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic inline const char *_chr_sanitize(unsigned char c)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschsmtp_reply_parse_enhanced_code(struct smtp_reply_parser *parser,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch const char **pos)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch const char *p = *pos;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch unsigned int prevx = parser->state.reply->enhanced_code.x,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch return; /* failed on earlier line */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* status-code ::= class "." subject "." detail
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch class ::= "2" / "4" / "5"
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch subject ::= 1*3digit
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch detail ::= 1*3digit
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (p[1] != '.' || (p[0] != '2' && p[0] != '4' && p[0] != '5'))
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch x = p[0] - '0';
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* subject */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (digits == 0 || (*p != ' ' && *p != '\r' && *p != '\n'))
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* code is syntactically valid; strip code from textstring */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* check for match with status */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* ignore code */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* check for code consistency */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* ignore code */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic void smtp_reply_parser_finish_line(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (parser->enhanced_codes && str_len(parser->strbuf) > 5) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch smtp_reply_parse_enhanced_code(parser, &text);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.reply_size += str_len(parser->strbuf);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch array_append(&parser->state.reply_lines, &text, 1);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic int smtp_reply_parse_more(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-line = *( Reply-code "-" [ textstring ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code [ SP textstring ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code = %x32-35 %x30-35 %x30-39
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch / ( "250-" Domain [ SP ehlo-greet ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch *( "250-" ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "250" SP ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* Reply-code */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if ((ret=smtp_reply_parse_code(parser, &status)) <= 0) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Invalid status code in reply");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch } else if (status != parser->state.reply->status) {
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Inconsistent status codes in reply");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_SEP;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* "-" / SP / CRLF */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* "-" [ textstring ] CRLF */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* SP [ textstring ] CRLF ; allow missing text */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Encountered unexpected %s after reply status code",
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (parser->state.state != SMTP_REPLY_PARSE_STATE_TEXT)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* textstring / (Domain [ SP ehlo-greet ]) */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* handle first line of EHLO success response
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch differently because it can contain control
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch characters (WHY??!) */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if ((ret=smtp_reply_parse_ehlo_domain(parser)) <= 0)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if ((ret=smtp_reply_parse_textstring(parser)) <= 0)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* CRLF / LF */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Encountered stray CR in reply text");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Encountered stray %s in reply text",
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* SP ehlo-greet */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_EHLO_GREET;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* fall through */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch /* ehlo-greet */
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if ((ret=smtp_reply_parse_ehlo_greet(parser)) <= 0)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschstatic int smtp_reply_parse(struct smtp_reply_parser *parser)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch while ((ret = i_stream_read_more(parser->input,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if ((ret = smtp_reply_parse_more(parser)) < 0)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch i_stream_skip(parser->input, parser->cur - parser->begin);
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch if (parser->state.state == SMTP_REPLY_PARSE_STATE_INIT)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Premature end of input");
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "Stream error: %s",
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschint smtp_reply_parse_next(struct smtp_reply_parser *parser,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch bool enhanced_codes, struct smtp_reply **reply_r,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch const char **error_r)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch (parser->enhanced_codes == enhanced_codes && !parser->ehlo));
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-line = *( Reply-code "-" [ textstring ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code [ SP textstring ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Reply-code = %x32-35 %x30-35 %x30-39
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch Greeting is not handled specially here.
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Boschint smtp_reply_parse_ehlo(struct smtp_reply_parser *parser,
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch struct smtp_reply **reply_r, const char **error_r)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch / ( "250-" Domain [ SP ehlo-greet ] CRLF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch *( "250-" ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch "250" SP ehlo-line CRLF )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; string of any characters other than CR or LF
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-line = ehlo-keyword *( SP ehlo-param )
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; additional syntax of ehlo-params depends on
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; ehlo-keyword
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ehlo-param = 1*(%d33-126)
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; any CHAR excluding <SP> and all
a8433392e8a5ddfe8a125716f62b2a4dc8f1f01eStephan Bosch ; control characters (US-ASCII 0-31 and 127