smtp-reply-parser.c revision bcb4e51a409d94ae670de96afb8483a4f7855294
766N/A/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
0N/A
0N/A#include "lib.h"
766N/A#include "array.h"
766N/A#include "str.h"
766N/A#include "strfuncs.h"
766N/A#include "istream.h"
0N/A#include "smtp-parser.h"
180N/A
180N/A#include "smtp-reply-parser.h"
51N/A
732N/A#include <ctype.h>
51N/A
766N/A/* From RFC 5321:
766N/A
180N/A Reply-line = *( Reply-code "-" [ textstring ] CRLF )
0N/A Reply-code [ SP textstring ] CRLF
737N/A Reply-code = %x32-35 %x30-35 %x30-39
737N/A textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
737N/A
737N/A Greeting = ( "220 " (Domain / address-literal)
737N/A [ SP textstring ] CRLF ) /
769N/A ( "220-" (Domain / address-literal)
748N/A [ SP textstring ] CRLF
748N/A *( "220-" [ textstring ] CRLF )
749N/A "220" [ SP textstring ] CRLF )
751N/A
751N/A ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
751N/A / ( "250-" Domain [ SP ehlo-greet ] CRLF
751N/A *( "250-" ehlo-line CRLF )
759N/A "250" SP ehlo-line CRLF )
765N/A ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
767N/A ; string of any characters other than CR or LF
768N/A ehlo-line = ehlo-keyword *( SP ehlo-param )
737N/A ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
737N/A ; additional syntax of ehlo-params depends on
713N/A ; ehlo-keyword
713N/A ehlo-param = 1*(%d33-126)
713N/A ; any CHAR excluding <SP> and all
713N/A ; control characters (US-ASCII 0-31 and 127
728N/A ; inclusive)
729N/A
713N/A From RFC 2034:
715N/A
718N/A status-code ::= class "." subject "." detail
719N/A class ::= "2" / "4" / "5"
722N/A subject ::= 1*3digit
723N/A detail ::= 1*3digit
724N/A */
727N/A
731N/Aenum smtp_reply_parser_state {
713N/A SMTP_REPLY_PARSE_STATE_INIT = 0,
713N/A SMTP_REPLY_PARSE_STATE_CODE,
655N/A SMTP_REPLY_PARSE_STATE_SEP,
655N/A SMTP_REPLY_PARSE_STATE_TEXT,
655N/A SMTP_REPLY_PARSE_STATE_EHLO_SPACE,
655N/A SMTP_REPLY_PARSE_STATE_EHLO_GREET,
686N/A SMTP_REPLY_PARSE_STATE_CR,
655N/A SMTP_REPLY_PARSE_STATE_CRLF,
662N/A SMTP_REPLY_PARSE_STATE_LF
659N/A};
658N/A
660N/Astruct smtp_reply_parser_state_data {
661N/A enum smtp_reply_parser_state state;
670N/A unsigned int line;
687N/A
712N/A struct smtp_reply *reply;
676N/A ARRAY_TYPE(const_string) reply_lines;
689N/A size_t reply_size;
678N/A
677N/A bool last_line:1;
684N/A};
684N/A
690N/Astruct smtp_reply_parser {
693N/A struct istream *input;
700N/A
701N/A size_t max_reply_size;
704N/A
708N/A const unsigned char *begin, *cur, *end;
655N/A
655N/A string_t *strbuf;
617N/A
617N/A struct smtp_reply_parser_state_data state;
617N/A pool_t reply_pool;
617N/A
618N/A char *error;
619N/A
621N/A bool enhanced_codes:1;
617N/A bool ehlo:1;
622N/A};
623N/A
625N/Astatic inline void ATTR_FORMAT(2, 3)
629N/Asmtp_reply_parser_error(struct smtp_reply_parser *parser,
630N/A const char *format, ...)
631N/A{
632N/A va_list args;
637N/A
637N/A i_free(parser->error);
637N/A
638N/A va_start(args, format);
638N/A parser->error = i_strdup_vprintf(format, args);
639N/A va_end(args);
640N/A}
641N/A
645N/Astruct smtp_reply_parser *
647N/Asmtp_reply_parser_init(struct istream *input, size_t max_reply_size)
648N/A{
648N/A struct smtp_reply_parser *parser;
649N/A
617N/A parser = i_new(struct smtp_reply_parser, 1);
617N/A parser->max_reply_size =
565N/A (max_reply_size > 0 ? max_reply_size : (size_t)-1);
565N/A parser->input = input;
565N/A i_stream_ref(input);
565N/A parser->strbuf = str_new(default_pool, 128);
712N/A return parser;
565N/A}
565N/A
579N/Avoid smtp_reply_parser_deinit(struct smtp_reply_parser **_parser)
568N/A{
576N/A struct smtp_reply_parser *parser = *_parser;
577N/A
580N/A *_parser = NULL;
585N/A
587N/A str_free(&parser->strbuf);
593N/A pool_unref(&parser->reply_pool);
595N/A i_stream_unref(&parser->input);
595N/A i_free(parser->error);
596N/A i_free(parser);
600N/A}
602N/A
603N/Avoid smtp_reply_parser_set_stream(struct smtp_reply_parser *parser,
604N/A struct istream *input)
565N/A{
565N/A i_stream_unref(&parser->input);
525N/A if (input != NULL) {
525N/A parser->input = input;
525N/A i_stream_ref(parser->input);
488N/A }
524N/A}
524N/A
524N/Astatic void
524N/Asmtp_reply_parser_restart(struct smtp_reply_parser *parser)
524N/A{
524N/A str_truncate(parser->strbuf, 0);
524N/A pool_unref(&parser->reply_pool);
524N/A i_zero(&parser->state);
524N/A
524N/A parser->reply_pool = pool_alloconly_create("smtp_reply", 256);
524N/A parser->state.reply = p_new(parser->reply_pool, struct smtp_reply, 1);
524N/A p_array_init(&parser->state.reply_lines, parser->reply_pool, 8);
524N/A
524N/A}
524N/A
524N/Astatic int smtp_reply_parse_code
524N/A(struct smtp_reply_parser *parser, unsigned int *code_r)
540N/A{
544N/A const unsigned char *first = parser->cur;
488N/A const unsigned char *p;
488N/A
534N/A /* Reply-code = %x32-35 %x30-35 %x30-39
534N/A */
534N/A while (parser->cur < parser->end && i_isdigit(*parser->cur))
534N/A parser->cur++;
534N/A
534N/A if (str_len(parser->strbuf) + (parser->cur-first) > 3)
534N/A return -1;
534N/A
440N/A str_append_n(parser->strbuf, first, parser->cur - first);
440N/A if (parser->cur == parser->end)
440N/A return 0;
440N/A if (str_len(parser->strbuf) != 3)
440N/A return -1;
442N/A p = str_data(parser->strbuf);
443N/A if (p[0] < '2' || p[0] > '5' || p[1] > '5')
445N/A return -1;
482N/A *code_r = (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
482N/A str_truncate(parser->strbuf, 0);
444N/A return 1;
460N/A}
468N/A
470N/Astatic int smtp_reply_parse_textstring(struct smtp_reply_parser *parser)
472N/A{
440N/A const unsigned char *first = parser->cur;
440N/A
336N/A /* textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
336N/A */
336N/A while (parser->cur < parser->end && smtp_char_is_textstr(*parser->cur))
336N/A parser->cur++;
365N/A
408N/A if (((parser->cur-first) + parser->state.reply_size +
336N/A str_len(parser->strbuf)) > parser->max_reply_size) {
361N/A smtp_reply_parser_error(parser,
389N/A "Reply exceeds size limit");
390N/A return -1;
413N/A }
430N/A
370N/A str_append_n(parser->strbuf, first, parser->cur - first);
378N/A if (parser->cur == parser->end)
380N/A return 0;
392N/A return 1;
393N/A}
412N/A
421N/Astatic int smtp_reply_parse_ehlo_domain(struct smtp_reply_parser *parser)
423N/A{
336N/A const unsigned char *first = parser->cur;
336N/A
204N/A /* Domain [ SP ...
204N/A */
204N/A while (parser->cur < parser->end && *parser->cur != ' ' &&
204N/A smtp_char_is_textstr(*parser->cur))
254N/A parser->cur++;
254N/A
204N/A if (((parser->cur-first) + parser->state.reply_size +
217N/A str_len(parser->strbuf)) > parser->max_reply_size) {
265N/A smtp_reply_parser_error(parser,
316N/A "Reply exceeds size limit");
206N/A return -1;
307N/A }
316N/A str_append_n(parser->strbuf, first, parser->cur - first);
322N/A if (parser->cur == parser->end)
208N/A return 0;
209N/A return 1;
210N/A}
216N/A
224N/Astatic int smtp_reply_parse_ehlo_greet(struct smtp_reply_parser *parser)
225N/A{
240N/A const unsigned char *first = parser->cur;
241N/A
247N/A /* ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
249N/A *
252N/A * The greet is not supposed to be empty, but we don't really care
253N/A */
262N/A
264N/A if (parser->cur == parser->end)
267N/A return 0;
267N/A if (smtp_char_is_ehlo_greet(*parser->cur)) {
270N/A for (;;) {
270N/A while (parser->cur < parser->end &&
272N/A smtp_char_is_textstr(*parser->cur))
275N/A parser->cur++;
282N/A
283N/A if (((parser->cur-first) + parser->state.reply_size +
328N/A str_len(parser->strbuf)) >
204N/A parser->max_reply_size) {
204N/A smtp_reply_parser_error(parser,
135N/A "Reply exceeds size limit");
135N/A return -1;
135N/A }
135N/A
179N/A /* sanitize bad characters */
143N/A str_append_n(parser->strbuf,
158N/A first, parser->cur - first);
152N/A
159N/A if (parser->cur == parser->end)
179N/A return 0;
180N/A if (!smtp_char_is_ehlo_greet(*parser->cur))
181N/A break;
180N/A str_append_c(parser->strbuf, ' ');
135N/A parser->cur++;
137N/A first = parser->cur;
139N/A }
144N/A }
153N/A return 1;
155N/A}
169N/A
174N/Astatic inline const char *_chr_sanitize(unsigned char c)
188N/A{
189N/A if (c >= 0x20 && c < 0x7F)
135N/A return t_strdup_printf("'%c'", c);
135N/A return t_strdup_printf("0x%02x", c);
3N/A}
3N/A
3N/Astatic void
3N/Asmtp_reply_parse_enhanced_code(struct smtp_reply_parser *parser,
22N/A const char **pos)
22N/A{
3N/A const char *p = *pos;
38N/A unsigned int digits, x, y, z;
35N/A unsigned int prevx = parser->state.reply->enhanced_code.x,
35N/A prevy = parser->state.reply->enhanced_code.y,
36N/A prevz = parser->state.reply->enhanced_code.z;
36N/A
90N/A if (prevx == 9)
40N/A return; /* failed on earlier line */
66N/A
66N/A parser->state.reply->enhanced_code.x = 9;
79N/A parser->state.reply->enhanced_code.y = 0;
75N/A parser->state.reply->enhanced_code.z = 0;
76N/A
101N/A /* status-code ::= class "." subject "." detail
104N/A class ::= "2" / "4" / "5"
180N/A subject ::= 1*3digit
180N/A detail ::= 1*3digit
180N/A */
180N/A
22N/A /* class */
22N/A if (p[1] != '.' || (p[0] != '2' && p[0] != '4' && p[0] != '5'))
33N/A return;
33N/A x = p[0] - '0';
45N/A p += 2;
45N/A
55N/A /* subject */
57N/A digits = 0;
58N/A y = 0;
60N/A while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
62N/A y = y*10 + (*p - '0');
73N/A p++;
73N/A }
93N/A if (digits == 0 || *p != '.')
93N/A return;
81N/A p++;
85N/A
86N/A /* detail */
88N/A digits = 0;
91N/A z = 0;
96N/A while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
3N/A z = z*10 + (*p - '0');
3N/A p++;
0N/A }
0N/A if (digits == 0 || (*p != ' ' && *p != '\r' && *p != '\n'))
0N/A return;
0N/A p++;
0N/A
0N/A /* code is syntactically valid; strip code from textstring */
0N/A *pos = p;
0N/A
0N/A /* check for match with status */
0N/A if (x != parser->state.reply->status / 100) {
0N/A /* ignore code */
0N/A return;
0N/A }
0N/A
0N/A /* check for code consistency */
0N/A if (parser->state.line > 0 &&
0N/A (prevx != x || prevy != y || prevz != z)) {
0N/A /* ignore code */
0N/A return;
0N/A }
0N/A
0N/A parser->state.reply->enhanced_code.x = x;
0N/A parser->state.reply->enhanced_code.y = y;
0N/A parser->state.reply->enhanced_code.z = z;
0N/A}
0N/A
0N/Astatic void smtp_reply_parser_finish_line(struct smtp_reply_parser *parser)
0N/A{
0N/A const char *text = str_c(parser->strbuf);
0N/A
0N/A if (parser->enhanced_codes && str_len(parser->strbuf) > 5) {
0N/A smtp_reply_parse_enhanced_code(parser, &text);
0N/A }
0N/A
0N/A parser->state.line++;
0N/A parser->state.reply_size += str_len(parser->strbuf);
0N/A text = p_strdup(parser->reply_pool, text);
0N/A array_append(&parser->state.reply_lines, &text, 1);
0N/A str_truncate(parser->strbuf, 0);
0N/A}
0N/A
0N/Astatic int smtp_reply_parse_more(struct smtp_reply_parser *parser)
0N/A{
0N/A unsigned int status;
0N/A int ret;
0N/A
0N/A /*
0N/A Reply-line = *( Reply-code "-" [ textstring ] CRLF )
0N/A Reply-code [ SP textstring ] CRLF
0N/A Reply-code = %x32-35 %x30-35 %x30-39
0N/A
0N/A ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
0N/A / ( "250-" Domain [ SP ehlo-greet ] CRLF
0N/A *( "250-" ehlo-line CRLF )
0N/A "250" SP ehlo-line CRLF )
0N/A */
0N/A
0N/A for (;;) {
0N/A switch (parser->state.state) {
0N/A case SMTP_REPLY_PARSE_STATE_INIT:
0N/A smtp_reply_parser_restart(parser);
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
0N/A /* fall through */
0N/A /* Reply-code */
0N/A case SMTP_REPLY_PARSE_STATE_CODE:
0N/A if ((ret=smtp_reply_parse_code(parser, &status)) <= 0) {
0N/A if (ret < 0) {
0N/A smtp_reply_parser_error(parser,
0N/A "Invalid status code in reply");
0N/A }
0N/A return ret;
0N/A }
0N/A if (parser->state.line == 0) {
0N/A parser->state.reply->status = status;
0N/A } else if (status != parser->state.reply->status) {
0N/A smtp_reply_parser_error(parser,
0N/A "Inconsistent status codes in reply");
0N/A return -1;
0N/A }
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_SEP;
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A /* fall through */
0N/A /* "-" / SP / CRLF */
0N/A case SMTP_REPLY_PARSE_STATE_SEP:
0N/A switch (*parser->cur) {
0N/A /* "-" [ textstring ] CRLF */
0N/A case '-':
0N/A parser->cur++;
0N/A parser->state.last_line = FALSE;
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_TEXT;
0N/A break;
0N/A /* SP [ textstring ] CRLF ; allow missing text */
0N/A case ' ':
0N/A parser->cur++;
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_TEXT;
0N/A parser->state.last_line = TRUE;
0N/A break;
0N/A /* CRLF */
0N/A case '\r':
0N/A case '\n':
0N/A parser->state.last_line = TRUE;
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
0N/A break;
0N/A default:
0N/A smtp_reply_parser_error(parser,
0N/A "Encountered unexpected %s after reply status code",
0N/A _chr_sanitize(*parser->cur));
0N/A return -1;
0N/A }
0N/A if (parser->state.state != SMTP_REPLY_PARSE_STATE_TEXT)
0N/A break;
0N/A /* fall through */
0N/A /* textstring / (Domain [ SP ehlo-greet ]) */
0N/A case SMTP_REPLY_PARSE_STATE_TEXT:
0N/A if (parser->ehlo &&
0N/A parser->state.reply->status == 250 &&
0N/A parser->state.line == 0) {
0N/A /* handle first line of EHLO success response
0N/A differently because it can contain control
0N/A characters (WHY??!) */
0N/A if ((ret=smtp_reply_parse_ehlo_domain(parser)) <= 0)
0N/A return ret;
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_EHLO_SPACE;
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A break;
0N/A }
0N/A if ((ret=smtp_reply_parse_textstring(parser)) <= 0)
0N/A return ret;
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A /* fall through */
0N/A /* CR */
0N/A case SMTP_REPLY_PARSE_STATE_CR:
0N/A if (*parser->cur == '\r') {
0N/A parser->cur++;
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_CRLF;
0N/A } else {
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_LF;
0N/A }
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A /* fall through */
0N/A /* CRLF / LF */
0N/A case SMTP_REPLY_PARSE_STATE_CRLF:
0N/A case SMTP_REPLY_PARSE_STATE_LF:
0N/A if (*parser->cur != '\n') {
0N/A if (parser->state.state ==
0N/A SMTP_REPLY_PARSE_STATE_CRLF) {
0N/A smtp_reply_parser_error(parser,
0N/A "Encountered stray CR in reply text");
0N/A } else {
0N/A smtp_reply_parser_error(parser,
0N/A "Encountered stray %s in reply text",
0N/A _chr_sanitize(*parser->cur));
0N/A }
0N/A return -1;
0N/A }
0N/A parser->cur++;
0N/A smtp_reply_parser_finish_line(parser);
0N/A if (parser->state.last_line) {
0N/A parser->state.state =
0N/A SMTP_REPLY_PARSE_STATE_INIT;
0N/A return 1;
0N/A }
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
0N/A break;
0N/A /* SP ehlo-greet */
0N/A case SMTP_REPLY_PARSE_STATE_EHLO_SPACE:
0N/A if (*parser->cur != ' ') {
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
0N/A break;
0N/A }
0N/A parser->cur++;
0N/A str_append_c(parser->strbuf, ' ');
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_EHLO_GREET;
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A /* fall through */
0N/A /* ehlo-greet */
0N/A case SMTP_REPLY_PARSE_STATE_EHLO_GREET:
0N/A if ((ret=smtp_reply_parse_ehlo_greet(parser)) <= 0)
0N/A return ret;
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
0N/A if (parser->cur == parser->end)
0N/A return 0;
0N/A break;
0N/A default:
0N/A i_unreached();
0N/A }
0N/A }
0N/A
0N/A i_unreached();
0N/A return -1;
0N/A}
0N/A
0N/Astatic int smtp_reply_parse(struct smtp_reply_parser *parser)
0N/A{
0N/A size_t size;
0N/A int ret;
0N/A
0N/A while ((ret = i_stream_read_more(parser->input,
0N/A &parser->begin, &size)) > 0) {
0N/A parser->cur = parser->begin;
0N/A parser->end = parser->cur + size;
0N/A
0N/A if ((ret = smtp_reply_parse_more(parser)) < 0)
0N/A return -1;
0N/A
0N/A i_stream_skip(parser->input, parser->cur - parser->begin);
0N/A if (ret > 0)
0N/A return 1;
0N/A }
0N/A
0N/A i_assert(ret != -2);
0N/A if (ret < 0) {
0N/A i_assert(parser->input->eof);
0N/A if (parser->input->stream_errno == 0) {
0N/A if (parser->state.state == SMTP_REPLY_PARSE_STATE_INIT)
0N/A return 0;
0N/A smtp_reply_parser_error(parser,
0N/A "Premature end of input");
0N/A } else {
0N/A smtp_reply_parser_error(parser,
0N/A "Stream error: %s",
0N/A i_stream_get_error(parser->input));
0N/A }
0N/A }
0N/A return ret;
0N/A}
0N/A
0N/Aint smtp_reply_parse_next(struct smtp_reply_parser *parser,
0N/A bool enhanced_codes, struct smtp_reply **reply_r,
0N/A const char **error_r)
0N/A{
0N/A int ret;
0N/A
0N/A i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
0N/A (parser->enhanced_codes == enhanced_codes && !parser->ehlo));
0N/A
0N/A parser->enhanced_codes = enhanced_codes;
0N/A parser->ehlo = FALSE;
0N/A
0N/A i_free_and_null(parser->error);
0N/A
0N/A /*
0N/A Reply-line = *( Reply-code "-" [ textstring ] CRLF )
0N/A Reply-code [ SP textstring ] CRLF
0N/A Reply-code = %x32-35 %x30-35 %x30-39
0N/A textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
0N/A
0N/A Greeting is not handled specially here.
0N/A */
0N/A if ((ret=smtp_reply_parse(parser)) <= 0) {
0N/A *error_r = parser->error;
0N/A return ret;
0N/A }
0N/A
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
0N/A parser->state.reply->text_lines =
0N/A array_idx(&parser->state.reply_lines, 0);
0N/A *reply_r = parser->state.reply;
0N/A return 1;
0N/A}
0N/A
0N/Aint smtp_reply_parse_ehlo(struct smtp_reply_parser *parser,
0N/A struct smtp_reply **reply_r, const char **error_r)
0N/A{
0N/A int ret;
0N/A
0N/A i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
0N/A (!parser->enhanced_codes && parser->ehlo));
0N/A
0N/A parser->enhanced_codes = FALSE;
0N/A parser->ehlo = TRUE;
0N/A
0N/A i_free_and_null(parser->error);
0N/A
0N/A /*
0N/A ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
0N/A / ( "250-" Domain [ SP ehlo-greet ] CRLF
0N/A *( "250-" ehlo-line CRLF )
0N/A "250" SP ehlo-line CRLF )
0N/A ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127)
0N/A ; string of any characters other than CR or LF
0N/A ehlo-line = ehlo-keyword *( SP ehlo-param )
0N/A ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
0N/A ; additional syntax of ehlo-params depends on
0N/A ; ehlo-keyword
0N/A ehlo-param = 1*(%d33-126)
0N/A ; any CHAR excluding <SP> and all
0N/A ; control characters (US-ASCII 0-31 and 127
0N/A ; inclusive)
0N/A */
0N/A if ((ret=smtp_reply_parse(parser)) <= 0) {
0N/A *error_r = parser->error;
0N/A return ret;
0N/A }
0N/A
0N/A parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
0N/A parser->state.reply->text_lines =
0N/A array_idx(&parser->state.reply_lines, 0);
0N/A *reply_r = parser->state.reply;
0N/A return 1;
0N/A}
0N/A