bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch#define SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH 32
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parser_error(struct smtp_command_parser *parser,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const char *format, ...)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->error = i_strdup_vprintf(format, args);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parser_init(struct istream *input,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser = i_new(struct smtp_command_parser, 1);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (parser->limits.max_parameters_size == 0) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschvoid smtp_command_parser_deinit(struct smtp_command_parser **_parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch struct smtp_command_parser *parser = *_parser;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parser_restart(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschvoid smtp_command_parser_set_stream(struct smtp_command_parser *parser,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschstatic inline const char *_chr_sanitize(unsigned char c)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (c == 0x0a)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch return "<LF>";
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (c == 0x0d)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch return "<CR>";
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschstatic int smtp_command_parse_identifier(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const unsigned char *p;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* The commands themselves are alphabetic characters.
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if ((p - parser->cur) > SMTP_COMMAND_PARSER_MAX_COMMAND_LENGTH) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Command name is too long");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.cmd_name = str_ucase(i_strdup_until(parser->cur, p));
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschstatic int smtp_command_parse_parameters(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* We assume parameters to match textstr
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch => HT, SP, Printable US-ASCII
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch while (p < parser->end && smtp_char_is_textstr(*p))
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (max_size > 0 && (uoff_t)(p - parser->cur) > max_size) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "%s line is too long",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* In the interest of improved interoperability, SMTP receivers SHOULD
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch tolerate trailing white space before the terminating <CRLF>.
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch WSP = SP / HTAB ; white space
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch --> Trim the end of the buffer
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch while (mp > parser->cur && (*(mp-1) == ' ' || *(mp-1) == '\t'))
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (!parser->auth_response && mp > parser->cur && *parser->cur == ' ') {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Duplicate space after command name");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.cmd_params = i_strdup_until(parser->cur, mp);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parse_line(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* RFC 5321, Section 4.1.1:
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch SMTP commands are character strings terminated by <CRLF>. The
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch commands themselves are alphabetic characters terminated by <SP> if
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parameters follow and <CRLF> otherwise. (In the interest of improved
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch interoperability, SMTP receivers SHOULD tolerate trailing white space
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch before the terminating <CRLF>.)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* parse AUTH response as bare parameters */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* fall through */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if ((ret=smtp_command_parse_identifier(parser)) <= 0)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_SP;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* fall through */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Unexpected character %s in command name",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* fall through */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if ((ret=smtp_command_parse_parameters(parser)) <= 0)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_CR;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* fall through */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Unexpected character %s in %s",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "AUTH response" :
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "command parameters"));
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_LF;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* fall through */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Expected LF after CR at end of %s, "
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "but found %s",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* skip until end of line */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_INIT;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschstatic int smtp_command_parse(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch while ((ret = i_stream_read_data(parser->input, &begin, &size,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch i_stream_skip(parser->input, parser->cur - begin);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch old_bytes = i_stream_get_data_size(parser->input);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* should not really happen */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "%s line is too long",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Premature end of input");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Stream error: %s",
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschbool smtp_command_parser_pending_data(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch return i_stream_have_bytes_left(parser->data);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parse_finish_data(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch while ((ret = i_stream_read_data(parser->data, &data, &size, 0)) > 0)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (ret == 0 || parser->data->stream_errno != 0) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Invalid command data");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Command data too large");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Stream error while skipping command data: "
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschint smtp_command_parse_next(struct smtp_command_parser *parser,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const char **cmd_name_r, const char **cmd_params_r,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const char **error_r)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT ||
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* make sure we finished streaming payload from previous command
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch before we continue. */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if ((ret = smtp_command_parse_finish_data(parser)) <= 0) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch i_assert(parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch *cmd_params_r = (parser->state.cmd_params == NULL ?
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parse_data_with_size(struct smtp_command_parser *parser,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* not supposed to happen; command should check size */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->data = i_stream_create_error_str(EMSGSIZE,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Command data size exceeds maximum");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch // FIXME: make exact_size stream type
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch i_stream_create_limit(parser->input, size), size);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschsmtp_command_parse_data_with_dot(struct smtp_command_parser *parser)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch data = i_stream_create_dot(parser->input, TRUE);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if (parser->limits.max_data_size != (uoff_t)-1) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch "Command data size exceeds maximum");
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Boschint smtp_command_parse_auth_response(struct smtp_command_parser *parser,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const char **line_r,
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch const char **error_r)
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state == SMTP_COMMAND_PARSE_STATE_INIT ||
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state == SMTP_COMMAND_PARSE_STATE_ERROR);
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch *error_code_r = parser->error_code = SMTP_COMMAND_PARSE_ERROR_NONE;
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch /* make sure we finished streaming payload from previous command
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch before we continue. */
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch if ((ret = smtp_command_parse_finish_data(parser)) <= 0) {
8141e652481ff9db3bce36fdc1fe04c75a3ba7e3Stephan Bosch parser->state.state = SMTP_COMMAND_PARSE_STATE_ERROR;