diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-smtp/smtp-params.c | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-smtp/smtp-params.c')
-rw-r--r-- | src/lib-smtp/smtp-params.c | 1375 |
1 files changed, 1375 insertions, 0 deletions
diff --git a/src/lib-smtp/smtp-params.c b/src/lib-smtp/smtp-params.c new file mode 100644 index 0000000..3994ce5 --- /dev/null +++ b/src/lib-smtp/smtp-params.c @@ -0,0 +1,1375 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "message-address.h" +#include "smtp-common.h" +#include "smtp-parser.h" +#include "smtp-syntax.h" +#include "smtp-address.h" + +#include "smtp-params.h" + +#include <ctype.h> + +/* + * Common + */ + +/* parse */ + +static int +smtp_param_do_parse(struct smtp_parser *parser, struct smtp_param *param_r) +{ + const unsigned char *pbegin = parser->cur; + + /* esmtp-param = esmtp-keyword ["=" esmtp-value] + esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + esmtp-value = 1*(%d33-60 / %d62-126) + ; any CHAR excluding "=", SP, and control + ; characters. If this string is an email address, + ; i.e., a Mailbox, then the "xtext" syntax [32] + ; SHOULD be used. + */ + + if (parser->cur >= parser->end || !i_isalnum(*parser->cur)) { + parser->error = "Unexpected character in parameter keyword"; + return -1; + } + parser->cur++; + + while (parser->cur < parser->end && + (i_isalnum(*parser->cur) || *parser->cur == '-')) + parser->cur++; + param_r->keyword = t_strndup(pbegin, parser->cur - pbegin); + + if (parser->cur >= parser->end) { + param_r->value = NULL; + return 1; + } + if (*parser->cur != '=') { + parser->error = "Unexpected character in parameter keyword"; + return -1; + } + parser->cur++; + + pbegin = parser->cur; + while (parser->cur < parser->end && + smtp_char_is_esmtp_value(*parser->cur)) + parser->cur++; + + if (parser->cur < parser->end) { + parser->error = "Unexpected character in parameter value"; + return -1; + } + param_r->value = t_strndup(pbegin, parser->cur - pbegin); + return 1; +} + +int smtp_param_parse(pool_t pool, const char *text, + struct smtp_param *param_r, const char **error_r) +{ + struct smtp_parser parser; + + i_zero(param_r); + + if (text == NULL || *text == '\0') { + if (error_r != NULL) + *error_r = "Parameter is empty"; + return -1; + } + + smtp_parser_init(&parser, pool, text); + + if (smtp_param_do_parse(&parser, param_r) <= 0) { + if (error_r != NULL) + *error_r = parser.error; + return -1; + } + return 1; +} + +/* manipulate */ + +void smtp_params_copy(pool_t pool, ARRAY_TYPE(smtp_param) *dst, + const ARRAY_TYPE(smtp_param) *src) +{ + const struct smtp_param *param; + + if (!array_is_created(src)) + return; + + p_array_init(dst, pool, array_count(src)); + array_foreach(src, param) { + struct smtp_param param_new; + + param_new.keyword = p_strdup(pool, param->keyword); + param_new.value = p_strdup(pool, param->value); + array_push_back(dst, ¶m_new); + } +} + +void smtp_params_add_one(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const char *value) +{ + struct smtp_param param; + + if (!array_is_created(params)) + p_array_init(params, pool, 4); + + i_zero(¶m); + param.keyword = p_strdup(pool, keyword); + param.value = p_strdup(pool, value); + array_push_back(params, ¶m); +} + +void smtp_params_add_encoded(ARRAY_TYPE(smtp_param) *params, pool_t pool, + const char *keyword, const unsigned char *value, + size_t value_len) +{ + string_t *value_enc = t_str_new(value_len * 2); + + smtp_xtext_encode(value_enc, value, value_len); + smtp_params_add_one(params, pool, keyword, str_c(value_enc)); +} + +bool smtp_params_drop_one(ARRAY_TYPE(smtp_param) *params, const char *keyword, + const char **value_r) +{ + const struct smtp_param *param; + + if (!array_is_created(params)) + return FALSE; + + array_foreach(params, param) { + if (strcasecmp(param->keyword, keyword) == 0) { + if (value_r != NULL) + *value_r = param->value; + array_delete(params, + array_foreach_idx(params, param), 1); + return TRUE; + } + } + return FALSE; +} + +/* write */ + +static bool smtp_param_value_valid(const char *value) +{ + const char *p = value; + + while (*p != '\0' && smtp_char_is_esmtp_value(*p)) + p++; + return (*p == '\0'); +} + +void smtp_param_write(string_t *out, const struct smtp_param *param) +{ + str_append(out, t_str_ucase(param->keyword)); + if (param->value != NULL) { + i_assert(smtp_param_value_valid(param->value)); + str_append_c(out, '='); + str_append(out, param->value); + } +} + +static void +smtp_params_write(string_t *buffer, const char *const *param_keywords, + const ARRAY_TYPE(smtp_param) *params) ATTR_NULL(2) +{ + const struct smtp_param *param; + + if (param_keywords == NULL || *param_keywords == NULL) + return; + if (!array_is_created(params)) + return; + + array_foreach(params, param) { + if (str_array_icase_find(param_keywords, param->keyword)) + smtp_param_write(buffer, param); + str_append_c(buffer, ' '); + } +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_get_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword) +{ + const struct smtp_param *param; + + if (!array_is_created(params)) + return NULL; + + array_foreach(params, param) { + if (strcasecmp(param->keyword, keyword) == 0) + return param; + } + return NULL; +} + +int smtp_params_decode_param(const ARRAY_TYPE(smtp_param) *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + const struct smtp_param *param; + + param = smtp_params_get_param(params, keyword); + if (param == NULL) + return 0; + + *value_r = t_str_new(strlen(param->value) * 2); + if (smtp_xtext_decode(*value_r, param->value, allow_nul, error_r) <= 0) + return -1; + return 1; +} + +bool smtp_params_equal(const ARRAY_TYPE(smtp_param) *params1, + const ARRAY_TYPE(smtp_param) *params2) +{ + const struct smtp_param *param1, *param2; + + if (!array_is_created(params1) || array_count(params1) == 0) { + return (!array_is_created(params2) || + array_count(params2) == 0); + } + if (!array_is_created(params2) || array_count(params2) == 0) + return FALSE; + + if (array_count(params1) != array_count(params2)) + return FALSE; + + array_foreach(params1, param1) { + param2 = smtp_params_get_param(params2, param1->keyword); + if (param2 == NULL) + return FALSE; + if (null_strcmp(param1->value, param2->value) != 0) + return FALSE; + } + return TRUE; +} + +/* + * MAIL parameters + */ + +/* parse */ + +struct smtp_params_mail_parser { + pool_t pool; + struct smtp_params_mail *params; + enum smtp_capability caps; + + enum smtp_param_parse_error error_code; + const char *error; +}; + +static int +smtp_params_mail_parse_auth(struct smtp_params_mail_parser *pmparser, + const char *xtext) +{ + struct smtp_params_mail *params = pmparser->params; + struct smtp_address *auth_addr; + const char *value, *error; + + /* AUTH=: RFC 4954, Section 5 + + We ignore this parameter, but we do check it for validity + */ + + /* cannot specify this multiple times */ + if (params->auth != NULL) { + pmparser->error = "Duplicate AUTH= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (xtext == NULL) { + pmparser->error = "Missing AUTH= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + if (smtp_xtext_parse(xtext, &value, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid AUTH= parameter value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + if (strcmp(value, "<>") == 0) { + params->auth = p_new(pmparser->pool, struct smtp_address, 1); + } else if (smtp_address_parse_mailbox( + pmparser->pool, value, + SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART, + &auth_addr, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid AUTH= address value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } else { + params->auth = auth_addr; + } + /* ignore, our own AUTH data is added below */ + return 0; +} + +static int +smtp_params_mail_parse_body(struct smtp_params_mail_parser *pmparser, + const char *value, const char *const *extensions) +{ + struct smtp_params_mail *params = pmparser->params; + enum smtp_capability caps = pmparser->caps; + + /* BODY=<type>: RFC 6152 */ + + /* cannot specify this multiple times */ + if (params->body.type != SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED) { + pmparser->error = "Duplicate BODY= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing BODY= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + value = t_str_ucase(value); + params->body.ext = NULL; + /* =7BIT: RFC 6152 */ + if (strcmp(value, "7BIT") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_7BIT; + /* =8BITMIME: RFC 6152 */ + } else if ((caps & SMTP_CAPABILITY_8BITMIME) != 0 && + strcmp(value, "8BITMIME") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME; + /* =BINARYMIME: RFC 3030 */ + } else if ((caps & SMTP_CAPABILITY_BINARYMIME) != 0 && + (caps & SMTP_CAPABILITY_CHUNKING) != 0 && + strcmp(value, "BINARYMIME") == 0) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME; + /* =?? */ + } else if (extensions != NULL && + str_array_icase_find(extensions, value)) { + params->body.type = SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION; + params->body.ext = p_strdup(pmparser->pool, value); + } else { + pmparser->error = "Unsupported mail BODY type"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +static int +smtp_params_mail_parse_envid(struct smtp_params_mail_parser *pmparser, + const char *xtext) +{ + struct smtp_params_mail *params = pmparser->params; + const unsigned char *p, *pend; + const char *envid, *error; + + /* ENVID=<envid>: RFC 3461 */ + + /* cannot specify this multiple times */ + if (params->envid != NULL) { + pmparser->error = "Duplicate ENVID= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (xtext == NULL) { + pmparser->error = "Missing ENVID= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* check xtext */ + if (smtp_xtext_parse(xtext, &envid, &error) < 0) { + pmparser->error = t_strdup_printf( + "Invalid ENVID= parameter value: %s", error); + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* RFC 3461, Section 4.4: + + Due to limitations in the Delivery Status Notification format, the + value of the ENVID parameter prior to encoding as "xtext" MUST + consist entirely of printable (graphic and white space) characters + from the US-ASCII repertoire. + */ + p = (const unsigned char *)envid; + pend = p + strlen(envid); + while (p < pend && smtp_char_is_textstr(*p)) + p++; + if (p < pend) { + pmparser->error = + "Invalid ENVID= parameter value: " + "Contains non-printable characters"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + params->envid = p_strdup(pmparser->pool, envid); + return 0; +} + +static int +smtp_params_mail_parse_ret(struct smtp_params_mail_parser *pmparser, + const char *value) +{ + struct smtp_params_mail *params = pmparser->params; + + /* RET=<keyword>: RFC 3461 */ + + /* cannot specify this multiple times */ + if (params->ret != SMTP_PARAM_MAIL_RET_UNSPECIFIED) { + pmparser->error = "Duplicate RET= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing RET= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + value = t_str_ucase(value); + /* =FULL */ + if (strcmp(value, "FULL") == 0) { + params->ret = SMTP_PARAM_MAIL_RET_FULL; + /* =HDRS */ + } else if (strcmp(value, "HDRS") == 0) { + params->ret = SMTP_PARAM_MAIL_RET_HDRS; + } else { + pmparser->error = "Unsupported RET= parameter keyword"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +static int +smtp_params_mail_parse_size(struct smtp_params_mail_parser *pmparser, + const char *value) +{ + struct smtp_params_mail *params = pmparser->params; + + /* SIZE=<size-value>: RFC 1870 */ + + /* cannot specify this multiple times */ + if (params->size != 0) { + pmparser->error = "Duplicate SIZE= parameter"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + pmparser->error = "Missing SIZE= parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* size-value ::= 1*20DIGIT */ + if (str_to_uoff(value, ¶ms->size) < 0) { + pmparser->error = "Unsupported SIZE parameter value"; + pmparser->error_code = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + return 0; +} + +int smtp_params_mail_parse(pool_t pool, const char *args, + enum smtp_capability caps, + const char *const *extensions, + const char *const *body_extensions, + struct smtp_params_mail *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) +{ + struct smtp_params_mail_parser pmparser; + struct smtp_param param; + const char *const *argv; + const char *error; + int ret = 0; + + i_zero(params_r); + + i_zero(&pmparser); + pmparser.pool = pool; + pmparser.params = params_r; + pmparser.caps = caps; + + argv = t_strsplit(args, " "); + for (; *argv != NULL; argv++) { + if (smtp_param_parse(pool_datastack_create(), *argv, + ¶m, &error) < 0) { + *error_r = t_strdup_printf( + "Invalid MAIL parameter: %s", error); + *error_code_r = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* parse known parameters */ + param.keyword = t_str_ucase(param.keyword); + if ((caps & SMTP_CAPABILITY_AUTH) != 0 && + strcmp(param.keyword, "AUTH") == 0) { + if (smtp_params_mail_parse_auth( + &pmparser, param.value) < 0) { + ret = -1; + break; + } + } else if (strcmp(param.keyword, "BODY") == 0) { + if (smtp_params_mail_parse_body(&pmparser, param.value, + body_extensions) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "ENVID") == 0) { + if (smtp_params_mail_parse_envid(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "RET") == 0) { + if (smtp_params_mail_parse_ret(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if ((caps & SMTP_CAPABILITY_SIZE) != 0 && + strcmp(param.keyword, "SIZE") == 0) { + if (smtp_params_mail_parse_size(&pmparser, + param.value) < 0) { + ret = -1; + break; + } + } else if (extensions != NULL && + str_array_icase_find(extensions, param.keyword)) { + /* add the rest to ext_param for specific + applications */ + smtp_params_mail_add_extra(params_r, pool, + param.keyword, param.value); + } else { + /* RFC 5321, Section 4.1.1.11: + If the server SMTP does not recognize or cannot + implement one or more of the parameters associated + with a particular MAIL FROM or RCPT TO command, it + will return code 555. */ + *error_r = "Unsupported parameters"; + *error_code_r = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + } + + if (ret < 0) { + *error_r = pmparser.error; + *error_code_r = pmparser.error_code; + } + return ret; +} + +/* manipulate */ + +void smtp_params_mail_copy(pool_t pool, struct smtp_params_mail *dst, + const struct smtp_params_mail *src) +{ + i_zero(dst); + + if (src == NULL) + return; + + dst->auth = smtp_address_clone(pool, src->auth); + dst->body.type = src->body.type; + dst->body.ext = p_strdup(pool, src->body.ext); + dst->envid = p_strdup(pool, src->envid); + dst->ret = src->ret; + dst->size = src->size; + + smtp_params_copy(pool, &dst->extra_params, &src->extra_params); +} + +void smtp_params_mail_add_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, const char *value) +{ + smtp_params_add_one(¶ms->extra_params, pool, keyword, value); +} + +void smtp_params_mail_encode_extra(struct smtp_params_mail *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len) +{ + smtp_params_add_encoded(¶ms->extra_params, pool, + keyword, value, value_len); +} + +bool smtp_params_mail_drop_extra(struct smtp_params_mail *params, + const char *keyword, const char **value_r) +{ + return smtp_params_drop_one(¶ms->extra_params, keyword, value_r); +} + +/* write */ + +static void +smtp_params_mail_write_auth(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* add AUTH= parameter */ + string_t *auth_addr; + + if (params->auth == NULL) + return; + if ((caps & SMTP_CAPABILITY_AUTH) == 0) + return; + + auth_addr = t_str_new(256); + + if (params->auth->localpart == NULL) + str_append(auth_addr, "<>"); + else + smtp_address_write(auth_addr, params->auth); + str_append(buffer, "AUTH="); + smtp_xtext_encode(buffer, str_data(auth_addr), str_len(auth_addr)); + str_append_c(buffer, ' '); +} + +static void +smtp_params_mail_write_body(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* BODY=<type>: RFC 6152 */ + /* =7BIT: RFC 6152 */ + switch (params->body.type) { + case SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_BODY_TYPE_7BIT: + str_append(buffer, "BODY=7BIT "); + break; + /* =8BITMIME: RFC 6152 */ + case SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME: + i_assert((caps & SMTP_CAPABILITY_8BITMIME) != 0); + str_append(buffer, "BODY=8BITMIME "); + break; + /* =BINARYMIME: RFC 3030 */ + case SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME: + i_assert((caps & SMTP_CAPABILITY_BINARYMIME) != 0 && + (caps & SMTP_CAPABILITY_CHUNKING) != 0); + str_append(buffer, "BODY=BINARYMIME "); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION: + str_append(buffer, "BODY="); + str_append(buffer, params->body.ext); + str_append_c(buffer, ' '); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_write_envid(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + const char *envid = params->envid; + + /* ENVID=<envid>: RFC 3461 */ + + if (envid == NULL) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + + str_append(buffer, "ENVID="); + smtp_xtext_encode(buffer, (const unsigned char *)envid, strlen(envid)); + str_append_c(buffer, ' '); +} + +static void +smtp_params_mail_write_ret(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + /* RET=<keyword>: RFC 3461 */ + switch (params->ret) { + case SMTP_PARAM_MAIL_RET_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_RET_HDRS: + str_append(buffer, "RET=HDRS "); + break; + case SMTP_PARAM_MAIL_RET_FULL: + str_append(buffer, "RET=FULL "); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_write_size(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_mail *params) +{ + /* SIZE=<size-value>: RFC 1870 */ + + if (params->size == 0) + return; + if ((caps & SMTP_CAPABILITY_SIZE) == 0) + return; + + /* proxy the SIZE parameter (account for additional size) */ + str_printfa(buffer, "SIZE=%"PRIuUOFF_T" ", params->size); +} + +void smtp_params_mail_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_mail *params) +{ + size_t init_len = str_len(buffer); + + smtp_params_mail_write_auth(buffer, caps, params); + smtp_params_mail_write_body(buffer, caps, params); + smtp_params_mail_write_envid(buffer, caps, params); + smtp_params_mail_write_ret(buffer, caps, params); + smtp_params_mail_write_size(buffer, caps, params); + + smtp_params_write(buffer, extra_params, ¶ms->extra_params); + + if (str_len(buffer) > init_len) + str_truncate(buffer, str_len(buffer)-1); +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_mail_get_extra(const struct smtp_params_mail *params, + const char *keyword) +{ + return smtp_params_get_param(¶ms->extra_params, keyword); +} + +int smtp_params_mail_decode_extra(const struct smtp_params_mail *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + return smtp_params_decode_param(¶ms->extra_params, + keyword, value_r, allow_nul, error_r); +} + +/* events */ + +static void +smtp_params_mail_add_auth_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* AUTH: RFC 4954 */ + if (params->auth == NULL) + return; + + event_add_str(event, "mail_param_auth", + smtp_address_encode(params->auth)); +} + +static void +smtp_params_mail_add_body_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* BODY: RFC 6152 */ + switch (params->body.type) { + case SMTP_PARAM_MAIL_BODY_TYPE_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_BODY_TYPE_7BIT: + event_add_str(event, "mail_param_body", "7BIT"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_8BITMIME: + event_add_str(event, "mail_param_body", "8BITMIME"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_BINARYMIME: + event_add_str(event, "mail_param_body", "BINARYMIME"); + break; + case SMTP_PARAM_MAIL_BODY_TYPE_EXTENSION: + event_add_str(event, "mail_param_body", params->body.ext); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_add_envid_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* ENVID: RFC 3461, Section 4.4 */ + if (params->envid == NULL) + return; + + event_add_str(event, "mail_param_envid", params->envid); +} + +static void +smtp_params_mail_add_ret_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* RET: RFC 3461, Section 4.3 */ + switch (params->ret) { + case SMTP_PARAM_MAIL_RET_UNSPECIFIED: + break; + case SMTP_PARAM_MAIL_RET_HDRS: + event_add_str(event, "mail_param_ret", "HDRS"); + break; + case SMTP_PARAM_MAIL_RET_FULL: + event_add_str(event, "mail_param_ret", "FULL"); + break; + default: + i_unreached(); + } +} + +static void +smtp_params_mail_add_size_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + /* SIZE: RFC 1870 */ + if (params->size == 0) + return; + + event_add_int(event, "mail_param_size", params->size); +} + +void smtp_params_mail_add_to_event(const struct smtp_params_mail *params, + struct event *event) +{ + smtp_params_mail_add_auth_to_event(params, event); + smtp_params_mail_add_body_to_event(params, event); + smtp_params_mail_add_envid_to_event(params, event); + smtp_params_mail_add_ret_to_event(params, event); + smtp_params_mail_add_size_to_event(params, event); +} + +/* + * RCPT parameters + */ + +/* parse */ + +struct smtp_params_rcpt_parser { + pool_t pool; + struct smtp_params_rcpt *params; + enum smtp_param_rcpt_parse_flags flags; + enum smtp_capability caps; + + enum smtp_param_parse_error error_code; + const char *error; +}; + +static int +smtp_params_rcpt_parse_notify(struct smtp_params_rcpt_parser *prparser, + const char *value) +{ + struct smtp_params_rcpt *params = prparser->params; + const char *const *list; + bool valid, unsupported; + + /* NOTIFY=<type>: RFC 3461 + + notify-esmtp-value = "NEVER" / 1#notify-list-element + notify-list-element = "SUCCESS" / "FAILURE" / "DELAY" + + We check and normalize this parameter. + */ + + /* cannot specify this multiple times */ + if (params->notify != SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) { + prparser->error = "Duplicate NOTIFY= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + prparser->error = "Missing NOTIFY= parameter value"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + valid = TRUE; + unsupported = FALSE; + list = t_strsplit(value, ","); /* RFC 822, Section 2.7 */ + while (*list != NULL) { + if (**list != '\0') { + /* NEVER */ + if (strcasecmp(*list, "NEVER") == 0) { + if (params->notify != SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + valid = FALSE; + params->notify = SMTP_PARAM_RCPT_NOTIFY_NEVER; + /* SUCCESS */ + } else if (strcasecmp(*list, "SUCCESS") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_SUCCESS; + /* FAILURE */ + } else if (strcasecmp(*list, "FAILURE") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_FAILURE; + /* DELAY */ + } else if (strcasecmp(*list, "DELAY") == 0) { + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) + valid = FALSE; + params->notify |= SMTP_PARAM_RCPT_NOTIFY_DELAY; + } else { + unsupported = TRUE; + } + } + list++; + } + + if (!valid || unsupported || + params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) { + prparser->error = "Invalid NOTIFY= parameter value"; + prparser->error_code = ((valid && unsupported) ? + SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED : + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX); + return -1; + } + return 0; +} + +static int +smtp_params_rcpt_parse_orcpt_rfc822(struct smtp_params_rcpt_parser *prparser, + const char *addr_str, pool_t pool, + const struct smtp_address **addr_r) +{ + struct message_address *rfc822_addr; + struct smtp_address *addr; + + rfc822_addr = message_address_parse(pool_datastack_create(), + (const unsigned char *)addr_str, + strlen(addr_str), 2, 0); + if (rfc822_addr == NULL || rfc822_addr->next != NULL) + return -1; + if (rfc822_addr->invalid_syntax) { + if (HAS_NO_BITS(prparser->flags, + SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART) || + rfc822_addr->mailbox == NULL || + *rfc822_addr->mailbox == '\0') + return -1; + rfc822_addr->invalid_syntax = FALSE; + } + if (smtp_address_create_from_msg(pool, rfc822_addr, &addr) < 0) + return -1; + *addr_r = addr; + return 0; +} + +static int +smtp_params_rcpt_parse_orcpt(struct smtp_params_rcpt_parser *prparser, + const char *value) +{ + struct smtp_params_rcpt *params = prparser->params; + struct smtp_parser parser; + const unsigned char *p, *pend; + string_t *address; + const char *addr_type; + int ret; + + /* ORCPT=<address>: RFC 3461 + + orcpt-parameter = "ORCPT=" original-recipient-address + original-recipient-address = addr-type ";" xtext + addr-type = atom + + We check and normalize this parameter. + */ + + /* cannot specify this multiple times */ + if (params->orcpt.addr_type != NULL) { + prparser->error = "Duplicate ORCPT= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + /* value required */ + if (value == NULL) { + prparser->error = "Missing ORCPT= parameter value"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* check addr-type */ + smtp_parser_init(&parser, pool_datastack_create(), value); + if (smtp_parser_parse_atom(&parser, &addr_type) <= 0 || + parser.cur >= parser.end || *parser.cur != ';') { + prparser->error = "Invalid addr-type for ORCPT= parameter"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + params->orcpt.addr_type = p_strdup(prparser->pool, addr_type); + parser.cur++; + + /* check xtext */ + address = t_str_new(256); + if ((ret=smtp_parser_parse_xtext(&parser, address)) <= 0 || + parser.cur < parser.end) { + if (ret < 0) { + prparser->error = t_strdup_printf( + "Invalid ORCPT= parameter: %s", + parser.error); + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } else if (parser.cur < parser.end) { + prparser->error = "Invalid ORCPT= parameter: " + "Invalid character in xtext"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } else { + prparser->error = "Invalid ORCPT= parameter: " + "Empty address value"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + } + return -1; + } + + /* RFC 3461, Section 4.2: + + Due to limitations in the Delivery Status Notification format, the + value of the original recipient address prior to encoding as "xtext" + MUST consist entirely of printable (graphic and white space) + characters from the US-ASCII repertoire. + */ + p = str_data(address); + pend = p + str_len(address); + while (p < pend && smtp_char_is_textstr(*p)) + p++; + if (p < pend) { + prparser->error = + "Invalid ORCPT= address value: " + "Contains non-printable characters"; + prparser->error_code = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + params->orcpt.addr_raw = p_strdup(prparser->pool, str_c(address)); + + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + if (smtp_params_rcpt_parse_orcpt_rfc822( + prparser, params->orcpt.addr_raw, prparser->pool, + ¶ms->orcpt.addr) < 0) { + prparser->error = "Invalid ORCPT= address value: " + "Invalid RFC822 address"; + prparser->error_code = + SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + } + return 0; +} + +int smtp_params_rcpt_parse(pool_t pool, const char *args, + enum smtp_param_rcpt_parse_flags flags, + enum smtp_capability caps, + const char *const *extensions, + struct smtp_params_rcpt *params_r, + enum smtp_param_parse_error *error_code_r, + const char **error_r) +{ + struct smtp_params_rcpt_parser prparser; + struct smtp_param param; + const char *const *argv; + const char *error; + int ret = 0; + + i_zero(params_r); + + i_zero(&prparser); + prparser.pool = pool; + prparser.params = params_r; + prparser.flags = flags; + prparser.caps = caps; + + argv = t_strsplit(args, " "); + for (; *argv != NULL; argv++) { + if (smtp_param_parse(pool_datastack_create(), *argv, + ¶m, &error) < 0) { + *error_r = t_strdup_printf( + "Invalid RCPT parameter: %s", error); + *error_code_r = SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX; + return -1; + } + + /* parse known parameters */ + param.keyword = t_str_ucase(param.keyword); + if ((caps & SMTP_CAPABILITY_DSN) != 0 && + strcmp(param.keyword, "NOTIFY") == 0) { + if (smtp_params_rcpt_parse_notify + (&prparser, param.value) < 0) { + ret = -1; + break; + } + } else if (((caps & SMTP_CAPABILITY_DSN) != 0 || + (caps & SMTP_CAPABILITY__ORCPT) != 0) && + strcmp(param.keyword, "ORCPT") == 0) { + if (smtp_params_rcpt_parse_orcpt + (&prparser, param.value) < 0) { + ret = -1; + break; + } + } else if (extensions != NULL && + str_array_icase_find(extensions, param.keyword)) { + /* add the rest to ext_param for specific applications + */ + smtp_params_rcpt_add_extra(params_r, pool, + param.keyword, param.value); + } else { + /* RFC 5321, Section 4.1.1.11: + If the server SMTP does not recognize or cannot + implement one or more of the parameters associated + with a particular MAIL FROM or RCPT TO command, it + will return code 555. */ + *error_r = "Unsupported parameters"; + *error_code_r = SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED; + return -1; + } + } + + if (ret < 0) { + *error_r = prparser.error; + *error_code_r = prparser.error_code; + } + return ret; +} + +/* manipulate */ + +void smtp_params_rcpt_copy(pool_t pool, struct smtp_params_rcpt *dst, + const struct smtp_params_rcpt *src) +{ + i_zero(dst); + + if (src == NULL) + return; + + dst->notify = src->notify; + dst->orcpt.addr_type = p_strdup(pool, src->orcpt.addr_type); + dst->orcpt.addr_raw = p_strdup(pool, src->orcpt.addr_raw); + dst->orcpt.addr = smtp_address_clone(pool, src->orcpt.addr); + + smtp_params_copy(pool, &dst->extra_params, &src->extra_params); +} + +void smtp_params_rcpt_add_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, const char *value) +{ + smtp_params_add_one(¶ms->extra_params, pool, keyword, value); +} + +void smtp_params_rcpt_encode_extra(struct smtp_params_rcpt *params, pool_t pool, + const char *keyword, + const unsigned char *value, + size_t value_len) +{ + smtp_params_add_encoded(¶ms->extra_params, pool, + keyword, value, value_len); +} + +bool smtp_params_rcpt_drop_extra(struct smtp_params_rcpt *params, + const char *keyword, const char **value_r) +{ + return smtp_params_drop_one(¶ms->extra_params, keyword, value_r); +} + +void smtp_params_rcpt_set_orcpt(struct smtp_params_rcpt *params, pool_t pool, + struct smtp_address *rcpt) +{ + params->orcpt.addr_type = "rfc822"; + params->orcpt.addr = smtp_address_clone(pool, rcpt); + params->orcpt.addr_raw = p_strdup(pool, smtp_address_encode(rcpt)); +} + +/* write */ + +static void +smtp_params_rcpt_write_notify(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_rcpt *params) +{ + if (params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0) + return; + + /* NOTIFY=<type>: RFC 3461 + + notify-esmtp-value = "NEVER" / 1#notify-list-element + notify-list-element = "SUCCESS" / "FAILURE" / "DELAY" + */ + + str_append(buffer, "NOTIFY="); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) { + i_assert(params->notify == SMTP_PARAM_RCPT_NOTIFY_NEVER); + str_append(buffer, "NEVER"); + } else { + bool comma = FALSE; + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) != 0) { + str_append(buffer, "SUCCESS"); + comma = TRUE; + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) != 0) { + if (comma) + str_append_c(buffer, ','); + str_append(buffer, "FAILURE"); + comma = TRUE; + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) != 0) { + if (comma) + str_append_c(buffer, ','); + str_append(buffer, "DELAY"); + } + } + str_append_c(buffer, ' '); +} + +static void +smtp_params_rcpt_write_orcpt(string_t *buffer, enum smtp_capability caps, + const struct smtp_params_rcpt *params) +{ + if (!smtp_params_rcpt_has_orcpt(params)) + return; + if ((caps & SMTP_CAPABILITY_DSN) == 0 && + (caps & SMTP_CAPABILITY__ORCPT) == 0) + return; + + /* ORCPT=<address>: RFC 3461 */ + + str_printfa(buffer, "ORCPT=%s;", params->orcpt.addr_type); + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + smtp_xtext_encode_cstr( + buffer, smtp_address_encode(params->orcpt.addr)); + } else { + i_assert(params->orcpt.addr_raw != NULL); + smtp_xtext_encode_cstr(buffer, params->orcpt.addr_raw); + } + str_append_c(buffer, ' '); +} + +void smtp_params_rcpt_write(string_t *buffer, enum smtp_capability caps, + const char *const *extra_params, + const struct smtp_params_rcpt *params) +{ + size_t init_len = str_len(buffer); + + smtp_params_rcpt_write_notify(buffer, caps, params); + smtp_params_rcpt_write_orcpt(buffer, caps, params); + + smtp_params_write(buffer, extra_params, ¶ms->extra_params); + + if (str_len(buffer) > init_len) + str_truncate(buffer, str_len(buffer)-1); +} + +/* evaluate */ + +const struct smtp_param * +smtp_params_rcpt_get_extra(const struct smtp_params_rcpt *params, + const char *keyword) +{ + return smtp_params_get_param(¶ms->extra_params, keyword); +} + +int smtp_params_rcpt_decode_extra(const struct smtp_params_rcpt *params, + const char *keyword, string_t **value_r, + bool allow_nul, const char **error_r) +{ + return smtp_params_decode_param(¶ms->extra_params, + keyword, value_r, allow_nul, error_r); +} + +bool smtp_params_rcpt_equal(const struct smtp_params_rcpt *params1, + const struct smtp_params_rcpt *params2) +{ + if (params1 == NULL || params2 == NULL) + return (params1 == params2); + + /* NOTIFY: RFC 3461, Section 4.1 */ + if (params1->notify != params2->notify) + return FALSE; + + /* ORCPT: RFC 3461, Section 4.2 */ + if (null_strcasecmp(params1->orcpt.addr_type, + params2->orcpt.addr_type) != 0) + return FALSE; + if (null_strcasecmp(params1->orcpt.addr_type, "rfc822") == 0) { + if (!smtp_address_equals(params1->orcpt.addr, + params2->orcpt.addr)) + return FALSE; + } else { + if (null_strcmp(params1->orcpt.addr_raw, + params2->orcpt.addr_raw) != 0) + return FALSE; + } + + /* extra parameters */ + return smtp_params_equal(¶ms1->extra_params, + ¶ms2->extra_params); +} + +/* events */ + +static void +smtp_params_rcpt_add_notify_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + /* NOTIFY: RFC 3461, Section 4.1 */ + if (params->notify == SMTP_PARAM_RCPT_NOTIFY_UNSPECIFIED) + return; + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_NEVER) != 0) { + i_assert(params->notify == + SMTP_PARAM_RCPT_NOTIFY_NEVER); + event_add_str(event, "rcpt_param_notify", "NEVER"); + } else { + string_t *str = t_str_new(32); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_SUCCESS) != 0) + str_append(str, "SUCCESS"); + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_FAILURE) != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_append(str, "FAILURE"); + } + if ((params->notify & SMTP_PARAM_RCPT_NOTIFY_DELAY) != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_append(str, "DELAY"); + } + event_add_str(event, "rcpt_param_notify", str_c(str)); + } +} + +static void +smtp_params_rcpt_add_orcpt_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + /* ORCPT: RFC 3461, Section 4.2 */ + if (params->orcpt.addr_type == NULL) + return; + + event_add_str(event, "rcpt_param_orcpt_type", + params->orcpt.addr_type); + if (strcasecmp(params->orcpt.addr_type, "rfc822") == 0) { + event_add_str(event, "rcpt_param_orcpt", + smtp_address_encode(params->orcpt.addr)); + } else { + i_assert(params->orcpt.addr_raw != NULL); + event_add_str(event, "rcpt_param_orcpt", + params->orcpt.addr_raw); + } +} + +void smtp_params_rcpt_add_to_event(const struct smtp_params_rcpt *params, + struct event *event) +{ + smtp_params_rcpt_add_notify_to_event(params, event); + smtp_params_rcpt_add_orcpt_to_event(params, event); +} |