[moving to tech@] This seems to be a long-standing issue in opensmtpd. There seem to be (at least) one fairly popular application that uses a, upon a first look, odd ORCPT parameter:
> >>> On Fri, Oct 28, 2022 at 04:16:36PM +0200, Tim Kuijsten wrote: > >>>> I have refined and more thoroughly tested a previous patch that > >>>> relaxes the ORCPT address check. > >>>> > >>>> Over the years mail has been rejected by senders that use RCPT > >>>> TO commands like: > >>>> RCPT TO:<i...@exmpale.com> > >>>> ORCPT=rfc822;groupwise-i...@example.com:0:0 or RCPT > >>>> TO:<i...@exmpale.com> > >>>> ORCPT=rfc822;groupwise-i...@example.com:0:0 > >>>> NOTIFY=SUCCESS,FAILURE > >>>> > >>>> In the above the domain part of the ORCPT address resolves to > >>>> example.com:0:0 which is rejected by smtpd with the message: > >>>> smtpd[20797]: 1a3a396cd4c57d05 smtp failed-command command="RCPT > >>>> TO:<i...@example.com> > >>>> ORCPT=rfc822;groupwise-i...@example.com:0:0 > >>>> NOTIFY=SUCCESS,FAILURE" result="553 ORCPT address syntax error" > >>>> > >>>> [...] Quoting RFC 3461, the ORCPT parameter is defined as (ΒΆ 4.2) orcpt-parameter = "ORCPT=" original-recipient-address original-recipient-address = addr-type ";" xtext addr-type = atom The "addr-type" portion MUST be an IANA-registered electronic mail address-type (as defined in [3]), while the "xtext" portion contains an encoded representation of the original recipient address using the rules in section 5 of this document. [...] The "addr-type" portion of the original-recipient-address is used to indicate the "type" of the address which appears in the ORCPT parameter value. However, the address associated with the ORCPT keyword is NOT constrained to conform to the syntax rules for that "addr-type". I'd like to emphasize the last sentence, which is the one I missed upon the first couple of look at this RFC: even if addr-type is rfc822, the xtext part (once decoded) is not constrained to conform to the syntax rules of rfc822. That is, this groupwise thingy is actually confirming to the rfc when using a param of "rfc822;groupwise-i...@example.com:0:0". Honesty, I fail to understand the meaning of the addr-type if any gargabe is allowed. The "once decoded" part is important since the RFC allows for ANY character to be encoded, so rfc822;+45+78+62+6d+70+6c+65+40... (i.e. example@...) is still a valid ORCPT param. Tassilo' diff (which is a slightly modified version of the one originally sent in the bug report by Tim) is available here https://www.mail-archive.com/misc@opensmtpd.org/msg06054.html and it relaxes the checks but IMHO it doesn't address the underlying issue: we expect a valid rfc822 address where it's not mandatory. I think we should just keep the ORCPT address as on opaque string (modulo some validation) without trying to parse it. I still have to test the diff below in a real-world scenario, but I'm sending it out so other can hopefully give it a spin. (and I couldn't resist splitting a long line in the process.) Thanks, Omar Polo diff /home/op/w/smtpd commit - 45a7eac758c7b1e9c4f16ab2dfcb25672b49aad9 path + /home/op/w/smtpd blob - c9611beb48feab47602b766061a7546c375160a8 file + envelope.c --- envelope.c +++ envelope.c @@ -443,7 +443,8 @@ ascii_load_field(const char *field, struct envelope *e return ascii_load_uint8(&ep->dsn_notify, buf); if (strcasecmp("dsn-orcpt", field) == 0) - return ascii_load_mailaddr(&ep->dsn_orcpt, buf); + return ascii_load_string(ep->dsn_orcpt, buf, + sizeof(ep->dsn_orcpt)); if (strcasecmp("dsn-ret", field) == 0) return ascii_load_dsn_ret(&ep->dsn_ret, buf); @@ -703,11 +704,8 @@ ascii_dump_field(const char *field, const struct envel if (strcasecmp(field, "dsn-ret") == 0) return ascii_dump_dsn_ret(ep->dsn_ret, buf, len); - if (strcasecmp(field, "dsn-orcpt") == 0) { - if (ep->dsn_orcpt.user[0] && ep->dsn_orcpt.domain[0]) - return ascii_dump_mailaddr(&ep->dsn_orcpt, buf, len); - return 1; - } + if (strcasecmp(field, "dsn-orcpt") == 0) + return ascii_dump_string(ep->dsn_orcpt, buf, len); if (strcasecmp(field, "dsn-envid") == 0) return ascii_dump_string(ep->dsn_envid, buf, len); blob - f0bb42c53ffb8f98012d542209bb777755ecd1ae file + mta.c --- mta.c +++ mta.c @@ -809,11 +809,8 @@ mta_handle_envelope(struct envelope *evp, const char * if (strcmp(buf, e->dest)) e->rcpt = xstrdup(buf); e->task = task; - if (evp->dsn_orcpt.user[0] && evp->dsn_orcpt.domain[0]) { - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->dsn_orcpt.user, evp->dsn_orcpt.domain); - e->dsn_orcpt = xstrdup(buf); - } + if (evp->dsn_orcpt[0] != '\0') + e->dsn_orcpt = xstrdup(evp->dsn_orcpt); (void)strlcpy(e->dsn_envid, evp->dsn_envid, sizeof e->dsn_envid); e->dsn_notify = evp->dsn_notify; blob - 160b41d30b838d8cdd6be0db6ae9f3a6ac05a3dd file + mta_session.c --- mta_session.c +++ mta_session.c @@ -809,7 +809,7 @@ again: e->dest, e->dsn_notify ? " NOTIFY=" : "", e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", - e->dsn_orcpt ? " ORCPT=rfc822;" : "", + e->dsn_orcpt ? " ORCPT=" : "", e->dsn_orcpt ? e->dsn_orcpt : ""); } else mta_send(s, "RCPT TO:<%s>", e->dest); blob - 7848ece404a05d36d61525cb965b1a5550a9c048 file + smtp_session.c --- smtp_session.c +++ smtp_session.c @@ -2471,16 +2471,15 @@ smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) " combined with other options"); return; } - } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { + } else if (ADVERTISE_EXT_DSN(tx->session) && + strncasecmp(opt, "ORCPT=", 6) == 0) { + size_t len = sizeof(tx->evp.dsn_orcpt); + opt += 6; - if (strncasecmp(opt, "rfc822;", 7) == 0) - opt += 7; - - if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || - !valid_localpart(tx->evp.dsn_orcpt.user) || - (strlen(tx->evp.dsn_orcpt.domain) != 0 && - !valid_domainpart(tx->evp.dsn_orcpt.domain))) { + if ((p = strchr(opt, ';')) == NULL || + !valid_xtext(p + 1) || + strlcpy(opt, tx->evp.dsn_orcpt, len) >= len) { smtp_reply(tx->session, "553 ORCPT address syntax error"); return; blob - 25090c3acf9980c0fbe22784a5242dae64c8ec53 file + smtpd.h --- smtpd.h +++ smtpd.h @@ -467,6 +467,7 @@ struct maddrmap { #define DSN_NEVER 0x08 #define DSN_ENVID_LEN 100 +#define DSN_ORCPT_LEN 500 #define SMTPD_ENVELOPE_VERSION 3 struct envelope { @@ -507,7 +508,7 @@ struct envelope { time_t nexttry; time_t lastbounce; - struct mailaddr dsn_orcpt; + char dsn_orcpt[DSN_ORCPT_LEN+1]; char dsn_envid[DSN_ENVID_LEN+1]; uint8_t dsn_notify; enum dsn_ret dsn_ret; @@ -1703,6 +1704,7 @@ int valid_localpart(const char *); int valid_domainpart(const char *); int valid_domainname(const char *); int valid_smtp_response(const char *); +int valid_xtext(const char *); int secure_file(int, char *, char *, uid_t, int); int lowercase(char *, const char *, size_t); void xlowercase(char *, const char *, size_t); blob - feb663cc61ec2572b57741fd694926f0c26967c7 file + util.c --- util.c +++ util.c @@ -531,6 +531,30 @@ valid_smtp_response(const char *s) } int +valid_xtext(const char *s) +{ + for (; *s != '\0'; ++s) { + if (*s < '!' || *s > '~' || *s == '=') + return 0; + + if (*s != '+') + continue; + + s++; + if (!isdigit((unsigned char)*s) && + !(*s >= 'A' && *s <= 'F')) + return 0; + + s++; + if (!isdigit((unsigned char)*s) && + !(*s >= 'A' && *s <= 'F')) + return 0; + } + + return 1; +} + +int secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread) { char buf[PATH_MAX];