Coverage Report

Created: 2026-04-27 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-smtp/smtp-reply-parser.c
Line
Count
Source
1
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str.h"
6
#include "strfuncs.h"
7
#include "istream.h"
8
#include "smtp-parser.h"
9
10
#include "smtp-reply-parser.h"
11
12
#include <ctype.h>
13
14
/* From RFC 5321:
15
16
   Reply-line     = *( Reply-code "-" [ textstring ] CRLF )
17
                      Reply-code [ SP textstring ] CRLF
18
   Reply-code     = %x32-35 %x30-35 %x30-39
19
   textstring     = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
20
21
   Greeting       = ( "220 " (Domain / address-literal)
22
                      [ SP textstring ] CRLF ) /
23
                    ( "220-" (Domain / address-literal)
24
                        [ SP textstring ] CRLF
25
                      *( "220-" [ textstring ] CRLF )
26
                        "220" [ SP textstring ] CRLF )
27
28
   ehlo-ok-rsp    = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
29
                    / ( "250-" Domain [ SP ehlo-greet ] CRLF
30
                      *( "250-" ehlo-line CRLF )
31
                    "250" SP ehlo-line CRLF )
32
   ehlo-greet     = 1*(%d0-9 / %d11-12 / %d14-127)
33
                    ; string of any characters other than CR or LF
34
   ehlo-line      = ehlo-keyword *( SP ehlo-param )
35
   ehlo-keyword   = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
36
                    ; additional syntax of ehlo-params depends on
37
                    ; ehlo-keyword
38
   ehlo-param     = 1*(%d33-126)
39
                    ; any CHAR excluding <SP> and all
40
                    ; control characters (US-ASCII 0-31 and 127
41
                    ; inclusive)
42
43
   From RFC 2034:
44
45
   status-code  ::= class "." subject "." detail
46
   class        ::= "2" / "4" / "5"
47
   subject      ::= 1*3digit
48
   detail       ::= 1*3digit
49
 */
50
51
enum smtp_reply_parser_state {
52
  SMTP_REPLY_PARSE_STATE_INIT = 0,
53
  SMTP_REPLY_PARSE_STATE_CODE,
54
  SMTP_REPLY_PARSE_STATE_SEP,
55
  SMTP_REPLY_PARSE_STATE_TEXT,
56
  SMTP_REPLY_PARSE_STATE_EHLO_SPACE,
57
  SMTP_REPLY_PARSE_STATE_EHLO_GREET,
58
  SMTP_REPLY_PARSE_STATE_CR,
59
  SMTP_REPLY_PARSE_STATE_CRLF,
60
  SMTP_REPLY_PARSE_STATE_LF
61
};
62
63
struct smtp_reply_parser_state_data {
64
  enum smtp_reply_parser_state state;
65
  unsigned int line;
66
67
  struct smtp_reply *reply;
68
  ARRAY_TYPE(const_string) reply_lines;
69
  size_t reply_size;
70
71
  bool last_line:1;
72
};
73
74
struct smtp_reply_parser {
75
  struct istream *input;
76
77
  size_t max_reply_size;
78
79
  const unsigned char *begin, *cur, *end;
80
81
  string_t *strbuf;
82
83
  struct smtp_reply_parser_state_data state;
84
  pool_t reply_pool;
85
86
  char *error;
87
88
  bool enhanced_codes:1;
89
  bool ehlo:1;
90
};
91
92
bool smtp_reply_parse_enhanced_code(const char *text,
93
            struct smtp_reply_enhanced_code *enh_code_r,
94
            const char **pos_r)
95
249
{
96
249
  const char *p = text;
97
249
  unsigned int digits, x, y, z;
98
99
249
  i_zero(enh_code_r);
100
101
  /* status-code ::= class "." subject "." detail
102
     class       ::= "2" / "4" / "5"
103
     subject     ::= 1*3digit
104
     detail      ::= 1*3digit
105
  */
106
107
  /* class */
108
249
  if (p[1] != '.' || (p[0] != '2' && p[0] != '4' && p[0] != '5'))
109
0
    return FALSE;
110
249
  x = p[0] - '0';
111
249
  p += 2;
112
113
  /* subject */
114
249
  digits = 0;
115
249
  y = 0;
116
498
  while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
117
249
    y = y*10 + (*p - '0');
118
249
    p++;
119
249
  }
120
249
  if (digits == 0 || *p != '.')
121
0
    return FALSE;
122
249
  p++;
123
124
  /* detail */
125
249
  digits = 0;
126
249
  z = 0;
127
498
  while (*p != '\0' && i_isdigit(*p) && digits++ < 3) {
128
249
    z = z*10 + (*p - '0');
129
249
    p++;
130
249
  }
131
249
  if (digits == 0 || (pos_r == NULL && *p != '\0'))
132
0
    return FALSE;
133
134
249
  if (pos_r != NULL) {
135
    /* code is syntactically valid; strip code from textstring */
136
0
    *pos_r = p;
137
0
  }
138
139
249
  enh_code_r->x = x;
140
249
  enh_code_r->y = y;
141
249
  enh_code_r->z = z;
142
249
  return TRUE;
143
249
}
144
145
static inline void ATTR_FORMAT(2, 3)
146
smtp_reply_parser_error(struct smtp_reply_parser *parser,
147
      const char *format, ...)
148
0
{
149
0
  va_list args;
150
151
0
  i_free(parser->error);
152
153
0
  va_start(args, format);
154
0
  parser->error = i_strdup_vprintf(format, args);
155
0
  va_end(args);
156
0
}
157
158
struct smtp_reply_parser *
159
smtp_reply_parser_init(struct istream *input, size_t max_reply_size)
160
0
{
161
0
  struct smtp_reply_parser *parser;
162
163
0
  parser = i_new(struct smtp_reply_parser, 1);
164
0
  parser->max_reply_size =
165
0
    (max_reply_size > 0 ? max_reply_size : SIZE_MAX);
166
0
  parser->input = input;
167
0
  i_stream_ref(input);
168
0
  parser->strbuf = str_new(default_pool, 128);
169
0
  return parser;
170
0
}
171
172
void smtp_reply_parser_deinit(struct smtp_reply_parser **_parser)
173
0
{
174
0
  struct smtp_reply_parser *parser = *_parser;
175
176
0
  *_parser = NULL;
177
178
0
  str_free(&parser->strbuf);
179
0
  pool_unref(&parser->reply_pool);
180
0
  i_stream_unref(&parser->input);
181
0
  i_free(parser->error);
182
0
  i_free(parser);
183
0
}
184
185
void smtp_reply_parser_set_stream(struct smtp_reply_parser *parser,
186
          struct istream *input)
187
0
{
188
0
  i_stream_unref(&parser->input);
189
0
  if (input != NULL) {
190
0
    parser->input = input;
191
0
    i_stream_ref(parser->input);
192
0
  }
193
0
}
194
195
static void
196
smtp_reply_parser_restart(struct smtp_reply_parser *parser)
197
0
{
198
0
  str_truncate(parser->strbuf, 0);
199
0
  pool_unref(&parser->reply_pool);
200
0
  i_zero(&parser->state);
201
202
0
  parser->reply_pool = pool_alloconly_create("smtp_reply", 1024);
203
0
  parser->state.reply = p_new(parser->reply_pool, struct smtp_reply, 1);
204
0
  p_array_init(&parser->state.reply_lines, parser->reply_pool, 8);
205
206
0
}
207
208
static int smtp_reply_parse_code
209
(struct smtp_reply_parser *parser, unsigned int *code_r)
210
0
{
211
0
  const unsigned char *first = parser->cur;
212
0
  const unsigned char *p;
213
214
  /* Reply-code     = %x32-35 %x30-35 %x30-39
215
   */
216
0
  while (parser->cur < parser->end && i_isdigit(*parser->cur))
217
0
    parser->cur++;
218
219
0
  if (str_len(parser->strbuf) + (parser->cur-first) > 3)
220
0
    return -1;
221
222
0
  str_append_data(parser->strbuf, first, parser->cur - first);
223
0
  if (parser->cur == parser->end)
224
0
    return 0;
225
0
  if (str_len(parser->strbuf) != 3)
226
0
    return -1;
227
0
  p = str_data(parser->strbuf);
228
0
  if (p[0] < '2' || p[0] > '5' || p[1] > '5')
229
0
    return -1;
230
0
  *code_r = (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
231
0
  str_truncate(parser->strbuf, 0);
232
0
  return 1;
233
0
}
234
235
static int smtp_reply_parse_textstring(struct smtp_reply_parser *parser)
236
0
{
237
0
  const unsigned char *first = parser->cur;
238
239
  /* textstring = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
240
   */
241
0
  while (parser->cur < parser->end && smtp_char_is_textstr(*parser->cur))
242
0
    parser->cur++;
243
244
0
  if (((parser->cur-first) + parser->state.reply_size +
245
0
    str_len(parser->strbuf)) > parser->max_reply_size) {
246
0
    smtp_reply_parser_error(parser,
247
0
      "Reply exceeds size limit");
248
0
    return -1;
249
0
  }
250
251
0
  str_append_data(parser->strbuf, first, parser->cur - first);
252
0
  if (parser->cur == parser->end)
253
0
    return 0;
254
0
  return 1;
255
0
}
256
257
static int smtp_reply_parse_ehlo_domain(struct smtp_reply_parser *parser)
258
0
{
259
0
  const unsigned char *first = parser->cur;
260
261
  /* Domain [ SP ...
262
   */
263
0
  while (parser->cur < parser->end && *parser->cur != ' ' &&
264
0
    smtp_char_is_textstr(*parser->cur))
265
0
    parser->cur++;
266
267
0
  if (((parser->cur-first) + parser->state.reply_size +
268
0
    str_len(parser->strbuf)) > parser->max_reply_size) {
269
0
    smtp_reply_parser_error(parser,
270
0
      "Reply exceeds size limit");
271
0
    return -1;
272
0
  }
273
0
  str_append_data(parser->strbuf, first, parser->cur - first);
274
0
  if (parser->cur == parser->end)
275
0
    return 0;
276
0
  return 1;
277
0
}
278
279
static int smtp_reply_parse_ehlo_greet(struct smtp_reply_parser *parser)
280
0
{
281
0
  const unsigned char *first = parser->cur;
282
283
  /* ehlo-greet     = 1*(%d0-9 / %d11-12 / %d14-127)
284
   *
285
   * The greet is not supposed to be empty, but we don't really care
286
   */
287
288
0
  if (parser->cur == parser->end)
289
0
    return 0;
290
0
  if (smtp_char_is_ehlo_greet(*parser->cur)) {
291
0
    for (;;) {
292
0
      while (parser->cur < parser->end &&
293
0
        smtp_char_is_textstr(*parser->cur))
294
0
        parser->cur++;
295
296
0
      if (((parser->cur-first) + parser->state.reply_size +
297
0
        str_len(parser->strbuf)) >
298
0
        parser->max_reply_size) {
299
0
        smtp_reply_parser_error(parser,
300
0
          "Reply exceeds size limit");
301
0
        return -1;
302
0
      }
303
304
      /* sanitize bad characters */
305
0
      str_append_data(parser->strbuf,
306
0
        first, parser->cur - first);
307
308
0
      if (parser->cur == parser->end)
309
0
        return 0;
310
0
      if (!smtp_char_is_ehlo_greet(*parser->cur))
311
0
        break;
312
0
      str_append_c(parser->strbuf, ' ');
313
0
      parser->cur++;
314
0
      first = parser->cur;
315
0
    }
316
0
  }
317
0
  return 1;
318
0
}
319
320
static inline const char *_chr_sanitize(unsigned char c)
321
0
{
322
0
  if (c >= 0x20 && c < 0x7F)
323
0
    return t_strdup_printf("'%c'", c);
324
0
  return t_strdup_printf("0x%02x", c);
325
0
}
326
327
static void
328
smtp_reply_parser_parse_enhanced_code(const char *text,
329
              struct smtp_reply_parser *parser,
330
              const char **pos_r)
331
0
{
332
0
  struct smtp_reply_enhanced_code code;
333
0
  struct smtp_reply_enhanced_code *cur_code =
334
0
    &parser->state.reply->enhanced_code;
335
336
0
  if (cur_code->x == 9)
337
0
    return; /* failed on earlier line */
338
339
0
  if (!smtp_reply_parse_enhanced_code(text, &code, pos_r)) {
340
    /* failed to parse an enhanced code */
341
0
    i_zero(cur_code);
342
0
    cur_code->x = 9;
343
0
    return;
344
0
  }
345
346
0
  if (**pos_r != ' ' && **pos_r != '\r' && **pos_r != '\n')
347
0
    return;
348
0
  (*pos_r)++;
349
350
  /* check for match with status */
351
0
  if (code.x != parser->state.reply->status / 100) {
352
    /* ignore code */
353
0
    return;
354
0
  }
355
356
  /* check for code consistency */
357
0
  if (parser->state.line > 0 &&
358
0
      (cur_code->x != code.x || cur_code->y != code.y ||
359
0
       cur_code->z != code.z)) {
360
    /* ignore code */
361
0
    return;
362
0
  }
363
364
0
  *cur_code = code;
365
0
}
366
367
static void smtp_reply_parser_finish_line(struct smtp_reply_parser *parser)
368
0
{
369
0
  const char *text = str_c(parser->strbuf);
370
371
0
  if (parser->enhanced_codes && str_len(parser->strbuf) > 5)
372
0
    smtp_reply_parser_parse_enhanced_code(text, parser, &text);
373
374
0
  parser->state.line++;
375
0
  parser->state.reply_size += str_len(parser->strbuf);
376
0
  text = p_strdup(parser->reply_pool, text);
377
0
  array_push_back(&parser->state.reply_lines, &text);
378
0
  str_truncate(parser->strbuf, 0);
379
0
}
380
381
static int smtp_reply_parse_more(struct smtp_reply_parser *parser)
382
0
{
383
0
  unsigned int status;
384
0
  int ret;
385
386
  /*
387
     Reply-line     = *( Reply-code "-" [ textstring ] CRLF )
388
                       Reply-code [ SP textstring ] CRLF
389
     Reply-code     = %x32-35 %x30-35 %x30-39
390
391
     ehlo-ok-rsp    = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
392
                       / ( "250-" Domain [ SP ehlo-greet ] CRLF
393
                        *( "250-" ehlo-line CRLF )
394
                       "250" SP ehlo-line CRLF )
395
   */
396
397
0
  for (;;) {
398
0
    switch (parser->state.state) {
399
0
    case SMTP_REPLY_PARSE_STATE_INIT:
400
0
      smtp_reply_parser_restart(parser);
401
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
402
      /* fall through */
403
    /* Reply-code */
404
0
    case SMTP_REPLY_PARSE_STATE_CODE:
405
0
      if ((ret=smtp_reply_parse_code(parser, &status)) <= 0) {
406
0
        if (ret < 0) {
407
0
          smtp_reply_parser_error(parser,
408
0
            "Invalid status code in reply");
409
0
        }
410
0
        return ret;
411
0
      }
412
0
      if (parser->state.line == 0) {
413
0
        parser->state.reply->status = status;
414
0
      } else if (status != parser->state.reply->status) {
415
0
        smtp_reply_parser_error(parser,
416
0
          "Inconsistent status codes in reply");
417
0
        return -1;
418
0
      }
419
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_SEP;
420
0
      if (parser->cur == parser->end)
421
0
        return 0;
422
      /* fall through */
423
    /* "-" / SP / CRLF */
424
0
    case SMTP_REPLY_PARSE_STATE_SEP:
425
0
      switch (*parser->cur) {
426
      /* "-" [ textstring ] CRLF */
427
0
      case '-':
428
0
        parser->cur++;
429
0
        parser->state.last_line = FALSE;
430
0
        parser->state.state =
431
0
          SMTP_REPLY_PARSE_STATE_TEXT;
432
0
        break;
433
      /* SP [ textstring ] CRLF ; allow missing text */
434
0
      case ' ':
435
0
        parser->cur++;
436
0
        parser->state.state =
437
0
          SMTP_REPLY_PARSE_STATE_TEXT;
438
0
        parser->state.last_line = TRUE;
439
0
        break;
440
      /* CRLF */
441
0
      case '\r':
442
0
      case '\n':
443
0
        parser->state.last_line = TRUE;
444
0
        parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
445
0
        break;
446
0
      default:
447
0
        smtp_reply_parser_error(parser,
448
0
          "Encountered unexpected %s after reply status code",
449
0
          _chr_sanitize(*parser->cur));
450
0
        return -1;
451
0
      }
452
0
      if (parser->state.state != SMTP_REPLY_PARSE_STATE_TEXT)
453
0
        break;
454
      /* fall through */
455
    /* textstring / (Domain [ SP ehlo-greet ]) */
456
0
    case SMTP_REPLY_PARSE_STATE_TEXT:
457
0
      if (parser->ehlo &&
458
0
        parser->state.reply->status == 250 &&
459
0
        parser->state.line == 0) {
460
        /* handle first line of EHLO success response
461
           differently because it can contain control
462
           characters (WHY??!) */
463
0
        if ((ret=smtp_reply_parse_ehlo_domain(parser)) <= 0)
464
0
          return ret;
465
0
        parser->state.state =
466
0
          SMTP_REPLY_PARSE_STATE_EHLO_SPACE;
467
0
        if (parser->cur == parser->end)
468
0
          return 0;
469
0
        break;
470
0
      }
471
0
      if ((ret=smtp_reply_parse_textstring(parser)) <= 0)
472
0
        return ret;
473
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
474
0
      if (parser->cur == parser->end)
475
0
        return 0;
476
      /* fall through */
477
    /* CR */
478
0
    case SMTP_REPLY_PARSE_STATE_CR:
479
0
      if (*parser->cur == '\r') {
480
0
        parser->cur++;
481
0
        parser->state.state =
482
0
          SMTP_REPLY_PARSE_STATE_CRLF;
483
0
      } else {
484
0
        parser->state.state =
485
0
          SMTP_REPLY_PARSE_STATE_LF;
486
0
      }
487
0
      if (parser->cur == parser->end)
488
0
        return 0;
489
      /* fall through */
490
    /* CRLF / LF */
491
0
    case SMTP_REPLY_PARSE_STATE_CRLF:
492
0
    case SMTP_REPLY_PARSE_STATE_LF:
493
0
      if (*parser->cur != '\n') {
494
0
        if (parser->state.state ==
495
0
          SMTP_REPLY_PARSE_STATE_CRLF) {
496
0
          smtp_reply_parser_error(parser,
497
0
            "Encountered stray CR in reply text");
498
0
        } else {
499
0
          smtp_reply_parser_error(parser,
500
0
            "Encountered stray %s in reply text",
501
0
            _chr_sanitize(*parser->cur));
502
0
        }
503
0
        return -1;
504
0
      }
505
0
      parser->cur++;
506
0
      smtp_reply_parser_finish_line(parser);
507
0
      if (parser->state.last_line) {
508
0
        parser->state.state =
509
0
          SMTP_REPLY_PARSE_STATE_INIT;
510
0
        return 1;
511
0
      }
512
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_CODE;
513
0
      break;
514
    /* SP ehlo-greet */
515
0
    case SMTP_REPLY_PARSE_STATE_EHLO_SPACE:
516
0
      if (*parser->cur != ' ') {
517
0
        parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
518
0
        break;
519
0
      }
520
0
      parser->cur++;
521
0
      str_append_c(parser->strbuf, ' ');
522
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_EHLO_GREET;
523
0
      if (parser->cur == parser->end)
524
0
        return 0;
525
      /* fall through */
526
    /* ehlo-greet */
527
0
    case SMTP_REPLY_PARSE_STATE_EHLO_GREET:
528
0
      if ((ret=smtp_reply_parse_ehlo_greet(parser)) <= 0)
529
0
        return ret;
530
0
      parser->state.state = SMTP_REPLY_PARSE_STATE_CR;
531
0
      if (parser->cur == parser->end)
532
0
        return 0;
533
0
      break;
534
0
    default:
535
0
      i_unreached();
536
0
    }
537
0
  }
538
539
0
  i_unreached();
540
0
}
541
542
static int smtp_reply_parse(struct smtp_reply_parser *parser)
543
0
{
544
0
  size_t size;
545
0
  int ret;
546
547
0
  while ((ret = i_stream_read_more(parser->input,
548
0
           &parser->begin, &size)) > 0) {
549
0
    parser->cur = parser->begin;
550
0
    parser->end = parser->cur + size;
551
552
0
    if ((ret = smtp_reply_parse_more(parser)) < 0)
553
0
      return -1;
554
555
0
    i_stream_skip(parser->input, parser->cur - parser->begin);
556
0
    if (ret > 0)
557
0
      return 1;
558
0
  }
559
560
0
  i_assert(ret != -2);
561
0
  if (ret < 0) {
562
0
    i_assert(parser->input->eof);
563
0
    if (parser->input->stream_errno == 0) {
564
0
      if (parser->state.state == SMTP_REPLY_PARSE_STATE_INIT)
565
0
        return 0;
566
0
      smtp_reply_parser_error(parser,
567
0
        "Premature end of input");
568
0
    } else {
569
0
      smtp_reply_parser_error(parser,
570
0
        "Stream error: %s",
571
0
        i_stream_get_error(parser->input));
572
0
    }
573
0
  }
574
0
  return ret;
575
0
}
576
577
int smtp_reply_parse_next(struct smtp_reply_parser *parser,
578
        bool enhanced_codes, struct smtp_reply **reply_r,
579
        const char **error_r)
580
0
{
581
0
  int ret;
582
583
0
  i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
584
0
    (parser->enhanced_codes == enhanced_codes && !parser->ehlo));
585
586
0
  parser->enhanced_codes = enhanced_codes;
587
0
  parser->ehlo = FALSE;
588
589
0
  i_free_and_null(parser->error);
590
591
  /*
592
     Reply-line     = *( Reply-code "-" [ textstring ] CRLF )
593
                      Reply-code [ SP textstring ] CRLF
594
     Reply-code     = %x32-35 %x30-35 %x30-39
595
     textstring     = 1*(%d09 / %d32-126) ; HT, SP, Printable US-ASCII
596
597
     Greeting is not handled specially here.
598
   */
599
0
  if ((ret=smtp_reply_parse(parser)) <= 0) {
600
0
    *error_r = parser->error;
601
0
    return ret;
602
0
  }
603
604
0
  i_assert(array_count(&parser->state.reply_lines) > 0);
605
0
  array_append_zero(&parser->state.reply_lines);
606
607
0
  parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
608
0
  parser->state.reply->text_lines =
609
0
    array_front(&parser->state.reply_lines);
610
0
  *reply_r = parser->state.reply;
611
0
  return 1;
612
0
}
613
614
int smtp_reply_parse_ehlo(struct smtp_reply_parser *parser,
615
        struct smtp_reply **reply_r, const char **error_r)
616
0
{
617
0
  int ret;
618
619
0
  i_assert(parser->state.state == SMTP_REPLY_PARSE_STATE_INIT ||
620
0
    (!parser->enhanced_codes && parser->ehlo));
621
622
0
  parser->enhanced_codes = FALSE;
623
0
  parser->ehlo = TRUE;
624
625
0
  i_free_and_null(parser->error);
626
627
  /*
628
     ehlo-ok-rsp    = ( "250" SP Domain [ SP ehlo-greet ] CRLF )
629
                      / ( "250-" Domain [ SP ehlo-greet ] CRLF
630
                        *( "250-" ehlo-line CRLF )
631
                      "250" SP ehlo-line CRLF )
632
     ehlo-greet     = 1*(%d0-9 / %d11-12 / %d14-127)
633
                      ; string of any characters other than CR or LF
634
     ehlo-line      = ehlo-keyword *( SP ehlo-param )
635
     ehlo-keyword   = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
636
                      ; additional syntax of ehlo-params depends on
637
                      ; ehlo-keyword
638
     ehlo-param     = 1*(%d33-126)
639
                      ; any CHAR excluding <SP> and all
640
                      ; control characters (US-ASCII 0-31 and 127
641
                      ; inclusive)
642
   */
643
0
  if ((ret=smtp_reply_parse(parser)) <= 0) {
644
0
    *error_r = parser->error;
645
0
    return ret;
646
0
  }
647
648
0
  i_assert(array_count(&parser->state.reply_lines) > 0);
649
0
  array_append_zero(&parser->state.reply_lines);
650
651
0
  parser->state.state = SMTP_REPLY_PARSE_STATE_INIT;
652
0
  parser->state.reply->text_lines =
653
0
    array_front(&parser->state.reply_lines);
654
0
  *reply_r = parser->state.reply;
655
0
  return 1;
656
0
}