diff --git a/src/lib-sieve/sieve-lexer.c b/src/lib-sieve/sieve-lexer.c index 26373caf69c49a0c81a751928d1a0e06f515d03a..e66c358975f09fa965fd47d20ee957b3f7dcbd6c 100644 --- a/src/lib-sieve/sieve-lexer.c +++ b/src/lib-sieve/sieve-lexer.c @@ -286,6 +286,14 @@ static inline int sieve_lexer_curchar(struct sieve_lexer *lexer) return lexer->buffer[lexer->buffer_pos]; } +static inline const char *_char_sanitize(int ch) +{ + if ( ch > 31 && ch < 127 ) + return t_strdup_printf("'%c'", ch); + + return t_strdup_printf("0x%02x", ch); +} + /* sieve_lexer_scan_raw_token: * Scans valid tokens and whitespace */ @@ -308,13 +316,24 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) case '#': sieve_lexer_shift(lexer); while ( sieve_lexer_curchar(lexer) != '\n' ) { - if ( sieve_lexer_curchar(lexer) == -1 ) { - sieve_lexer_error(lexer, "end of file before end of hash comment"); - lexer->token_type = STT_ERROR; + switch( sieve_lexer_curchar(lexer) ) { + case -1: + sieve_lexer_error(lexer, "end of file before end of hash comment"); + lexer->token_type = STT_ERROR; return FALSE; + case '\0': + sieve_lexer_error(lexer, "encountered NUL character in hash comment"); + lexer->token_type = STT_ERROR; + return FALSE; + default: + break; } + + /* Stray CR is ignored */ + sieve_lexer_shift(lexer); } + sieve_lexer_shift(lexer); lexer->token_type = STT_WHITESPACE; @@ -333,7 +352,14 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) sieve_lexer_shift(lexer); while ( TRUE ) { - if ( sieve_lexer_curchar(lexer) == '*' ) { + switch ( sieve_lexer_curchar(lexer) ) { + case -1: + sieve_lexer_error(lexer, + "end of file before end of bracket comment ('/* ... */') " + "started at line %d", start_line); + lexer->token_type = STT_ERROR; + return FALSE; + case '*': sieve_lexer_shift(lexer); if ( sieve_lexer_curchar(lexer) == '/' ) { @@ -349,16 +375,15 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) lexer->token_type = STT_ERROR; return FALSE; } - - } else if ( sieve_lexer_curchar(lexer) == -1 ) { + break; + case '\0': sieve_lexer_error(lexer, - "end of file before end of bracket comment ('/* ... */') " - "started at line %d", start_line); + "encountered NUL character in bracket comment"); lexer->token_type = STT_ERROR; - return FALSE; - - } else - sieve_lexer_shift(lexer); + return FALSE; + default: + sieve_lexer_shift(lexer); + } } i_unreached(); @@ -395,22 +420,57 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) str = lexer->token_str_value; while ( sieve_lexer_curchar(lexer) != '"' ) { - if ( sieve_lexer_curchar(lexer) == -1 ) { + if ( sieve_lexer_curchar(lexer) == '\\' ) { + sieve_lexer_shift(lexer); + } + + switch ( sieve_lexer_curchar(lexer) ) { + + /* End of file */ + case -1: sieve_lexer_error(lexer, "end of file before end of quoted string " "started at line %d", start_line); lexer->token_type = STT_ERROR; return FALSE; - } - - if ( sieve_lexer_curchar(lexer) == '\\' ) { + + /* NUL character */ + case '\0': + sieve_lexer_error(lexer, + "encountered NUL character in quoted string " + "started at line %d", start_line); + lexer->token_type = STT_ERROR; + return FALSE; + + /* CR .. check for LF */ + case '\r': sieve_lexer_shift(lexer); - } - if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) - str_append_c(str, sieve_lexer_curchar(lexer)); + if ( sieve_lexer_curchar(lexer) != '\n' ) { + sieve_lexer_error(lexer, + "found stray carriage-return (CR) character " + "in quoted string started at line %d", start_line); + lexer->token_type = STT_ERROR; + return FALSE; + } - sieve_lexer_shift(lexer); + if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) + str_append(str, "\r\n"); + break; + + /* Loose LF is allowed (non-standard) and converted to CRLF */ + case '\n': + if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) + str_append(str, "\r\n"); + break; + + /* Other characters */ + default: + if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) + str_append_c(str, sieve_lexer_curchar(lexer)); + } + + sieve_lexer_shift(lexer); } sieve_lexer_shift(lexer); @@ -582,26 +642,30 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) sieve_lexer_shift(lexer); /* Discard hash comment or handle single CRLF */ - if ( sieve_lexer_curchar(lexer) == '#' ) { + switch ( sieve_lexer_curchar(lexer) ) { + case '#': while ( sieve_lexer_curchar(lexer) != '\n' ) sieve_lexer_shift(lexer); - } else if ( sieve_lexer_curchar(lexer) == '\r' ) { + break; + case '\r': sieve_lexer_shift(lexer); + break; } /* Terminating LF required */ - if ( sieve_lexer_curchar(lexer) == '\n' ) { + switch ( sieve_lexer_curchar(lexer) ) { + case '\n': sieve_lexer_shift(lexer); - } else { - if ( sieve_lexer_curchar(lexer) == -1 ) { - sieve_lexer_error(lexer, - "end of file before end of multi-line string"); - } else { - sieve_lexer_error(lexer, - "invalid character '%c' after 'text:' in multiline string", - sieve_lexer_curchar(lexer)); - } - + break; + case -1: + sieve_lexer_error(lexer, + "end of file before end of multi-line string"); + lexer->token_type = STT_ERROR; + return FALSE; + default: + sieve_lexer_error(lexer, + "invalid character %s after 'text:' in multiline string", + _char_sanitize(sieve_lexer_curchar(lexer))); lexer->token_type = STT_ERROR; return FALSE; } @@ -617,18 +681,20 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) if ( sieve_lexer_curchar(lexer) == '.' ) { sieve_lexer_shift(lexer); - /* Check for CRLF */ + /* Check for CR.. */ if ( sieve_lexer_curchar(lexer) == '\r' ) { sieve_lexer_shift(lexer); cr_shifted = TRUE; } + /* ..LF */ if ( sieve_lexer_curchar(lexer) == '\n' ) { sieve_lexer_shift(lexer); + /* Check whether length limit was violated */ if ( str_len(str) > SIEVE_MAX_STRING_LEN ) { sieve_lexer_error(lexer, - "literal string started at line %d is too long " + "multi-line string started at line %d is too long " "(longer than %llu bytes)", start_line, (long long) SIEVE_MAX_STRING_LEN); lexer->token_type = STT_ERROR; @@ -638,8 +704,10 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) lexer->token_type = STT_STRING; return TRUE; } else if ( cr_shifted ) { - sieve_lexer_error(lexer, - "found CR without subsequent LF in multi-line string literal"); + /* Seen CR, but no LF */ + sieve_lexer_error(lexer, + "found stray carriage-return (CR) character " + "in multi-line string started at line %d", start_line); lexer->token_type = STT_ERROR; return FALSE; } @@ -652,22 +720,45 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) } /* Scan the rest of the line */ - while ( sieve_lexer_curchar(lexer) != '\n' ) { - if ( sieve_lexer_curchar(lexer) == -1 ) { + while ( sieve_lexer_curchar(lexer) != '\n' && + sieve_lexer_curchar(lexer) != '\r' ) { + + switch ( sieve_lexer_curchar(lexer) ) { + case -1: sieve_lexer_error(lexer, "end of file before end of multi-line string"); lexer->token_type = STT_ERROR; return FALSE; - } - - if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) - str_append_c(str, sieve_lexer_curchar(lexer)); + case '\0': + sieve_lexer_error(lexer, + "encountered NUL character in quoted string " + "started at line %d", start_line); + lexer->token_type = STT_ERROR; + return FALSE; + default: + if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) + str_append_c(str, sieve_lexer_curchar(lexer)); + } sieve_lexer_shift(lexer); } + /* If exited loop due to CR, skip it */ + if ( sieve_lexer_curchar(lexer) == '\r' ) { + sieve_lexer_shift(lexer); + } + + /* Now we must see an LF */ + if ( sieve_lexer_curchar(lexer) != '\n' ) { + sieve_lexer_error(lexer, + "found stray carriage-return (CR) character " + "in multi-line string started at line %d", start_line); + lexer->token_type = STT_ERROR; + return FALSE; + } + if ( str_len(str) <= SIEVE_MAX_STRING_LEN ) - str_append_c(str, '\n'); + str_append(str, "\r\n"); sieve_lexer_shift(lexer); } @@ -692,8 +783,8 @@ static bool sieve_lexer_scan_raw_token(struct sieve_lexer *lexer) /* Error (unknown character and EOF handled already) */ if ( lexer->token_type != STT_GARBAGE ) - sieve_lexer_error(lexer, "unexpected character(s) starting with '%c'", - sieve_lexer_curchar(lexer)); + sieve_lexer_error(lexer, "unexpected character(s) starting with %s", + _char_sanitize(sieve_lexer_curchar(lexer))); sieve_lexer_shift(lexer); lexer->token_type = STT_GARBAGE; return FALSE; diff --git a/src/testsuite/mail-raw.c b/src/testsuite/mail-raw.c index 7d0b8d83653b20f2cda2f01c48ec70471e172c35..8b805d5f19c25a84e2a65085e61bd99c0fff7bfd 100644 --- a/src/testsuite/mail-raw.c +++ b/src/testsuite/mail-raw.c @@ -56,8 +56,8 @@ struct mail_raw *mail_raw_open(string_t *mail_data) mailr = p_new(pool, struct mail_raw, 1); mailr->pool = pool; - mailr->input = i_stream_create_crlf(i_stream_create_from_data - (str_data(mail_data), str_len(mail_data))); + mailr->input = i_stream_create_from_data + (str_data(mail_data), str_len(mail_data)); mailr->box = mailbox_open(raw_ns->storage, "Dovecot Raw Mail", mailr->input, MAILBOX_OPEN_NO_INDEX_FILES);