diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-address.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-address.c | 558 |
1 files changed, 558 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-address.c b/pigeonhole/src/lib-sieve/sieve-address.c new file mode 100644 index 0000000..f9456f8 --- /dev/null +++ b/pigeonhole/src/lib-sieve/sieve-address.c @@ -0,0 +1,558 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +#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> + +/* + * Header address list + */ + +/* Forward declarations */ + +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 smtp_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); + +/* Stringlist object */ + +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_address( + struct sieve_header_address_list *addrlist, struct smtp_address *addr_r) +{ + struct smtp_address adummy; + int ret = 0; + + if (addr_r == NULL) + addr_r = &adummy; + + while (addrlist->cur_address != NULL) { + const struct message_address *aitem = addrlist->cur_address; + + addrlist->cur_address = addrlist->cur_address->next; + + if (!aitem->invalid_syntax && aitem->domain != NULL && + smtp_address_init_from_msg(addr_r, aitem) >= 0) + return 1; + ret = -1; + } + return ret; +} + +static int +sieve_header_address_list_next_item(struct sieve_address_list *_addrlist, + struct smtp_address *addr_r, + string_t **unparsed_r) +{ + struct sieve_header_address_list *addrlist = + (struct sieve_header_address_list *)_addrlist; + const struct sieve_runtime_env *runenv = _addrlist->strlist.runenv; + string_t *value_item = NULL; + bool trace = _addrlist->strlist.trace; + + if (addr_r != NULL) + smtp_address_init(addr_r, NULL, NULL); + if (unparsed_r != NULL) + *unparsed_r = NULL; + + for (;;) { + int ret; + + if ((ret = sieve_header_address_list_next_address( + addrlist, addr_r)) < 0 && + value_item != NULL) { + /* completely invalid address list is returned as-is */ + if (trace) { + sieve_runtime_trace( + runenv, 0, + "invalid address value `%s'", + str_sanitize(str_c(value_item), 80)); + } + if (unparsed_r != NULL) + *unparsed_r = value_item; + return 1; + } + if (ret > 0) { + if (trace) { + sieve_runtime_trace( + runenv, 0, + "address value `%s'", + str_sanitize(smtp_address_encode(addr_r), + 80)); + } + return 1; + } + + /* Read next header value from source list */ + if ((ret = sieve_stringlist_next_item(addrlist->field_values, + &value_item)) <= 0) + return ret; + if (str_len(value_item) == 0) { + /* empty header value is returned as-is */ + if (trace) { + sieve_runtime_trace(runenv, 0, + "empty address value"); + } + addrlist->cur_address = NULL; + if (unparsed_r != NULL) + *unparsed_r = value_item; + return 1; + } + + if (trace) { + sieve_runtime_trace( + 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, 0); + } + i_unreached(); +} + +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 smtp_address addr; + int ret; + + if ((ret = sieve_header_address_list_next_item( + addrlist, &addr, str_r)) <= 0) + return ret; + + if (addr.localpart != NULL) { + const char *addr_str = smtp_address_encode(&addr); + + if (str_r != NULL) + *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); +} + +/* + * RFC 2822 addresses + */ + +/* Mail message address according to RFC 2822 and implemented in the Dovecot + message address parser: + + address = mailbox / group + mailbox = name-addr / addr-spec + name-addr = [display-name] angle-addr + angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr + group = display-name ":" [mailbox-list / CFWS] ";" [CFWS] + display-name = phrase + + addr-spec = local-part "@" domain + local-part = dot-atom / quoted-string / obs-local-part + domain = dot-atom / domain-literal / obs-domain + domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] + dcontent = dtext / quoted-pair + dtext = NO-WS-CTL / ; Non white space controls + %d33-90 / ; The rest of the US-ASCII + %d94-126 ; characters not including "[", + ; "]", or "\" + + atext = ALPHA / DIGIT / ; Any character except controls, + "!" / "#" / ; SP, and specials. + "$" / "%" / ; Used for atoms + "&" / "'" / + "*" / "+" / + "-" / "/" / + "=" / "?" / + "^" / "_" / + "`" / "{" / + "|" / "}" / + "~" + atom = [CFWS] 1*atext [CFWS] + dot-atom = [CFWS] dot-atom-text [CFWS] + dot-atom-text = 1*atext *("." 1*atext) + word = atom / quoted-string + phrase = 1*word / obs-phrase + + Message address specification as allowed bij the RFC 5228 SIEVE + specification: + sieve-address = addr-spec ; simple address + / phrase "<" addr-spec ">" ; name & addr-spec\ + + Which concisely is about equal to: + sieve-address = mailbox + */ + +/* + * Address parse context + */ + +struct sieve_message_address_parser { + struct rfc822_parser_context parser; + + string_t *str; + string_t *local_part; + string_t *domain; + + string_t *error; +}; + +/* + * Error handling + */ + +static inline void ATTR_FORMAT(2, 3) +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); + } +} + +/* + Partial RFC 2822 address parser + + FIXME: lots of overlap with dovecot/src/lib-mail/message-parser.c + --> this implementation adds textual error reporting + MERGE! + */ + +static int check_local_part(struct sieve_message_address_parser *ctx) +{ + const unsigned char *p, *pend; + + p = str_data(ctx->local_part); + pend = p + str_len(ctx->local_part); + while (p < pend) { + if (*p < 0x20 || *p > 0x7e) + return -1; + p++; + } + return 0; +} + +static int parse_local_part(struct sieve_message_address_parser *ctx) +{ + int ret; + + /* + local-part = dot-atom / quoted-string / obs-local-part + obs-local-part = word *("." word) + */ + 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 = -1; + /* NOTE: this deviates from dot-atom syntax to allow some + Japanese mail addresses with dots at non-standard places to + be accepted. */ + do { + while (*ctx->parser.data == '.') { + str_append_c(ctx->local_part, '.'); + ctx->parser.data++; + if (ctx->parser.data == ctx->parser.end) { + /* @domain is missing, but local-part + parsing was successful */ + return 0; + } + ret = 1; + } + if (*ctx->parser.data == '@') + break; + ret = rfc822_parse_atom(&ctx->parser, ctx->local_part); + } while (ret > 0 && *ctx->parser.data == '.'); + } + + if (ret < 0 || check_local_part(ctx) < 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) +{ + /* addr-spec = local-part "@" domain */ + 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; + + /* sieve-address = addr-spec ; simple address + / phrase "<" addr-spec ">" ; name & addr-spec + */ + + /* Record parser state in case we fail at our first attempt */ + start = ctx->parser.data; + + /* First try: phrase "<" addr-spec ">" ; name & addr-spec */ + str_truncate(ctx->str, 0); + if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 || + *ctx->parser.data != '<') { + /* Failed; try just bare addr-spec */ + ctx->parser.data = start; + return parse_addr_spec(ctx); + } + + /* "<" addr-spec ">" */ + ctx->parser.data++; + + if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0) { + if (ret < 0) + sieve_address_error(ctx, "invalid characters after <"); + return ret; + } + + if (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) +{ + /* Initialize parser */ + + rfc822_parser_init(&ctx->parser, address, addr_size, NULL); + + /* Parse */ + + rfc822_skip_lwsp(&ctx->parser); + + if (ctx->parser.data == ctx->parser.end) { + sieve_address_error(ctx, "empty address"); + return FALSE; + } + + if (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) { + /* Not gonna happen */ + 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; +} + +static bool +sieve_address_do_validate(const unsigned char *address, size_t size, + const char **error_r) +{ + struct sieve_message_address_parser ctx; + + *error_r = NULL; + + if (address == NULL) { + *error_r = "null address"; + return FALSE; + } + + i_zero(&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, address, size)) { + *error_r = str_c(ctx.error); + return FALSE; + } + + return TRUE; +} + +static const struct smtp_address * +sieve_address_do_parse(const unsigned char *address, size_t size, + const char **error_r) +{ + struct sieve_message_address_parser ctx; + + *error_r = NULL; + + if (address == NULL) + return NULL; + + i_zero(&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, address, size)) { + *error_r = str_c(ctx.error); + return NULL; + } + + (void)str_lcase(str_c_modifiable(ctx.domain)); + + return smtp_address_create_temp(str_c(ctx.local_part), + str_c(ctx.domain)); +} + +/* + * Sieve address + */ + +const struct smtp_address * +sieve_address_parse(const char *address, const char **error_r) +{ + return sieve_address_do_parse((const unsigned char *)address, + strlen(address), error_r); +} + +const struct smtp_address * +sieve_address_parse_str(string_t *address, const char **error_r) +{ + return sieve_address_do_parse(str_data(address), str_len(address), + error_r); +} + +bool sieve_address_validate(const char *address, const char **error_r) +{ + return sieve_address_do_validate((const unsigned char *)address, + strlen(address), error_r); +} + +bool sieve_address_validate_str(string_t *address, const char **error_r) +{ + return sieve_address_do_validate(str_data(address), str_len(address), + error_r); +} |