/* 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 /* * 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); }