summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/sieve-address.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-address.c')
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address.c558
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);
+}