#include "lib.h"
#include "str.h"
#include "str-sanitize.h"
#include "rfc822-parser.h"
#include "message-address.h"
#include "sieve-common.h"
#include "sieve-runtime-trace.h"
#include "sieve-address.h"
#include <ctype.h>
static int sieve_header_address_list_next_string_item
(struct sieve_stringlist *_strlist, string_t **str_r);
static int sieve_header_address_list_next_item
(struct sieve_address_list *_addrlist, struct sieve_address *addr_r,
string_t **unparsed_r);
static void sieve_header_address_list_reset
(struct sieve_stringlist *_strlist);
static void sieve_header_address_list_set_trace
(struct sieve_stringlist *_strlist, bool trace);
struct sieve_header_address_list {
struct sieve_address_list addrlist;
struct sieve_stringlist *field_values;
const struct message_address *cur_address;
};
struct sieve_address_list *sieve_header_address_list_create
(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values)
{
struct sieve_header_address_list *addrlist;
addrlist = t_new(struct sieve_header_address_list, 1);
addrlist->addrlist.strlist.runenv = renv;
addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
addrlist->addrlist.strlist.next_item =
sieve_header_address_list_next_string_item;
addrlist->addrlist.strlist.reset = sieve_header_address_list_reset;
addrlist->addrlist.strlist.set_trace = sieve_header_address_list_set_trace;
addrlist->addrlist.next_item = sieve_header_address_list_next_item;
addrlist->field_values = field_values;
return &addrlist->addrlist;
}
static int sieve_header_address_list_next_item
(struct sieve_address_list *_addrlist, struct sieve_address *addr_r,
string_t **unparsed_r)
{
struct sieve_header_address_list *addrlist =
(struct sieve_header_address_list *) _addrlist;
const struct message_address *aitem;
bool valid = TRUE;
if ( addr_r != NULL ) addr_r->local_part = NULL;
if ( unparsed_r != NULL ) *unparsed_r = NULL;
while ( addrlist->cur_address == NULL ) {
string_t *value_item = NULL;
int ret;
if ( (ret=sieve_stringlist_next_item(addrlist->field_values, &value_item))
<= 0 )
return ret;
if ( _addrlist->strlist.trace ) {
sieve_runtime_trace(_addrlist->strlist.runenv, 0,
"parsing address header value `%s'",
str_sanitize(str_c(value_item), 80));
}
addrlist->cur_address = message_address_parse
(pool_datastack_create(), (const unsigned char *) str_data(value_item),
str_len(value_item), 256, FALSE);
aitem = addrlist->cur_address;
while ( aitem != NULL) {
if ( aitem->invalid_syntax )
valid = FALSE;
aitem = aitem->next;
}
if ( addrlist->cur_address == NULL || !valid ) {
addrlist->cur_address = NULL;
if ( unparsed_r != NULL) *unparsed_r = value_item;
return 1;
}
aitem = addrlist->cur_address;
while ( aitem != NULL && aitem->domain == NULL ) {
aitem = aitem->next;
}
addrlist->cur_address = aitem;
}
if ( addr_r != NULL ) {
addr_r->local_part = addrlist->cur_address->mailbox;
addr_r->domain = addrlist->cur_address->domain;
}
aitem = addrlist->cur_address->next;
while ( aitem != NULL && aitem->domain == NULL ) {
aitem = aitem->next;
}
addrlist->cur_address = aitem;
return 1;
}
static int sieve_header_address_list_next_string_item
(struct sieve_stringlist *_strlist, string_t **str_r)
{
struct sieve_address_list *addrlist = (struct sieve_address_list *)_strlist;
struct sieve_address addr;
int ret;
if ( (ret=sieve_header_address_list_next_item(addrlist, &addr, str_r)) <= 0 )
return ret;
if ( addr.local_part != NULL ) {
const char *addr_str = sieve_address_to_string(&addr);
*str_r = t_str_new_const(addr_str, strlen(addr_str));
}
return 1;
}
static void sieve_header_address_list_reset
(struct sieve_stringlist *_strlist)
{
struct sieve_header_address_list *addrlist =
(struct sieve_header_address_list *)_strlist;
sieve_stringlist_reset(addrlist->field_values);
addrlist->cur_address = NULL;
}
static void sieve_header_address_list_set_trace
(struct sieve_stringlist *_strlist, bool trace)
{
struct sieve_header_address_list *addrlist =
(struct sieve_header_address_list *)_strlist;
sieve_stringlist_set_trace(addrlist->field_values, trace);
}
struct sieve_message_address_parser {
struct rfc822_parser_context parser;
string_t *str;
string_t *local_part;
string_t *domain;
string_t *error;
};
static inline void sieve_address_error
(struct sieve_message_address_parser *ctx, const char *fmt, ...)
ATTR_FORMAT(2, 3);
static inline void sieve_address_error
(struct sieve_message_address_parser *ctx, const char *fmt, ...)
{
va_list args;
if ( str_len(ctx->error) == 0 ) {
va_start(args, fmt);
str_vprintfa(ctx->error, fmt, args);
va_end(args);
}
}
static int parse_local_part(struct sieve_message_address_parser *ctx)
{
int ret;
if (ctx->parser.data == ctx->parser.end) {
sieve_address_error(ctx, "empty local part");
return -1;
}
str_truncate(ctx->local_part, 0);
if (*ctx->parser.data == '"')
ret = rfc822_parse_quoted_string(&ctx->parser, ctx->local_part);
else
ret = rfc822_parse_dot_atom(&ctx->parser, ctx->local_part);
if (ret < 0) {
sieve_address_error(ctx, "invalid local part");
return -1;
}
return ret;
}
static int parse_domain(struct sieve_message_address_parser *ctx)
{
int ret;
str_truncate(ctx->domain, 0);
if ((ret = rfc822_parse_domain(&ctx->parser, ctx->domain)) < 0) {
sieve_address_error(ctx, "invalid or missing domain");
return -1;
}
return ret;
}
static int parse_addr_spec(struct sieve_message_address_parser *ctx)
{
int ret;
if ((ret = parse_local_part(ctx)) < 0)
return ret;
if ( ret > 0 && *ctx->parser.data == '@') {
return parse_domain(ctx);
}
sieve_address_error(ctx, "invalid or lonely local part '%s' (expecting '@')",
str_sanitize(str_c(ctx->local_part), 80));
return -1;
}
static int parse_mailbox(struct sieve_message_address_parser *ctx)
{
int ret;
const unsigned char *start;
start = ctx->parser.data;
str_truncate(ctx->str, 0);
if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
*ctx->parser.data != '<') {
ctx->parser.data = start;
return parse_addr_spec(ctx);
}
ctx->parser.data++;
if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0 ) {
if ( ret < 0 )
sieve_address_error(ctx, "invalid characters after <");
return ret;
}
if ((ret = parse_addr_spec(ctx)) < 0)
return -1;
if (*ctx->parser.data != '>') {
sieve_address_error(ctx, "missing '>'");
return -1;
}
ctx->parser.data++;
if ( (ret=rfc822_skip_lwsp(&ctx->parser)) < 0 )
sieve_address_error(ctx, "address ends with invalid characters");
return ret;
}
static bool parse_mailbox_address
(struct sieve_message_address_parser *ctx, const unsigned char *address,
unsigned int addr_size)
{
int ret;
rfc822_parser_init(&ctx->parser, address, addr_size, NULL);
rfc822_skip_lwsp(&ctx->parser);
if (ctx->parser.data == ctx->parser.end) {
sieve_address_error(ctx, "empty address");
return FALSE;
}
if ((ret = parse_mailbox(ctx)) < 0) {
return FALSE;
}
if (ctx->parser.data != ctx->parser.end) {
if ( *ctx->parser.data == ',' )
sieve_address_error(ctx, "not a single addres (found ',')");
else
sieve_address_error(ctx, "address ends in invalid characters");
return FALSE;
}
if ( str_len(ctx->domain) == 0 ) {
sieve_address_error(ctx, "missing domain");
return FALSE;
}
if ( str_len(ctx->local_part) == 0 ) {
sieve_address_error(ctx, "missing local part");
return FALSE;
}
return TRUE;
}
bool sieve_rfc2822_mailbox_validate(const char *address, const char **error_r)
{
struct sieve_message_address_parser ctx;
if ( address == NULL ) {
*error_r = "null address";
return FALSE;
}
memset(&ctx, 0, sizeof(ctx));
ctx.local_part = t_str_new(128);
ctx.domain = t_str_new(128);
ctx.str = t_str_new(128);
ctx.error = t_str_new(128);
if ( !parse_mailbox_address(&ctx, (const unsigned char *) address,
strlen(address)) ) {
if ( error_r != NULL )
*error_r = str_c(ctx.error);
return FALSE;
}
if ( error_r != NULL )
*error_r = NULL;
return TRUE;
}
const char *sieve_rfc2822_mailbox_normalize
(const char *address, const char **error_r)
{
struct sieve_message_address_parser ctx;
if ( error_r != NULL )
*error_r = NULL;
if ( address == NULL ) return NULL;
memset(&ctx, 0, sizeof(ctx));
ctx.local_part = t_str_new(128);
ctx.domain = t_str_new(128);
ctx.str = t_str_new(128);
ctx.error = t_str_new(128);
if ( !parse_mailbox_address(&ctx, (const unsigned char *) address,
strlen(address)) ) {
if ( error_r != NULL )
*error_r = str_c(ctx.error);
return NULL;
}
(void)str_lcase(str_c_modifiable(ctx.domain));
return t_strconcat(str_c(ctx.local_part), "@", str_c(ctx.domain), NULL);
}
const char *sieve_address_normalize
(string_t *address, const char **error_r)
{
struct sieve_message_address_parser ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.local_part = t_str_new(128);
ctx.domain = t_str_new(128);
ctx.str = t_str_new(128);
ctx.error = t_str_new(128);
if ( !parse_mailbox_address(&ctx, str_data(address), str_len(address)) )
{
*error_r = str_c(ctx.error);
return NULL;
}
*error_r = NULL;
(void)str_lcase(str_c_modifiable(ctx.domain));
return t_strconcat(str_c(ctx.local_part), "@", str_c(ctx.domain), NULL);
}
bool sieve_address_validate
(string_t *address, const char **error_r)
{
struct sieve_message_address_parser ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.local_part = ctx.domain = ctx.str = t_str_new(128);
ctx.error = t_str_new(128);
if ( !parse_mailbox_address(&ctx, str_data(address), str_len(address)) )
{
*error_r = str_c(ctx.error);
return FALSE;
}
*error_r = NULL;
return TRUE;
}
int sieve_address_compare
(const char *address1, const char *address2, bool normalized ATTR_UNUSED)
{
i_assert(address1 != NULL);
i_assert(address2 != NULL);
return strcasecmp(address1, address2);
}
#define AB (1<<0)
#define DB (1<<1)
#define QB (1<<2)
#define IS_DTEXT(C) ((rfc2821_chars[C] & DB) != 0)
#define IS_QTEXT(C) ((rfc2821_chars[C] & QB) == 0)
#define IS_TEXT(C) ((C) != '\r' && (C) != '\n' && (C) < 128)
static unsigned char rfc2821_chars[256] = {
DB, DB, DB, DB, DB, DB, DB, DB, DB, QB, QB, DB, DB, QB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, QB, DB|AB, QB|DB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB, DB, DB|AB, DB|AB, DB, DB|AB, DB, DB|AB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB|AB, DB, DB|AB, DB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, 0, QB, 0, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|QB,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
struct sieve_envelope_address_parser {
pool_t pool;
const unsigned char *data;
const unsigned char *end;
string_t *str;
struct sieve_address *address;
};
static int path_skip_white_space(struct sieve_envelope_address_parser *parser)
{
while ( parser->data < parser->end &&
(*parser->data == ' ' || *parser->data == '\t') )
parser->data++;
return parser->data < parser->end;
}
static int path_skip_address_literal
(struct sieve_envelope_address_parser *parser)
{
int count = 0;
i_assert ( *parser->data == '[' );
str_append_c(parser->str, *parser->data);
parser->data++;
while ( parser->data < parser->end ) {
if ( *parser->data == '\\' ) {
str_append_c(parser->str, *parser->data);
parser->data++;
if ( parser->data < parser->end ) {
if ( !IS_TEXT(*parser->data) )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
} else return -1;
} else {
if ( !IS_DTEXT(*parser->data) )
break;
str_append_c(parser->str, *parser->data);
parser->data++;
}
count++;
}
if ( count == 0 || parser->data >= parser->end || *parser->data != ']' )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
return parser->data < parser->end;
}
static int path_parse_domain
(struct sieve_envelope_address_parser *parser, bool skip)
{
int ret;
str_truncate(parser->str, 0);
if ( *parser->data == '[' ) {
ret = path_skip_address_literal(parser);
if ( ret < 0 ) return ret;
} else {
for (;;) {
if ( !i_isalnum(*parser->data) )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
while ( parser->data < parser->end ) {
if ( !i_isalnum(*parser->data) && *parser->data != '-' )
break;
str_append_c(parser->str, *parser->data);
parser->data++;
}
if ( !i_isalnum(*(parser->data-1)) )
return -1;
if ( (ret=path_skip_white_space(parser)) < 0 )
return ret;
if ( *parser->data != '.' )
break;
str_append_c(parser->str, *parser->data);
parser->data++;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
}
}
if ( !skip )
parser->address->domain = p_strdup(parser->pool, str_c(parser->str));
return path_skip_white_space(parser);
}
static int path_skip_source_route(struct sieve_envelope_address_parser *parser)
{
int ret;
if ( *parser->data == '@' ) {
parser->data++;
for (;;) {
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
if ( (ret=path_parse_domain(parser, TRUE)) <= 0 )
return -1;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return ret;
if ( *parser->data != ',' )
break;
parser->data++;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
if ( *parser->data != '@' )
return -1;
parser->data++;
}
if ( *parser->data != ':' )
return -1;
parser->data++;
}
return path_skip_white_space(parser);
}
static int path_parse_local_part(struct sieve_envelope_address_parser *parser)
{
int ret;
str_truncate(parser->str, 0);
if ( *parser->data == '"' ) {
str_append_c(parser->str, *parser->data);
parser->data++;
while ( parser->data < parser->end ) {
if ( *parser->data == '\\' ) {
str_append_c(parser->str, *parser->data);
parser->data++;
if ( parser->data < parser->end ) {
if ( !IS_TEXT(*parser->data) )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
} else return -1;
} else {
if ( !IS_QTEXT(*parser->data) )
break;
str_append_c(parser->str, *parser->data);
parser->data++;
}
}
if ( *parser->data != '"' )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
if ( (ret=path_skip_white_space(parser)) < 0 )
return ret;
} else {
for (;;) {
if ( !IS_ATEXT(*parser->data) )
return -1;
str_append_c(parser->str, *parser->data);
parser->data++;
while ( parser->data < parser->end && IS_ATEXT(*parser->data)) {
str_append_c(parser->str, *parser->data);
parser->data++;
}
if ( (ret=path_skip_white_space(parser)) < 0 )
return ret;
if ( *parser->data != '.' )
break;
str_append_c(parser->str, *parser->data);
parser->data++;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
}
}
parser->address->local_part = p_strdup(parser->pool, str_c(parser->str));
return parser->data < parser->end;
}
static int path_parse_mailbox(struct sieve_envelope_address_parser *parser)
{
int ret;
if ( (ret=path_parse_local_part(parser)) < 0
|| (ret=path_skip_white_space(parser)) < 0 )
return -1;
if ( ret == 0 || *parser->data != '@' ) {
return ret;
}
parser->data++;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
return path_parse_domain(parser, FALSE);
}
static int path_parse(struct sieve_envelope_address_parser *parser)
{
int ret;
bool brackets = FALSE;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return ret;
if ( *parser->data == '<' ) {
parser->data++;
brackets = TRUE;
if ( (ret=path_skip_white_space(parser)) <= 0 )
return -1;
if ( *parser->data == '>' ) {
parser->data++;
return path_skip_white_space(parser);
}
}
if ( (ret=path_skip_source_route(parser)) <= 0 )
return -1;
if ( (ret=path_parse_mailbox(parser)) < 0 )
return -1;
if ( ret > 0 && (ret=path_skip_white_space(parser)) < 0 )
return -1;
if ( brackets ) {
if ( ret <= 0 ) return -1;
if ( *parser->data != '>' )
return -1;
parser->data++;
}
return parser->data < parser->end;
}
const struct sieve_address *sieve_address_parse_envelope_path
(pool_t pool, const char *field_value)
{
struct sieve_envelope_address_parser parser;
int ret;
if ( field_value == NULL ) {
return p_new(pool, struct sieve_address, 1);
}
parser.pool = pool;
parser.data = (const unsigned char *) field_value;
parser.end = (const unsigned char *) field_value + strlen(field_value);
parser.address = p_new(pool, struct sieve_address, 1);
parser.str = t_str_new(256);
if ( (ret=path_parse(&parser)) < 0 )
return NULL;
if ( ret > 0 && path_skip_white_space(&parser) < 0 )
return NULL;
if ( parser.data != parser.end )
return NULL;
return parser.address;
}