Commit message: lib-smtp: smtp-command-parser - Fix infinite loop occurring when command stream ends in UTF-8 character.

Broken by 8f08f1944be438a2422b604c08e5060b5c7bd72f.
CVE: CVE-2020-7046
CWE: CWE-835
Vulnerability description: lib-smtp in submission-login and lmtp in Dovecot 2.3.9 before 2.3.9.3 mishandles truncated UTF-8 data in command parameters, as demonstrated by the unauthenticated triggering of a submission-login infinite loop.

Project: core

static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
{
	const unsigned char *p, *mp;
	uoff_t max_size = (parser->auth_response ?
		parser->limits.max_auth_size :
		parser->limits.max_parameters_size);

	/* We assume parameters to match textstr (HT, SP, Printable US-ASCII).
	   For command parameters, we also accept valid UTF-8 characters.
	 */
	p = parser->cur + parser->state.poff;
	while (p < parser->end) {
		unichar_t ch;
		int nch = 1;

		if (parser->auth_response)
			ch = *p;
		else {
			nch = uni_utf8_get_char_n(p, (size_t)(p - parser->end),
						  &ch);
		}
		if (nch < 0) {
			smtp_command_parser_error(parser,
				SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
				"Invalid UTF-8 character in command parameters");
			return -1;
		}
		if ((parser->auth_response || (ch & 0x80) == 0x00) &&
		    !smtp_char_is_textstr((unsigned char)ch))
			break;
		p += nch;
	}
	if (max_size > 0 && (uoff_t)(p - parser->cur) > max_size) {
		smtp_command_parser_error(parser,
			SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
			"%s line is too long",
			(parser->auth_response ?
				"AUTH response" : "Command"));
		return -1;
	}
	parser->state.poff = p - parser->cur;
	if (p == parser->end)
		return 0;

	/* In the interest of improved interoperability, SMTP receivers SHOULD
	   tolerate trailing white space before the terminating <CRLF>.

	   WSP =  SP / HTAB ; white space

	   --> Trim the end of the buffer
	 */
	mp = p;
	if (mp > parser->cur) {
		while (mp > parser->cur && (*(mp-1) == ' ' || *(mp-1) == '\t'))
			mp--;
	}

	if (!parser->auth_response && mp > parser->cur && *parser->cur == ' ') {
		smtp_command_parser_error(parser,
			SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
			"Duplicate space after command name");
		return -1;
	}

	parser->state.cmd_params = i_strdup_until(parser->cur, mp);
	parser->cur = p;
	parser->state.poff = 0;
	return 1;
}

Project: core

static int smtp_command_parse_parameters(struct smtp_command_parser *parser)
{
	const unsigned char *p, *mp;
	uoff_t max_size = (parser->auth_response ?
		parser->limits.max_auth_size :
		parser->limits.max_parameters_size);
	int nch = 1;

	/* We assume parameters to match textstr (HT, SP, Printable US-ASCII).
	   For command parameters, we also accept valid UTF-8 characters.
	 */
	p = parser->cur + parser->state.poff;
	while (p < parser->end) {
		unichar_t ch;

		if (parser->auth_response)
			ch = *p;
		else {
			nch = uni_utf8_get_char_n(p, (size_t)(p - parser->end),
						  &ch);
		}
		if (nch == 0)
			break;
		if (nch < 0) {
			smtp_command_parser_error(parser,
				SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
				"Invalid UTF-8 character in command parameters");
			return -1;
		}
		if ((parser->auth_response || (ch & 0x80) == 0x00) &&
		    !smtp_char_is_textstr((unsigned char)ch))
			break;
		p += nch;
	}
	if (max_size > 0 && (uoff_t)(p - parser->cur) > max_size) {
		smtp_command_parser_error(parser,
			SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG,
			"%s line is too long",
			(parser->auth_response ?
				"AUTH response" : "Command"));
		return -1;
	}
	parser->state.poff = p - parser->cur;
	if (p == parser->end || nch == 0)
		return 0;

	/* In the interest of improved interoperability, SMTP receivers SHOULD
	   tolerate trailing white space before the terminating <CRLF>.

	   WSP =  SP / HTAB ; white space

	   --> Trim the end of the buffer
	 */
	mp = p;
	if (mp > parser->cur) {
		while (mp > parser->cur && (*(mp-1) == ' ' || *(mp-1) == '\t'))
			mp--;
	}

	if (!parser->auth_response && mp > parser->cur && *parser->cur == ' ') {
		smtp_command_parser_error(parser,
			SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND,
			"Duplicate space after command name");
		return -1;
	}

	parser->state.cmd_params = i_strdup_until(parser->cur, mp);
	parser->cur = p;
	parser->state.poff = 0;
	return 1;
}