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