]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: smtp-address - Implement workarounds for parsing addresses with a bad local...
authorStephan Bosch <stephan.bosch@dovecot.fi>
Sun, 4 Nov 2018 18:00:47 +0000 (19:00 +0100)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Fri, 4 Oct 2019 11:59:35 +0000 (13:59 +0200)
Always parse it as normal when it starts with `<' or `"'. The workaround
consists of parsing the address by splitting it on the last `@'. The address is
accepted when the resulting localpart and domain can be used to compose a valid
address. This workaround is now shared with username-to-address parsing, which
existed before.

src/lib-smtp/smtp-address.c
src/lib-smtp/smtp-address.h

index cfb9062ff1471587eea767ec7cdbcfcda7381d35..315c95df7c969af639e906494b906b68fb340a0f 100644 (file)
@@ -45,6 +45,7 @@ struct smtp_address_parser {
        struct smtp_parser parser;
 
        struct smtp_address address;
+       const unsigned char *address_end;
 
        bool parse:1;
        bool path:1;
@@ -86,20 +87,71 @@ smtp_parse_localpart(struct smtp_parser *parser, const char **localpart_r)
        return smtp_parser_parse_dot_string(parser, localpart_r);
 }
 
+static int
+smtp_address_parser_find_end(struct smtp_address_parser *aparser)
+{
+       struct smtp_parser *parser = &aparser->parser;
+       const char *begin = (const char *)parser->begin, *end;
+
+       if (aparser->address_end != NULL)
+               return 0;
+
+       if (smtp_address_parse_any(begin, NULL, &end) < 0) {
+               parser->error = "Invalid character";
+               return -1;
+       }
+       aparser->address_end = (const unsigned char *)end;
+       if (aparser->path) {
+               i_assert(aparser->address_end > parser->begin);
+               aparser->address_end--;
+       }
+       return 0;
+}
+
 static int
 smtp_parse_mailbox(struct smtp_address_parser *aparser,
                   enum smtp_address_parse_flags flags)
 {
        struct smtp_parser *parser = &aparser->parser;
        const char **value = NULL;
+       const unsigned char *p, *dp;
        int ret;
 
        /* Mailbox = Local-part "@" ( Domain / address-literal )
         */
 
        value = (aparser->parse ? &aparser->address.localpart : NULL);
-       if ((ret = smtp_parse_localpart(parser, value)) <= 0)
-               return ret;
+       if ((flags & SMTP_ADDRESS_PARSE_FLAG_STRICT) != 0 ||
+           (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART) == 0 ||
+           aparser->path || *parser->cur == '\"') {
+               if ((ret = smtp_parse_localpart(parser, value)) <= 0)
+                       return ret;
+       } else {
+               /* find the end of the address */
+               if (smtp_address_parser_find_end(aparser) < 0)
+                       return -1;
+               /* use the right-most '@' as separator */
+               dp = aparser->address_end - 1;
+               while (dp > parser->cur && *dp != '@')
+                       dp--;
+               if (dp == parser->cur)
+                       dp = aparser->address_end;
+               /* check whether the resulting localpart could be encoded as
+                  quoted string */
+               for (p = parser->cur; p < dp; p++) {
+                       if (!smtp_char_is_qtext(*p) &&
+                           !smtp_char_is_qpair(*p)) {
+                               parser->error =
+                                       "Invalid character in localpart";
+                               return -1;
+                       }
+               }
+               if (aparser->parse) {
+                       aparser->address.localpart =
+                               p_strdup_until(parser->pool, parser->cur, dp);
+               }
+               parser->cur = dp;
+       }
 
        if ((parser->cur >= parser->end || *parser->cur != '@') &&
            (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART) == 0) {
@@ -230,70 +282,6 @@ smtp_parse_path(struct smtp_address_parser *aparser,
        return 1;
 }
 
-static int smtp_parse_username(struct smtp_address_parser *aparser)
-{
-       struct smtp_parser *parser = &aparser->parser;
-       const char **value = NULL;
-       const unsigned char *p, *dp;
-       int ret;
-
-       /* Best-effort extraction of SMTP address from a user name.
-        */
-
-       value = (aparser->parse ? &aparser->address.localpart : NULL);
-       if (*parser->cur == '\"') {
-               /* if the local part is a quoted string, parse it as any other
-                  SMTP address */
-               if ((ret = smtp_parse_localpart(parser, value)) <= 0)
-                       return ret;
-       } else {
-               /* use the right-most '@' as separator */
-               dp = parser->end - 1;
-               while (dp > parser->cur && *dp != '@')
-                       dp--;
-               if (dp == parser->cur)
-                       dp = parser->end;
-               /* check whether the resulting localpart could be encoded as
-                  quoted string */
-               for (p = parser->cur; p < dp; p++) {
-                       if (!smtp_char_is_qtext(*p) &&
-                           !smtp_char_is_qpair(*p)) {
-                               parser->error =
-                                       "Invalid character in user name";
-                               return -1;
-                       }
-               }
-               if (aparser->parse) {
-                       aparser->address.localpart =
-                               p_strdup_until(parser->pool, parser->cur, dp);
-               }
-               parser->cur = dp;
-       }
-
-       if (parser->cur < parser->end && *parser->cur != '@') {
-               parser->error = "Invalid character in user name";
-               return -1;
-       }
-
-       if (parser->cur >= parser->end || *parser->cur != '@')
-               return 1;
-       parser->cur++;
-
-       value = (aparser->parse ? &aparser->address.domain : NULL);
-       if ((ret = smtp_parser_parse_domain(parser, value)) == 0 &&
-           (ret = smtp_parser_parse_address_literal(
-               parser, value, NULL)) == 0) {
-               if (parser->cur >= parser->end) {
-                       parser->error = "Missing domain after '@'";
-                       return -1;
-               } else {
-                       parser->error = "Invalid domain";
-                       return -1;
-               }
-       }
-       return ret;
-}
-
 int smtp_address_parse_mailbox(pool_t pool, const char *mailbox,
                               enum smtp_address_parse_flags flags,
                               struct smtp_address **address_r,
@@ -324,6 +312,7 @@ int smtp_address_parse_mailbox(pool_t pool, const char *mailbox,
 
        i_zero(&aparser);
        smtp_parser_init(&aparser.parser, pool_datastack_create(), mailbox);
+       aparser.address_end = aparser.parser.end;
        aparser.parse = (address_r != NULL);
 
        if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) {
@@ -371,6 +360,7 @@ int smtp_address_parse_path_full(pool_t pool, const char *path,
 
        i_zero(&aparser);
        smtp_parser_init(&aparser.parser, pool_datastack_create(), path);
+       aparser.address_end = (endp_r != NULL ? NULL : aparser.parser.end);
        aparser.parse = (address_r != NULL);
 
        if ((ret = smtp_parse_path(&aparser, flags)) <= 0) {
@@ -406,6 +396,10 @@ int smtp_address_parse_username(pool_t pool, const char *username,
                                struct smtp_address **address_r,
                                const char **error_r)
 {
+       enum smtp_address_parse_flags flags =
+               SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+               SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART;
+
        struct smtp_address_parser aparser;
        int ret;
 
@@ -422,9 +416,10 @@ int smtp_address_parse_username(pool_t pool, const char *username,
 
        i_zero(&aparser);
        smtp_parser_init(&aparser.parser, pool_datastack_create(), username);
+       aparser.address_end = aparser.parser.end;
        aparser.parse = (address_r != NULL);
 
-       if ((ret = smtp_parse_username(&aparser)) <= 0) {
+       if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) {
                if (error_r != NULL) {
                        *error_r = (ret < 0 ? aparser.parser.error :
                                "Invalid character in user name");
index e1adabd3fa15e2f0acfc7672255c2d5e0ac7702e..d3cb224933ddc5a5daa4fe1ec9ca6a9e904fa8fa 100644 (file)
@@ -7,13 +7,20 @@ struct message_address;
 
 enum smtp_address_parse_flags {
        /* Strictly enforce the RFC 5321 syntax */
-       SMTP_ADDRESS_PARSE_FLAG_STRICT            = BIT(0),
+       SMTP_ADDRESS_PARSE_FLAG_STRICT              = BIT(0),
        /* Allow an empty/NULL address */
-       SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY       = BIT(1),
+       SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY         = BIT(1),
        /* Allow an address without a domain part */
-       SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART   = BIT(2),
+       SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART     = BIT(2),
        /* Allow omission of the <...> brackets in a path */
-       SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL = BIT(3)
+       SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL   = BIT(3),
+       /* Allow localpart to have all kinds of bad unquoted characters by
+          parsing the last '@' in the string directly as the localpart/domain
+          separator. Addresses starting with `<' or `"' are parsed as normal.
+          The address is rejected when the resulting localpart and domain
+          cannot be used to construct a valid RFC 5321 address.
+        */
+       SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART = BIT(4),
 };
 
 struct smtp_address {