1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "unichar.h"
5 #include "istream.h"
6 #include "istream-failure-at.h"
7 #include "istream-sized.h"
8 #include "istream-dot.h"
9
10 #include "smtp-parser.h"
11 #include "smtp-command-parser.h"
12
13 #include <ctype.h>
14
15 #define SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH 32
16
17 enum smtp_command_parser_state {
18 SMTP_COMMAND_PARSE_STATE_INIT = 0,
19 SMTP_COMMAND_PARSE_STATE_SKIP_LINE,
20 SMTP_COMMAND_PARSE_STATE_COMMAND,
21 SMTP_COMMAND_PARSE_STATE_SP,
22 SMTP_COMMAND_PARSE_STATE_PARAMETERS,
23 SMTP_COMMAND_PARSE_STATE_CR,
24 SMTP_COMMAND_PARSE_STATE_LF,
25 SMTP_COMMAND_PARSE_STATE_ERROR,
26 };
27
28 struct smtp_command_parser_state_data {
29 enum smtp_command_parser_state state;
30
31 char *cmd_name;
32 char *cmd_params;
33
34 uoff_t poff;
35 };
36
37 struct smtp_command_parser {
38 struct istream *input;
39
40 struct smtp_command_limits limits;
41
42 const unsigned char *cur, *end;
43 struct istream *data;
44
45 struct smtp_command_parser_state_data state;
46
47 enum smtp_command_parse_error error_code;
48 char *error;
49
50 bool auth_response:1;
51 };
52
53 static inline void ATTR_FORMAT(3, 4)
smtp_command_parser_error(struct smtp_command_parser * parser,enum smtp_command_parse_error code,const char * format,...)54 smtp_command_parser_error(struct smtp_command_parser *parser,
55 enum smtp_command_parse_error code,
56 const char *format, ...)
57 {
58 va_list args;
59
60 parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;
61
62 i_free(parser->error);
63 parser->error_code = code;
64
65 va_start(args, format);
66 parser->error = i_strdup_vprintf(format, args);
67 va_end(args);
68 }
69
70 struct smtp_command_parser *
smtp_command_parser_init(struct istream * input,const struct smtp_command_limits * limits)71 smtp_command_parser_init(struct istream *input,
72 const struct smtp_command_limits *limits)
73 {
74 struct smtp_command_parser *parser;
75
76 parser = i_new(struct smtp_command_parser, 1);
77 parser->input = input;
78 i_stream_ref(input);
79
80 if (limits != NULL)
81 parser->limits = *limits;
82 if (parser->limits.max_parameters_size == 0) {
83 parser->limits.max_parameters_size =
84 SMTP_COMMAND_DEFAULT_MAX_PARAMETERS_SIZE;
85 }
86 if (parser->limits.max_auth_size == 0) {
87 parser->limits.max_auth_size =
88 SMTP_COMMAND_DEFAULT_MAX_AUTH_SIZE;
89 }
90 if (parser->limits.max_data_size == 0) {
91 parser->limits.max_data_size =
92 SMTP_COMMAND_DEFAULT_MAX_DATA_SIZE;
93 }
94
95 return parser;
96 }
97
smtp_command_parser_deinit(struct smtp_command_parser ** _parser)98 void smtp_command_parser_deinit(struct smtp_command_parser **_parser)
99 {
100 struct smtp_command_parser *parser = *_parser;
101
102 i_stream_unref(&parser->data);
103 i_free(parser->state.cmd_name);
104 i_free(parser->state.cmd_params);
105 i_free(parser->error);
106 i_stream_unref(&parser->input);
107 i_free(parser);
108 *_parser = NULL;
109 }
110
smtp_command_parser_restart(struct smtp_command_parser * parser)111 static void smtp_command_parser_restart(struct smtp_command_parser *parser)
112 {
113 i_free(parser->state.cmd_name);
114 i_free(parser->state.cmd_params);
115
116 i_zero(&parser->state);
117 }
118
smtp_command_parser_set_stream(struct smtp_command_parser * parser,struct istream * input)119 void smtp_command_parser_set_stream(struct smtp_command_parser *parser,
120 struct istream *input)
121 {
122 i_stream_unref(&parser->input);
123 if (input != NULL) {
124 parser->input = input;
125 i_stream_ref(parser->input);
126 }
127 }
128
_chr_sanitize(unsigned char c)129 static inline const char *_chr_sanitize(unsigned char c)
130 {
131 if (c >= 0x20 && c < 0x7F)
132 return t_strdup_printf("`%c'", c);
133 if (c == 0x0a)
134 return "<LF>";
135 if (c == 0x0d)
136 return "<CR>";
137 return t_strdup_printf("<0x%02x>", c);
138 }
139
smtp_command_parse_identifier(struct smtp_command_parser * parser)140 static int smtp_command_parse_identifier(struct smtp_command_parser *parser)
141 {
142 const unsigned char *p;
143
144 /* The commands themselves are alphabetic characters.
145 */
146 p = parser->cur + parser->state.poff;
147 i_assert(p <= parser->end);
148 while (p < parser->end && i_isalpha(*p))
149 p++;
150 if ((p - parser->cur) > SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH) {
151 smtp_command_parser_error(
152 parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
153 "Command name is too long");
154 return -1;
155 }
156 parser->state.poff = p - parser->cur;
157 if (p == parser->end)
158 return 0;
159 parser->state.cmd_name = i_strdup_until(parser->cur, p);
160 parser->cur = p;
161 parser->state.poff = 0;
162 return 1;
163 }
164
smtp_command_parse_parameters(struct smtp_command_parser * parser)165 static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
166 {
167 const unsigned char *p, *mp;
168 uoff_t max_size = (parser->auth_response ?
169 parser->limits.max_auth_size :
170 parser->limits.max_parameters_size);
171 int nch = 1;
172
173 /* We assume parameters to match textstr (HT, SP, Printable US-ASCII).
174 For command parameters, we also accept valid UTF-8 characters.
175 */
176 p = parser->cur + parser->state.poff;
177 while (p < parser->end) {
178 unichar_t ch;
179
180 if (parser->auth_response)
181 ch = *p;
182 else {
183 nch = uni_utf8_get_char_n(p, (size_t)(parser->end - p),
184 &ch);
185 }
186 if (nch == 0)
187 break;
188 if (nch < 0) {
189 smtp_command_parser_error(
190 parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
191 "Invalid UTF-8 character in command parameters");
192 return -1;
193 }
194 if (nch == 1 && !smtp_char_is_textstr((unsigned char)ch))
195 break;
196 p += nch;
197 }
198 if (max_size > 0 && (uoff_t)(p - parser->cur) > max_size) {
199 smtp_command_parser_error(
200 parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
201 "%s line is too long",
202 (parser->auth_response ? "AUTH response" : "Command"));
203 return -1;
204 }
205 parser->state.poff = p - parser->cur;
206 if (p == parser->end || nch == 0)
207 return 0;
208
209 /* In the interest of improved interoperability, SMTP receivers SHOULD
210 tolerate trailing white space before the terminating <CRLF>.
211
212 WSP = SP / HTAB ; white space
213
214 --> Trim the end of the buffer
215 */
216 mp = p;
217 if (mp > parser->cur) {
218 while (mp > parser->cur && (*(mp-1) == ' ' || *(mp-1) == '\t'))
219 mp--;
220 }
221
222 if (!parser->auth_response && mp > parser->cur && *parser->cur == ' ') {
223 smtp_command_parser_error(
224 parser, SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
225 "Duplicate space after command name");
226 return -1;
227 }
228
229 parser->state.cmd_params = i_strdup_until(parser->cur, mp);
230 parser->cur = p;
231 parser->state.poff = 0;
232 return 1;
233 }
234
smtp_command_parse_line(struct smtp_command_parser * parser)235 static int smtp_command_parse_line(struct smtp_command_parser *parser)
236 {
237 int ret;
238
239 /* RFC 5321, Section 4.1.1:
240
241 SMTP commands are character strings terminated by <CRLF>. The
242 commands themselves are alphabetic characters terminated by <SP> if
243 parameters follow and <CRLF> otherwise. (In the interest of improved
244 interoperability, SMTP receivers SHOULD tolerate trailing white space
245 before the terminating <CRLF>.)
246 */
247 for (;;) {
248 switch (parser->state.state) {
249 case SMTP_COMMAND_PARSE_STATE_INIT:
250 smtp_command_parser_restart(parser);
251 if (parser->auth_response) {
252 /* Parse AUTH response as bare parameters */
253 parser->state.state =
254 SMTP_COMMAND_PARSE_STATE_PARAMETERS;
255 } else {
256 parser->state.state =
257 SMTP_COMMAND_PARSE_STATE_COMMAND;
258 }
259 if (parser->cur == parser->end)
260 return 0;
261 if (parser->auth_response)
262 break;
263 /* fall through */
264 case SMTP_COMMAND_PARSE_STATE_COMMAND:
265 ret = smtp_command_parse_identifier(parser);
266 if (ret <= 0)
267 return ret;
268 parser->state.state = SMTP_COMMAND_PARSE_STATE_SP;
269 if (parser->cur == parser->end)
270 return 0;
271 /* fall through */
272 case SMTP_COMMAND_PARSE_STATE_SP:
273 if (*parser->cur == '\r') {
274 parser->state.state =
275 SMTP_COMMAND_PARSE_STATE_CR;
276 break;
277 } else if (*parser->cur == '\n') {
278 parser->state.state =
279 SMTP_COMMAND_PARSE_STATE_LF;
280 break;
281 } else if (*parser->cur != ' ') {
282 smtp_command_parser_error(parser,
283 SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
284 "Unexpected character %s in command name",
285 _chr_sanitize(*parser->cur));
286 return -1;
287 }
288 parser->cur++;
289 parser->state.state =
290 SMTP_COMMAND_PARSE_STATE_PARAMETERS;
291 if (parser->cur >= parser->end)
292 return 0;
293 /* fall through */
294 case SMTP_COMMAND_PARSE_STATE_PARAMETERS:
295 ret = smtp_command_parse_parameters(parser);
296 if (ret <= 0)
297 return ret;
298 parser->state.state = SMTP_COMMAND_PARSE_STATE_CR;
299 if (parser->cur == parser->end)
300 return 0;
301 /* fall through */
302 case SMTP_COMMAND_PARSE_STATE_CR:
303 if (*parser->cur == '\r') {
304 parser->cur++;
305 } else if (*parser->cur != '\n') {
306 smtp_command_parser_error(parser,
307 SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
308 "Unexpected character %s in %s",
309 _chr_sanitize(*parser->cur),
310 (parser->auth_response ?
311 "AUTH response" :
312 "command parameters"));
313 return -1;
314 }
315 parser->state.state = SMTP_COMMAND_PARSE_STATE_LF;
316 if (parser->cur == parser->end)
317 return 0;
318 /* fall through */
319 case SMTP_COMMAND_PARSE_STATE_LF:
320 if (*parser->cur != '\n') {
321 smtp_command_parser_error(parser,
322 SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
323 "Expected LF after CR at end of %s, "
324 "but found %s",
325 (parser->auth_response ?
326 "AUTH response" : "command"),
327 _chr_sanitize(*parser->cur));
328 return -1;
329 }
330 parser->cur++;
331 parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT;
332 return 1;
333 case SMTP_COMMAND_PARSE_STATE_ERROR:
334 /* Skip until end of line */
335 while (parser->cur < parser->end &&
336 *parser->cur != '\n')
337 parser->cur++;
338 if (parser->cur == parser->end)
339 return 0;
340 parser->cur++;
341 parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT;
342 break;
343 default:
344 i_unreached();
345 }
346 }
347
348 i_unreached();
349 return -1;
350 }
351
smtp_command_parse(struct smtp_command_parser * parser)352 static int smtp_command_parse(struct smtp_command_parser *parser)
353 {
354 const unsigned char *begin;
355 size_t size, old_bytes = 0;
356 int ret;
357
358 while ((ret = i_stream_read_data(parser->input, &begin, &size,
359 old_bytes)) > 0) {
360 parser->cur = begin;
361 parser->end = parser->cur + size;
362
363 ret = smtp_command_parse_line(parser);
364 i_stream_skip(parser->input, parser->cur - begin);
365 if (ret != 0)
366 return ret;
367 old_bytes = i_stream_get_data_size(parser->input);
368 }
369
370 if (ret == -2) {
371 /* Should not really happen */
372 smtp_command_parser_error(
373 parser, SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
374 "%s line is too long",
375 (parser->auth_response ? "AUTH response" : "Command"));
376 return -1;
377 }
378 if (ret < 0) {
379 i_assert(parser->input->eof);
380 if (parser->input->stream_errno == 0) {
381 if (parser->state.state ==
382 SMTP_COMMAND_PARSE_STATE_INIT)
383 ret = -2;
384 smtp_command_parser_error(
385 parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND,
386 "Premature end of input");
387 } else {
388 smtp_command_parser_error(
389 parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM,
390 "%s", i_stream_get_disconnect_reason(parser->input));
391 }
392 }
393 return ret;
394 }
395
smtp_command_parser_pending_data(struct smtp_command_parser * parser)396 bool smtp_command_parser_pending_data(struct smtp_command_parser *parser)
397 {
398 if (parser->data == NULL)
399 return FALSE;
400 return i_stream_have_bytes_left(parser->data);
401 }
402
smtp_command_parse_finish_data(struct smtp_command_parser * parser)403 static int smtp_command_parse_finish_data(struct smtp_command_parser *parser)
404 {
405 const unsigned char *data;
406 size_t size;
407 int ret;
408
409 parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
410 parser->error = NULL;
411
412 if (parser->data == NULL)
413 return 1;
414 if (parser->data->eof) {
415 i_stream_unref(&parser->data);
416 return 1;
417 }
418
419 while ((ret = i_stream_read_data(parser->data, &data, &size, 0)) > 0)
420 i_stream_skip(parser->data, size);
421 if (ret == 0 || parser->data->stream_errno != 0) {
422 switch (parser->data->stream_errno) {
423 case 0:
424 return 0;
425 case EMSGSIZE:
426 smtp_command_parser_error(
427 parser, SMTP_COMMAND_PARSE_ERROR_DATA_TOO_LARGE,
428 "Command data too large");
429 break;
430 default:
431 smtp_command_parser_error(
432 parser, SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM,
433 "%s", i_stream_get_disconnect_reason(parser->data));
434 }
435 return -1;
436 }
437 i_stream_unref(&parser->data);
438 return 1;
439 }
440
smtp_command_parse_next(struct smtp_command_parser * parser,const char ** cmd_name_r,const char ** cmd_params_r,enum smtp_command_parse_error * error_code_r,const char ** error_r)441 int smtp_command_parse_next(struct smtp_command_parser *parser,
442 const char **cmd_name_r, const char **cmd_params_r,
443 enum smtp_command_parse_error *error_code_r,
444 const char **error_r)
445 {
446 int ret;
447
448 i_assert(!parser->auth_response ||
449 parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT ||
450 parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR);
451 parser->auth_response = FALSE;
452
453 *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
454 *error_r = NULL;
455
456 i_free_and_null(parser->error);
457
458 /* Make sure we finished streaming payload from previous command
459 before we continue. */
460 ret = smtp_command_parse_finish_data(parser);
461 if (ret <= 0) {
462 if (ret < 0) {
463 *error_code_r = parser->error_code;
464 *error_r = parser->error;
465 }
466 return ret;
467 }
468
469 ret = smtp_command_parse(parser);
470 if (ret <= 0) {
471 if (ret < 0) {
472 *error_code_r = parser->error_code;
473 *error_r = parser->error;
474 parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;
475 }
476 return ret;
477 }
478
479 i_assert(parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT);
480 *cmd_name_r = parser->state.cmd_name;
481 *cmd_params_r = (parser->state.cmd_params == NULL ?
482 "" : parser->state.cmd_params);
483 return 1;
484 }
485
486 struct istream *
smtp_command_parse_data_with_size(struct smtp_command_parser * parser,uoff_t size)487 smtp_command_parse_data_with_size(struct smtp_command_parser *parser,
488 uoff_t size)
489 {
490 i_assert(parser->data == NULL);
491 if (size > parser->limits.max_data_size) {
492 /* Not supposed to happen; command should check size */
493 parser->data = i_stream_create_error_str(EMSGSIZE,
494 "Command data size exceeds maximum "
495 "(%"PRIuUOFF_T" > %"PRIuUOFF_T")",
496 size, parser->limits.max_data_size);
497 } else {
498 // FIXME: Make exact_size stream type
499 struct istream *limit_input =
500 i_stream_create_limit(parser->input, size);
501 parser->data = i_stream_create_min_sized(limit_input, size);
502 i_stream_unref(&limit_input);
503 }
504 i_stream_ref(parser->data);
505 return parser->data;
506 }
507
508 struct istream *
smtp_command_parse_data_with_dot(struct smtp_command_parser * parser)509 smtp_command_parse_data_with_dot(struct smtp_command_parser *parser)
510 {
511 struct istream *data;
512 i_assert(parser->data == NULL);
513
514 data = i_stream_create_dot(parser->input, TRUE);
515 if (parser->limits.max_data_size != UOFF_T_MAX) {
516 parser->data = i_stream_create_failure_at(
517 data, parser->limits.max_data_size, EMSGSIZE,
518 t_strdup_printf("Command data size exceeds maximum "
519 "(> %"PRIuUOFF_T")",
520 parser->limits.max_data_size));
521 i_stream_unref(&data);
522 } else {
523 parser->data = data;
524 }
525 i_stream_ref(parser->data);
526 return parser->data;
527 }
528
smtp_command_parse_auth_response(struct smtp_command_parser * parser,const char ** line_r,enum smtp_command_parse_error * error_code_r,const char ** error_r)529 int smtp_command_parse_auth_response(struct smtp_command_parser *parser,
530 const char **line_r,
531 enum smtp_command_parse_error *error_code_r,
532 const char **error_r)
533 {
534 int ret;
535
536 i_assert(parser->auth_response ||
537 parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT ||
538 parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR);
539 parser->auth_response = TRUE;
540
541 *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
542 *error_r = NULL;
543
544 i_free_and_null(parser->error);
545
546 /* Make sure we finished streaming payload from previous command
547 before we continue. */
548 ret = smtp_command_parse_finish_data(parser);
549 if (ret <= 0) {
550 if (ret < 0) {
551 *error_code_r = parser->error_code;
552 *error_r = parser->error;
553 }
554 return ret;
555 }
556
557 ret = smtp_command_parse(parser);
558 if (ret <= 0) {
559 if (ret < 0) {
560 *error_code_r = parser->error_code;
561 *error_r = parser->error;
562 parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;
563 }
564 return ret;
565 }
566
567 i_assert(parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT);
568 *line_r = parser->state.cmd_params;
569 parser->auth_response = FALSE;
570 return 1;
571 }
572