From 24bab5179e031eb5f66dc3be381dabfca00f7bd0 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Tue, 24 Nov 2015 15:49:27 -0800 Subject: [PATCH] Rewrite address local-to-intl conversion functions. This is patch 2 of 4 implementing support for SMTPUTF8 (RFC 6531). Perform charset conversion from local to UTF-8 for both the user and domain parts of the address. If IDN is enabled and the options (added in the next patch) are turned on, encode/decode the domain part. Use the intl_checked and is_intl status bits to record the intl/local status of the ADDRESS mailbox part. --- mutt_idna.c | 326 ++++++++++++++++++++++++++++------------------------ 1 file changed, 176 insertions(+), 150 deletions(-) diff --git a/mutt_idna.c b/mutt_idna.c index 4169f788..04a7c4ca 100644 --- a/mutt_idna.c +++ b/mutt_idna.c @@ -24,200 +24,233 @@ #include "charset.h" #include "mutt_idna.h" +#ifdef HAVE_LIBIDN +static int check_idn (char *domain) +{ + if (! domain) + return 0; + if (ascii_strncasecmp (domain, "xn--", 4) == 0) + return 1; -/* check whether an address is an IDN */ + while ((domain = strchr (domain, '.')) != NULL) + { + if (ascii_strncasecmp (++domain, "xn--", 4) == 0) + return 1; + } -static int check_idn (ADDRESS *ap) -{ - char *p = 0; + return 0; +} +#endif /* HAVE_LIBIDN */ - if (!ap || !ap->mailbox) - return 0; +static int mbox_to_udomain (const char *mbx, char **user, char **domain) +{ + char *buff = NULL; + char *p; - if (!ap->intl_checked) + buff = safe_strdup (mbx); + p = strchr (buff, '@'); + if (!p || !p[1]) { - ap->intl_checked = 1; - for (p = strchr (ap->mailbox, '@'); p && *p; p = strchr (p, '.')) - if (ascii_strncasecmp (++p, "xn--", 4) == 0) - { - ap->is_intl = 1; - break; - } + FREE (&buff); + return -1; } - - return ap->is_intl; + + *p = '\0'; + *user = safe_strdup (buff); + *domain = safe_strdup (p + 1); + FREE (&buff); + return 0; } -static int intl_to_local (const char *in, char **out, int flags) +static int addr_is_local (ADDRESS *a) { - *out = NULL; + return (a->intl_checked && !a->is_intl); +} - if (!option (OPTUSEIDN)) - goto notrans; +static int addr_is_intl (ADDRESS *a) +{ + return (a->intl_checked && a->is_intl); +} - if (!in) - goto notrans; +static void set_local_mailbox (ADDRESS *a, char *local_mailbox) +{ + FREE (&a->mailbox); + a->mailbox = local_mailbox; + a->intl_checked = 1; + a->is_intl = 0; +} + +static void set_intl_mailbox (ADDRESS *a, char *intl_mailbox) +{ + FREE (&a->mailbox); + a->mailbox = intl_mailbox; + a->intl_checked = 1; + a->is_intl = 1; +} + +static char *intl_to_local (ADDRESS *a, int flags) +{ + char *user = NULL, *domain = NULL, *mailbox = NULL; + char *orig_domain = NULL, *reversed_domain = NULL; + char *tmp = NULL; +#ifdef HAVE_LIBIDN + int is_idn_encoded = 0; +#endif /* HAVE_LIBIDN */ + + if (mbox_to_udomain (a->mailbox, &user, &domain) == -1) + goto cleanup; + orig_domain = safe_strdup (domain); - /* Is this the right function? Interesting effects with some bad identifiers! */ #ifdef HAVE_LIBIDN - if (idna_to_unicode_8z8z (in, out, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) - goto notrans; + is_idn_encoded = check_idn (domain); + if (is_idn_encoded && option (OPTUSEIDN)) + { + if (idna_to_unicode_8z8z (domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) + goto cleanup; + mutt_str_replace (&domain, tmp); + FREE (&tmp); + } #endif /* HAVE_LIBIDN */ /* we don't want charset-hook effects, so we set flags to 0 */ - if (mutt_convert_string (out, "utf-8", Charset, 0) == -1) - goto notrans; + if (mutt_convert_string (&user, "utf-8", Charset, 0) == -1) + goto cleanup; - /* + if (mutt_convert_string (&domain, "utf-8", Charset, 0) == -1) + goto cleanup; + + /* * make sure that we can convert back and come out with the same - * domain name. + * domain name. */ - if ((flags & MI_MAY_BE_IRREVERSIBLE) == 0) { - int irrev = 0; - char *t2 = NULL; - char *tmp = safe_strdup (*out); + reversed_domain = safe_strdup (domain); + + if (mutt_convert_string (&reversed_domain, Charset, "utf-8", 0) == -1) + { + dprint (1, (debugfile, + "intl_to_local: Not reversible. Charset conv to utf-8 failed for domain = '%s'.\n", + reversed_domain)); + goto cleanup; + } - /* we don't want charset-hook effects, so we set flags to 0 */ - if (mutt_convert_string (&tmp, Charset, "utf-8", 0) == -1) - irrev = 1; #ifdef HAVE_LIBIDN - if (!irrev && idna_to_ascii_8z (tmp, &t2, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) - irrev = 1; -#endif /* HAVE_LIBIDN */ - if (!irrev && ascii_strcasecmp (t2, in)) + /* If the original domain was UTF-8, idna encoding here could + * produce a non-matching domain! Thus we only want to do the + * idna_to_ascii_8z() if the original domain was IDNA encoded. + */ + if (is_idn_encoded && option (OPTUSEIDN)) { - dprint (1, (debugfile, "intl_to_local: Not reversible. in = '%s', t2 = '%s'.\n", - in, t2)); - irrev = 1; + if (idna_to_ascii_8z (reversed_domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) + { + dprint (1, (debugfile, + "intl_to_local: Not reversible. idna_to_ascii_8z failed for domain = '%s'.\n", + reversed_domain)); + goto cleanup; + } + mutt_str_replace (&reversed_domain, tmp); } - - FREE (&t2); - FREE (&tmp); +#endif /* HAVE_LIBIDN */ - if (irrev) - goto notrans; + if (ascii_strcasecmp (orig_domain, reversed_domain)) + { + dprint (1, (debugfile, "intl_to_local: Not reversible. orig = '%s', reversed = '%s'.\n", + orig_domain, reversed_domain)); + goto cleanup; + } } - return 0; - - notrans: - FREE (out); /* __FREE_CHECKED__ */ - *out = safe_strdup (in); - return 1; + mailbox = safe_malloc (mutt_strlen (user) + mutt_strlen (domain) + 2); + sprintf (mailbox, "%s@%s", NONULL(user), NONULL(domain)); /* __SPRINTF_CHECKED__ */ + +cleanup: + FREE (&user); + FREE (&domain); + FREE (&tmp); + FREE (&orig_domain); + FREE (&reversed_domain); + + return mailbox; } -static int local_to_intl (const char *in, char **out) +static char *local_to_intl (ADDRESS *a) { - int rv = 0; - char *tmp = safe_strdup (in); - *out = NULL; + char *user = NULL, *domain = NULL, *mailbox = NULL; + char *tmp = NULL; + + if (mbox_to_udomain (a->mailbox, &user, &domain) == -1) + goto cleanup; - if (!in) - { - *out = NULL; - return -1; - } - /* we don't want charset-hook effects, so we set flags to 0 */ - if (mutt_convert_string (&tmp, Charset, "utf-8", 0) == -1) - rv = -1; + if (mutt_convert_string (&user, Charset, "utf-8", 0) == -1) + goto cleanup; + + if (mutt_convert_string (&domain, Charset, "utf-8", 0) == -1) + goto cleanup; #ifdef HAVE_LIBIDN - if (!rv && idna_to_ascii_8z (tmp, out, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) - rv = -2; + if (idna_to_ascii_8z (domain, &tmp, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS) + goto cleanup; + mutt_str_replace (&domain, tmp); #endif /* HAVE_LIBIDN */ - + + mailbox = safe_malloc (mutt_strlen (user) + mutt_strlen (domain) + 2); + sprintf (mailbox, "%s@%s", NONULL(user), NONULL(domain)); /* __SPRINTF_CHECKED__ */ + +cleanup: + FREE (&user); + FREE (&domain); FREE (&tmp); - if (rv < 0) - { - FREE (out); /* __FREE_CHECKED__ */ - *out = safe_strdup (in); - } - return rv; + + return mailbox; } /* higher level functions */ -static int mbox_to_udomain (const char *mbx, char **user, char **domain) -{ - static char *buff = NULL; - char *p; - - mutt_str_replace (&buff, mbx); - - p = strchr (buff, '@'); - if (!p || !p[1]) - return -1; - *p = '\0'; - *user = buff; - *domain = p + 1; - return 0; -} - int mutt_addrlist_to_intl (ADDRESS *a, char **err) { - char *user = NULL, *domain = NULL; - char *tmp = NULL; - int e = 0; - + char *intl_mailbox = NULL; + int rv = 0; + if (err) *err = NULL; for (; a; a = a->next) { - if (!a->mailbox) - continue; - if (mbox_to_udomain (a->mailbox, &user, &domain) == -1) + if (!a->mailbox || addr_is_intl (a)) continue; - - if (local_to_intl (domain, &tmp) < 0) - { - e = 1; - if (err) - *err = safe_strdup (domain); - } - else + + intl_mailbox = local_to_intl (a); + if (! intl_mailbox) { - safe_realloc (&a->mailbox, mutt_strlen (user) + mutt_strlen (tmp) + 2); - sprintf (a->mailbox, "%s@%s", NONULL(user), NONULL(tmp)); /* __SPRINTF_CHECKED__ */ - a->intl_checked = 0; + rv = -1; + if (err && !*err) + *err = safe_strdup (a->mailbox); + continue; } - - FREE (&tmp); - - if (e) - return -1; + + set_intl_mailbox (a, intl_mailbox); } - - return 0; + + return rv; } int mutt_addrlist_to_local (ADDRESS *a) { - char *user, *domain; - char *tmp = NULL; - + char *local_mailbox = NULL; + for (; a; a = a->next) { - if (!a->mailbox) + if (!a->mailbox || addr_is_local (a)) continue; - if (!check_idn (a)) - continue; - if (mbox_to_udomain (a->mailbox, &user, &domain) == -1) - continue; - if (intl_to_local (domain, &tmp, 0) == 0) - { - safe_realloc (&a->mailbox, mutt_strlen (user) + mutt_strlen (tmp) + 2); - sprintf (a->mailbox, "%s@%s", NONULL (user), NONULL (tmp)); /* __SPRINTF_CHECKED__ */ - a->intl_checked = 0; - } - - FREE (&tmp); + + local_mailbox = intl_to_local (a, 0); + if (local_mailbox) + set_local_mailbox (a, local_mailbox); } - + return 0; } @@ -225,27 +258,19 @@ int mutt_addrlist_to_local (ADDRESS *a) const char *mutt_addr_for_display (ADDRESS *a) { static char *buff = NULL; - char *tmp = NULL; - /* user and domain will be either allocated or reseted to the NULL in - * the mbox_to_udomain(), but for safety... */ - char *domain = NULL; - char *user = NULL; - + char *local_mailbox = NULL; + FREE (&buff); - if (!check_idn (a)) - return a->mailbox; - if (mbox_to_udomain (a->mailbox, &user, &domain) != 0) + if (!a->mailbox || addr_is_local (a)) return a->mailbox; - if (intl_to_local (domain, &tmp, MI_MAY_BE_IRREVERSIBLE) != 0) - { - FREE (&tmp); + + local_mailbox = intl_to_local (a, MI_MAY_BE_IRREVERSIBLE); + if (! local_mailbox) return a->mailbox; - } - - safe_realloc (&buff, mutt_strlen (tmp) + mutt_strlen (user) + 2); - sprintf (buff, "%s@%s", NONULL(user), NONULL(tmp)); /* __SPRINTF_CHECKED__ */ - FREE (&tmp); + + mutt_str_replace (&buff, local_mailbox); + FREE (&local_mailbox); return buff; } @@ -265,6 +290,7 @@ void mutt_env_to_local (ENVELOPE *e) /* Note that `a' in the `env->a' expression is macro argument, not * "real" name of an `env' compound member. Real name will be substituted * by preprocessor at the macro-expansion time. + * Note that #a escapes and double quotes the argument. */ #define H_TO_INTL(a) \ if (mutt_addrlist_to_intl (env->a, err) && !e) \ -- 2.40.0