From 1d830b2df99ab8df9c94a08f7a35c3708abcbd5c Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Sun, 21 Dec 2008 14:26:22 +0100 Subject: [PATCH] Enotify: added recipient verification and implemented proper To and Cc header composition. --- TODO | 6 +- src/lib-sieve/plugins/enotify/ntfy-mailto.c | 253 ++++++++++++------ src/lib-sieve/sieve-address.c | 49 +++- src/lib-sieve/sieve-address.h | 6 +- tests/extensions/enotify/errors.svtest | 2 +- .../enotify/errors/url-mailto.sieve | 6 + 6 files changed, 236 insertions(+), 86 deletions(-) diff --git a/TODO b/TODO index 94d8b5a6e..6feabb232 100644 --- a/TODO +++ b/TODO @@ -7,11 +7,7 @@ Current: - Check whether handling of error conditions matches the standard - Limit the number of notifications generated (on a per-method basis) * Implement mailto method for the enotify extension: - - Implement verification of recipient addresses in mailto uris. - - Add addresses contained in to to and cc URI headers to the - recipients. Conversely, the generated To header must include the - URI recipients apart from those stated explicitly in the To URI - header. + - Check that URI headers are not duplicated if not allowed. * Implement means to configure which extensions to provide and incorporate enotify extension into default compile. diff --git a/src/lib-sieve/plugins/enotify/ntfy-mailto.c b/src/lib-sieve/plugins/enotify/ntfy-mailto.c index 3085739a2..4a3d11c0c 100644 --- a/src/lib-sieve/plugins/enotify/ntfy-mailto.c +++ b/src/lib-sieve/plugins/enotify/ntfy-mailto.c @@ -36,7 +36,13 @@ struct ntfy_mailto_header_field { const char *body; }; -ARRAY_DEFINE_TYPE(recipients, const char *); +struct ntfy_mailto_recipient { + const char *full; + const char *normalized; + bool carbon_copy; +}; + +ARRAY_DEFINE_TYPE(recipients, struct ntfy_mailto_recipient); ARRAY_DEFINE_TYPE(headers, struct ntfy_mailto_header_field); /* @@ -89,6 +95,7 @@ static const char *_reserved_headers[] = { "received", "message-id", "data", + "bcc", NULL }; @@ -110,6 +117,18 @@ static inline bool _ntfy_mailto_header_allowed(const char *field_name) * Mailto URI parsing */ +/* Util functions */ + +static inline pool_t array_get_pool_i(struct array *array) +{ + return buffer_get_pool(array->buffer); +} +#define array_get_pool(array) \ + array_get_pool_i(&(array)->arr) + +#define _uri_parse_error(LOG, ...) \ + sieve_enotify_error(LOG, "invalid mailto URI: " __VA_ARGS__ ) + /* FIXME: much of this implementation will be common to other URI schemes. This * should be merged into a common implementation. */ @@ -174,24 +193,40 @@ static bool _parse_hex_value(const char **in, char *out) return (*out != '\0'); } -static inline pool_t array_get_pool_i(struct array *array) +static bool _uri_add_valid_recipient +(const struct sieve_enotify_log *nlog, ARRAY_TYPE(recipients) *recipients_r, + string_t *recipient, bool cc) { - return buffer_get_pool(array->buffer); + const char *error; + const char *normalized; + + /* Verify recipient */ + if ( (normalized=sieve_address_normalize(recipient, &error)) == NULL ) { + _uri_parse_error(nlog, "invalid recipient '%s': %s", + str_sanitize(str_c(recipient), 80), error); + return FALSE; + } + + /* Add recipient to the list */ + if ( recipients_r != NULL ) { + pool_t pool = array_get_pool(recipients_r); + struct ntfy_mailto_recipient *new_recipient; + + new_recipient = array_append_space(recipients_r); + new_recipient->carbon_copy = cc; + new_recipient->full = p_strdup(pool, str_c(recipient)); + new_recipient->normalized = p_strdup(pool, normalized); + } + + return TRUE; } -#define array_get_pool(array) \ - array_get_pool_i(&(array)->arr) static bool _uri_parse_recipients (const struct sieve_enotify_log *nlog, const char **uri_p, ARRAY_TYPE(recipients) *recipients_r) { string_t *to = t_str_new(128); - const char *recipient; const char *p = *uri_p; - pool_t pool = NULL; - - if ( recipients_r != NULL ) - pool = array_get_pool(recipients_r); while ( *p != '\0' && *p != '?' ) { if ( *p == '%' ) { @@ -202,23 +237,15 @@ static bool _uri_parse_recipients /* Parse 2-digit hex value */ if ( !_parse_hex_value(&p, &ch) ) { - sieve_enotify_error(nlog, "invalid mailto URI: invalid %% encoding"); + _uri_parse_error(nlog, "invalid %% encoding"); return FALSE; } /* Check for delimiter */ if ( ch == ',' ) { - recipient = str_c(to); - - /* Verify recipient */ - - // FIXME .... - - /* Add recipient to the list */ - if ( recipients_r != NULL ) { - recipient = p_strdup(pool, recipient); - array_append(recipients_r, &recipient, 1); - } + /* Verify and add recipient */ + if ( !_uri_add_valid_recipient(nlog, recipients_r, to, FALSE) ) + return FALSE; /* Reset for next recipient */ str_truncate(to, 0); @@ -236,25 +263,48 @@ static bool _uri_parse_recipients /* Skip '?' */ if ( *p != '\0' ) p++; - recipient = str_c(to); - - /* Verify recipient */ + /* Verify and add recipient */ + if ( !_uri_add_valid_recipient(nlog, recipients_r, to, FALSE) ) + return FALSE; - // FIXME .... - - if ( recipients_r != NULL ) { - /* Add recipient to the list */ - recipient = p_strdup(pool, recipient); - array_append(recipients_r, &recipient, 1); - } - *uri_p = p; return TRUE; } +static bool _uri_parse_header_recipients +(const struct sieve_enotify_log *nlog, string_t *rcpt_header, + ARRAY_TYPE(recipients) *recipients_r, 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_add_valid_recipient(nlog, recipients_r, 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_add_valid_recipient(nlog, recipients_r, to, cc) ) + return FALSE; + + return TRUE; +} + static bool _uri_parse_headers (const struct sieve_enotify_log *nlog, const char **uri_p, - ARRAY_TYPE(headers) *headers_r, const char **body, const char **subject) + ARRAY_TYPE(headers) *headers_r, ARRAY_TYPE(recipients) *recipients_r, + const char **body, const char **subject) { string_t *field = t_str_new(128); const char *p = *uri_p; @@ -264,8 +314,13 @@ static bool _uri_parse_headers pool = array_get_pool(headers_r); while ( *p != '\0' ) { - enum { _HNAME_GENERIC, _HNAME_SUBJECT, _HNAME_BODY } hname_type = - _HNAME_GENERIC; + enum { + _HNAME_GENERIC, + _HNAME_TO, + _HNAME_CC, + _HNAME_SUBJECT, + _HNAME_BODY + } hname_type = _HNAME_GENERIC; struct ntfy_mailto_header_field *hdrf = NULL; const char *field_name; @@ -277,14 +332,12 @@ static bool _uri_parse_headers if ( ch == '%' ) { /* Encoded, parse 2-digit hex value */ if ( !_parse_hex_value(&p, &ch) ) { - printf("F: %s\n", p); - sieve_enotify_error(nlog, "invalid mailto URI: invalid %% encoding"); + _uri_parse_error(nlog, "invalid %% encoding"); return FALSE; } } else if ( ch != '=' && !_is_qchar(ch) ) { - sieve_enotify_error(nlog, - "invalid mailto URI: " - "invalid character '%c' in header field name part", *p); + _uri_parse_error + (nlog, "invalid character '%c' in header field name part", ch); return FALSE; } @@ -294,14 +347,17 @@ static bool _uri_parse_headers /* Verify field name */ if ( !rfc2822_header_field_name_verify(str_c(field), str_len(field)) ) { - sieve_enotify_error(nlog, - "invalid mailto URI: invalid header field name"); + _uri_parse_error(nlog, "invalid header field name"); return FALSE; } /* Add new header field to array and assign its name */ field_name = str_c(field); - if ( strcasecmp(field_name, "subject") == 0 ) + 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; @@ -309,7 +365,8 @@ static bool _uri_parse_headers if ( headers_r != NULL ) { hdrf = array_append_space(headers_r); hdrf->name = p_strdup(pool, field_name); - } + } else + hdrf = NULL; } else { hdrf = NULL; } @@ -325,13 +382,12 @@ static bool _uri_parse_headers if ( ch == '%' ) { /* Encoded, parse 2-digit hex value */ if ( !_parse_hex_value(&p, &ch) ) { - sieve_enotify_error(nlog, "invalid mailto URI: invalid %% encoding"); + _uri_parse_error(nlog, "invalid %% encoding"); return FALSE; } } else if ( ch != '=' && !_is_qchar(ch) ) { - sieve_enotify_error(nlog, - "invalid mailto URI: " - "invalid character '%c' in header field value part", *p); + _uri_parse_error + (nlog, "invalid character '%c' in header field value part", ch); return FALSE; } str_append_c(field, ch); @@ -343,29 +399,35 @@ static bool _uri_parse_headers // FIXME: verify body ... } else { if ( !rfc2822_header_field_body_verify(str_c(field), str_len(field)) ) { - sieve_enotify_error(nlog, - "invalid mailto URI: invalid header field body"); + _uri_parse_error + (nlog, "invalid header field body"); return FALSE; } } /* Assign field body */ - if ( headers_r != NULL ) { - switch ( hname_type ) { - case _HNAME_SUBJECT: - if ( subject != NULL ) - *subject = p_strdup(pool, str_c(field)); - break; - case _HNAME_BODY: - if ( subject != NULL ) - *body = p_strdup(pool, str_c(field)); - break; - case _HNAME_GENERIC: - if ( hdrf != NULL ) - hdrf->body = p_strdup(pool, str_c(field)); - break; - } + switch ( hname_type ) { + case _HNAME_TO: + if ( !_uri_parse_header_recipients(nlog, field, recipients_r, FALSE) ) + return FALSE; + break; + case _HNAME_CC: + if ( !_uri_parse_header_recipients(nlog, field, recipients_r, TRUE) ) + return FALSE; + break; + case _HNAME_SUBJECT: + if ( subject != NULL ) + *subject = p_strdup(pool, str_c(field)); + break; + case _HNAME_BODY: + if ( subject != NULL ) + *body = p_strdup(pool, str_c(field)); + break; + case _HNAME_GENERIC: + if ( hdrf != NULL ) + hdrf->body = p_strdup(pool, str_c(field)); + break; } /* Reset for next name */ @@ -413,7 +475,7 @@ static bool ntfy_mailto_parse_uri while ( *p != '\0' ) { /* Extract hfield item by searching for '&' and decoding '%' items */ - if ( !_uri_parse_headers(nlog, &p, headers_r, body, subject) ) + if ( !_uri_parse_headers(nlog, &p, headers_r, recipients_r, body, subject) ) return FALSE; } @@ -435,7 +497,7 @@ static bool ntfy_mailto_compile_check_from (const struct sieve_enotify_log *nlog, string_t *from) { const char *error; - bool result; + bool result = FALSE; T_BEGIN { result = sieve_address_validate(from, &error); @@ -487,7 +549,7 @@ static void ntfy_mailto_action_print const struct sieve_enotify_action *act) { unsigned int count, i; - const char *const *recipients; + const struct ntfy_mailto_recipient *recipients; const struct ntfy_mailto_header_field *headers; struct ntfy_mailto_context *mtctx = (struct ntfy_mailto_context *) act->method_context; @@ -503,7 +565,10 @@ static void ntfy_mailto_action_print sieve_result_printf(rpenv, " => recipients :\n" ); recipients = array_get(&mtctx->recipients, &count); for ( i = 0; i < count; i++ ) { - sieve_result_printf(rpenv, " + %s\n", recipients[i]); + if ( recipients[i].carbon_copy ) + sieve_result_printf(rpenv, " + Cc: %s\n", recipients[i].full); + else + sieve_result_printf(rpenv, " + To: %s\n", recipients[i].full); } sieve_result_printf(rpenv, " => headers :\n" ); @@ -546,7 +611,8 @@ static bool ntfy_mailto_send const char *from = NULL; const char *subject = mtctx->subject; const char *body = mtctx->body; - const char *const *recipients; + string_t *to, *cc; + const struct ntfy_mailto_recipient *recipients; void *smtp_handle; unsigned int count, i; FILE *f; @@ -562,7 +628,7 @@ static bool ntfy_mailto_send /* Determine from address */ if ( msgdata->return_path != NULL ) { if ( act->from == NULL ) - from = senv->postmaster_address; + from = t_strdup_printf("Postmaster <%s>", senv->postmaster_address); else /* FIXME: validate */ from = act->from; @@ -583,21 +649,51 @@ static bool ntfy_mailto_send subject = "Notification: (no subject)"; } - /* Send message to all recipients */ recipients = array_get(&mtctx->recipients, &count); + + /* Compose To and Cc headers */ + to = NULL; + cc = NULL; + for ( i = 0; i < count; i++ ) { + if ( recipients[i].carbon_copy ) { + if ( cc == NULL ) { + cc = t_str_new(256); + str_append(cc, recipients[i].full); + } else { + str_append(cc, ", "); + str_append(cc, recipients[i].full); + } + } else { + if ( to == NULL ) { + to = t_str_new(256); + str_append(to, recipients[i].full); + } else { + str_append(to, ", "); + str_append(to, recipients[i].full); + } + } + } + + /* Send message to all recipients */ for ( i = 0; i < count; i++ ) { const struct ntfy_mailto_header_field *headers; unsigned int h, hcount; - smtp_handle = senv->smtp_open(recipients[i], from, &f); + smtp_handle = senv->smtp_open(recipients[i].normalized, from, &f); outmsgid = sieve_get_new_message_id(senv); rfc2822_header_field_write(f, "X-Sieve", SIEVE_IMPLEMENTATION); rfc2822_header_field_write(f, "Message-ID", outmsgid); rfc2822_header_field_write(f, "Date", message_date_create(ioloop_time)); - rfc2822_header_field_printf(f, "From", "<%s>", from); - rfc2822_header_field_printf(f, "To", "<%s>", recipients[i]); rfc2822_header_field_write(f, "Subject", subject); + + rfc2822_header_field_printf(f, "From", "%s", from); + + if ( to != NULL ) + rfc2822_header_field_printf(f, "To", "%s", str_c(to)); + + if ( cc != NULL ) + rfc2822_header_field_printf(f, "Cc", "%s", str_c(cc)); rfc2822_header_field_printf(f, "Auto-Submitted", "auto-notified; owner-email=\"%s\"", msgdata->to_address); @@ -648,12 +744,13 @@ static bool ntfy_mailto_send if ( senv->smtp_close(smtp_handle) ) { sieve_enotify_log(nlog, - "sent mail notification to <%s>", str_sanitize(recipients[i], 80)); + "sent mail notification to <%s>", + str_sanitize(recipients[i].normalized, 80)); } else { sieve_enotify_error(nlog, "failed to send mail notification to <%s> " "(refer to system log for more information)", - str_sanitize(recipients[i], 80)); + str_sanitize(recipients[i].normalized, 80)); } } diff --git a/src/lib-sieve/sieve-address.c b/src/lib-sieve/sieve-address.c index 5ec44ccc3..ec962f806 100644 --- a/src/lib-sieve/sieve-address.c +++ b/src/lib-sieve/sieve-address.c @@ -242,7 +242,7 @@ static bool parse_mailbox_address return TRUE; } -bool sieve_validate_rfc2822_mailbox(const char *address, const char **error_r) +bool sieve_rfc2822_mailbox_validate(const char *address, const char **error_r) { struct sieve_message_address_parser ctx; @@ -266,6 +266,30 @@ bool sieve_validate_rfc2822_mailbox(const char *address, const char **error_r) return TRUE; } +const char *sieve_rfc2822_mailbox_normalize + (const char *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, (const unsigned char *) address, + strlen(address)) ) { + if ( error_r != NULL ) + *error_r = str_c(ctx.error); + return NULL; + } + + if ( error_r != NULL ) + *error_r = NULL; + + return t_strconcat(str_c(ctx.local_part), "@", str_c(ctx.domain), NULL); +} /* * Sieve address @@ -300,7 +324,30 @@ bool sieve_address_validate { struct sieve_message_address_parser ctx; + memset(&ctx, 0, sizeof(ctx));bool sieve_validate_rfc2822_mailbox(const char *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, (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; +} + ctx.local_part = ctx.domain = ctx.str = t_str_new(128); ctx.error = t_str_new(128); diff --git a/src/lib-sieve/sieve-address.h b/src/lib-sieve/sieve-address.h index e4a51148d..b93c8d159 100644 --- a/src/lib-sieve/sieve-address.h +++ b/src/lib-sieve/sieve-address.h @@ -17,7 +17,11 @@ struct sieve_address { * RFC 2822 addresses */ -bool sieve_validate_rfc2822_mailbox(const char *address, const char **error_r); +bool sieve_rfc2822_mailbox_validate + (const char *address, const char **error_r); +const char *sieve_rfc2822_mailbox_normalize + (const char *address, const char **error_r); + const char *sieve_address_normalize (string_t *address, const char **error_r); diff --git a/tests/extensions/enotify/errors.svtest b/tests/extensions/enotify/errors.svtest index be79496fc..cb7d4e3d6 100644 --- a/tests/extensions/enotify/errors.svtest +++ b/tests/extensions/enotify/errors.svtest @@ -20,7 +20,7 @@ test "Invalid mailto URL (FIXME: count only)" { test_fail "compile should have failed"; } - if not test_error :count "eq" :comparator "i;ascii-numeric" "2" { + if not test_error :count "eq" :comparator "i;ascii-numeric" "4" { test_fail "wrong number of errors reported"; } } diff --git a/tests/extensions/enotify/errors/url-mailto.sieve b/tests/extensions/enotify/errors/url-mailto.sieve index 5a4d32f6c..5970086dc 100644 --- a/tests/extensions/enotify/errors/url-mailto.sieve +++ b/tests/extensions/enotify/errors/url-mailto.sieve @@ -3,3 +3,9 @@ require "enotify"; # 1: Invalid header name notify "mailto:stephan@rename-it.nl?header:=frop"; +# 2: Invalid recipient +notify "mailto:stephan%23rename-it.nl"; + +# 3: Invalid to header recipient +notify "mailto:stephan@rename-it.nl?to=nico%23vestingbar.nl"; + -- GitLab