diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c new file mode 100644 index 0000000..c5a0953 --- /dev/null +++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c @@ -0,0 +1,683 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and + draft-duerst-mailto-bis-05.txt. Should fully migrate to new + specification when it matures. This requires modifications to the + address parser (no whitespace allowed within the address itself) and + UTF-8 support will be required in the URL. + */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "str-sanitize.h" + +#include "rfc2822.h" + +#include "sieve-common.h" +#include "sieve-error.h" +#include "sieve-address.h" +#include "sieve-message.h" + +#include "uri-mailto.h" + +/* Parser object */ + +struct uri_mailto_parser { + pool_t pool; + const struct uri_mailto_log *log; + + struct uri_mailto *uri; + + const char **reserved_headers; + const char **unique_headers; + + int max_recipients; + int max_headers; +}; + +/* Error handling */ + +#define uri_mailto_error(PARSER, ...) \ + uri_mailto_log((PARSER), LOG_TYPE_ERROR, \ + __FILE__, __LINE__, __VA_ARGS__) +#define uri_mailto_warning(PARSER, ...) \ + uri_mailto_log((PARSER), LOG_TYPE_WARNING, \ + __FILE__, __LINE__, __VA_ARGS__) + +static inline void ATTR_FORMAT(5, 6) +uri_mailto_log(struct uri_mailto_parser *parser, enum log_type log_type, + const char *csrc_filename, unsigned int csrc_linenum, + const char *fmt, ...) +{ + va_list args; + + if (parser->log == NULL || parser->log->logv == NULL) + return; + + va_start(args, fmt); + parser->log->logv(parser->log->context, log_type, + csrc_filename, csrc_linenum, fmt, args); + va_end(args); +} + + +/* + * Error handling + */ + +/* + * Reserved and unique headers + */ + +static inline bool +uri_mailto_header_is_reserved(struct uri_mailto_parser *parser, + const char *field_name) +{ + const char **hdr = parser->reserved_headers; + + if (hdr == NULL) + return FALSE; + + /* Check whether it is reserved */ + while (*hdr != NULL) { + if (strcasecmp(field_name, *hdr) == 0) + return TRUE; + hdr++; + } + return FALSE; +} + +static inline bool +uri_mailto_header_is_unique(struct uri_mailto_parser *parser, + const char *field_name) +{ + const char **hdr = parser->unique_headers; + + if (hdr == NULL) + return FALSE; + + /* Check whether it is supposed to be unique */ + while (*hdr != NULL) { + if (strcasecmp(field_name, *hdr) == 0) + return TRUE; + hdr++; + } + return FALSE; +} + +/* + * Low-level URI parsing. + * + * FIXME: much of this implementation will be common to other URI schemes. This + * should be merged into a common implementation. + */ + +static const char _qchar_lookup[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70 + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F0 +}; + +static inline bool _is_qchar(char c) +{ + return ((_qchar_lookup[(unsigned char)c] & 0x01) != 0); +} + +static inline int _decode_hex_digit(unsigned char digit) +{ + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return (int) digit - '0'; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + return (int) digit - 'a' + 0x0a; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + return (int) digit - 'A' + 0x0A; + } + return -1; +} + +static bool _parse_hex_value(const char **in, char *out) +{ + int value, digit; + + if ((digit = _decode_hex_digit((unsigned char)**in)) < 0) + return FALSE; + + value = digit << 4; + (*in)++; + + if ((digit = _decode_hex_digit((unsigned char)**in)) < 0) + return FALSE; + + value |= digit; + (*in)++; + + if (value == 0) + return FALSE; + + *out = (char)value; + return TRUE; +} + +/* + * URI recipient parsing + */ + +static bool +uri_mailto_add_valid_recipient(struct uri_mailto_parser *parser, + string_t *recipient, bool cc) +{ + struct uri_mailto *uri = parser->uri; + struct uri_mailto_recipient *new_recipient; + struct uri_mailto_recipient *rcpts; + unsigned int count, i; + const char *error; + const struct smtp_address *address; + + /* Verify recipient */ + if ((address = sieve_address_parse_str(recipient, &error)) == NULL) { + uri_mailto_error(parser, "invalid recipient '%s': %s", + str_sanitize(str_c(recipient), 80), error); + return FALSE; + } + + /* Add recipient to the uri */ + if (uri != NULL) { + /* Get current recipients */ + rcpts = array_get_modifiable(&uri->recipients, &count); + + /* Enforce limits */ + if (parser->max_recipients > 0 && + (int)count >= parser->max_recipients) { + if ((int)count == parser->max_recipients) { + uri_mailto_warning( + parser, + "more than the maximum %u recipients specified; " + "rest is discarded", + parser->max_recipients); + } + return TRUE; + } + + /* Check for duplicate first */ + for (i = 0; i < count; i++) { + if (smtp_address_equals(rcpts[i].address, address)) { + /* Upgrade existing Cc: recipient to a To: + recipient if possible */ + rcpts[i].carbon_copy = + (rcpts[i].carbon_copy && cc); + + uri_mailto_warning( + parser, + "ignored duplicate recipient '%s'", + str_sanitize(str_c(recipient), 80)); + return TRUE; + } + } + + /* Add */ + new_recipient = array_append_space(&uri->recipients); + new_recipient->carbon_copy = cc; + new_recipient->full = p_strdup(parser->pool, str_c(recipient)); + new_recipient->address = + smtp_address_clone(parser->pool, address); + } + + return TRUE; +} + +static bool +uri_mailto_parse_recipients(struct uri_mailto_parser *parser, + const char **uri_p) +{ + string_t *to = t_str_new(128); + const char *p = *uri_p; + + if (*p == '\0' || *p == '?') + return TRUE; + + while (*p != '\0' && *p != '?') { + if (*p == '%') { + /* % encoded character */ + char ch; + + p++; + + /* Parse 2-digit hex value */ + if (!_parse_hex_value(&p, &ch)) { + uri_mailto_error(parser, "invalid %% encoding"); + return FALSE; + } + + /* Check for delimiter */ + if (ch == ',') { + /* Verify and add recipient */ + if (!uri_mailto_add_valid_recipient( + parser, to, FALSE)) + return FALSE; + + /* Reset for next recipient */ + str_truncate(to, 0); + } else { + /* Content character */ + str_append_c(to, ch); + } + } else { + if (*p == ':' || *p == ';' || *p == ',' || + !_is_qchar(*p)) { + uri_mailto_error( + parser, + "invalid character '%c' in 'to' part", *p); + return FALSE; + } + + /* Content character */ + str_append_c(to, *p); + p++; + } + } + + i_assert(*p == '\0' || *p == '?'); + + /* Verify and add recipient */ + if (!uri_mailto_add_valid_recipient(parser, to, FALSE)) + return FALSE; + + *uri_p = p; + return TRUE; +} + +static bool +uri_mailto_parse_header_recipients(struct uri_mailto_parser *parser, + string_t *rcpt_header, bool cc) +{ + string_t *to = t_str_new(128); + const char *p = (const char *)str_data(rcpt_header); + const char *pend = p + str_len(rcpt_header); + + while (p < pend) { + if (*p == ',') { + /* Verify and add recipient */ + if (!uri_mailto_add_valid_recipient(parser, to, cc)) + return FALSE; + + /* Reset for next recipient */ + str_truncate(to, 0); + } else { + /* Content character */ + str_append_c(to, *p); + } + p++; + } + + /* Verify and add recipient */ + if (!uri_mailto_add_valid_recipient(parser, to, cc)) + return FALSE; + + return TRUE; +} + +/* URI header parsing */ + +static bool +uri_mailto_header_is_duplicate(struct uri_mailto_parser *parser, + const char *field_name) +{ + struct uri_mailto *uri = parser->uri; + + if (uri == NULL) + return FALSE; + + if (uri_mailto_header_is_unique(parser, field_name)) { + const struct uri_mailto_header_field *hdrs; + unsigned int count, i; + + hdrs = array_get(&uri->headers, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(hdrs[i].name, field_name) == 0) + return TRUE; + } + } + return FALSE; +} + +static bool +uri_mailto_parse_headers(struct uri_mailto_parser *parser, const char **uri_p) +{ + struct uri_mailto *uri = parser->uri; + unsigned int header_count = 0; + string_t *field = t_str_new(128); + const char *p = *uri_p; + + while (*p != '\0') { + enum { + _HNAME_IGNORED, + _HNAME_GENERIC, + _HNAME_TO, + _HNAME_CC, + _HNAME_SUBJECT, + _HNAME_BODY, + } hname_type = _HNAME_GENERIC; + struct uri_mailto_header_field *hdrf = NULL; + const char *field_name; + + /* Parse field name */ + while (*p != '\0' && *p != '=') { + char ch = *p; + p++; + + if (ch == '%') { + /* Encoded, parse 2-digit hex value */ + if (!_parse_hex_value(&p, &ch)) { + uri_mailto_error(parser, + "invalid %% encoding"); + return FALSE; + } + } else if (ch != '=' && !_is_qchar(ch)) { + uri_mailto_error( + parser, + "invalid character '%c' in header field name part", + ch); + return FALSE; + } + + str_append_c(field, ch); + } + if (*p != '\0') + p++; + + /* Verify field name */ + if (!rfc2822_header_field_name_verify(str_c(field), + str_len(field))) { + uri_mailto_error(parser, "invalid header field name"); + return FALSE; + } + + if (parser->max_headers > -1 && + (int)header_count >= parser->max_headers) { + /* Refuse to accept more headers than allowed by policy + */ + if ((int)header_count == parser->max_headers) { + uri_mailto_warning( + parser, + "more than the maximum %u headers specified; " + "rest is discarded", + parser->max_headers); + } + + hname_type = _HNAME_IGNORED; + } else { + /* Add new header field to array and assign its name */ + + field_name = str_c(field); + if (strcasecmp(field_name, "to") == 0) + hname_type = _HNAME_TO; + else if (strcasecmp(field_name, "cc") == 0) + hname_type = _HNAME_CC; + else if (strcasecmp(field_name, "subject") == 0) + hname_type = _HNAME_SUBJECT; + else if (strcasecmp(field_name, "body") == 0) + hname_type = _HNAME_BODY; + else if (!uri_mailto_header_is_reserved(parser, field_name)) { + if (uri != NULL) { + if (!uri_mailto_header_is_duplicate(parser, field_name)) { + hdrf = array_append_space(&uri->headers); + hdrf->name = p_strdup(parser->pool, field_name); + } else { + uri_mailto_warning( + parser, + "ignored duplicate for unique header field '%s'", + str_sanitize(field_name, 32)); + hname_type = _HNAME_IGNORED; + } + } else { + hname_type = _HNAME_IGNORED; + } + } else { + uri_mailto_warning( + parser, + "ignored reserved header field '%s'", + str_sanitize(field_name, 32)); + hname_type = _HNAME_IGNORED; + } + } + + header_count++; + + /* Reset for field body */ + str_truncate(field, 0); + + /* Parse field body */ + while (*p != '\0' && *p != '&') { + char ch = *p; + p++; + + if (ch == '%') { + /* Encoded, parse 2-digit hex value */ + if (!_parse_hex_value(&p, &ch)) { + uri_mailto_error(parser, + "invalid %% encoding"); + return FALSE; + } + } else if (ch != '=' && !_is_qchar(ch)) { + uri_mailto_error( + parser, + "invalid character '%c' in header field value part", + ch); + return FALSE; + } + str_append_c(field, ch); + } + if (*p != '\0') + p++; + + /* Verify field body */ + if (hname_type == _HNAME_BODY) { + // FIXME: verify body ... + } else { + if (!rfc2822_header_field_body_verify( + str_c(field), str_len(field), FALSE, FALSE)) { + uri_mailto_error(parser, + "invalid header field body"); + return FALSE; + } + } + + /* Assign field body */ + + switch (hname_type) { + case _HNAME_IGNORED: + break; + case _HNAME_TO: + /* Gracefully allow duplicate To fields */ + if (!uri_mailto_parse_header_recipients( + parser, field, FALSE)) + return FALSE; + break; + case _HNAME_CC: + /* Gracefully allow duplicate Cc fields */ + if (!uri_mailto_parse_header_recipients( + parser, field, TRUE)) + return FALSE; + break; + case _HNAME_SUBJECT: + /* Ignore duplicate subject field */ + if (uri != NULL) { + if (uri->subject == NULL) { + uri->subject = + p_strdup(parser->pool, str_c(field)); + } else { + uri_mailto_warning( + parser, + "ignored duplicate subject field"); + } + } + break; + case _HNAME_BODY: + /* Ignore duplicate body field */ + if (uri != NULL) { + if (uri->body == NULL) { + uri->body = p_strdup( + parser->pool, str_c(field)); + } else { + uri_mailto_warning( + parser, "ignored duplicate body field"); + } + } + break; + case _HNAME_GENERIC: + if (uri != NULL && hdrf != NULL) + hdrf->body = p_strdup(parser->pool, str_c(field)); + break; + } + + /* Reset for next name */ + str_truncate(field, 0); + } + + /* Skip '&' */ + if (*p != '\0') + p++; + + *uri_p = p; + return TRUE; +} + +static bool +uri_mailto_parse_uri(struct uri_mailto_parser *parser, const char *uri_body) +{ + const char *p = uri_body; + + /* + * mailtoURI = "mailto:" [ to ] [ hfields ] + * to = [ addr-spec *("%2C" addr-spec ) ] + * hfields = "?" hfield *( "&" hfield ) + * hfield = hfname "=" hfvalue + * hfname = *qchar + * hfvalue = *qchar + * addr-spec = local-part "@" domain + * local-part = dot-atom / quoted-string + * qchar = unreserved / pct-encoded / some-delims + * some-delims = "!" / "$" / "'" / "(" / ")" / "*" + * / "+" / "," / ";" / ":" / "@" + * + * to ~= *tqchar + * tqchar ~= <qchar> without ";" and ":" + * + * Scheme 'mailto:' already parsed, starting parse after colon + */ + + /* First extract to-part by searching for '?' and decoding % items + */ + + if (!uri_mailto_parse_recipients(parser, &p)) + return FALSE; + + if (*p == '\0') + return TRUE; + i_assert(*p == '?'); + p++; + + /* Extract hfield items */ + + while (*p != '\0') { + /* Extract hfield item by searching for '&' and decoding '%' + items */ + if (!uri_mailto_parse_headers(parser, &p)) + return FALSE; + } + return TRUE; +} + +/* + * Validation + */ + +bool uri_mailto_validate(const char *uri_body, + const char **reserved_headers, + const char **unique_headers, int max_recipients, + int max_headers, const struct uri_mailto_log *log) +{ + struct uri_mailto_parser parser; + + i_zero(&parser); + parser.log = log; + parser.max_recipients = max_recipients; + parser.max_headers = max_headers; + parser.reserved_headers = reserved_headers; + parser.unique_headers = unique_headers; + + /* If no errors are reported, we don't need to record any data */ + if (log != NULL) { + parser.pool = pool_datastack_create(); + + parser.uri = p_new(parser.pool, struct uri_mailto, 1); + p_array_init(&parser.uri->recipients, parser.pool, max_recipients); + p_array_init(&parser.uri->headers, parser.pool, max_headers); + } + + if (!uri_mailto_parse_uri(&parser, uri_body)) + return FALSE; + + if (log != NULL) { + if (array_count(&parser.uri->recipients) == 0) { + uri_mailto_warning( + &parser, + "notification URI specifies no recipients"); + } + } + return TRUE; +} + +/* + * Parsing + */ + +struct uri_mailto * +uri_mailto_parse(const char *uri_body, pool_t pool, + const char **reserved_headers, const char **unique_headers, + int max_recipients, int max_headers, + const struct uri_mailto_log *log) +{ + struct uri_mailto_parser parser; + + parser.pool = pool; + parser.log = log; + parser.max_recipients = max_recipients; + parser.max_headers = max_headers; + parser.reserved_headers = reserved_headers; + parser.unique_headers = unique_headers; + + parser.uri = p_new(pool, struct uri_mailto, 1); + p_array_init(&parser.uri->recipients, pool, max_recipients); + p_array_init(&parser.uri->headers, pool, max_headers); + + if (!uri_mailto_parse_uri(&parser, uri_body)) + return NULL; + + if (log != NULL) { + if (array_count(&parser.uri->recipients) == 0) { + uri_mailto_warning( + &parser, + "notification URI specifies no recipients"); + } + } + return parser.uri; +} |