diff --git a/postfix/.indent.pro b/postfix/.indent.pro index b9f3a9ea1..c15aa6353 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -125,6 +125,7 @@ -TDICT_THASH -TDICT_UNION -TDICT_UNIX +-TDICT_UTF8_BACKUP -TDNS_FIXED -TDNS_REPLY -TDNS_RR diff --git a/postfix/HISTORY b/postfix/HISTORY index 4b8cf432e..318c8c754 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -21209,3 +21209,206 @@ Apologies for any names omitted. either their result is a valid ASCII domain name or that it converts into a valid ASCII domain name. Files: util/midna.c, util/midna_test.in, util/midna_test.ref. + +20141230 + + Cleanup: s/midna/midna_domain/ for better specificity, + because we also need functions that act only on the domain + portion of an email address. Files: bounce/bounce_template.c, + global/midna_adomain.c, posttls-finger/posttls-finger.c, + smtp/smtp_addr.c, smtpd/smtpd_check.c, tls/tls_client.c, + util/midna_domain.[hc], util/valid_utf8_hostname.c. + + Infrastructure: function midna_adomain_to_utf8() (and + midna_adomain_to_ascii) to convert the domain portion of + an email address before table lookup. Files: + global/midna_adomain.[hc]. + +20141230-20140109 + + What is described here is the result of four iterations to + deal with malformed UTF-8 without massively contaminating + every Postfix program with new error-handling code paths, + in particular without triggering fatal errors that didn't + happen before. + + Infrastructure: function casefold() to support caseless + string comparison, primarily for table lookups. This function + supports two modes: case folding a la lowercase() for ASCII + byte values, and UTF-8 case folding. As recommended at + http://www.w3.org/International/wiki/Case_folding for + caseless string comparison, this uses the en_US locale to + avoid surprises. The implementatin handles + the entire RFC 3629 Unicode range (code points U+0000..U+10FFFF + including surrogates) and is chroot(2) safe. Files: casefold.c, stringops.h. + + Infrastructure: revised the midna_domain_to_ascii and + midna_domain_to_utf8 domain name conversion functions after + careful reading of the UTS #46 specification, and after + observing that ICU 4.8 library functions indeed implement + this spec, at least with default options. In particular, + midna_domain_to_utf8 takes an UTF-8 domain name and verifies + that its A-label form will pass the valid_hostname() test. + File: util/midna_domain.c. + + Infrastructure: handle UTF-8 errors in lookup table keys + or values without massively contaminating every Postfix + program with new error-handling code paths, in particular + without triggering fatal errors that didn't happen before. + The lookup/update/delete functions log a warning and ignore + a request with a bad key (it cannot exist); the update + functions ignore a request to store a bad value (it cannot + exist); and the lookup function reports a bad value as a + configuration error (it should not exist, but there it is). + Table iterators still report all (key, value) pairs in a + table. Files: util/dict.h, util/dict_open.c, util/dict_utf8.c, + global/mkmap_open.c. + + Note that with SMTPUTF8 turned on, each table-driven mechanism + (access, aliases, etc.) needs to make its own decision + whether UTF-8 syntax is required. We cannot blindly require + that everything has valid UTF-8 syntax. That would make + header/body_checks useless for content inspection, because + headers may be malformed and bodies may contain legitimate + binary content that isn't UTF-8. + + Note that with SMTPUTF8 turned off, Postfix must remain + 8-bit clean as it always has been. Table operations must + not complain that something violates UTF-8 syntax rules. + + UTF-8 sanitization in the Postfix SMTP server. With + smtputf8_enable=yes, SMTP commands with UTF-8 syntax errors + are rejected, table lookup results with invalid UTF-8 syntax + are handled as configuration errors, and UTF-8 syntax errors + in policy server replies result in execution of the policy + server's default action. + +20150102 + + Cleanup: propagate DICT_ERR_CONFIG through the proxymap + protocol. Files: global/dict_proxy.[hc], proxymap/proxymap.c. + +20150106 + + Robustness: don't segfault due to excessive recursion in + tok822_free_tree() after a faulty configuration runs into + the virtual_alias_recursion_limit. File: global/tok822_tree.c. + +20150109 + + Cleanup: the dict debug module now proxies dict flags. + File: util/dict_debug.c. + + With "smtputf8_enable = yes", the postmap and postalias + commands now enable UTF-8 by default (use "-u" to disable) + with one exception: UTF-8 remains disabled for header/body_checks + emulation (use "-U" to enable). Files: postmap/postmap.c, + postalias/postalias.c. + +20150110 + + Cleanup: the "inline" and "texthash" implementations now + reuse the "internal" database instead of reinventing the + wheel. Files: util/dict_inline.c, util/dict_thash.c. + + As a first step, with "smtputf8_enable = yes" all features + based on Postfix matchlists enable UTF-8 syntax checks and + UTF-8 casefolding for table patterns, but NOT YET for string + patterns. The list of features includes authorized_flush_users, + authorized_mailq_users, authorized_submit_users, debug_peer_list, + fast_flush_domains, mydestination, permit_mx_backup_networks, + qmqpd_authorized_clients, smtp_connection_cache_destinations, + smtpd_authorized_verp_clients, smtpd_authorized_xclient_hosts, + smtpd_authorized_xforward_hosts, + smtpd_client_event_limit_exceptions, + smtpd_log_access_permit_actions, smtpd_sasl_exceptions_networks, + the "domains" feature in ldap_table(5), memcache_table(5) + mysql_table(5), pgsql_table(5) and sqlite_table(5), + virtual_alias_domains, virtual_mailbox_domains. + +20150111 + + Cleanup: simplified the interposition layer that adds UTF-8 + support to Postfix lookup tables. Files: util/dict_utf8.c. + + With "smtputf8_enable = yes", Enable UTF-8 syntax checks + and UTF-8 casefolding for SMTP server access maps, alias_maps, + canonical_maps, fallback_transport_maps, + lmtp_tls_session_cache_database, local_recipient_maps, + mailbox_command_maps, mailbox_transport_maps, rbl_reply_maps, + recipient_bcc_maps, recipient_canonical_maps, relay_recipient_maps, + relocated_maps, sender_bcc_maps, sender_canonical_maps, + sender_dependent_relayhost_maps, sender_dependent_transport_maps, + smtp_generic_maps, smtp_sasl_auth_cache_name, + smtp_sasl_password_maps, smtp_tls_per_site, smtp_tls_policy_maps, + smtp_tls_session_cache_database, smtpd_sender_login_maps, + smtpd_tls_session_cache_database, transport_maps, + virtual_alias_maps, virtual_gid_maps, virtual_mailbox_maps, + virtual_uid_maps. + +20150112 + + Infrastructure: support for UTF-8 casefolding in match_lists. + Instead of using strcasecmp(), casefold all fixed-string + patterns during initialization, casefold a search string + at the beginning of the search, and use strcmp() for + comparison. Files: util/casefold.c util/dict.h, util/dict_utf8.c, + util/match_list.c, util/match_list.h, util/match_ops.c, + util/stringops.h, global/addr_match_list.c, global/domain_list.c, + global/namadr_list.c, global/string_list.c. + +20150113 + + Cleanup: show the configuration parameter name in error + messages while parsing or searching match_list-based features + such as mydestination, relay_domains and a few dozen more. + Files: cleanup/cleanup_init.c, flush/flush.c, + global/addr_match_list.c, global/debug_peer.c, + global/domain_list.c, global/flush_clnt.c, + global/match_parent_style.c, global/namadr_list.c, + global/resolve_local.c, global/string_list.c, global/user_acl.[hc], + postdrop/postdrop.c, postqueue/postqueue.c, + postscreen/postscreen.c, qmqpd/qmqpd.c, sendmail/sendmail.c., + smtp/smtp.c, smtp/smtp_sasl_glue.c, smtpd/smtpd.c, + smtpd/smtpd_check.c, trivial-rewrite/resolve.c, + util/match_list.[hc], util/match_ops.c. + + Cleanup: apply printable() to all bounce(8) service + string-valued protocol fields. File: bounce/bounce.c. + + Apparenly the UCI 4.8 ucasemap_utf8FoldCase() function does + not complain about UTF-8 syntax errors, so we add our own + redundant check. File: util/casefold.c. + +20150115 + + Bitrot: prepare for future changes in OpenSSL. Viktor + Dukhovni. Files: tls/tls.h, tls/tls_dh.c, tls/tls_misc.c, + tls/tls_rsa.c, tls/tls_server.c. + + Documentation: "avoid hash files here, use btree or lmdb + instead". File: proto/ADDRESS_VERIFICATION_README.html. + + Safety: virtual_alias_address_length_limit (default: 1000) + to stop aliasing loops that exponentially increase the + address length with each iteration. Files: global/mail_params.h, + mantools/postlink, proto/postconf.proto, cleanup/cleanup.c, + cleanup/cleanup_init.c, cleanup/cleanup_map1n.c. + +20150116 + + TLS wrappermode in the Postfix smtp(8) client. This introduces + a new parameter "smtp_tls_wrappermode" (default: no). Files: + global/mail_params.h, mantools/postlink, proto/postconf.proto, + smtp/lmtp_params.c, smtp/smtp.[hc], smtp/smtp_connect.c, + smtp/smtp_params.c, smtp/smtp_proto.c. + + TLS wrappermode in posttls-finger(1), and some DANE-related + cleanups. This introduces a new option "-w". Viktor Dukhovni. + Files: posttls-finger/posttls-finger.c, smtp/smtp_tls_policy.c, + tls/tls.h, tls/tls_client.c, tls/tls_fprint.c. + +20150117 + + Cleanup: missing " in \%s\" in postscreen(8) fatal error + messages. Iain Hibbert. File: postconf/postconf_master.c. diff --git a/postfix/README_FILES/ADDRESS_VERIFICATION_README b/postfix/README_FILES/ADDRESS_VERIFICATION_README index 69a5f93d3..fb99af30f 100644 --- a/postfix/README_FILES/ADDRESS_VERIFICATION_README +++ b/postfix/README_FILES/ADDRESS_VERIFICATION_README @@ -204,7 +204,7 @@ verification for specific domains that often appear in forged email. # Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify /etc/postfix/sender_access: @@ -245,7 +245,7 @@ be blocked: # Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify This is also a good way to populate your cache with address verification @@ -297,7 +297,7 @@ verification results. If you specify an empty value, all address verification results are lost after "postfix reload" or "postfix stop". # Example 1: Default setting for Postfix 2.7 and later. - # Note: avoid hash files here. Use btree instead. + # Note: avoid hash files here. Use btree or lmdb instead. /etc/postfix/main.cf: address_verify_map = btree:$data_directory/verify_cache diff --git a/postfix/README_FILES/SMTPUTF8_README b/postfix/README_FILES/SMTPUTF8_README index c0aa9ae8b..dcc13a189 100644 --- a/postfix/README_FILES/SMTPUTF8_README +++ b/postfix/README_FILES/SMTPUTF8_README @@ -65,8 +65,25 @@ With SMTPUTF8 support enabled, Postfix changes behavior with respect to earlier Postfix releases: * UTF-8 is permitted in the myorigin parameter value. However, the myhostname - and mydomain parameters must specify ASCII-only domain names. This - limitation may be removed later. + and mydomain parameters must currently specify ASCII-only domain names. + This limitation may be removed later. + + * UTF-8 is the only form of non-ASCII text that Postfix supports in access + tables, address rewriting tables, and other tables that are indexed with an + email address, hostname, or domain name. + + * The header_checks-like and body_checks-like features are not UTF-8 enabled, + and therefore they do not enforce UTF-8 syntax rules on inputs and outputs. + The reason is that non-ASCII text may be sent in encodings other than UTF- + 8, and that real email sometimes contains malformed headers. Instead of + skipping non-UTF-8 content, Postfix should be able to filter it. You may + try to enable UTF-8 processing by starting a PCRE pattern with the sequence + (*UTF8), but this is will result in "message not accepted, try again later" + errors when the PCRE pattern matcher encounters non-UTF-8 input. Other + features that are not UTF-8 enabled are smtpd_command_filter, + smtp_reply_filter, the *_delivery_status_filter features, and the + *_dns_reply_filter features (the latter because DNS is by definition an + ASCII protocol). * The Postfix SMTP server announces SMTPUTF8 support in the EHLO response. @@ -95,8 +112,8 @@ Postfix releases: commands. * The Postfix SMTP server accepts UTF-8 in email address domains, but only - after the remote SMTP client client issues the SMTPUTF8 request in MAIL - FROM or VRFY commands. + after the remote SMTP client issues the SMTPUTF8 request in MAIL FROM or + VRFY commands. Postfix already permitted UTF-8 in message header values and in address localparts. This does not change. @@ -180,26 +197,38 @@ disabled. LLiimmiittaattiioonnss ooff tthhee ccuurrrreenntt iimmpplleemmeennttaattiioonn +The Postfix implementation is a work in progress; limitations are steadily +being removed. The text below describes the situation at one point in time. + +NNoo aauuttoommaattiicc ccoonnvveerrssiioonnss bbeettwweeeenn AASSCCIIII aanndd UUTTFF--88 ddoommaaiinn nnaammeess.. + +Some background: According to RFC 6530 and related documents, "Internationalized" domain names can appear in two forms: the UTF-8 form, and -the ASCII (xn--mumble) form. The initial Postfix SMTPUTF8 implementation -performs no automatic conversions on UTF8 strings beyond what is needed to -perform DNS lookups. +the ASCII (xn--mumble) form. "Internationalized" address localparts must be +encoded in UTF-8; the RFCs do not define an ASCII form for the same +information. -NNoo cchhaarraacctteerrsseett ccaannoonniiccaalliizzaattiioonn ffoorr nnoonn--AASSCCIIII ddoommaaiinn nnaammeess.. +Postfix currently does not convert internationalized domain names from UTF- +8 into ASCII (or from ASCII into UTF-8) before using domain names in SMTP +commands and responses, before looking up domain names in mydestination, +relay_domains, access tables, etc., before using domain names in a policy +daemon or Milter request, or before logging domain names. -Postfix currently does not translate domain names from UTF-8 into ASCII (or -ASCII into UTF-8) before looking up the domain name in mydestination, -relay_domains, access tables, etc., before logging the domain name, or before -using the domain name in a policy daemon or Milter request. You will have to -configure both UTF-8 and ASCII forms in Postfix configuration files; and both -forms will have to be handled by logfile tools, policy daemons and Milters. +Postfix does, however, casefold domain names and email addresses before +matching them against a Postfix configuration parameter or lookup table. -NNoo ccaassee ccaannoonniiccaalliizzaattiioonn ffoorr nnoonn--AASSCCIIII cchhaarraacctteerrss.. + * The Postfix parameters myhostname and mydomain must be in ASCII form. One + is a substring of the other, and the myhostname value is used in SMTP + commands and responses that require ASCII. The parameter myorigin (added to + local addresses without domain) supports UTF-8. -Postfix currently does not case-fold non-ASCII characters when looking up an -"Internationalized" domain name in mydestination, relay_domains, access maps, -etc. Some non-ASCII scripts do not distinguish between upper and lower case, -some have different numbers of upper and lower case characters. + * You need to configure both the ASCII and UTF-8 forms of an + Internationalized domain name in Postfix parameters such as mydestination + and relay_domains, as well as lookup table search keys. + + * Milters, content filters, policy servers and logfile analysis tools need to + be able to handle both the ASCII and UTF-8 forms of Internationalized + domain names. CCoommppaattiibbiilliittyy wwiitthh pprree--SSMMTTPPUUTTFF88 eennvviirroonnmmeennttss @@ -209,28 +238,30 @@ With Postfix, there is no need to split mailing lists into UTF-8 and non-UTF- 8 members. Postfix will try to deliver the non-UTF8 subscribers over "traditional" non-SMTPUTF8 sessions, as long as the message has an ASCII envelope sender address and all-ASCII header values. The mailing list manager -will have to apply RFC 2047 encoding to satisfy that last condition. +may have to apply RFC 2047 encoding to satisfy that last condition. PPrree--eexxiissttiinngg nnoonn--AASSCCIIII eemmaaiill fflloowwss -In pre-SMTPUTF8 environments, email with UTF-8 in address localparts (and in -headers) works just fine. The vast majority of email software including Postfix -is perfectly capable of handling such email, even if pre-SMTPUTF8 standards do -not support this. +With "smtputf8_enable = no", Postfix handles email with non-ASCII in address +localparts (and in headers) as before. The vast majority of email software is +perfectly capable of handling such email, even if pre-SMTPUTF8 standards do not +support such practice. -Therefore, when Postfix SMTPUTF8 support is turned on, Postfix must not -suddenly start to break pre-existing email flows with UTF-8 in addres -localparts (and in headers). - -Thus, Postfix continues to permit UTF-8 in address localparts (and in headers) -in email from and to pre-SMTPUTF8 systems. At least, that is the default (see -autodetection above). +However, when you specify "smtputf8_enable = yes", Postfix requires that non- +ASCII address information is encoded in UTF-8 and will reject other encodings +such as ISO-8859. It is not practical for Postfix to support multiple encodings +at the same time. There is no problem with RFC 2047 encodings such as "=?ISO- +8859-1?Q?text?=", because those use only characters from the ASCII +characterset. CCrreeddiittss - * Arnt Gulbrandsen posted his patch for Unicode email support on May 15, - 2014. This work was sponsored by CNNIC. + * May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode email support. + This work was sponsored by CNNIC. - * Wietse integrated Arnt Gulbrandsen's code and released Postfix with - SMTPUTF8 support on July 15, 2014. + * July 15, 2014: Wietse integrated Arnt Gulbrandsen's code and released + Postfix with SMTPUTF8 support. + + * January 2015: Wietse added UTF-8 support for casefolding in Postfix lookup + tables and caseless string comparison in Postfix list-based features. diff --git a/postfix/README_FILES/TLS_README b/postfix/README_FILES/TLS_README index adca4c825..06ab19129 100644 --- a/postfix/README_FILES/TLS_README +++ b/postfix/README_FILES/TLS_README @@ -1,3 +1,5 @@ +-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> + PPoossttffiixx TTLLSS SSuuppppoorrtt ------------------------------------------------------------------------------- @@ -1811,13 +1813,62 @@ Example: CClliieenntt--ssiiddee SSMMTTPPSS ssuuppppoorrtt -Although the Postfix SMTP client by itself doesn't support TLS wrapper mode, it +These sections show how to send mail to a server that does not support +STARTTLS, but that provides the deprecated SMTPS service on TCP port 465. +Depending on the Postfix version, some additional tooling may be required. + +PPoossttffiixx >>== 22..1122 + +The Postfix SMTP client has SMTPS support built-in as of version 2.12. Use one +of the following examples, to send all remote mail, or to send only some remote +mail, to an SMTPS server. + +PPoossttffiixx >>== 22..1122:: SSeennddiinngg aallll rreemmoottee mmaaiill ttoo aann SSMMTTPPSS sseerrvveerr + +The first example will send all remote mail over SMTPS through a provider's +server called "mail.example.com": + + /etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 + +Use "postfix reload" to make the change effective. + +See SOHO_README for additional information about SASL authentication. + +PPoossttffiixx >>== 22..1122:: SSeennddiinngg oonnllyy mmaaiill ffoorr aa ssppeecciiffiicc ddeessttiinnaattiioonn vviiaa SSMMTTPPSS + +The second example will send only mail for "example.com" via SMTPS. This time, +Postfix uses a transport map to deliver only mail for "example.com" via SMTPS: + + /etc/postfix/main.cf: + transport_maps = hash:/etc/postfix/transport + + /etc/postfix/transport: + example.com relay-smtps:example.com:465 + + /etc/postfix/master.cf: + relay-smtps unix - - n - - smtp + # Client-side SMTPS requires "encrypt" or stronger. + -o smtp_tls_security_level=encrypt + -o smtp_tls_wrappermode=yes + +Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the +change effective. + +See SOHO_README for additional information about SASL authentication. + +PPoossttffiixx << 22..1122 + +Although older Postfix SMTP client versions do not support TLS wrapper mode, it is relatively easy to forward a connection through the stunnel program if Postfix needs to deliver mail to some legacy system that doesn't support -STARTTLS. Use one of the following two examples, to send only some remote mail, -or to send all remote mail, to an SMTPS server. +STARTTLS. -SSeennddiinngg aallll rreemmoottee mmaaiill ttoo aann SSMMTTPPSS sseerrvveerr +PPoossttffiixx << 22..1122:: SSeennddiinngg aallll rreemmoottee mmaaiill ttoo aann SSMMTTPPSS sseerrvveerr The first example uses SMTPS to send all remote mail to a provider's mail server called "mail.example.com". @@ -1847,7 +1898,9 @@ local stunnel listener on port 11125: Use "postfix reload" to make the change effective. -SSeennddiinngg oonnllyy mmaaiill ffoorr aa ssppeecciiffiicc ddeessttiinnaattiioonn vviiaa SSMMTTPPSS +See SOHO_README for additional information about SASL authentication. + +PPoossttffiixx << 22..1122:: SSeennddiinngg oonnllyy mmaaiill ffoorr aa ssppeecciiffiicc ddeessttiinnaattiioonn vviiaa SSMMTTPPSS The second example will use SMTPS to send only mail for "example.com" via SMTPS. It uses the same stunnel configuration file as the first example, so it @@ -1865,6 +1918,8 @@ This time, the Postfix side uses a transport map to direct only mail for Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the change effective. +See SOHO_README for additional information about SASL authentication. + MMiisscceellllaanneeoouuss cclliieenntt ccoonnttrroollss The smtp_starttls_timeout parameter limits the time of Postfix SMTP client diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 7621437f4..b83e5f0f3 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -8,6 +8,20 @@ Wish list: Things to do after the stable release: + UTF8 DNS[BW]L domain name. + + Consolidate maps flags in mail_params.h instead of having + multiple copies scattered across programs. + + Try to allow UTF-8 myhostname/mydomain, at least in bounce + template expansion. + + No enhanced status code when rejecting connection before + the HELO handshake is completed. + + Maybe don't whitelist a client that has maxed out its + per-MTA connection count limit. + Inline support for pcre:{/pattern/=action, ...} and ditto support for regexp: and cidr: tables. Factor out and reuse code that already exists in inline: and other tables. diff --git a/postfix/html/ADDRESS_VERIFICATION_README.html b/postfix/html/ADDRESS_VERIFICATION_README.html index b0fc5e461..14648f472 100644 --- a/postfix/html/ADDRESS_VERIFICATION_README.html +++ b/postfix/html/ADDRESS_VERIFICATION_README.html @@ -346,7 +346,7 @@ in forged email.
# Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify /etc/postfix/sender_access: @@ -393,7 +393,7 @@ you can see what mail would be blocked: # Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify @@ -461,7 +461,7 @@ results are lost after "postfix reload" or "postfix stop".@@ -461,7 +461,7 @@ results are lost after "postfix reload" or "postfix stop".# Example 1: Default setting for Postfix 2.7 and later. -# Note: avoid hash files here. Use btree instead. +# Note: avoid hash files here. Use btree or lmdb instead. /etc/postfix/main.cf: address_verify_map = btree:$data_directory/verify_cache diff --git a/postfix/html/SMTPUTF8_README.html b/postfix/html/SMTPUTF8_README.html index 3c3a593e5..e64f83e9e 100644 --- a/postfix/html/SMTPUTF8_README.html +++ b/postfix/html/SMTPUTF8_README.html @@ -110,8 +110,27 @@ respect to earlier Postfix releases:@@ -257,29 +276,47 @@ delivered it if SMTPUTF8 support was disabled.
UTF-8 is permitted in the myorigin parameter value. However, -the myhostname and mydomain parameters must specify ASCII-only -domain names. This limitation may be removed later.
+the myhostname and mydomain parameters must currently specify +ASCII-only domain names. This limitation may be removed later. + +UTF-8 is the only form of non-ASCII text that Postfix +supports in access tables, address rewriting tables, and other +tables that are indexed with an email address, hostname, or domain +name.
+ +The header_checks-like and body_checks-like features are +not UTF-8 enabled, and therefore they do not enforce UTF-8 syntax +rules on inputs and outputs. The reason is that non-ASCII text may +be sent in encodings other than UTF-8, and that real email sometimes +contains malformed headers. Instead of skipping non-UTF-8 content, +Postfix should be able to filter it. You may try to enable UTF-8 +processing by starting a PCRE pattern with the sequence (*UTF8), +but this is will result in "message not accepted, try again later" +errors when the PCRE pattern matcher encounters non-UTF-8 input. +Other features that are not UTF-8 enabled are smtpd_command_filter, +smtp_reply_filter, the *_delivery_status_filter features, and the +*_dns_reply_filter features (the latter because DNS is by definition +an ASCII protocol).
The Postfix SMTP server announces SMTPUTF8 support in the EHLO response.
@@ -145,7 +164,7 @@ MAIL FROM and VRFY commands. MAIL FROM commands.The Postfix SMTP server accepts UTF-8 in email address -domains, but only after the remote SMTP client client issues the +domains, but only after the remote SMTP client issues the SMTPUTF8 request in MAIL FROM or VRFY commands.
Limitations of the current implementation
-"Internationalized" domain names can appear in two forms: the -UTF-8 form, and the ASCII (xn--mumble) form. The initial Postfix -SMTPUTF8 implementation performs no automatic conversions on UTF8 -strings beyond what is needed to perform DNS lookups.
+The Postfix implementation is a work in progress; limitations +are steadily being removed. The text below describes the situation +at one point in time.
-No characterset canonicalization for non-ASCII domain names. -
+No automatic conversions between ASCII and UTF-8 domain names.
-Postfix currently does not translate domain names from UTF-8 -into ASCII (or ASCII into UTF-8) before looking up the domain name -in mydestination, relay_domains, access tables, etc., before logging -the domain name, or before using the domain name in a policy daemon -or Milter request. You will have to configure both UTF-8 and ASCII -forms in Postfix configuration files; and both forms will have to -be handled by logfile tools, policy daemons and Milters.
+Some background: According to RFC 6530 and related documents, +"Internationalized" domain names can appear in two forms: the UTF-8 +form, and the ASCII (xn--mumble) form. "Internationalized" address +localparts must be encoded in UTF-8; the RFCs do not define an ASCII +form for the same information.
-No case canonicalization for non-ASCII characters.
+Postfix currently does not convert internationalized domain +names from UTF-8 into ASCII (or from ASCII into UTF-8) before using +domain names in SMTP commands and responses, before looking up +domain names in mydestination, relay_domains, access tables, etc., +before using domain names in a policy daemon or Milter request, +or before logging domain names.
-Postfix currently does not case-fold non-ASCII characters when -looking up an "Internationalized" domain name in mydestination, -relay_domains, access maps, etc. Some non-ASCII scripts do not -distinguish between upper and lower case, some have different numbers -of upper and lower case characters.
+Postfix does, however, casefold domain names and email addresses +before matching them against a Postfix configuration parameter or +lookup table.
+ ++ +
The Postfix parameters myhostname and mydomain must be in +ASCII form. One is a substring of the other, and the myhostname +value is used in SMTP commands and responses that require ASCII. +The parameter myorigin (added to local addresses without domain) +supports UTF-8.
+ +You need to configure both the ASCII and UTF-8 forms of +an Internationalized domain name in Postfix parameters such as +mydestination and relay_domains, as well as lookup table search +keys.
+ +Milters, content filters, policy servers and logfile +analysis tools need to be able to handle both the ASCII and UTF-8 +forms of Internationalized domain names.
+ +Compatibility with pre-SMTPUTF8 environments
@@ -290,33 +327,36 @@ environments non-UTF-8 members. Postfix will try to deliver the non-UTF8 subscribers over "traditional" non-SMTPUTF8 sessions, as long as the message has an ASCII envelope sender address and all-ASCII header values. -The mailing list manager will have to apply RFC 2047 encoding to +The mailing list manager may have to apply RFC 2047 encoding to satisfy that last condition.Pre-existing non-ASCII email flows
-In pre-SMTPUTF8 environments, email with UTF-8 in address -localparts (and in headers) works just fine. The vast majority -of email software including Postfix is perfectly capable of handling -such email, even if pre-SMTPUTF8 standards do not support this.
+With "smtputf8_enable = no", Postfix handles email with non-ASCII +in address localparts (and in headers) as before. The vast majority +of email software is perfectly capable of handling such email, even +if pre-SMTPUTF8 standards do not support such practice.
-Therefore, when Postfix SMTPUTF8 support is turned on, Postfix -must not suddenly start to break pre-existing email flows with UTF-8 -in addres localparts (and in headers).
- -Thus, Postfix continues to permit UTF-8 in address localparts -(and in headers) in email from and to pre-SMTPUTF8 systems. At -least, that is the default (see autodetection above).
+However, when you specify "smtputf8_enable = yes", Postfix +requires that non-ASCII address information is encoded in UTF-8 and +will reject other encodings such as ISO-8859. It is not practical +for Postfix to support multiple encodings at the same time. There +is no problem with RFC 2047 encodings such as "=?ISO-8859-1?Q?text?=", +because those use only characters from the ASCII characterset.
Credits
-
diff --git a/postfix/html/TLS_README.html b/postfix/html/TLS_README.html index 2548b9f57..e8cd2fcfa 100644 --- a/postfix/html/TLS_README.html +++ b/postfix/html/TLS_README.html @@ -1,4 +1,4 @@ - @@ -2370,14 +2370,75 @@ the SSL/TLS protocols used with opportunistic TLS.Arnt Gulbrandsen posted his patch for Unicode email support -on May 15, 2014. This work was sponsored by CNNIC.
+May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode +email support. This work was sponsored by CNNIC.
-Wietse integrated Arnt Gulbrandsen's code and released -Postfix with SMTPUTF8 support on July 15, 2014.
+July 15, 2014: Wietse integrated Arnt Gulbrandsen's code +and released Postfix with SMTPUTF8 support.
+ +January 2015: Wietse added UTF-8 support for casefolding +in Postfix lookup tables and caseless string comparison in Postfix +list-based features.
Client-side SMTPS support
-Although the Postfix SMTP client by itself doesn't support TLS +
These sections show how to send mail to a server that does not +support STARTTLS, but that provides the deprecated SMTPS service +on TCP port 465. Depending on the Postfix version, some additional +tooling may be required.
+ +Postfix ≥ 2.12
+ +The Postfix SMTP client has SMTPS support built-in as of version +2.12. Use one of the following examples, to send all remote mail, +or to send only some remote mail, to an SMTPS server.
+ +Postfix ≥ 2.12: Sending all remote mail to an SMTPS server
+ +The first example will send all remote mail over SMTPS through +a provider's server called "mail.example.com":
+ +++ ++/etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 ++Use "postfix reload" to make the change effective.
+ +See SOHO_README for additional information about SASL authentication. +
+ +Postfix ≥ 2.12: Sending only mail for a specific destination +via SMTPS
+ +The second example will send only mail for "example.com" via +SMTPS. This time, Postfix uses a transport map to deliver only +mail for "example.com" via SMTPS:
+ +++ ++/etc/postfix/main.cf: + transport_maps = hash:/etc/postfix/transport + +/etc/postfix/transport: + example.com relay-smtps:example.com:465 + +/etc/postfix/master.cf: + relay-smtps unix - - n - - smtp + # Client-side SMTPS requires "encrypt" or stronger. + -o smtp_tls_security_level=encrypt + -o smtp_tls_wrappermode=yes ++Use "postmap hash:/etc/postfix/transport" and "postfix reload" +to make the change effective.
+ +See SOHO_README for additional information about SASL +authentication.
+ +Postfix < 2.12
+ +Although older Postfix SMTP client versions do not support TLS wrapper mode, it is relatively easy to forward a connection through the stunnel program if Postfix needs to deliver mail to some legacy -system that doesn't support STARTTLS. Use one of the following two -examples, to send only some remote mail, or to send all remote mail, -to an SMTPS server.
+system that doesn't support STARTTLS. -Sending all remote mail to an SMTPS server
+Postfix < 2.12: Sending all remote mail to an SMTPS server
The first example uses SMTPS to send all remote mail to a provider's mail server called "mail.example.com".
@@ -2420,7 +2481,10 @@ mail through the local stunnel listener on port 11125:Use "postfix reload" to make the change effective.
-Sending only mail for a specific destination via SMTPS
+See SOHO_README for additional information about SASL +authentication.
+ +Postfix < 2.12: Sending only mail for a specific destination via SMTPS
The second example will use SMTPS to send only mail for "example.com" via SMTPS. It uses the same stunnel configuration @@ -2442,6 +2506,9 @@ mail for "example.com" through the tunnel:
Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the change effective.
+See SOHO_README for additional information about SASL authentication. +
+Miscellaneous client controls
The smtp_starttls_timeout parameter limits the time of Postfix diff --git a/postfix/html/cleanup.8.html b/postfix/html/cleanup.8.html index d17607c2d..6e14e5837 100644 --- a/postfix/html/cleanup.8.html +++ b/postfix/html/cleanup.8.html @@ -390,29 +390,35 @@ CLEANUP(8) CLEANUP(8) virtual_alias_recursion_limit (1000) The maximal nesting depth of virtual alias expansion. + Available in Postfix version 2.12 and later: + + virtual_alias_address_length_limit (1000) + The maximal length of an email address after virtual alias + expansion. + MISCELLANEOUS CONTROLS config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to handle a + How much time a Postfix daemon process may take to handle a request before it is terminated by a built-in watchdog timer. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal point when log- + The maximal number of digits after the decimal point when log- ging sub-second delay values. delay_warning_time (0h) - The time after which the sender receives a copy of the message + The time after which the sender receives a copy of the message headers of mail that is still queued. ipc_timeout (3600s) - The time limit for sending or receiving information over an + The time limit for sending or receiving information over an internal communication channel. max_idle (100s) - The maximum amount of time that an idle Postfix daemon process + The maximum amount of time that an idle Postfix daemon process waits for an incoming connection before terminating voluntarily. max_use (100) @@ -423,7 +429,7 @@ CLEANUP(8) CLEANUP(8) The internet hostname of this mail system. myorigin ($myhostname) - The domain name that locally-posted mail appears to come from, + The domain name that locally-posted mail appears to come from, and that locally posted mail is delivered to. process_id (read-only) @@ -436,15 +442,15 @@ CLEANUP(8) CLEANUP(8) The location of the Postfix top-level queue directory. soft_bounce (no) - Safety net to keep mail queued that would otherwise be returned + Safety net to keep mail queued that would otherwise be returned to the sender. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the process name in - syslog records, so that "smtpd" becomes, for example, "post- + The mail system name that is prepended to the process name in + syslog records, so that "smtpd" becomes, for example, "post- fix/smtpd". Available in Postfix version 2.1 and later: diff --git a/postfix/html/lmtp.8.html b/postfix/html/lmtp.8.html index 54d6807f1..705c5ab22 100644 --- a/postfix/html/lmtp.8.html +++ b/postfix/html/lmtp.8.html @@ -555,50 +555,56 @@ SMTP(8) SMTP(8) tlsmgr_service_name (tlsmgr) The name of the tlsmgr(8) service entry in master.cf. + Available in Postfix version 2.12 and later: + + smtp_tls_wrappermode (no) + Request that the Postfix SMTP client connects using the legacy + SMTPS protocol instead of using the STARTTLS command. + OBSOLETE STARTTLS CONTROLS - The following configuration parameters exist for compatibility with - Postfix versions before 2.3. Support for these will be removed in a + The following configuration parameters exist for compatibility with + Postfix versions before 2.3. Support for these will be removed in a future release. smtp_use_tls (no) - Opportunistic mode: use TLS when a remote SMTP server announces + Opportunistic mode: use TLS when a remote SMTP server announces STARTTLS support, otherwise send the mail in the clear. smtp_enforce_tls (no) - Enforcement mode: require that remote SMTP servers use TLS + Enforcement mode: require that remote SMTP servers use TLS encryption, and never send mail in the clear. smtp_tls_enforce_peername (yes) - With mandatory TLS encryption, require that the remote SMTP - server hostname matches the information in the remote SMTP + With mandatory TLS encryption, require that the remote SMTP + server hostname matches the information in the remote SMTP server certificate. smtp_tls_per_site (empty) - Optional lookup tables with the Postfix SMTP client TLS usage - policy by next-hop destination and by remote SMTP server host- + Optional lookup tables with the Postfix SMTP client TLS usage + policy by next-hop destination and by remote SMTP server host- name. smtp_tls_cipherlist (empty) - Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS + Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS cipher list. RESOURCE AND RATE CONTROLS smtp_destination_concurrency_limit ($default_destination_concur- rency_limit) - The maximal number of parallel deliveries to the same destina- + The maximal number of parallel deliveries to the same destina- tion via the smtp message delivery transport. smtp_destination_recipient_limit ($default_destination_recipient_limit) - The maximal number of recipients per message for the smtp mes- + The maximal number of recipients per message for the smtp mes- sage delivery transport. smtp_connect_timeout (30s) - The Postfix SMTP client time limit for completing a TCP connec- + The Postfix SMTP client time limit for completing a TCP connec- tion, or zero (use the operating system built-in time limit). smtp_helo_timeout (300s) - The Postfix SMTP client time limit for sending the HELO or EHLO - command, and for receiving the initial remote SMTP server + The Postfix SMTP client time limit for sending the HELO or EHLO + command, and for receiving the initial remote SMTP server response. lmtp_lhlo_timeout (300s) @@ -610,19 +616,19 @@ SMTP(8) SMTP(8) mand, and for receiving the remote SMTP server response. smtp_mail_timeout (300s) - The Postfix SMTP client time limit for sending the MAIL FROM + The Postfix SMTP client time limit for sending the MAIL FROM command, and for receiving the remote SMTP server response. smtp_rcpt_timeout (300s) - The Postfix SMTP client time limit for sending the SMTP RCPT TO + The Postfix SMTP client time limit for sending the SMTP RCPT TO command, and for receiving the remote SMTP server response. smtp_data_init_timeout (120s) - The Postfix SMTP client time limit for sending the SMTP DATA + The Postfix SMTP client time limit for sending the SMTP DATA command, and for receiving the remote SMTP server response. smtp_data_xfer_timeout (180s) - The Postfix SMTP client time limit for sending the SMTP message + The Postfix SMTP client time limit for sending the SMTP message content. smtp_data_done_timeout (600s) @@ -636,13 +642,13 @@ SMTP(8) SMTP(8) Available in Postfix version 2.1 and later: smtp_mx_address_limit (5) - The maximal number of MX (mail exchanger) IP addresses that can - result from Postfix SMTP client mail exchanger lookups, or zero + The maximal number of MX (mail exchanger) IP addresses that can + result from Postfix SMTP client mail exchanger lookups, or zero (no limit). smtp_mx_session_limit (2) - The maximal number of SMTP sessions per delivery request before - the Postfix SMTP client gives up or delivers to a fall-back + The maximal number of SMTP sessions per delivery request before + the Postfix SMTP client gives up or delivers to a fall-back relay host, or zero (no limit). smtp_rset_timeout (20s) @@ -652,17 +658,17 @@ SMTP(8) SMTP(8) Available in Postfix version 2.2 and earlier: lmtp_cache_connection (yes) - Keep Postfix LMTP client connections open for up to $max_idle + Keep Postfix LMTP client connections open for up to $max_idle seconds. Available in Postfix version 2.2 and later: smtp_connection_cache_destinations (empty) - Permanently enable SMTP connection caching for the specified + Permanently enable SMTP connection caching for the specified destinations. smtp_connection_cache_on_demand (yes) - Temporarily enable SMTP connection caching while a destination + Temporarily enable SMTP connection caching while a destination has a high volume of mail in the active queue. smtp_connection_reuse_time_limit (300s) @@ -676,23 +682,23 @@ SMTP(8) SMTP(8) Available in Postfix version 2.3 and later: connection_cache_protocol_timeout (5s) - Time limit for connection cache connect, send or receive opera- + Time limit for connection cache connect, send or receive opera- tions. Available in Postfix version 2.9 and later: smtp_per_record_deadline (no) - Change the behavior of the smtp_*_timeout time limits, from a - time limit per read or write system call, to a time limit to - send or receive a complete record (an SMTP command line, SMTP - response line, SMTP message content line, or TLS protocol mes- + Change the behavior of the smtp_*_timeout time limits, from a + time limit per read or write system call, to a time limit to + send or receive a complete record (an SMTP command line, SMTP + response line, SMTP message content line, or TLS protocol mes- sage). Available in Postfix version 2.11 and later: smtp_connection_reuse_count_limit (0) - When SMTP connection caching is enabled, the number of times - that an SMTP session may be reused before it is closed, or zero + When SMTP connection caching is enabled, the number of times + that an SMTP session may be reused before it is closed, or zero (no limit). SMTPUTF8 CONTROLS @@ -703,21 +709,21 @@ SMTP(8) SMTP(8) in RFC 6531..6533. smtputf8_autodetect_classes (sendmail, verify) - Detect that a message requires SMTPUTF8 support for the speci- + Detect that a message requires SMTPUTF8 support for the speci- fied mail origin classes. TROUBLE SHOOTING CONTROLS debug_peer_level (2) - The increment in verbose logging level when a remote client or + The increment in verbose logging level when a remote client or server matches a pattern in the debug_peer_list parameter. debug_peer_list (empty) - Optional list of remote client or server hostname or network + Optional list of remote client or server hostname or network address patterns that cause the verbose logging level to increase by the amount specified in $debug_peer_level. error_notice_recipient (postmaster) - The recipient of postmaster notifications about mail delivery + The recipient of postmaster notifications about mail delivery problems that are caused by policy, resource, software or proto- col errors. @@ -731,46 +737,46 @@ SMTP(8) SMTP(8) MISCELLANEOUS CONTROLS best_mx_transport (empty) - Where the Postfix SMTP client should deliver mail when it + Where the Postfix SMTP client should deliver mail when it detects a "mail loops back to myself" error condition. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to handle a + How much time a Postfix daemon process may take to handle a request before it is terminated by a built-in watchdog timer. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal point when log- + The maximal number of digits after the decimal point when log- ging sub-second delay values. disable_dns_lookups (no) Disable DNS lookups in the Postfix SMTP and LMTP clients. inet_interfaces (all) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on. inet_protocols (all) - The Internet protocols Postfix will attempt to use when making + The Internet protocols Postfix will attempt to use when making or accepting connections. ipc_timeout (3600s) - The time limit for sending or receiving information over an + The time limit for sending or receiving information over an internal communication channel. lmtp_assume_final (no) - When a remote LMTP server announces no DSN support, assume that - the server performs final delivery, and send "delivered" deliv- + When a remote LMTP server announces no DSN support, assume that + the server performs final delivery, and send "delivered" deliv- ery status notifications instead of "relayed". lmtp_tcp_port (24) The default TCP port that the Postfix LMTP client connects to. max_idle (100s) - The maximum amount of time that an idle Postfix daemon process + The maximum amount of time that an idle Postfix daemon process waits for an incoming connection before terminating voluntarily. max_use (100) @@ -784,20 +790,20 @@ SMTP(8) SMTP(8) The process name of a Postfix command or daemon process. proxy_interfaces (empty) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on by way of a proxy or network address translation unit. smtp_address_preference (any) The address type ("ipv6", "ipv4" or "any") that the Postfix SMTP - client will try first, when a destination has IPv6 and IPv4 + client will try first, when a destination has IPv6 and IPv4 addresses with equal MX preference. smtp_bind_address (empty) - An optional numerical network address that the Postfix SMTP + An optional numerical network address that the Postfix SMTP client should bind to when making an IPv4 connection. smtp_bind_address6 (empty) - An optional numerical network address that the Postfix SMTP + An optional numerical network address that the Postfix SMTP client should bind to when making an IPv6 connection. smtp_helo_name ($myhostname) @@ -817,8 +823,8 @@ SMTP(8) SMTP(8) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the process name in - syslog records, so that "smtpd" becomes, for example, "post- + The mail system name that is prepended to the process name in + syslog records, so that "smtpd" becomes, for example, "post- fix/smtpd". Available with Postfix 2.2 and earlier: @@ -836,7 +842,7 @@ SMTP(8) SMTP(8) Available with Postfix 2.12 and later: smtp_address_verify_target (rcpt) - In the context of email address verification, the SMTP protocol + In the context of email address verification, the SMTP protocol stage that determines whether an email address is deliverable. SEE ALSO diff --git a/postfix/html/postalias.1.html b/postfix/html/postalias.1.html index 124781b05..3e67e0b76 100644 --- a/postfix/html/postalias.1.html +++ b/postfix/html/postalias.1.html @@ -10,7 +10,7 @@ POSTALIAS(1) POSTALIAS(1) postalias - Postfix alias database maintenance SYNOPSIS - postalias [-Nfinoprsvw] [-c config_dir] [-d key] [-q key] + postalias [-Nfinoprsuvw] [-c config_dir] [-d key] [-q key] [file_type:]file_name ... DESCRIPTION @@ -95,6 +95,10 @@ POSTALIAS(1) POSTALIAS(1) order. This feature is available in Postfix version 2.2 and later, and is not available for all database types. + -u Disable UTF-8 support. UTF-8 support is enabled by default when + "smtputf8_enable = yes". It requires that keys and values are + valid UTF-8 strings. + -v Enable verbose logging for debugging purposes. Multiple -v options make the software increasingly verbose. @@ -183,12 +187,16 @@ POSTALIAS(1) POSTALIAS(1) The default database type for use in newaliases(1), postalias(1) and postmap(1) commands. + smtputf8_enable (yes) + Enable experimental SMTPUTF8 support for the protocols described + in RFC 6531..6533. + syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the process name in - syslog records, so that "smtpd" becomes, for example, "post- + The mail system name that is prepended to the process name in + syslog records, so that "smtpd" becomes, for example, "post- fix/smtpd". STANDARDS diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index cace24f43..4aff5f470 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -9891,8 +9891,8 @@ SMTP servers that reject recipients after the DATA command. Use
@@ -12916,6 +12916,35 @@ example.com verify match=hostname:nexthop/etc/postfix/transport: - smtp-domain_that_verifies_after_data smtp-data-target: - lmtp-domain_that_verifies_after_data lmtp-data-target: + smtp-domain-that-verifies-after-data smtp-data-target: + lmtp-domain-that-verifies-after-data lmtp-data-target:This feature is available in Postfix 2.3 and later.
+ + +smtp_tls_wrappermode +(default: no) + + Request that the Postfix SMTP client connects using the +legacy SMTPS protocol instead of using the STARTTLS command.
+ +This mode requires "smtp_tls_security_level = encrypt" or +stronger.
+ +Example: deliver all remote mail via a provider's server +"mail.example.com".
+ ++/etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 ++ +More examples are in TLS_README, including examples for older +Postfix versions.
+ +This feature is available in Postfix 2.12 and later.
+ +smtp_use_tls @@ -18869,6 +18898,22 @@ This feature is available in Postfix 1.1 and later. + + + virtual_alias_address_length_limit +(default: 1000) + + +The maximal length of an email address after virtual alias expansion. +This stops virtual aliasing loops that increase the address length +exponentially. +
+ ++This feature is available in Postfix 2.12 and later. +
+ +virtual_alias_domains diff --git a/postfix/html/postmap.1.html b/postfix/html/postmap.1.html index 55d1e9b2b..9822a2dd1 100644 --- a/postfix/html/postmap.1.html +++ b/postfix/html/postmap.1.html @@ -10,7 +10,7 @@ POSTMAP(1) POSTMAP(1) postmap - Postfix lookup table management SYNOPSIS - postmap [-Nbfhimnoprsvw] [-c config_dir] [-d key] [-q key] + postmap [-NbfhimnoprsuUvw] [-c config_dir] [-d key] [-q key] [file_type:]file_name ... DESCRIPTION @@ -66,6 +66,10 @@ POSTMAP(1) POSTMAP(1) style lookup keys for attachment MIME headers and for attached message/* headers. + NOTE: with "smtputf8_enable = yes", the -b option option dis- + ables UTF-8 syntax checks on query keys and lookup results. + Specify the -U option to force UTF-8 syntax checks anyway. + This feature is available in Postfix version 2.6 and later. -c config_dir @@ -99,6 +103,10 @@ POSTMAP(1) POSTMAP(1) also generates header-style lookup keys for attachment MIME headers and for attached message/* headers. + NOTE: with "smtputf8_enable = yes", the -b option option dis- + ables UTF-8 syntax checks on query keys and lookup results. + Specify the -U option to force UTF-8 syntax checks anyway. + This feature is available in Postfix version 2.6 and later. -i Incremental mode. Read entries from standard input and do not @@ -145,10 +153,17 @@ POSTMAP(1) POSTMAP(1) This feature is available in Postfix version 2.2 and later, and is not available for all database types. - -v Enable verbose logging for debugging purposes. Multiple -v + -u Disable UTF-8 support. UTF-8 support is enabled by default when + "smtputf8_enable = yes". It requires that keys and values are + valid UTF-8 strings. + + -U With "smtputf8_enable = yes", force UTF-8 syntax checks with the + -b and -h options. + + -v Enable verbose logging for debugging purposes. Multiple -v options make the software increasingly verbose. - -w When updating a table, do not complain about attempts to update + -w When updating a table, do not complain about attempts to update existing entries, and ignore those attempts. Arguments: @@ -160,32 +175,32 @@ POSTMAP(1) POSTMAP(1) The postmap(1) command can query any supported file type, but it can create only the following file types: - btree The output file is a btree file, named file_name.db. - This is available on systems with support for db data- + btree The output file is a btree file, named file_name.db. + This is available on systems with support for db data- bases. - cdb The output consists of one file, named file_name.cdb. - This is available on systems with support for cdb data- + cdb The output consists of one file, named file_name.cdb. + This is available on systems with support for cdb data- bases. dbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for dbm databases. - hash The output file is a hashed file, named file_name.db. - This is available on systems with support for db data- + hash The output file is a hashed file, named file_name.db. + This is available on systems with support for db data- bases. - fail A table that reliably fails all requests. The lookup ta- - ble name is used for logging only. This table exists to + fail A table that reliably fails all requests. The lookup ta- + ble name is used for logging only. This table exists to simplify Postfix error tests. sdbm The output consists of two files, named file_name.pag and file_name.dir. This is available on systems with support for sdbm databases. - When no file_type is specified, the software uses the database - type specified via the default_database_type configuration + When no file_type is specified, the software uses the database + type specified via the default_database_type configuration parameter. file_name @@ -194,11 +209,11 @@ POSTMAP(1) POSTMAP(1) DIAGNOSTICS Problems are logged to the standard error stream and to syslogd(8). No - output means that no problems were detected. Duplicate entries are + output means that no problems were detected. Duplicate entries are skipped and are flagged with a warning. postmap(1) terminates with zero exit status in case of success (includ- - ing successful "postmap -q" lookup) and terminates with non-zero exit + ing successful "postmap -q" lookup) and terminates with non-zero exit status in case of failure. ENVIRONMENT @@ -209,12 +224,12 @@ POSTMAP(1) POSTMAP(1) Enable verbose logging for debugging purposes. CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant to this pro- - gram. The text below provides only a parameter summary. See post- + The following main.cf parameters are especially relevant to this pro- + gram. The text below provides only a parameter summary. See post- conf(5) for more details including examples. berkeley_db_create_buffer_size (16777216) - The per-table I/O buffer size for programs that create Berkeley + The per-table I/O buffer size for programs that create Berkeley DB hash or btree tables. berkeley_db_read_buffer_size (131072) @@ -222,13 +237,17 @@ POSTMAP(1) POSTMAP(1) hash or btree tables. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. default_database_type (see 'postconf -d' output) The default database type for use in newaliases(1), postalias(1) and postmap(1) commands. + smtputf8_enable (yes) + Enable experimental SMTPUTF8 support for the protocols described + in RFC 6531..6533. + syslog_facility (mail) The syslog facility of Postfix logging. diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index 54d6807f1..705c5ab22 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -555,50 +555,56 @@ SMTP(8) SMTP(8) tlsmgr_service_name (tlsmgr) The name of the tlsmgr(8) service entry in master.cf. + Available in Postfix version 2.12 and later: + + smtp_tls_wrappermode (no) + Request that the Postfix SMTP client connects using the legacy + SMTPS protocol instead of using the STARTTLS command. + OBSOLETE STARTTLS CONTROLS - The following configuration parameters exist for compatibility with - Postfix versions before 2.3. Support for these will be removed in a + The following configuration parameters exist for compatibility with + Postfix versions before 2.3. Support for these will be removed in a future release. smtp_use_tls (no) - Opportunistic mode: use TLS when a remote SMTP server announces + Opportunistic mode: use TLS when a remote SMTP server announces STARTTLS support, otherwise send the mail in the clear. smtp_enforce_tls (no) - Enforcement mode: require that remote SMTP servers use TLS + Enforcement mode: require that remote SMTP servers use TLS encryption, and never send mail in the clear. smtp_tls_enforce_peername (yes) - With mandatory TLS encryption, require that the remote SMTP - server hostname matches the information in the remote SMTP + With mandatory TLS encryption, require that the remote SMTP + server hostname matches the information in the remote SMTP server certificate. smtp_tls_per_site (empty) - Optional lookup tables with the Postfix SMTP client TLS usage - policy by next-hop destination and by remote SMTP server host- + Optional lookup tables with the Postfix SMTP client TLS usage + policy by next-hop destination and by remote SMTP server host- name. smtp_tls_cipherlist (empty) - Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS + Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS cipher list. RESOURCE AND RATE CONTROLS smtp_destination_concurrency_limit ($default_destination_concur- rency_limit) - The maximal number of parallel deliveries to the same destina- + The maximal number of parallel deliveries to the same destina- tion via the smtp message delivery transport. smtp_destination_recipient_limit ($default_destination_recipient_limit) - The maximal number of recipients per message for the smtp mes- + The maximal number of recipients per message for the smtp mes- sage delivery transport. smtp_connect_timeout (30s) - The Postfix SMTP client time limit for completing a TCP connec- + The Postfix SMTP client time limit for completing a TCP connec- tion, or zero (use the operating system built-in time limit). smtp_helo_timeout (300s) - The Postfix SMTP client time limit for sending the HELO or EHLO - command, and for receiving the initial remote SMTP server + The Postfix SMTP client time limit for sending the HELO or EHLO + command, and for receiving the initial remote SMTP server response. lmtp_lhlo_timeout (300s) @@ -610,19 +616,19 @@ SMTP(8) SMTP(8) mand, and for receiving the remote SMTP server response. smtp_mail_timeout (300s) - The Postfix SMTP client time limit for sending the MAIL FROM + The Postfix SMTP client time limit for sending the MAIL FROM command, and for receiving the remote SMTP server response. smtp_rcpt_timeout (300s) - The Postfix SMTP client time limit for sending the SMTP RCPT TO + The Postfix SMTP client time limit for sending the SMTP RCPT TO command, and for receiving the remote SMTP server response. smtp_data_init_timeout (120s) - The Postfix SMTP client time limit for sending the SMTP DATA + The Postfix SMTP client time limit for sending the SMTP DATA command, and for receiving the remote SMTP server response. smtp_data_xfer_timeout (180s) - The Postfix SMTP client time limit for sending the SMTP message + The Postfix SMTP client time limit for sending the SMTP message content. smtp_data_done_timeout (600s) @@ -636,13 +642,13 @@ SMTP(8) SMTP(8) Available in Postfix version 2.1 and later: smtp_mx_address_limit (5) - The maximal number of MX (mail exchanger) IP addresses that can - result from Postfix SMTP client mail exchanger lookups, or zero + The maximal number of MX (mail exchanger) IP addresses that can + result from Postfix SMTP client mail exchanger lookups, or zero (no limit). smtp_mx_session_limit (2) - The maximal number of SMTP sessions per delivery request before - the Postfix SMTP client gives up or delivers to a fall-back + The maximal number of SMTP sessions per delivery request before + the Postfix SMTP client gives up or delivers to a fall-back relay host, or zero (no limit). smtp_rset_timeout (20s) @@ -652,17 +658,17 @@ SMTP(8) SMTP(8) Available in Postfix version 2.2 and earlier: lmtp_cache_connection (yes) - Keep Postfix LMTP client connections open for up to $max_idle + Keep Postfix LMTP client connections open for up to $max_idle seconds. Available in Postfix version 2.2 and later: smtp_connection_cache_destinations (empty) - Permanently enable SMTP connection caching for the specified + Permanently enable SMTP connection caching for the specified destinations. smtp_connection_cache_on_demand (yes) - Temporarily enable SMTP connection caching while a destination + Temporarily enable SMTP connection caching while a destination has a high volume of mail in the active queue. smtp_connection_reuse_time_limit (300s) @@ -676,23 +682,23 @@ SMTP(8) SMTP(8) Available in Postfix version 2.3 and later: connection_cache_protocol_timeout (5s) - Time limit for connection cache connect, send or receive opera- + Time limit for connection cache connect, send or receive opera- tions. Available in Postfix version 2.9 and later: smtp_per_record_deadline (no) - Change the behavior of the smtp_*_timeout time limits, from a - time limit per read or write system call, to a time limit to - send or receive a complete record (an SMTP command line, SMTP - response line, SMTP message content line, or TLS protocol mes- + Change the behavior of the smtp_*_timeout time limits, from a + time limit per read or write system call, to a time limit to + send or receive a complete record (an SMTP command line, SMTP + response line, SMTP message content line, or TLS protocol mes- sage). Available in Postfix version 2.11 and later: smtp_connection_reuse_count_limit (0) - When SMTP connection caching is enabled, the number of times - that an SMTP session may be reused before it is closed, or zero + When SMTP connection caching is enabled, the number of times + that an SMTP session may be reused before it is closed, or zero (no limit). SMTPUTF8 CONTROLS @@ -703,21 +709,21 @@ SMTP(8) SMTP(8) in RFC 6531..6533. smtputf8_autodetect_classes (sendmail, verify) - Detect that a message requires SMTPUTF8 support for the speci- + Detect that a message requires SMTPUTF8 support for the speci- fied mail origin classes. TROUBLE SHOOTING CONTROLS debug_peer_level (2) - The increment in verbose logging level when a remote client or + The increment in verbose logging level when a remote client or server matches a pattern in the debug_peer_list parameter. debug_peer_list (empty) - Optional list of remote client or server hostname or network + Optional list of remote client or server hostname or network address patterns that cause the verbose logging level to increase by the amount specified in $debug_peer_level. error_notice_recipient (postmaster) - The recipient of postmaster notifications about mail delivery + The recipient of postmaster notifications about mail delivery problems that are caused by policy, resource, software or proto- col errors. @@ -731,46 +737,46 @@ SMTP(8) SMTP(8) MISCELLANEOUS CONTROLS best_mx_transport (empty) - Where the Postfix SMTP client should deliver mail when it + Where the Postfix SMTP client should deliver mail when it detects a "mail loops back to myself" error condition. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to handle a + How much time a Postfix daemon process may take to handle a request before it is terminated by a built-in watchdog timer. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal point when log- + The maximal number of digits after the decimal point when log- ging sub-second delay values. disable_dns_lookups (no) Disable DNS lookups in the Postfix SMTP and LMTP clients. inet_interfaces (all) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on. inet_protocols (all) - The Internet protocols Postfix will attempt to use when making + The Internet protocols Postfix will attempt to use when making or accepting connections. ipc_timeout (3600s) - The time limit for sending or receiving information over an + The time limit for sending or receiving information over an internal communication channel. lmtp_assume_final (no) - When a remote LMTP server announces no DSN support, assume that - the server performs final delivery, and send "delivered" deliv- + When a remote LMTP server announces no DSN support, assume that + the server performs final delivery, and send "delivered" deliv- ery status notifications instead of "relayed". lmtp_tcp_port (24) The default TCP port that the Postfix LMTP client connects to. max_idle (100s) - The maximum amount of time that an idle Postfix daemon process + The maximum amount of time that an idle Postfix daemon process waits for an incoming connection before terminating voluntarily. max_use (100) @@ -784,20 +790,20 @@ SMTP(8) SMTP(8) The process name of a Postfix command or daemon process. proxy_interfaces (empty) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on by way of a proxy or network address translation unit. smtp_address_preference (any) The address type ("ipv6", "ipv4" or "any") that the Postfix SMTP - client will try first, when a destination has IPv6 and IPv4 + client will try first, when a destination has IPv6 and IPv4 addresses with equal MX preference. smtp_bind_address (empty) - An optional numerical network address that the Postfix SMTP + An optional numerical network address that the Postfix SMTP client should bind to when making an IPv4 connection. smtp_bind_address6 (empty) - An optional numerical network address that the Postfix SMTP + An optional numerical network address that the Postfix SMTP client should bind to when making an IPv6 connection. smtp_helo_name ($myhostname) @@ -817,8 +823,8 @@ SMTP(8) SMTP(8) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the process name in - syslog records, so that "smtpd" becomes, for example, "post- + The mail system name that is prepended to the process name in + syslog records, so that "smtpd" becomes, for example, "post- fix/smtpd". Available with Postfix 2.2 and earlier: @@ -836,7 +842,7 @@ SMTP(8) SMTP(8) Available with Postfix 2.12 and later: smtp_address_verify_target (rcpt) - In the context of email address verification, the SMTP protocol + In the context of email address verification, the SMTP protocol stage that determines whether an email address is deliverable. SEE ALSO diff --git a/postfix/man/man1/postalias.1 b/postfix/man/man1/postalias.1 index 9dda165f6..4a8b0f47e 100644 --- a/postfix/man/man1/postalias.1 +++ b/postfix/man/man1/postalias.1 @@ -9,7 +9,7 @@ Postfix alias database maintenance .na .nf .fi -\fBpostalias\fR [\fB-Nfinoprsvw\fR] [\fB-c \fIconfig_dir\fR] +\fBpostalias\fR [\fB-Nfinoprsuvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION @@ -99,6 +99,10 @@ printed in database order, which is not necessarily the same as the original input order. This feature is available in Postfix version 2.2 and later, and is not available for all database types. +.IP \fB-u\fR +Disable UTF-8 support. UTF-8 support is enabled by default +when "smtputf8_enable = yes". It requires that keys and +values are valid UTF-8 strings. .IP \fB-v\fR Enable verbose logging for debugging purposes. Multiple \fB-v\fR options make the software increasingly verbose. @@ -188,6 +192,9 @@ hash or btree tables. .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) and \fBpostmap\fR(1) commands. +.IP "\fBsmtputf8_enable (yes)\fR" +Enable experimental SMTPUTF8 support for the protocols described +in RFC 6531..6533. .IP "\fBsyslog_facility (mail)\fR" The syslog facility of Postfix logging. .IP "\fBsyslog_name (see 'postconf -d' output)\fR" diff --git a/postfix/man/man1/postmap.1 b/postfix/man/man1/postmap.1 index cf6ae5240..6acadd528 100644 --- a/postfix/man/man1/postmap.1 +++ b/postfix/man/man1/postmap.1 @@ -9,7 +9,7 @@ Postfix lookup table management .na .nf .fi -\fBpostmap\fR [\fB-Nbfhimnoprsvw\fR] [\fB-c \fIconfig_dir\fR] +\fBpostmap\fR [\fB-NbfhimnoprsuUvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... .SH DESCRIPTION @@ -81,6 +81,11 @@ parsing with \fB-m\fR. With this, the \fB-b\fR option generates no body-style lookup keys for attachment MIME headers and for attached message/* headers. .sp +NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +option disables UTF-8 syntax checks on query keys and +lookup results. Specify the \fB-U\fR option to force UTF-8 +syntax checks anyway. +.sp This feature is available in Postfix version 2.6 and later. .IP "\fB-c \fIconfig_dir\fR" Read the \fBmain.cf\fR configuration file in the named directory @@ -114,6 +119,11 @@ parsing with \fB-m\fR. With this, the \fB-h\fR option also generates header-style lookup keys for attachment MIME headers and for attached message/* headers. .sp +NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +option disables UTF-8 syntax checks on query keys and +lookup results. Specify the \fB-U\fR option to force UTF-8 +syntax checks anyway. +.sp This feature is available in Postfix version 2.6 and later. .IP \fB-i\fR Incremental mode. Read entries from standard input and do not @@ -161,6 +171,13 @@ as the original input order. .sp This feature is available in Postfix version 2.2 and later, and is not available for all database types. +.IP \fB-u\fR +Disable UTF-8 support. UTF-8 support is enabled by default +when "smtputf8_enable = yes". It requires that keys and +values are valid UTF-8 strings. +.IP \fB-U\fR +With "smtputf8_enable = yes", force UTF-8 syntax checks +with the \fB-b\fR and \fB-h\fR options. .IP \fB-v\fR Enable verbose logging for debugging purposes. Multiple \fB-v\fR options make the software increasingly verbose. @@ -245,6 +262,9 @@ configuration files. .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) and \fBpostmap\fR(1) commands. +.IP "\fBsmtputf8_enable (yes)\fR" +Enable experimental SMTPUTF8 support for the protocols described +in RFC 6531..6533. .IP "\fBsyslog_facility (mail)\fR" The syslog facility of Postfix logging. .IP "\fBsyslog_name (see 'postconf -d' output)\fR" diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index f3cd3947d..bf2a43ef9 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -6113,8 +6113,8 @@ transport_maps to apply this feature selectively: .na .ft C /etc/postfix/transport: - smtp-domain_that_verifies_after_data smtp-data-target: - lmtp-domain_that_verifies_after_data lmtp-data-target: + smtp-domain-that-verifies-after-data smtp-data-target: + lmtp-domain-that-verifies-after-data lmtp-data-target: .fi .ad .ft R @@ -8538,6 +8538,33 @@ example.com verify match=hostname:nexthop .ft R .PP This feature is available in Postfix 2.3 and later. +.SH smtp_tls_wrappermode (default: no) +Request that the Postfix SMTP client connects using the +legacy SMTPS protocol instead of using the STARTTLS command. +.PP +This mode requires "smtp_tls_security_level = encrypt" or +stronger. +.PP +Example: deliver all remote mail via a provider's server +"mail.example.com". +.PP +.nf +.na +.ft C +/etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 +.fi +.ad +.ft R +.PP +More examples are in TLS_README, including examples for older +Postfix versions. +.PP +This feature is available in Postfix 2.12 and later. .SH smtp_use_tls (default: no) Opportunistic mode: use TLS when a remote SMTP server announces STARTTLS support, otherwise send the mail in the clear. Beware: @@ -12884,6 +12911,12 @@ The characters Postfix accepts as VERP delimiter characters on the Postfix \fBsendmail\fR(1) command line and in SMTP commands. .PP This feature is available in Postfix 1.1 and later. +.SH virtual_alias_address_length_limit (default: 1000) +The maximal length of an email address after virtual alias expansion. +This stops virtual aliasing loops that increase the address length +exponentially. +.PP +This feature is available in Postfix 2.12 and later. .SH virtual_alias_domains (default: $virtual_alias_maps) Postfix is final destination for the specified list of virtual alias domains, that is, domains for which all addresses are aliased diff --git a/postfix/man/man8/cleanup.8 b/postfix/man/man8/cleanup.8 index 254b4b4d0..2c358e8a3 100644 --- a/postfix/man/man8/cleanup.8 +++ b/postfix/man/man8/cleanup.8 @@ -348,6 +348,10 @@ The maximal number of addresses that virtual alias expansion produces from each original recipient. .IP "\fBvirtual_alias_recursion_limit (1000)\fR" The maximal nesting depth of virtual alias expansion. +.PP +Available in Postfix version 2.12 and later: +.IP "\fBvirtual_alias_address_length_limit (1000)\fR" +The maximal length of an email address after virtual alias expansion. .SH "MISCELLANEOUS CONTROLS" .na .nf diff --git a/postfix/man/man8/smtp.8 b/postfix/man/man8/smtp.8 index a4d1ead9a..7b4b04bca 100644 --- a/postfix/man/man8/smtp.8 +++ b/postfix/man/man8/smtp.8 @@ -494,6 +494,11 @@ not an alias and its address records lie in an unsigned zone. RFC 6698 trust-anchor digest support in the Postfix TLS library. .IP "\fBtlsmgr_service_name (tlsmgr)\fR" The name of the \fBtlsmgr\fR(8) service entry in master.cf. +.PP +Available in Postfix version 2.12 and later: +.IP "\fBsmtp_tls_wrappermode (no)\fR" +Request that the Postfix SMTP client connects using the +legacy SMTPS protocol instead of using the STARTTLS command. .SH "OBSOLETE STARTTLS CONTROLS" .na .nf diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 34234751d..df47611d0 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -614,6 +614,7 @@ while (<>) { s;\bunverified_recipient_reject_reason\b;$&;g; s;\bunverified_sender_reject_reason\b;$&;g; s;\bverp_delimiter_filter\b;$&;g; + s;\bvir[-]*\n*[ ]*tual_alias_address_length_limit\b;$&;g; s;\bvir[- ]*\n*[]*tual_alias_domains\b;$&;g; s;\bvir[- ]*\n*[]*tual_alias_expansion_limit\b;$&;g; s;\bvir[- ]*\n*[]*tual_alias_maps\b;$&;g; @@ -667,6 +668,7 @@ while (<>) { s;\bsmtp_tls_session_cache_timeout\b;$&;g; s;\bsmtp_tls_block_early_mail_reply\b;$&;g; s;\bsmtp_tls_force_insecure_host_tlsa_lookup\b;$&;g; + s;\bsmtp_tls_wrappermode\b;$&;g; s;\bsmtp_use_tls\b;$&;g; s;\bsmtp_header_checks\b;$&;g; s;\bsmtp_mime_header_checks\b;$&;g; diff --git a/postfix/proto/ADDRESS_VERIFICATION_README.html b/postfix/proto/ADDRESS_VERIFICATION_README.html index aabdb833b..f89befae2 100644 --- a/postfix/proto/ADDRESS_VERIFICATION_README.html +++ b/postfix/proto/ADDRESS_VERIFICATION_README.html @@ -346,7 +346,7 @@ in forged email. # Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify /etc/postfix/sender_access: @@ -393,7 +393,7 @@ you can see what mail would be blocked: # Default setting for Postfix 2.7 and later. # Note 1: Be sure to read the "Caching" section below! - # Note 2: Avoid hash files here. Use btree instead. + # Note 2: Avoid hash files here. Use btree or lmdb instead. address_verify_map = btree:/var/lib/postfix/verify
# Example 1: Default setting for Postfix 2.7 and later. -# Note: avoid hash files here. Use btree instead. +# Note: avoid hash files here. Use btree or lmdb instead. /etc/postfix/main.cf: address_verify_map = btree:$data_directory/verify_cache diff --git a/postfix/proto/SMTPUTF8_README.html b/postfix/proto/SMTPUTF8_README.html index a68059ff3..6e4f55139 100644 --- a/postfix/proto/SMTPUTF8_README.html +++ b/postfix/proto/SMTPUTF8_README.html @@ -110,8 +110,27 @@ respect to earlier Postfix releases:@@ -257,29 +276,47 @@ delivered it if SMTPUTF8 support was disabled.
UTF-8 is permitted in the myorigin parameter value. However, -the myhostname and mydomain parameters must specify ASCII-only -domain names. This limitation may be removed later.
+the myhostname and mydomain parameters must currently specify +ASCII-only domain names. This limitation may be removed later. + +UTF-8 is the only form of non-ASCII text that Postfix +supports in access tables, address rewriting tables, and other +tables that are indexed with an email address, hostname, or domain +name.
+ +The header_checks-like and body_checks-like features are +not UTF-8 enabled, and therefore they do not enforce UTF-8 syntax +rules on inputs and outputs. The reason is that non-ASCII text may +be sent in encodings other than UTF-8, and that real email sometimes +contains malformed headers. Instead of skipping non-UTF-8 content, +Postfix should be able to filter it. You may try to enable UTF-8 +processing by starting a PCRE pattern with the sequence (*UTF8), +but this is will result in "message not accepted, try again later" +errors when the PCRE pattern matcher encounters non-UTF-8 input. +Other features that are not UTF-8 enabled are smtpd_command_filter, +smtp_reply_filter, the *_delivery_status_filter features, and the +*_dns_reply_filter features (the latter because DNS is by definition +an ASCII protocol).
The Postfix SMTP server announces SMTPUTF8 support in the EHLO response.
@@ -145,7 +164,7 @@ MAIL FROM and VRFY commands. MAIL FROM commands.The Postfix SMTP server accepts UTF-8 in email address -domains, but only after the remote SMTP client client issues the +domains, but only after the remote SMTP client issues the SMTPUTF8 request in MAIL FROM or VRFY commands.
Limitations of the current implementation
-"Internationalized" domain names can appear in two forms: the -UTF-8 form, and the ASCII (xn--mumble) form. The initial Postfix -SMTPUTF8 implementation performs no automatic conversions on UTF8 -strings beyond what is needed to perform DNS lookups.
+The Postfix implementation is a work in progress; limitations +are steadily being removed. The text below describes the situation +at one point in time.
-No characterset canonicalization for non-ASCII domain names. -
+No automatic conversions between ASCII and UTF-8 domain names.
-Postfix currently does not translate domain names from UTF-8 -into ASCII (or ASCII into UTF-8) before looking up the domain name -in mydestination, relay_domains, access tables, etc., before logging -the domain name, or before using the domain name in a policy daemon -or Milter request. You will have to configure both UTF-8 and ASCII -forms in Postfix configuration files; and both forms will have to -be handled by logfile tools, policy daemons and Milters.
+Some background: According to RFC 6530 and related documents, +"Internationalized" domain names can appear in two forms: the UTF-8 +form, and the ASCII (xn--mumble) form. "Internationalized" address +localparts must be encoded in UTF-8; the RFCs do not define an ASCII +form for the same information.
-No case canonicalization for non-ASCII characters.
+Postfix currently does not convert internationalized domain +names from UTF-8 into ASCII (or from ASCII into UTF-8) before using +domain names in SMTP commands and responses, before looking up +domain names in mydestination, relay_domains, access tables, etc., +before using domain names in a policy daemon or Milter request, +or before logging domain names.
-Postfix currently does not case-fold non-ASCII characters when -looking up an "Internationalized" domain name in mydestination, -relay_domains, access maps, etc. Some non-ASCII scripts do not -distinguish between upper and lower case, some have different numbers -of upper and lower case characters.
+Postfix does, however, casefold domain names and email addresses +before matching them against a Postfix configuration parameter or +lookup table.
+ ++ +
The Postfix parameters myhostname and mydomain must be in +ASCII form. One is a substring of the other, and the myhostname +value is used in SMTP commands and responses that require ASCII. +The parameter myorigin (added to local addresses without domain) +supports UTF-8.
+ +You need to configure both the ASCII and UTF-8 forms of +an Internationalized domain name in Postfix parameters such as +mydestination and relay_domains, as well as lookup table search +keys.
+ +Milters, content filters, policy servers and logfile +analysis tools need to be able to handle both the ASCII and UTF-8 +forms of Internationalized domain names.
+ +Compatibility with pre-SMTPUTF8 environments
@@ -290,33 +327,36 @@ environments non-UTF-8 members. Postfix will try to deliver the non-UTF8 subscribers over "traditional" non-SMTPUTF8 sessions, as long as the message has an ASCII envelope sender address and all-ASCII header values. -The mailing list manager will have to apply RFC 2047 encoding to +The mailing list manager may have to apply RFC 2047 encoding to satisfy that last condition.Pre-existing non-ASCII email flows
-In pre-SMTPUTF8 environments, email with UTF-8 in address -localparts (and in headers) works just fine. The vast majority -of email software including Postfix is perfectly capable of handling -such email, even if pre-SMTPUTF8 standards do not support this.
+With "smtputf8_enable = no", Postfix handles email with non-ASCII +in address localparts (and in headers) as before. The vast majority +of email software is perfectly capable of handling such email, even +if pre-SMTPUTF8 standards do not support such practice.
-Therefore, when Postfix SMTPUTF8 support is turned on, Postfix -must not suddenly start to break pre-existing email flows with UTF-8 -in addres localparts (and in headers).
- -Thus, Postfix continues to permit UTF-8 in address localparts -(and in headers) in email from and to pre-SMTPUTF8 systems. At -least, that is the default (see autodetection above).
+However, when you specify "smtputf8_enable = yes", Postfix +requires that non-ASCII address information is encoded in UTF-8 and +will reject other encodings such as ISO-8859. It is not practical +for Postfix to support multiple encodings at the same time. There +is no problem with RFC 2047 encodings such as "=?ISO-8859-1?Q?text?=", +because those use only characters from the ASCII characterset.
Credits
-
diff --git a/postfix/proto/TLS_README.html b/postfix/proto/TLS_README.html index bc492ebd5..23994123a 100644 --- a/postfix/proto/TLS_README.html +++ b/postfix/proto/TLS_README.html @@ -1,4 +1,4 @@ - @@ -2370,14 +2370,75 @@ the SSL/TLS protocols used with opportunistic TLS.Arnt Gulbrandsen posted his patch for Unicode email support -on May 15, 2014. This work was sponsored by CNNIC.
+May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode +email support. This work was sponsored by CNNIC.
-Wietse integrated Arnt Gulbrandsen's code and released -Postfix with SMTPUTF8 support on July 15, 2014.
+July 15, 2014: Wietse integrated Arnt Gulbrandsen's code +and released Postfix with SMTPUTF8 support.
+ +January 2015: Wietse added UTF-8 support for casefolding +in Postfix lookup tables and caseless string comparison in Postfix +list-based features.
Client-side SMTPS support
-Although the Postfix SMTP client by itself doesn't support TLS +
These sections show how to send mail to a server that does not +support STARTTLS, but that provides the deprecated SMTPS service +on TCP port 465. Depending on the Postfix version, some additional +tooling may be required.
+ +Postfix ≥ 2.12
+ +The Postfix SMTP client has SMTPS support built-in as of version +2.12. Use one of the following examples, to send all remote mail, +or to send only some remote mail, to an SMTPS server.
+ +Postfix ≥ 2.12: Sending all remote mail to an SMTPS server
+ +The first example will send all remote mail over SMTPS through +a provider's server called "mail.example.com":
+ +++ ++/etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 ++Use "postfix reload" to make the change effective.
+ +See SOHO_README for additional information about SASL authentication. +
+ +Postfix ≥ 2.12: Sending only mail for a specific destination +via SMTPS
+ +The second example will send only mail for "example.com" via +SMTPS. This time, Postfix uses a transport map to deliver only +mail for "example.com" via SMTPS:
+ +++ ++/etc/postfix/main.cf: + transport_maps = hash:/etc/postfix/transport + +/etc/postfix/transport: + example.com relay-smtps:example.com:465 + +/etc/postfix/master.cf: + relay-smtps unix - - n - - smtp + # Client-side SMTPS requires "encrypt" or stronger. + -o smtp_tls_security_level=encrypt + -o smtp_tls_wrappermode=yes ++Use "postmap hash:/etc/postfix/transport" and "postfix reload" +to make the change effective.
+ +See SOHO_README for additional information about SASL +authentication.
+ +Postfix < 2.12
+ +Although older Postfix SMTP client versions do not support TLS wrapper mode, it is relatively easy to forward a connection through the stunnel program if Postfix needs to deliver mail to some legacy -system that doesn't support STARTTLS. Use one of the following two -examples, to send only some remote mail, or to send all remote mail, -to an SMTPS server.
+system that doesn't support STARTTLS. -Sending all remote mail to an SMTPS server
+Postfix < 2.12: Sending all remote mail to an SMTPS server
The first example uses SMTPS to send all remote mail to a provider's mail server called "mail.example.com".
@@ -2420,7 +2481,10 @@ mail through the local stunnel listener on port 11125:Use "postfix reload" to make the change effective.
-Sending only mail for a specific destination via SMTPS
+See SOHO_README for additional information about SASL +authentication.
+ +Postfix < 2.12: Sending only mail for a specific destination via SMTPS
The second example will use SMTPS to send only mail for "example.com" via SMTPS. It uses the same stunnel configuration @@ -2442,6 +2506,9 @@ mail for "example.com" through the tunnel:
Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the change effective.
+See SOHO_README for additional information about SASL authentication. +
+Miscellaneous client controls
The smtp_starttls_timeout parameter limits the time of Postfix diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index d3783b48d..f830add7a 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -15437,8 +15437,8 @@ transport_maps to apply this feature selectively:
@@ -16420,3 +16420,42 @@ with valid PTR etc. records./etc/postfix/transport: - smtp-domain_that_verifies_after_data smtp-data-target: - lmtp-domain_that_verifies_after_data lmtp-data-target: + smtp-domain-that-verifies-after-data smtp-data-target: + lmtp-domain-that-verifies-after-data lmtp-data-target:This feature is available in Postfix 2.12 and later.
+ +%PARAM smtp_tls_wrappermode no + +Request that the Postfix SMTP client connects using the +legacy SMTPS protocol instead of using the STARTTLS command.
+ +This mode requires "smtp_tls_security_level = encrypt" or +stronger.
+ +Example: deliver all remote mail via a provider's server +"mail.example.com".
+ ++/etc/postfix/main.cf: + # Client-side SMTPS requires "encrypt" or stronger. + smtp_tls_security_level = encrypt + smtp_tls_wrappermode = yes + # The [] suppress MX lookups. + relayhost = [mail.example.com]:465 ++ +More examples are in TLS_README, including examples for older +Postfix versions.
+ +This feature is available in Postfix 2.12 and later.
+ +%PARAM virtual_alias_address_length_limit 1000 + ++The maximal length of an email address after virtual alias expansion. +This stops virtual aliasing loops that increase the address length +exponentially. +
+ ++This feature is available in Postfix 2.12 and later. +
+ + diff --git a/postfix/src/bounce/Makefile.in b/postfix/src/bounce/Makefile.in index 7eb5fc3d6..ef57d240b 100644 --- a/postfix/src/bounce/Makefile.in +++ b/postfix/src/bounce/Makefile.in @@ -326,7 +326,7 @@ bounce_template.o: ../../include/mac_parse.h bounce_template.o: ../../include/mail_conf.h bounce_template.o: ../../include/mail_params.h bounce_template.o: ../../include/mail_proto.h -bounce_template.o: ../../include/midna.h +bounce_template.o: ../../include/midna_domain.h bounce_template.o: ../../include/msg.h bounce_template.o: ../../include/mymalloc.h bounce_template.o: ../../include/nvtable.h diff --git a/postfix/src/bounce/bounce.c b/postfix/src/bounce/bounce.c index b33fefc4c..3b067cb61 100644 --- a/postfix/src/bounce/bounce.c +++ b/postfix/src/bounce/bounce.c @@ -319,7 +319,9 @@ static int bounce_notify_proto(char *service_name, VSTREAM *client, msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } - printable(STR(dsn_envid), '?'); + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); if (msg_verbose) msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x", myname, flags, service_name, STR(queue_name), STR(queue_id), @@ -380,10 +382,12 @@ static int bounce_verp_proto(char *service_name, VSTREAM *client) msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } - printable(STR(dsn_envid), '?'); + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); + VS_NEUTER(verp_delims); if (strlen(STR(verp_delims)) != 2) { - msg_warn("malformed verp delimiter string: %s", - printable(STR(verp_delims), '?')); + msg_warn("malformed verp delimiter string: %s", STR(verp_delims)); return (-1); } if (msg_verbose) @@ -460,7 +464,9 @@ static int bounce_one_proto(char *service_name, VSTREAM *client) msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } - printable(STR(dsn_envid), '?'); + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); VS_NEUTER(rcpt_buf->address); VS_NEUTER(rcpt_buf->orig_addr); VS_NEUTER(rcpt_buf->dsn_orcpt); diff --git a/postfix/src/bounce/bounce_template.c b/postfix/src/bounce/bounce_template.c index ca8c8c86f..39fef8fda 100644 --- a/postfix/src/bounce/bounce_template.c +++ b/postfix/src/bounce/bounce_template.c @@ -118,7 +118,7 @@ #include#include #ifndef NO_EAI -#include +#include #endif /* Global library. */ @@ -462,7 +462,7 @@ static const char *bounce_template_lookup(const char *key, int unused_mode, "non-ASCII input value: \"%s\"", tp->origin, key, asc_val); return (asc_val); - } else if ((utf8_val = midna_to_utf8(asc_val)) == 0) { + } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) { msg_warn("%s: conversion \"%s\" failed: " "input value: \"%s\"", tp->origin, key, asc_val); diff --git a/postfix/src/cleanup/cleanup.c b/postfix/src/cleanup/cleanup.c index 26392e71e..b83c5c57d 100644 --- a/postfix/src/cleanup/cleanup.c +++ b/postfix/src/cleanup/cleanup.c @@ -320,6 +320,10 @@ /* from each original recipient. /* .IP "\fBvirtual_alias_recursion_limit (1000)\fR" /* The maximal nesting depth of virtual alias expansion. +/* .PP +/* Available in Postfix version 2.12 and later: +/* .IP "\fBvirtual_alias_address_length_limit (1000)\fR" +/* The maximal length of an email address after virtual alias expansion. /* MISCELLANEOUS CONTROLS /* .ad /* .fi diff --git a/postfix/src/cleanup/cleanup_init.c b/postfix/src/cleanup/cleanup_init.c index a117500d2..37ec5b0f7 100644 --- a/postfix/src/cleanup/cleanup_init.c +++ b/postfix/src/cleanup/cleanup_init.c @@ -164,6 +164,7 @@ char *var_cleanup_milters; /* non-SMTP mail */ char *var_milt_head_checks; /* post-Milter header checks */ int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */ int var_always_add_hdrs; /* always add missing headers */ +int var_virt_addrlen_limit; /* stop exponential growth */ const CONFIG_INT_TABLE cleanup_int_table[] = { VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0, @@ -171,6 +172,7 @@ const CONFIG_INT_TABLE cleanup_int_table[] = { VAR_QATTR_COUNT_LIMIT, DEF_QATTR_COUNT_LIMIT, &var_qattr_count_limit, 1, 0, VAR_VIRT_RECUR_LIMIT, DEF_VIRT_RECUR_LIMIT, &var_virt_recur_limit, 1, 0, VAR_VIRT_EXPAN_LIMIT, DEF_VIRT_EXPAN_LIMIT, &var_virt_expan_limit, 1, 0, + VAR_VIRT_ADDRLEN_LIMIT, DEF_VIRT_ADDRLEN_LIMIT, &var_virt_addrlen_limit, 1, 0, VAR_BODY_CHECK_LEN, DEF_BODY_CHECK_LEN, &var_body_check_len, 0, 0, 0, }; @@ -333,20 +335,24 @@ void cleanup_pre_jail(char *unused_name, char **unused_argv) if (*var_canonical_maps) cleanup_comm_canon_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_send_canon_maps) cleanup_send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_rcpt_canon_maps) cleanup_rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_virt_alias_maps) cleanup_virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_canon_classes) cleanup_comm_canon_flags = name_mask(VAR_CANON_CLASSES, canon_class_table, @@ -375,18 +381,21 @@ void cleanup_pre_jail(char *unused_name, char **unused_argv) maps_create(VAR_BODY_CHECKS, var_body_checks, DICT_FLAG_LOCK); if (*var_masq_exceptions) cleanup_masq_exceptions = - string_list_init(MATCH_FLAG_RETURN, var_masq_exceptions); + string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN, + var_masq_exceptions); if (*var_masq_classes) cleanup_masq_flags = name_mask(VAR_MASQ_CLASSES, masq_class_table, var_masq_classes); if (*var_send_bcc_maps) cleanup_send_bcc_maps = maps_create(VAR_SEND_BCC_MAPS, var_send_bcc_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_rcpt_bcc_maps) cleanup_rcpt_bcc_maps = maps_create(VAR_RCPT_BCC_MAPS, var_rcpt_bcc_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_cleanup_milters) cleanup_milters = milter_create(var_cleanup_milters, var_milt_conn_time, diff --git a/postfix/src/cleanup/cleanup_map1n.c b/postfix/src/cleanup/cleanup_map1n.c index 7f9919466..b01971e2d 100644 --- a/postfix/src/cleanup/cleanup_map1n.c +++ b/postfix/src/cleanup/cleanup_map1n.c @@ -139,6 +139,15 @@ ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, if ((lookup = mail_addr_map(maps, STR(state->temp1), propagate)) != 0) { saved_lhs = mystrdup(argv->argv[arg]); for (i = 0; i < lookup->argc; i++) { + if (strlen(lookup->argv[i]) > var_virt_addrlen_limit) { + msg_warn("%s: unreasonable %s result %.300s... -- " + "message not accepted, try again later", + state->queue_id, maps->title, lookup->argv[i]); + state->errs |= CLEANUP_STAT_DEFER; + UPDATE(state->reason, "4.6.0 Alias expansion error"); + UNEXPAND(argv, addr); + RETURN(argv); + } unquote_822_local(state->temp1, lookup->argv[i]); if (i == 0) { UPDATE(argv->argv[arg], STR(state->temp1)); diff --git a/postfix/src/cleanup/cleanup_masquerade.c b/postfix/src/cleanup/cleanup_masquerade.c index e81bc188c..58053c063 100644 --- a/postfix/src/cleanup/cleanup_masquerade.c +++ b/postfix/src/cleanup/cleanup_masquerade.c @@ -206,7 +206,8 @@ int main(int argc, char **argv) var_masq_exceptions = argv[1]; cleanup_masq_exceptions = - string_list_init(MATCH_FLAG_RETURN, var_masq_exceptions); + string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN, + var_masq_exceptions); masq_domains = argv_split(argv[2], CHARS_COMMA_SP); addr = vstring_alloc(1); if (strchr(argv[3], '@') == 0) diff --git a/postfix/src/flush/flush.c b/postfix/src/flush/flush.c index 52603cb46..fccb61cd7 100644 --- a/postfix/src/flush/flush.c +++ b/postfix/src/flush/flush.c @@ -805,7 +805,7 @@ static void flush_service(VSTREAM *client_stream, char *unused_service, static void pre_jail_init(char *unused_name, char **unused_argv) { - flush_domains = domain_list_init(MATCH_FLAG_RETURN + flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN | match_parent_style(VAR_FFLUSH_DOMAINS), var_fflush_domains); } diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 2f96f2521..ab6b8a1ec 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -33,7 +33,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \ smtp_reply_footer.c safe_ultostr.c verify_sender_addr.c \ dict_memcache.c mail_version.c memcache_proto.c server_acl.c \ mkmap_fail.c haproxy_srvr.c dsn_filter.c dynamicmaps.c uxtext.c \ - smtputf8.c mail_conf_over.c mail_parm_split.c + smtputf8.c mail_conf_over.c mail_parm_split.c midna_adomain.c OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \ clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \ @@ -68,40 +68,41 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ smtp_reply_footer.o safe_ultostr.o verify_sender_addr.o \ dict_memcache.o mail_version.o memcache_proto.o server_acl.o \ mkmap_fail.o haproxy_srvr.o dsn_filter.o dynamicmaps.o uxtext.o \ - smtputf8.o attr_override.o mail_parm_split.o $(NON_PLUGIN_MAP_OBJ) -# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. -# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), -# otherwise it sets the PLUGIN_* macros. -MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o mkmap_cdb.o \ - mkmap_lmdb.o mkmap_sdbm.o -HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ - canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ - conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ - deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \ - dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \ - dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \ - dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \ - file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \ - int_filt.h is_header.h lex_822.h log_adhoc.h mail_addr.h \ - mail_addr_crunch.h mail_addr_find.h mail_addr_map.h mail_conf.h \ - mail_copy.h mail_date.h mail_dict.h mail_error.h mail_flush.h \ - mail_open_ok.h mail_params.h mail_proto.h mail_queue.h mail_run.h \ - mail_scan_dir.h mail_stream.h mail_task.h mail_version.h maps.h \ - mark_corrupt.h match_parent_style.h mbox_conf.h mbox_open.h \ - mime_state.h mkmap.h msg_stats.h mynetworks.h mypwd.h namadr_list.h \ - off_cvt.h opened.h own_inet_addr.h pipe_command.h post_mail.h \ - qmgr_user.h qmqp_proto.h quote_821_local.h quote_822_local.h \ - quote_flags.h rcpt_buf.h rcpt_print.h rec_attr_map.h rec_streamlf.h \ - rec_type.h recipient_list.h record.h resolve_clnt.h resolve_local.h \ - rewrite_clnt.h scache.h sent.h smtp_stream.h split_addr.h \ - string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \ - trace.h user_acl.h valid_mailhost_addr.h verify.h verify_clnt.h \ - verp_sender.h wildcard_inet_addr.h xtext.h delivered_hdr.h \ - fold_addr.h header_body_checks.h data_redirect.h match_service.h \ - addr_match_list.h smtp_reply_footer.h safe_ultostr.h \ - verify_sender_addr.h dict_memcache.h memcache_proto.h server_acl.h \ - haproxy_srvr.h dsn_filter.h dynamicmaps.h uxtext.h smtputf8.h \ - attr_override.h mail_parm_split.h + smtputf8.o attr_override.o mail_parm_split.o midna_adomain.o \ + $(NON_PLUGIN_MAP_OBJ) + # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. + # When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), + # otherwise it sets the PLUGIN_* macros. + MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o mkmap_cdb.o \ + mkmap_lmdb.o mkmap_sdbm.o + HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ + canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ + conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ + deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \ + dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \ + dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \ + dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \ + file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \ + int_filt.h is_header.h lex_822.h log_adhoc.h mail_addr.h \ + mail_addr_crunch.h mail_addr_find.h mail_addr_map.h mail_conf.h \ + mail_copy.h mail_date.h mail_dict.h mail_error.h mail_flush.h \ + mail_open_ok.h mail_params.h mail_proto.h mail_queue.h mail_run.h \ + mail_scan_dir.h mail_stream.h mail_task.h mail_version.h maps.h \ + mark_corrupt.h match_parent_style.h mbox_conf.h mbox_open.h \ + mime_state.h mkmap.h msg_stats.h mynetworks.h mypwd.h namadr_list.h \ + off_cvt.h opened.h own_inet_addr.h pipe_command.h post_mail.h \ + qmgr_user.h qmqp_proto.h quote_821_local.h quote_822_local.h \ + quote_flags.h rcpt_buf.h rcpt_print.h rec_attr_map.h rec_streamlf.h \ + rec_type.h recipient_list.h record.h resolve_clnt.h resolve_local.h \ + rewrite_clnt.h scache.h sent.h smtp_stream.h split_addr.h \ + string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \ + trace.h user_acl.h valid_mailhost_addr.h verify.h verify_clnt.h \ + verp_sender.h wildcard_inet_addr.h xtext.h delivered_hdr.h \ + fold_addr.h header_body_checks.h data_redirect.h match_service.h \ + addr_match_list.h smtp_reply_footer.h safe_ultostr.h \ + verify_sender_addr.h dict_memcache.h memcache_proto.h server_acl.h \ + haproxy_srvr.h dsn_filter.h dynamicmaps.h uxtext.h smtputf8.h \ + attr_override.h mail_parm_split.h midna_adomain.h TESTSRC = rec2stream.c stream2rec.c recdump.c DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) @@ -687,8 +688,11 @@ abounce.o: mail_proto.h abounce.o: msg_stats.h abounce.o: recipient_list.h addr_match_list.o: ../../include/argv.h +addr_match_list.o: ../../include/check_arg.h addr_match_list.o: ../../include/match_list.h addr_match_list.o: ../../include/sys_defs.h +addr_match_list.o: ../../include/vbuf.h +addr_match_list.o: ../../include/vstring.h addr_match_list.o: addr_match_list.c addr_match_list.o: addr_match_list.h anvil_clnt.o: ../../include/attr.h @@ -877,9 +881,12 @@ db_common.o: db_common.c db_common.o: db_common.h db_common.o: string_list.h debug_peer.o: ../../include/argv.h +debug_peer.o: ../../include/check_arg.h debug_peer.o: ../../include/match_list.h debug_peer.o: ../../include/msg.h debug_peer.o: ../../include/sys_defs.h +debug_peer.o: ../../include/vbuf.h +debug_peer.o: ../../include/vstring.h debug_peer.o: debug_peer.c debug_peer.o: debug_peer.h debug_peer.o: mail_params.h @@ -1106,8 +1113,11 @@ dict_sqlite.o: dict_sqlite.c dict_sqlite.o: dict_sqlite.h dict_sqlite.o: string_list.h domain_list.o: ../../include/argv.h +domain_list.o: ../../include/check_arg.h domain_list.o: ../../include/match_list.h domain_list.o: ../../include/sys_defs.h +domain_list.o: ../../include/vbuf.h +domain_list.o: ../../include/vstring.h domain_list.o: domain_list.c domain_list.o: domain_list.h dot_lockfile.o: ../../include/check_arg.h @@ -1841,8 +1851,11 @@ mark_corrupt.o: mark_corrupt.h mark_corrupt.o: msg_stats.h mark_corrupt.o: recipient_list.h match_parent_style.o: ../../include/argv.h +match_parent_style.o: ../../include/check_arg.h match_parent_style.o: ../../include/match_list.h match_parent_style.o: ../../include/sys_defs.h +match_parent_style.o: ../../include/vbuf.h +match_parent_style.o: ../../include/vstring.h match_parent_style.o: mail_params.h match_parent_style.o: match_parent_style.c match_parent_style.o: match_parent_style.h @@ -1895,6 +1908,14 @@ memcache_proto.o: ../../include/vstring.h memcache_proto.o: ../../include/vstring_vstream.h memcache_proto.o: memcache_proto.c memcache_proto.o: memcache_proto.h +midna_adomain.o: ../../include/check_arg.h +midna_adomain.o: ../../include/midna_domain.h +midna_adomain.o: ../../include/stringops.h +midna_adomain.o: ../../include/sys_defs.h +midna_adomain.o: ../../include/vbuf.h +midna_adomain.o: ../../include/vstring.h +midna_adomain.o: midna_adomain.c +midna_adomain.o: midna_adomain.h mime_state.o: ../../include/check_arg.h mime_state.o: ../../include/msg.h mime_state.o: ../../include/mymalloc.h @@ -1994,6 +2015,7 @@ mkmap_open.o: ../../include/msg.h mkmap_open.o: ../../include/myflock.h mkmap_open.o: ../../include/mymalloc.h mkmap_open.o: ../../include/sigdelay.h +mkmap_open.o: ../../include/stringops.h mkmap_open.o: ../../include/sys_defs.h mkmap_open.o: ../../include/vbuf.h mkmap_open.o: ../../include/vstream.h @@ -2080,8 +2102,11 @@ mypwd.o: ../../include/sys_defs.h mypwd.o: mypwd.c mypwd.o: mypwd.h namadr_list.o: ../../include/argv.h +namadr_list.o: ../../include/check_arg.h namadr_list.o: ../../include/match_list.h namadr_list.o: ../../include/sys_defs.h +namadr_list.o: ../../include/vbuf.h +namadr_list.o: ../../include/vstring.h namadr_list.o: namadr_list.c namadr_list.o: namadr_list.h off_cvt.o: ../../include/check_arg.h @@ -2481,8 +2506,11 @@ stream2rec.o: rec_type.h stream2rec.o: record.h stream2rec.o: stream2rec.c string_list.o: ../../include/argv.h +string_list.o: ../../include/check_arg.h string_list.o: ../../include/match_list.h string_list.o: ../../include/sys_defs.h +string_list.o: ../../include/vbuf.h +string_list.o: ../../include/vstring.h string_list.o: string_list.c string_list.o: string_list.h strip_addr.o: ../../include/mymalloc.h diff --git a/postfix/src/global/addr_match_list.c b/postfix/src/global/addr_match_list.c index 946cf4ae3..de3d3f967 100644 --- a/postfix/src/global/addr_match_list.c +++ b/postfix/src/global/addr_match_list.c @@ -81,13 +81,15 @@ #ifdef TEST -#include #include #include #include +#include #include #include #include +#include +#include /* util_utf8_enable */ static void usage(char *progname) { @@ -113,7 +115,10 @@ int main(int argc, char **argv) } if (argc != optind + 2) usage(argv[0]); - list = addr_match_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]); + dict_allow_surrogate = 1; + util_utf8_enable = 1; + list = addr_match_list_init("command line", MATCH_FLAG_PARENT + | MATCH_FLAG_RETURN, argv[optind]); addr = argv[optind + 1]; if (strcmp(addr, "-") == 0) { VSTRING *buf = vstring_alloc(100); diff --git a/postfix/src/global/addr_match_list.h b/postfix/src/global/addr_match_list.h index 67c7e2316..f03c09d95 100644 --- a/postfix/src/global/addr_match_list.h +++ b/postfix/src/global/addr_match_list.h @@ -21,8 +21,8 @@ */ #define ADDR_MATCH_LIST MATCH_LIST -#define addr_match_list_init(f, p) \ - match_list_init((f), (p), 1, match_hostaddr) +#define addr_match_list_init(o, f, p) \ + match_list_init((o), (f), (p), 1, match_hostaddr) #define addr_match_list_match(l, a) \ match_list_match((l), (a)) #define addr_match_list_free match_list_free diff --git a/postfix/src/global/cleanup_strflags.c b/postfix/src/global/cleanup_strflags.c index 11c7e0881..d281c4462 100644 --- a/postfix/src/global/cleanup_strflags.c +++ b/postfix/src/global/cleanup_strflags.c @@ -54,6 +54,7 @@ static struct cleanup_flag_map cleanup_flag_map[] = { CLEANUP_FLAG_MILTER, "enable_milters", CLEANUP_FLAG_SMTP_REPLY, "enable_smtp_reply", CLEANUP_FLAG_SMTPUTF8, "smtputf8_requested", + CLEANUP_FLAG_AUTOUTF8, "smtputf8_autodetect", }; /* cleanup_strflags - map flags code to printable string */ diff --git a/postfix/src/global/db_common.c b/postfix/src/global/db_common.c index 1e5a1d4ed..491a72690 100644 --- a/postfix/src/global/db_common.c +++ b/postfix/src/global/db_common.c @@ -256,7 +256,8 @@ void db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr) domainlist = cfg_get_str(parser, "domain", "", 0, 0); if (*domainlist) { - ctx->domain = string_list_init(MATCH_FLAG_RETURN, domainlist); + ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN, + domainlist); if (ctx->domain == 0) /* diff --git a/postfix/src/global/debug_peer.c b/postfix/src/global/debug_peer.c index 023e1ffda..06fcfc863 100644 --- a/postfix/src/global/debug_peer.c +++ b/postfix/src/global/debug_peer.c @@ -99,7 +99,7 @@ void debug_peer_init(void) */ if (*var_debug_peer_list) debug_peer_list = - namadr_list_init(MATCH_FLAG_RETURN + namadr_list_init(VAR_DEBUG_PEER_LIST, MATCH_FLAG_RETURN | match_parent_style(VAR_DEBUG_PEER_LIST), var_debug_peer_list); } diff --git a/postfix/src/global/dict_ldap.c b/postfix/src/global/dict_ldap.c index aa8ad6b57..2acf69fe9 100644 --- a/postfix/src/global/dict_ldap.c +++ b/postfix/src/global/dict_ldap.c @@ -1340,7 +1340,8 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) /* * Don't frustrate future attempts to make Postfix UTF-8 transparent. */ - if (!valid_utf8_string(name, strlen(name))) { + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && !valid_utf8_string(name, strlen(name))) { if (msg_verbose) msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", myname, dict_ldap->parser->name, name); @@ -1351,10 +1352,10 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { - if (dict->fold_buf == 0) - dict->fold_buf = vstring_alloc(10); - vstring_strcpy(dict->fold_buf, name); - name = lowercase(vstring_str(dict->fold_buf)); + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); } /* diff --git a/postfix/src/global/dict_proxy.c b/postfix/src/global/dict_proxy.c index 29410d785..0fafdb4bb 100644 --- a/postfix/src/global/dict_proxy.c +++ b/postfix/src/global/dict_proxy.c @@ -156,6 +156,9 @@ static int dict_proxy_sequence(DICT *dict, int function, case PROXY_STAT_RETRY: *key = *value = 0; DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + *key = *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); default: msg_warn("%s sequence failed for table \"%s\" function %d: " "unexpected reply status %d", @@ -226,6 +229,8 @@ static const char *dict_proxy_lookup(DICT *dict, const char *key) DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, (char *) 0); case PROXY_STAT_RETRY: DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, (char *) 0); default: msg_warn("%s lookup failed for table \"%s\" key \"%s\": " "unexpected reply status %d", @@ -293,6 +298,8 @@ static int dict_proxy_update(DICT *dict, const char *key, const char *value) DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); case PROXY_STAT_RETRY: DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); default: msg_warn("%s update failed for table \"%s\" key \"%s\": " "unexpected reply status %d", @@ -360,6 +367,8 @@ static int dict_proxy_delete(DICT *dict, const char *key) DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); case PROXY_STAT_RETRY: DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); default: msg_warn("%s delete failed for table \"%s\" key \"%s\": " "unexpected reply status %d", diff --git a/postfix/src/global/dict_proxy.h b/postfix/src/global/dict_proxy.h index 80dd6e352..a5b79243a 100644 --- a/postfix/src/global/dict_proxy.h +++ b/postfix/src/global/dict_proxy.h @@ -37,6 +37,7 @@ extern DICT *dict_proxy_open(const char *, int, int); #define PROXY_STAT_RETRY 2 /* try lookup again later */ #define PROXY_STAT_BAD 3 /* invalid request parameter */ #define PROXY_STAT_DENY 4 /* table not approved for proxying */ +#define PROXY_STAT_CONFIG 5 /* DICT_ERR_CONFIG error */ /* LICENSE /* .ad diff --git a/postfix/src/global/dict_sqlite.c b/postfix/src/global/dict_sqlite.c index 3570f337c..3f581abfb 100644 --- a/postfix/src/global/dict_sqlite.c +++ b/postfix/src/global/dict_sqlite.c @@ -165,7 +165,8 @@ static const char *dict_sqlite_lookup(DICT *dict, const char *name) /* * Don't frustrate future attempts to make Postfix UTF-8 transparent. */ - if (!valid_utf8_string(name, strlen(name))) { + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && !valid_utf8_string(name, strlen(name))) { if (msg_verbose) msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", myname, dict_sqlite->parser->name, name); diff --git a/postfix/src/global/domain_list.c b/postfix/src/global/domain_list.c index 8433caaf7..2bcd38d2d 100644 --- a/postfix/src/global/domain_list.c +++ b/postfix/src/global/domain_list.c @@ -83,11 +83,13 @@ #ifdef TEST -#include #include #include +#include #include #include +#include +#include /* util_utf8_enable */ static void usage(char *progname) { @@ -113,7 +115,10 @@ int main(int argc, char **argv) } if (argc != optind + 2) usage(argv[0]); - list = domain_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]); + dict_allow_surrogate = 1; + util_utf8_enable = 1; + list = domain_list_init("command line", MATCH_FLAG_PARENT + | MATCH_FLAG_RETURN, argv[optind]); host = argv[optind + 1]; vstream_printf("%s: %s\n", host, domain_list_match(list, host) ? "YES" : list->error == 0 ? "NO" : "ERROR"); diff --git a/postfix/src/global/domain_list.h b/postfix/src/global/domain_list.h index 8612219d9..b0dfaec1f 100644 --- a/postfix/src/global/domain_list.h +++ b/postfix/src/global/domain_list.h @@ -21,7 +21,8 @@ */ #define DOMAIN_LIST MATCH_LIST -#define domain_list_init(f, p) match_list_init((f), (p), 1, match_hostname) +#define domain_list_init(o, f, p)\ + match_list_init((o), (f), (p), 1, match_hostname) #define domain_list_match match_list_match #define domain_list_free match_list_free diff --git a/postfix/src/global/flush_clnt.c b/postfix/src/global/flush_clnt.c index 07985dc20..7accaa98a 100644 --- a/postfix/src/global/flush_clnt.c +++ b/postfix/src/global/flush_clnt.c @@ -103,7 +103,7 @@ static DOMAIN_LIST *flush_domains; void flush_init(void) { - flush_domains = domain_list_init(MATCH_FLAG_RETURN + flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN | match_parent_style(VAR_FFLUSH_DOMAINS), var_fflush_domains); } diff --git a/postfix/src/global/mail_addr_find.c b/postfix/src/global/mail_addr_find.c index e21dd3600..f3a478be0 100644 --- a/postfix/src/global/mail_addr_find.c +++ b/postfix/src/global/mail_addr_find.c @@ -202,7 +202,8 @@ int main(int argc, char **argv) * Initialize. */ mail_conf_read(); - path = maps_create(argv[0], argv[1], DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + path = maps_create(argv[0], argv[1], DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX \ + | DICT_FLAG_UTF8_REQUEST); while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { extent = 0; result = mail_addr_find(path, STR(buffer), &extent); diff --git a/postfix/src/global/mail_addr_map.c b/postfix/src/global/mail_addr_map.c index e8bc6bc29..6a0479643 100644 --- a/postfix/src/global/mail_addr_map.c +++ b/postfix/src/global/mail_addr_map.c @@ -175,7 +175,8 @@ int main(int argc, char **argv) msg_verbose = 1; if (chdir(var_queue_dir) < 0) msg_fatal("chdir %s: %m", var_queue_dir); - path = maps_create(argv[0], argv[1], DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + path = maps_create(argv[0], argv[1], DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX \ + | DICT_FLAGS_UTF8_REQUEST); while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { msg_info("=== Address extension on, extension propagation on ==="); UPDATE(var_rcpt_delim, "+"); diff --git a/postfix/src/global/mail_params.c b/postfix/src/global/mail_params.c index 435bfd9bd..03c54eb17 100644 --- a/postfix/src/global/mail_params.c +++ b/postfix/src/global/mail_params.c @@ -636,6 +636,11 @@ void mail_params_init() VAR_DAEMON_OPEN_FATAL, DEF_DAEMON_OPEN_FATAL, &var_daemon_open_fatal, 0, }; + static const CONFIG_NBOOL_TABLE first_nbool_defaults[] = { + /* read and process the following before opening tables. */ + VAR_SMTPUTF8_ENABLE, DEF_SMTPUTF8_ENABLE, &var_smtputf8_enable, + 0, + }; static const CONFIG_STR_FN_TABLE function_str_defaults[] = { VAR_MYHOSTNAME, check_myhostname, &var_myhostname, 1, 0, VAR_MYDOMAIN, check_mydomainname, &var_mydomain, 1, 0, @@ -758,10 +763,6 @@ void mail_params_init() VAR_STRICT_SMTPUTF8, DEF_STRICT_SMTPUTF8, &var_strict_smtputf8, 0, }; - static const CONFIG_NBOOL_TABLE nbool_defaults[] = { - VAR_SMTPUTF8_ENABLE, DEF_SMTPUTF8_ENABLE, &var_smtputf8_enable, - 0, - }; const char *cp; /* @@ -790,6 +791,23 @@ void mail_params_init() if (var_daemon_open_fatal) dict_allow_surrogate = 0; + /* + * Should we open tables with UTF8 support, or in the legacy 8-bit clean + * mode with ASCII-only casefolding? + */ + get_mail_conf_nbool_table(first_nbool_defaults); + + /* + * Report run-time versus compile-time discrepancies. + */ +#ifdef NO_EAI + if (var_smtputf8_enable) + msg_warn("%s is true, but EAI support is not compiled in", + VAR_SMTPUTF8_ENABLE); + var_smtputf8_enable = 0; +#endif + util_utf8_enable = var_smtputf8_enable; + /* * What protocols should we attempt to support? The result is stored in * the global inet_proto_table variable. @@ -833,7 +851,6 @@ void mail_params_init() get_mail_conf_int_table(other_int_defaults); get_mail_conf_long_table(long_defaults); get_mail_conf_bool_table(bool_defaults); - get_mail_conf_nbool_table(nbool_defaults); get_mail_conf_time_table(time_defaults); check_default_privs(); check_mail_owner(); @@ -842,16 +859,6 @@ void mail_params_init() dict_db_cache_size = var_db_read_buf; dict_lmdb_map_size = var_lmdb_map_size; inet_windowsize = var_inet_windowsize; - temp_utf8_kludge = var_smtputf8_enable; - - /* - * Report run-time versus compile-time discrepancies. - */ -#ifdef NO_EAI - if (var_smtputf8_enable) - msg_warn("%s is true, but EAI support is not compiled in", - VAR_SMTPUTF8_ENABLE); -#endif /* * Variables whose defaults are determined at runtime, after other diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 4ae37622f..8d419e0fc 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1396,6 +1396,12 @@ extern bool var_smtp_enforce_tls; #define DEF_LMTP_TLS_ENFORCE_PN 1 extern bool var_smtp_tls_enforce_peername; +#define VAR_SMTP_TLS_WRAPPER "smtp_tls_wrappermode" +#define DEF_SMTP_TLS_WRAPPER 0 +#define VAR_LMTP_TLS_WRAPPER "lmtp_tls_wrappermode" +#define DEF_LMTP_TLS_WRAPPER 0 +extern bool var_smtp_tls_wrappermode; + #define VAR_SMTP_TLS_LEVEL "smtp_tls_security_level" #define DEF_SMTP_TLS_LEVEL "" #define VAR_LMTP_TLS_LEVEL "lmtp_tls_security_level" @@ -1874,6 +1880,10 @@ extern int var_virt_recur_limit; #define DEF_VIRT_EXPAN_LIMIT 1000 extern int var_virt_expan_limit; +#define VAR_VIRT_ADDRLEN_LIMIT "virtual_alias_address_length_limit" +#define DEF_VIRT_ADDRLEN_LIMIT 1000 +extern int var_virt_addrlen_limit; + /* * Message/queue size limits. */ diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 3fd05a693..14f2f9bb1 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20141228" +#define MAIL_RELEASE_DATE "20150117" #define MAIL_VERSION_NUMBER "2.12" #ifdef SNAPSHOT diff --git a/postfix/src/global/match_parent_style.c b/postfix/src/global/match_parent_style.c index b07a4fdcd..c3c9db66f 100644 --- a/postfix/src/global/match_parent_style.c +++ b/postfix/src/global/match_parent_style.c @@ -60,7 +60,8 @@ int match_parent_style(const char *name) */ if (match_par_dom_list == 0) match_par_dom_list = - string_list_init(MATCH_FLAG_NONE, var_par_dom_match); + string_list_init(VAR_PAR_DOM_MATCH, MATCH_FLAG_NONE, + var_par_dom_match); /* * Look up the parent domain matching policy. diff --git a/postfix/src/global/midna_adomain.c b/postfix/src/global/midna_adomain.c new file mode 100644 index 000000000..81c98d453 --- /dev/null +++ b/postfix/src/global/midna_adomain.c @@ -0,0 +1,119 @@ +/*++ +/* NAME +/* midna_adomain 3 +/* SUMMARY +/* address domain part conversion +/* SYNOPSIS +/* #include +/* +/* char *midna_adomain_to_ascii( +/* VSTRING *dest, +/* const char *name) +/* +/* char *midna_adomain_to_utf8( +/* VSTRING *dest, +/* const char *name) +/* DESCRIPTION +/* The functions in this module transform the domain portion +/* of an email address between ASCII and UTF-8 form. Both +/* functions tolerate a missing domain, and both functions +/* return a copy of the input when the domain portion requires +/* no conversion. +/* +/* midna_adomain_to_ascii() converts an UTF-8 or ASCII domain +/* portion to ASCII. The result is a null pointer when +/* conversion fails. This function verifies that the resulting +/* domain passes valid_hostname(). +/* +/* midna_adomain_to_utf8() converts an UTF-8 or ASCII domain +/* name to UTF-8. The result is a null pointer when conversion +/* fails. This function verifies that the resulting domain, +/* after conversion to ASCII, passes valid_hostname(). +/* SEE ALSO +/* midna_domain(3), Postfix ASCII/UTF-8 domain name conversion +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: conversion error or result validation error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include +#include + +#ifndef NO_EAI +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + +#define STR(x) vstring_str(x) + +/* midna_adomain_to_utf8 - convert address domain portion to UTF8 */ + +char *midna_adomain_to_utf8(VSTRING *dest, const char *src) +{ + const char *cp; + const char *domain_utf8; + + if ((cp = strrchr(src, '@')) == 0) { + vstring_strcpy(dest, src); + } else { + vstring_sprintf(dest, "%*s@", (int) (cp - src), src); + if (*(cp += 1)) { + if (allascii(cp) && strstr(cp, "--") == 0) { + vstring_strcat(dest, cp); + } else if ((domain_utf8 = midna_domain_to_utf8(cp)) == 0) { + return (0); + } else { + vstring_strcat(dest, domain_utf8); + } + } + } + return (STR(dest)); +} + +/* midna_adomain_to_ascii - convert address domain portion to ASCII */ + +char *midna_adomain_to_ascii(VSTRING *dest, const char *src) +{ + const char *cp; + const char *domain_ascii; + + if ((cp = strrchr(src, '@')) == 0) { + vstring_strcpy(dest, src); + } else { + vstring_sprintf(dest, "%*s@", (int) (cp - src), src); + if (*(cp += 1)) { + if (allascii(cp)) { + vstring_strcat(dest, cp); + } else if ((domain_ascii = midna_domain_to_ascii(cp + 1)) == 0) { + return (0); + } else { + vstring_strcat(dest, domain_ascii); + } + } + } + return (STR(dest)); +} + +#endif /* NO_IDNA */ diff --git a/postfix/src/global/midna_adomain.h b/postfix/src/global/midna_adomain.h new file mode 100644 index 000000000..14f02fefa --- /dev/null +++ b/postfix/src/global/midna_adomain.h @@ -0,0 +1,36 @@ +#ifndef _MIDNA_ADOMAIN_H_INCLUDED_ +#define _MIDNA_ADOMAIN_H_INCLUDED_ + +/*++ +/* NAME +/* midna_adomain 3h +/* SUMMARY +/* domain name conversion +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern char *midna_adomain_to_utf8(VSTRING *, const char *); +extern char *midna_adomain_to_ascii(VSTRING *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/postfix/src/global/mkmap_open.c b/postfix/src/global/mkmap_open.c index 9374f8b45..9d15eec30 100644 --- a/postfix/src/global/mkmap_open.c +++ b/postfix/src/global/mkmap_open.c @@ -99,6 +99,7 @@ #include #include #include +#include /* Global library. */ @@ -295,6 +296,13 @@ MKMAP *mkmap_open(const char *type, const char *path, if (mkmap->after_open) mkmap->after_open(mkmap); + /* + * Wrap the dictionary for UTF-8 syntax checks and casefolding. + */ + if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)) + mkmap->dict = dict_utf8_activate(mkmap->dict); + /* * Resume signal delivery if multi-writer safe. */ diff --git a/postfix/src/global/namadr_list.c b/postfix/src/global/namadr_list.c index fc7f681ed..1be7d54c9 100644 --- a/postfix/src/global/namadr_list.c +++ b/postfix/src/global/namadr_list.c @@ -89,12 +89,13 @@ #ifdef TEST -#include #include #include +#include #include #include #include +#include /* util_utf8_enable */ static void usage(char *progname) { @@ -122,7 +123,9 @@ int main(int argc, char **argv) if (argc != optind + 3) usage(argv[0]); dict_allow_surrogate = 1; - list = namadr_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]); + util_utf8_enable = 1; + list = namadr_list_init("command line", MATCH_FLAG_PARENT + | MATCH_FLAG_RETURN, argv[optind]); host = argv[optind + 1]; addr = argv[optind + 2]; vstream_printf("%s/%s: %s\n", host, addr, diff --git a/postfix/src/global/namadr_list.h b/postfix/src/global/namadr_list.h index b2322edf8..e327784a3 100644 --- a/postfix/src/global/namadr_list.h +++ b/postfix/src/global/namadr_list.h @@ -21,8 +21,8 @@ */ #define NAMADR_LIST MATCH_LIST -#define namadr_list_init(f, p) \ - match_list_init((f), (p), 2, match_hostname, match_hostaddr) +#define namadr_list_init(o, f, p) \ + match_list_init((o), (f), (p), 2, match_hostname, match_hostaddr) #define namadr_list_match match_list_match #define namadr_list_free match_list_free diff --git a/postfix/src/global/namadr_list.ref b/postfix/src/global/namadr_list.ref index b21fe54ea..7df05be91 100644 --- a/postfix/src/global/namadr_list.ref +++ b/postfix/src/global/namadr_list.ref @@ -2,18 +2,18 @@ dummy/168.100.189.2: YES dummy/168.100.189.2: NO dummy/168.100.189.3: YES dummy/168.100.189.16: NO -./namadr_list: warning: bad net/mask pattern: "168.100.189.0/98" +./namadr_list: warning: command line: bad net/mask pattern: "168.100.189.0/98" dummy/168.100.189.16: ERROR -./namadr_list: warning: bad net/mask pattern: "168.100.589.0/28" +./namadr_list: warning: command line: bad net/mask pattern: "168.100.589.0/28" dummy/168.100.189.16: ERROR dummy/168.100.989.16: NO ./namadr_list: error: unsupported dictionary type: 2001 ./namadr_list: warning: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 is unavailable. unsupported dictionary type: 2001 -./namadr_list: warning: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7: table lookup problem +./namadr_list: warning: command line: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7: table lookup problem dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: ERROR dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: YES dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: NO -./namadr_list: warning: non-null host address bits in "2001:240:5c7:0:2d0:b7ff:fe88:2ca7/64", perhaps you should use "2001:240:5c7::/64" instead +./namadr_list: warning: command line: non-null host address bits in "2001:240:5c7:0:2d0:b7ff:fe88:2ca7/64", perhaps you should use "2001:240:5c7::/64" instead dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: ERROR dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: YES dummy/2001:24:5c7:0:2d0:b7ff:fe88:2ca8: NO @@ -32,10 +32,10 @@ foo/168.100.189.3: YES bar/168.100.189.3: NO baz/168.100.189.3: YES x.x.x/127.0.0.1: NO -./namadr_list: warning: bad net/mask pattern: "be/be" +./namadr_list: warning: command line: bad net/mask pattern: "be/be" x.x.x/127.0.0.1: ERROR x.x.x/127.0.0.1: NO -./namadr_list: warning: bad address pattern: "be:be" +./namadr_list: warning: command line: bad address pattern: "be:be" x.x.x/::1: ERROR foo/168.100.189.3: YES bar/168.100.189.3: NO @@ -43,11 +43,11 @@ foo/168.100.189.3: NO bar/168.100.189.3: NO foo/168.100.189.3: YES bar/168.100.189.3: NO -./namadr_list: warning: fail:1: table lookup problem +./namadr_list: warning: command line: fail:1: table lookup problem bar/168.100.189.3: ERROR -./namadr_list: warning: fail:1: table lookup problem +./namadr_list: warning: command line: fail:1: table lookup problem bar/168.100.189.3: ERROR ./namadr_list: error: open file /tmp/nosuchfile: No such file or directory ./namadr_list: warning: non-existent:/tmp/nosuchfile is unavailable. open file /tmp/nosuchfile: No such file or directory -./namadr_list: warning: non-existent:/tmp/nosuchfile: table lookup problem +./namadr_list: warning: command line: non-existent:/tmp/nosuchfile: table lookup problem bar/168.100.189.3: ERROR diff --git a/postfix/src/global/resolve_local.c b/postfix/src/global/resolve_local.c index 98d04b2a8..c6ef84808 100644 --- a/postfix/src/global/resolve_local.c +++ b/postfix/src/global/resolve_local.c @@ -69,7 +69,8 @@ void resolve_local_init(void) /* Allow on-the-fly update to make testing easier. */ if (resolve_local_list) string_list_free(resolve_local_list); - resolve_local_list = string_list_init(MATCH_FLAG_RETURN, var_mydest); + resolve_local_list = string_list_init(VAR_MYDEST, MATCH_FLAG_RETURN, + var_mydest); } /* resolve_local - match domain against list of local destinations */ diff --git a/postfix/src/global/resolve_local.ref b/postfix/src/global/resolve_local.ref index ce46b616f..e77146999 100644 --- a/postfix/src/global/resolve_local.ref +++ b/postfix/src/global/resolve_local.ref @@ -1,6 +1,6 @@ mydestination=example.com destination=example.com YES mydestination=example.net destination=example.com NO -unknown: warning: fail:1_resolve_local: table lookup problem +unknown: warning: mydestination: fail:1_resolve_local: table lookup problem mydestination=fail:1_resolve_local destination=example.com ERROR mydestination=fail:1_resolve_local destination=example.com.. NO mydestination=fail:1_resolve_local destination= NO diff --git a/postfix/src/global/server_acl.c b/postfix/src/global/server_acl.c index 7605e5f59..daa2c3ec2 100644 --- a/postfix/src/global/server_acl.c +++ b/postfix/src/global/server_acl.c @@ -102,12 +102,12 @@ void server_acl_pre_jail_init(const char *mynetworks, const char *origin) addr_match_list_free(server_acl_mynetworks_host); } server_acl_mynetworks = - addr_match_list_init(MATCH_FLAG_RETURN | match_parent_style(origin), - mynetworks); + addr_match_list_init(origin, MATCH_FLAG_RETURN + | match_parent_style(origin), mynetworks); if (warn_compat_break_mynetworks_style) server_acl_mynetworks_host = - addr_match_list_init(MATCH_FLAG_RETURN | match_parent_style(origin), - mynetworks_host()); + addr_match_list_init(origin, MATCH_FLAG_RETURN + | match_parent_style(origin), mynetworks_host()); } /* server_acl_parse - parse access list */ @@ -138,7 +138,8 @@ SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin) } else { if (dict_handle(acl) == 0) dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX)); + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST)); } } argv_add(intern_acl, acl, (char *) 0); @@ -278,7 +279,7 @@ int main(void) } else if (STREQ(cmd, VAR_SERVER_ACL)) { UPDATE_VAR(var_server_acl, value); } else if (STREQ(cmd, "address")) { - server_acl_pre_jail_init(var_mynetworks, VAR_SERVER_ACL); + server_acl_pre_jail_init(var_mynetworks, VAR_MYNETWORKS); argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL); ret = server_acl_eval(value, argv, VAR_SERVER_ACL); argv_free(argv); diff --git a/postfix/src/global/server_acl.ref b/postfix/src/global/server_acl.ref index 442fda128..b70f3c654 100644 --- a/postfix/src/global/server_acl.ref +++ b/postfix/src/global/server_acl.ref @@ -9,7 +9,7 @@ 168.100.189.3: permit > mynetworks=fail:1 > address=168.100.189.4 -unknown: warning: fail:1: table lookup problem +unknown: warning: mynetworks: fail:1: table lookup problem unknown: warning: server_acl: permit_mynetworks: mynetworks lookup error -- ignoring the remainder of this access list 168.100.189.4: error > server_acl=fail:1,reject diff --git a/postfix/src/global/string_list.c b/postfix/src/global/string_list.c index a24e4e179..076556783 100644 --- a/postfix/src/global/string_list.c +++ b/postfix/src/global/string_list.c @@ -75,12 +75,14 @@ #ifdef TEST -#include #include #include +#include #include #include #include +#include +#include /* util_utf8_enable */ static void usage(char *progname) { @@ -106,7 +108,9 @@ int main(int argc, char **argv) } if (argc != optind + 2) usage(argv[0]); - list = string_list_init(MATCH_FLAG_RETURN, argv[optind]); + dict_allow_surrogate = 1; + util_utf8_enable = 1; + list = string_list_init("command line", MATCH_FLAG_RETURN, argv[optind]); string = argv[optind + 1]; vstream_printf("%s: %s\n", string, string_list_match(list, string) ? "YES" : list->error == 0 ? "NO" : "ERROR"); diff --git a/postfix/src/global/string_list.h b/postfix/src/global/string_list.h index 0a96bacfe..1079a7623 100644 --- a/postfix/src/global/string_list.h +++ b/postfix/src/global/string_list.h @@ -21,7 +21,8 @@ */ #define STRING_LIST MATCH_LIST -#define string_list_init(f, p) match_list_init((f), (p), 1, match_string) +#define string_list_init(o, f, p) \ + match_list_init((o), (f), (p), 1, match_string) #define string_list_match match_list_match #define string_list_free match_list_free diff --git a/postfix/src/global/tok822_tree.c b/postfix/src/global/tok822_tree.c index 16cec946a..a66cf1156 100644 --- a/postfix/src/global/tok822_tree.c +++ b/postfix/src/global/tok822_tree.c @@ -209,6 +209,7 @@ TOK822 *tok822_sub_append(TOK822 *t1, TOK822 *t2) return (t1->tail = tok822_append(t1->tail, t2)); } else { t1->head = t2; + t2->owner = t1; while (t2->next) (t2 = t2->next)->owner = t1; return (t1->tail = t2); @@ -227,6 +228,7 @@ TOK822 *tok822_sub_prepend(TOK822 *t1, TOK822 *t2) return (tp); } else { t1->head = t2; + t2->owner = t1; while (t2->next) (t2 = t2->next)->owner = t1; return (t1->tail = t2); @@ -259,11 +261,12 @@ TOK822 *tok822_sub_keep_after(TOK822 *t1, TOK822 *t2) TOK822 *tok822_free_tree(TOK822 *tp) { - if (tp) { - if (tp->next) - tok822_free_tree(tp->next); + TOK822 *next; + + for (/* void */; tp != 0; tp = next) { if (tp->head) tok822_free_tree(tp->head); + next = tp->next; tok822_free(tp); } return (0); diff --git a/postfix/src/global/user_acl.c b/postfix/src/global/user_acl.c index 9dd19c0f7..ecdde9f15 100644 --- a/postfix/src/global/user_acl.c +++ b/postfix/src/global/user_acl.c @@ -6,7 +6,8 @@ /* SYNOPSIS /* #include /* -/* const char *check_user_acl_byuid(acl, uid) +/* const char *check_user_acl_byuid(pname, acl, uid) +/* cobnst char *pname; /* const char *acl; /* uid_t uid; /* DESCRIPTION @@ -20,6 +21,8 @@ /* calls. /* /* Arguments: +/* .IP pname +/* The parameter name of the acl. /* .IP acl /* Authorized user name list suitable for input to string_list_init(3). /* .IP uid @@ -59,7 +62,7 @@ /* check_user_acl_byuid - check user authorization */ -const char *check_user_acl_byuid(char *acl, uid_t uid) +const char *check_user_acl_byuid(const char *pname, const char *acl, uid_t uid) { struct mypasswd *mypwd; STRING_LIST *list; @@ -101,7 +104,7 @@ const char *check_user_acl_byuid(char *acl, uid_t uid) name = mypwd->pw_name; } - list = string_list_init(MATCH_FLAG_NONE, acl); + list = string_list_init(pname, MATCH_FLAG_NONE, acl); if ((matched = string_list_match(list, name)) == 0) { if (!who) who = vstring_alloc(10); diff --git a/postfix/src/global/user_acl.h b/postfix/src/global/user_acl.h index 8a9afb2b7..4e36fbfcb 100644 --- a/postfix/src/global/user_acl.h +++ b/postfix/src/global/user_acl.h @@ -25,7 +25,7 @@ /* * External interface */ -extern const char *check_user_acl_byuid(char *, uid_t); +extern const char *check_user_acl_byuid(const char *, const char *, uid_t); /* AUTHOR(S) /* Wietse Venema diff --git a/postfix/src/local/local.c b/postfix/src/local/local.c index 8a517a061..bd77fbc9d 100644 --- a/postfix/src/local/local.c +++ b/postfix/src/local/local.c @@ -865,7 +865,8 @@ static void pre_init(char *unused_name, char **unused_argv) } alias_maps = maps_create("aliases", var_alias_maps, DICT_FLAG_LOCK | DICT_FLAG_PARANOID - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); flush_init(); } diff --git a/postfix/src/local/mailbox.c b/postfix/src/local/mailbox.c index b46018e9e..887333c62 100644 --- a/postfix/src/local/mailbox.c +++ b/postfix/src/local/mailbox.c @@ -277,7 +277,8 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) */ if (*var_mbox_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, - DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB); + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); /* The -1 is a hint for the down-stream deliver_completed() function. */ if (transp_maps && (map_transport = maps_find(transp_maps, state.msg_attr.user, @@ -332,10 +333,11 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) if (*var_mailbox_cmd_maps && cmd_maps == 0) cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, - DICT_FLAG_LOCK | DICT_FLAG_PARANOID); + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, - DICT_FLAG_NONE)) != 0) { + DICT_FLAG_NONE)) != 0) { status = deliver_command(state, usr_attr, map_command); } else if (cmd_maps && cmd_maps->error != 0) { /* Details in the logfile. */ diff --git a/postfix/src/local/unknown.c b/postfix/src/local/unknown.c index 9fbe6ea5b..733aa1350 100644 --- a/postfix/src/local/unknown.c +++ b/postfix/src/local/unknown.c @@ -109,7 +109,8 @@ int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr) */ if (*var_fbck_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps, - DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB); + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); /* The -1 is a hint for the down-stream deliver_completed() function. */ if (transp_maps && (map_transport = maps_find(transp_maps, state.msg_attr.user, diff --git a/postfix/src/postalias/postalias.c b/postfix/src/postalias/postalias.c index 633bdf770..7826725c6 100644 --- a/postfix/src/postalias/postalias.c +++ b/postfix/src/postalias/postalias.c @@ -5,7 +5,7 @@ /* Postfix alias database maintenance /* SYNOPSIS /* .fi -/* \fBpostalias\fR [\fB-Nfinoprsvw\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostalias\fR [\fB-Nfinoprsuvw\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION @@ -93,6 +93,10 @@ /* as the original input order. /* This feature is available in Postfix version 2.2 and later, /* and is not available for all database types. +/* .IP \fB-u\fR +/* Disable UTF-8 support. UTF-8 support is enabled by default +/* when "smtputf8_enable = yes". It requires that keys and +/* values are valid UTF-8 strings. /* .IP \fB-v\fR /* Enable verbose logging for debugging purposes. Multiple \fB-v\fR /* options make the software increasingly verbose. @@ -176,6 +180,9 @@ /* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" /* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) /* and \fBpostmap\fR(1) commands. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable experimental SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. /* .IP "\fBsyslog_facility (mail)\fR" /* The syslog facility of Postfix logging. /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" @@ -249,6 +256,7 @@ /* Application-specific. */ #define STR vstring_str +#define LEN VSTRING_LEN #define POSTALIAS_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */ #define POSTALIAS_FLAG_SAVE_PERM (1<<1) /* copy access permission @@ -309,7 +317,6 @@ static void postalias(char *map_type, char *path_name, int postalias_flags, && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); - /* * Open the database, create it when it does not exist, truncate it when * it does exist, and lock out any spectators. @@ -338,6 +345,17 @@ static void postalias(char *map_type, char *path_name, int postalias_flags, last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { + /* + * First some UTF-8 checks sans casefolding. + */ + if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) + && !allascii(STR(line_buffer)) + && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } + /* * Tokenize the input, so that we do the right thing when a * quoted localpart contains special characters such as "@", ":" @@ -655,7 +673,7 @@ static void postalias_seq(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-Nfinoprsvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-Nfinoprsuvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", myname); } @@ -670,7 +688,8 @@ int main(int argc, char **argv) struct stat st; int postalias_flags = POSTALIAS_FLAG_AS_OWNER | POSTALIAS_FLAG_SAVE_PERM; int open_flags = O_RDWR | O_CREAT | O_TRUNC; - int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX; + int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); char *query = 0; char *delkey = 0; int sequence = 0; @@ -720,7 +739,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rsvw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rsuvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -768,6 +787,9 @@ int main(int argc, char **argv) msg_fatal("specify only one of -s or -q or -d"); sequence = 1; break; + case 'u': + dict_flags &= ~DICT_FLAG_UTF8_REQUEST; + break; case 'v': msg_verbose++; break; diff --git a/postfix/src/postconf/postconf_master.c b/postfix/src/postconf/postconf_master.c index 8445edcf4..4842d1a8e 100644 --- a/postfix/src/postconf/postconf_master.c +++ b/postfix/src/postconf/postconf_master.c @@ -321,7 +321,7 @@ static void pcf_check_master_entry(ARGV *argv, const char *raw_text) for (field = PCF_MASTER_FLD_PRIVATE; field <= PCF_MASTER_FLD_CHROOT; field++) { cp = argv->argv[field]; if (cp[1] != 0 || strchr(pcf_valid_bool_types, *cp) == 0) - pcf_fix_fatal("invalid %s field \%s\" in \"%s\"", + pcf_fix_fatal("invalid %s field \"%s\" in \"%s\"", pcf_str_field_pattern(field), cp, raw_text); } @@ -330,12 +330,12 @@ static void pcf_check_master_entry(ARGV *argv, const char *raw_text) if (len > 0 && cp[len - 1] == '?') len--; if (!(cp[0] == '-' && len == 1) && strspn(cp, "0123456789") != len) - pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \%s\" in \"%s\"", + pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \"%s\" in \"%s\"", cp, raw_text); cp = argv->argv[PCF_MASTER_FLD_MAXPROC]; if (strcmp("-", cp) != 0 && cp[strspn(cp, "0123456789")] != 0) - pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \%s\" in \"%s\"", + pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \"%s\" in \"%s\"", cp, raw_text); } diff --git a/postfix/src/postdrop/postdrop.c b/postfix/src/postdrop/postdrop.c index 9c435fbf5..1a4096c7c 100644 --- a/postfix/src/postdrop/postdrop.c +++ b/postfix/src/postdrop/postdrop.c @@ -315,7 +315,8 @@ int main(int argc, char **argv) * or in the daemon process? */ mail_dict_init(); - if ((errstr = check_user_acl_byuid(var_submit_acl, uid)) != 0) + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, + uid)) != 0) msg_fatal("User %s(%ld) is not allowed to submit mail", errstr, (long) uid); diff --git a/postfix/src/postmap/postmap.c b/postfix/src/postmap/postmap.c index af3e5a440..b1e0eab2d 100644 --- a/postfix/src/postmap/postmap.c +++ b/postfix/src/postmap/postmap.c @@ -5,7 +5,7 @@ /* Postfix lookup table management /* SYNOPSIS /* .fi -/* \fBpostmap\fR [\fB-Nbfhimnoprsvw\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostmap\fR [\fB-NbfhimnoprsuUvw\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION @@ -71,6 +71,11 @@ /* generates no body-style lookup keys for attachment MIME /* headers and for attached message/* headers. /* .sp +/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +/* option disables UTF-8 syntax checks on query keys and +/* lookup results. Specify the \fB-U\fR option to force UTF-8 +/* syntax checks anyway. +/* .sp /* This feature is available in Postfix version 2.6 and later. /* .IP "\fB-c \fIconfig_dir\fR" /* Read the \fBmain.cf\fR configuration file in the named directory @@ -104,6 +109,11 @@ /* generates header-style lookup keys for attachment MIME /* headers and for attached message/* headers. /* .sp +/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +/* option disables UTF-8 syntax checks on query keys and +/* lookup results. Specify the \fB-U\fR option to force UTF-8 +/* syntax checks anyway. +/* .sp /* This feature is available in Postfix version 2.6 and later. /* .IP \fB-i\fR /* Incremental mode. Read entries from standard input and do not @@ -151,6 +161,13 @@ /* .sp /* This feature is available in Postfix version 2.2 and later, /* and is not available for all database types. +/* .IP \fB-u\fR +/* Disable UTF-8 support. UTF-8 support is enabled by default +/* when "smtputf8_enable = yes". It requires that keys and +/* values are valid UTF-8 strings. +/* .IP \fB-U\fR +/* With "smtputf8_enable = yes", force UTF-8 syntax checks +/* with the \fB-b\fR and \fB-h\fR options. /* .IP \fB-v\fR /* Enable verbose logging for debugging purposes. Multiple \fB-v\fR /* options make the software increasingly verbose. @@ -229,6 +246,9 @@ /* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" /* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) /* and \fBpostmap\fR(1) commands. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable experimental SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. /* .IP "\fBsyslog_facility (mail)\fR" /* The syslog facility of Postfix logging. /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" @@ -401,6 +421,17 @@ static void postmap(char *map_type, char *path_name, int postmap_flags, last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { + /* + * First some UTF-8 checks sans casefolding. + */ + if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) + && !allascii(STR(line_buffer)) + && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } + /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. @@ -769,7 +800,7 @@ static void postmap_seq(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-Nfinoprsvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-NfinoprsuUvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", myname); } @@ -784,11 +815,13 @@ int main(int argc, char **argv) struct stat st; int postmap_flags = POSTMAP_FLAG_AS_OWNER | POSTMAP_FLAG_SAVE_PERM; int open_flags = O_RDWR | O_CREAT | O_TRUNC; - int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX; + int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); char *query = 0; char *delkey = 0; int sequence = 0; int found; + int force_utf8 = 0; /* * Fingerprint executables and core dumps. @@ -834,7 +867,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "Nbc:d:fhimnopq:rsvw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nbc:d:fhimnopq:rsuUvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -891,6 +924,12 @@ int main(int argc, char **argv) msg_fatal("specify only one of -s or -q or -d"); sequence = 1; break; + case 'u': + dict_flags &= ~DICT_FLAG_UTF8_REQUEST; + break; + case 'U': + force_utf8 = 1; + break; case 'v': msg_verbose++; break; @@ -911,6 +950,9 @@ int main(int argc, char **argv) && (postmap_flags & POSTMAP_FLAG_ANY_KEY) == (postmap_flags & POSTMAP_FLAG_MIME_KEY)) msg_warn("ignoring -m option without -b or -h"); + if ((postmap_flags & (POSTMAP_FLAG_ANY_KEY & ~POSTMAP_FLAG_MIME_KEY)) + && force_utf8 == 0) + dict_flags &= ~DICT_FLAG_UTF8_MASK; /* * Use the map type specified by the user, or fall back to a default diff --git a/postfix/src/postqueue/postqueue.c b/postfix/src/postqueue/postqueue.c index caa0e059b..c90285db7 100644 --- a/postfix/src/postqueue/postqueue.c +++ b/postfix/src/postqueue/postqueue.c @@ -270,7 +270,8 @@ static void show_queue(void) uid_t uid = getuid(); if (uid != 0 && uid != var_owner_uid - && (errstr = check_user_acl_byuid(var_showq_acl, uid)) != 0) + && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl, + uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to view the mail queue", errstr, (long) uid); @@ -344,7 +345,8 @@ static void flush_queue(void) uid_t uid = getuid(); if (uid != 0 && uid != var_owner_uid - && (errstr = check_user_acl_byuid(var_flush_acl, uid)) != 0) + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to flush the mail queue", errstr, (long) uid); @@ -370,7 +372,8 @@ static void flush_site(const char *site) uid_t uid = getuid(); if (uid != 0 && uid != var_owner_uid - && (errstr = check_user_acl_byuid(var_flush_acl, uid)) != 0) + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to flush the mail queue", errstr, (long) uid); @@ -404,7 +407,8 @@ static void flush_file(const char *queue_id) uid_t uid = getuid(); if (uid != 0 && uid != var_owner_uid - && (errstr = check_user_acl_byuid(var_flush_acl, uid)) != 0) + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to flush the mail queue", errstr, (long) uid); diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c index d7dbaaa94..e4bf8e486 100644 --- a/postfix/src/postscreen/postscreen.c +++ b/postfix/src/postscreen/postscreen.c @@ -852,7 +852,8 @@ static void pre_jail_init(char *unused_name, char **unused_argv) psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL); /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ if (*var_psc_forbid_cmds) - psc_forbid_cmds = string_list_init(MATCH_FLAG_RETURN, + psc_forbid_cmds = string_list_init(VAR_PSC_FORBID_CMDS, + MATCH_FLAG_RETURN, var_psc_forbid_cmds); if (*var_psc_dnsbl_reply) psc_dnsbl_reply = dict_open(var_psc_dnsbl_reply, O_RDONLY, @@ -998,7 +999,8 @@ static void post_jail_init(char *unused_name, char **unused_argv) msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION, var_psc_barlf_action); /* Fail "closed" on error. */ - psc_wlist_if = addr_match_list_init(MATCH_FLAG_RETURN, var_psc_wlist_if); + psc_wlist_if = addr_match_list_init(VAR_PSC_WLIST_IF, MATCH_FLAG_RETURN, + var_psc_wlist_if); /* * Start the cache maintenance pseudo thread last. Early cleanup makes diff --git a/postfix/src/posttls-finger/Makefile.in b/postfix/src/posttls-finger/Makefile.in index 02b4261ab..e693e4795 100644 --- a/postfix/src/posttls-finger/Makefile.in +++ b/postfix/src/posttls-finger/Makefile.in @@ -74,7 +74,7 @@ posttls-finger.o: ../../include/iostuff.h posttls-finger.o: ../../include/mail_conf.h posttls-finger.o: ../../include/mail_params.h posttls-finger.o: ../../include/mail_server.h -posttls-finger.o: ../../include/midna.h +posttls-finger.o: ../../include/midna_domain.h posttls-finger.o: ../../include/msg.h posttls-finger.o: ../../include/msg_vstream.h posttls-finger.o: ../../include/myaddrinfo.h diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c index ac4bc8770..8be46e40b 100644 --- a/postfix/src/posttls-finger/posttls-finger.c +++ b/postfix/src/posttls-finger/posttls-finger.c @@ -323,7 +323,7 @@ #include #include #include -#include +#include #define STR(x) vstring_str(x) @@ -1103,7 +1103,7 @@ static DNS_RR *domain_addr(STATE *state, char *domain) * IDNA support. */ #ifndef NO_EAI - if (!allascii(domain) && (aname = midna_to_ascii(domain)) != 0) { + if (!allascii(domain) && (aname = midna_domain_to_ascii(domain)) != 0) { msg_info("%s asciified to %s", domain, aname); } else #endif @@ -1168,7 +1168,7 @@ static DNS_RR *host_addr(STATE *state, const char *host) * IDNA support. */ #ifndef NO_EAI - if (!allascii(host) && (ahost = midna_to_ascii(host)) != 0) { + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { msg_info("%s asciified to %s", host, ahost); } else #endif diff --git a/postfix/src/proxymap/proxymap.c b/postfix/src/proxymap/proxymap.c index 416f523cb..262aeeb14 100644 --- a/postfix/src/proxymap/proxymap.c +++ b/postfix/src/proxymap/proxymap.c @@ -382,7 +382,8 @@ static void proxymap_sequence_service(VSTREAM *client_stream) reply_status = PROXY_STAT_NOKEY; reply_key = reply_value = ""; } else { - reply_status = PROXY_STAT_RETRY; + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); reply_key = reply_value = ""; } } @@ -427,7 +428,8 @@ static void proxymap_lookup_service(VSTREAM *client_stream) reply_status = PROXY_STAT_NOKEY; reply_value = ""; } else { - reply_status = PROXY_STAT_RETRY; + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); reply_value = ""; } @@ -482,7 +484,8 @@ static void proxymap_update_service(VSTREAM *client_stream) } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; } else { - reply_status = PROXY_STAT_RETRY; + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); } } @@ -532,7 +535,8 @@ static void proxymap_delete_service(VSTREAM *client_stream) } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; } else { - reply_status = PROXY_STAT_RETRY; + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); } } diff --git a/postfix/src/qmqpd/qmqpd.c b/postfix/src/qmqpd/qmqpd.c index d1864ab2d..2f1347187 100644 --- a/postfix/src/qmqpd/qmqpd.c +++ b/postfix/src/qmqpd/qmqpd.c @@ -785,7 +785,7 @@ static void pre_jail_init(char *unused_name, char **unused_argv) { debug_peer_init(); qmqpd_clients = - namadr_list_init(MATCH_FLAG_RETURN + namadr_list_init(VAR_QMQPD_CLIENTS, MATCH_FLAG_RETURN | match_parent_style(VAR_QMQPD_CLIENTS), var_qmqpd_clients); } diff --git a/postfix/src/sendmail/sendmail.c b/postfix/src/sendmail/sendmail.c index f8ce7dfdb..4a91aa48e 100644 --- a/postfix/src/sendmail/sendmail.c +++ b/postfix/src/sendmail/sendmail.c @@ -646,7 +646,8 @@ static void enqueue(const int flags, const char *encoding, * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ - if ((errstr = check_user_acl_byuid(var_submit_acl, uid)) != 0) + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, + var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); @@ -1404,7 +1405,8 @@ int main(int argc, char **argv) msg_fatal_status(EX_USAGE, "stand-alone mode requires no recipient"); /* The actual enforcement happens in the postdrop command. */ - if ((errstr = check_user_acl_byuid(var_submit_acl, uid = getuid())) != 0) + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, + uid = getuid())) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); diff --git a/postfix/src/smtp/Makefile.in b/postfix/src/smtp/Makefile.in index bcbd2f2fa..7edc54ef7 100644 --- a/postfix/src/smtp/Makefile.in +++ b/postfix/src/smtp/Makefile.in @@ -154,7 +154,7 @@ smtp_addr.o: ../../include/inet_proto.h smtp_addr.o: ../../include/mail_params.h smtp_addr.o: ../../include/maps.h smtp_addr.o: ../../include/match_list.h -smtp_addr.o: ../../include/midna.h +smtp_addr.o: ../../include/midna_domain.h smtp_addr.o: ../../include/mime_state.h smtp_addr.o: ../../include/msg.h smtp_addr.o: ../../include/msg_stats.h @@ -380,12 +380,14 @@ smtp_proto.o: ../../include/mail_queue.h smtp_proto.o: ../../include/maps.h smtp_proto.o: ../../include/mark_corrupt.h smtp_proto.o: ../../include/match_list.h +smtp_proto.o: ../../include/match_parent_style.h smtp_proto.o: ../../include/mime_state.h smtp_proto.o: ../../include/msg.h smtp_proto.o: ../../include/msg_stats.h smtp_proto.o: ../../include/myaddrinfo.h smtp_proto.o: ../../include/myflock.h smtp_proto.o: ../../include/mymalloc.h +smtp_proto.o: ../../include/namadr_list.h smtp_proto.o: ../../include/name_code.h smtp_proto.o: ../../include/name_mask.h smtp_proto.o: ../../include/nvtable.h diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c index 9f405c4a4..5f47a2a9b 100644 --- a/postfix/src/smtp/lmtp_params.c +++ b/postfix/src/smtp/lmtp_params.c @@ -113,6 +113,7 @@ VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, #endif + VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth, VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr, VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index 977f8eb95..5692f5d65 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -464,6 +464,11 @@ /* RFC 6698 trust-anchor digest support in the Postfix TLS library. /* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" /* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 2.12 and later: +/* .IP "\fBsmtp_tls_wrappermode (no)\fR" +/* Request that the Postfix SMTP client connects using the +/* legacy SMTPS protocol instead of using the STARTTLS command. /* OBSOLETE STARTTLS CONTROLS /* .ad /* .fi @@ -850,6 +855,7 @@ bool var_smtp_use_tls; bool var_smtp_enforce_tls; char *var_smtp_tls_per_site; char *var_smtp_tls_policy; +bool var_smtp_tls_wrappermode; #ifdef USE_TLS char *var_smtp_sasl_tls_opts; @@ -1186,7 +1192,9 @@ static void pre_init(char *unused_name, char **unused_argv) * Session cache domain list. */ if (*var_smtp_cache_dest) - smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest); + smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST, + MATCH_FLAG_RETURN, + var_smtp_cache_dest); /* * EHLO keyword filter. @@ -1213,7 +1221,8 @@ static void pre_init(char *unused_name, char **unused_argv) if (*var_smtp_generic_maps) smtp_generic_maps = maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); /* * Header/body checks. diff --git a/postfix/src/smtp/smtp_addr.c b/postfix/src/smtp/smtp_addr.c index 7ac4607f4..d02b25319 100644 --- a/postfix/src/smtp/smtp_addr.c +++ b/postfix/src/smtp/smtp_addr.c @@ -85,7 +85,7 @@ #include #include #include -#include +#include /* Global library. */ @@ -378,7 +378,7 @@ DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, * IDNA support. */ #ifndef NO_EAI - if (!allascii(name) && (aname = midna_to_ascii(name)) != 0) { + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", name, aname); } else @@ -524,7 +524,7 @@ DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) * IDNA support. */ #ifndef NO_EAI - if (!allascii(host) && (ahost = midna_to_ascii(host)) != 0) { + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", host, ahost); } else diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index 02571d929..b81ead92a 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -820,9 +820,11 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, * specified, or when DNS lookups are disabled. */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); - if (var_helpful_warnings && ntohs(port) == 465) { - msg_info("CLIENT wrappermode (port smtps/465) is unimplemented"); - msg_info("instead, send to (port submission/587) with STARTTLS"); + if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 + && ntohs(port) == 465) { + msg_info("SMTPS wrappermode (TCP port 465) requires setting " + "\"%s = yes\", and \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); } #define NO_HOST "" /* safety */ #define NO_ADDR "" /* safety */ @@ -957,6 +959,13 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, continue; /* XXX Assume there is no code at the end of this loop. */ } + if (var_smtp_tls_wrappermode + && state->tls->level < TLS_LEV_ENCRYPT) { + msg_warn("%s requires \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { state->tls->level = TLS_LEV_NONE; diff --git a/postfix/src/smtp/smtp_map11.c b/postfix/src/smtp/smtp_map11.c index 96326ebe6..8579f915e 100644 --- a/postfix/src/smtp/smtp_map11.c +++ b/postfix/src/smtp/smtp_map11.c @@ -142,7 +142,9 @@ int main(int argc, char **argv) if (argc < 3) msg_fatal("usage: %s maptype:mapname address...", argv[0]); - maps = maps_create(argv[1], argv[1], DICT_FLAG_FOLD_FIX); + util_utf8_enable = 1; + maps = maps_create(argv[1], argv[1], DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); mail_params_init(); if (chdir(var_queue_dir) < 0) msg_fatal("chdir(%s): %m", var_queue_dir); diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c index cb66a6f27..234644137 100644 --- a/postfix/src/smtp/smtp_params.c +++ b/postfix/src/smtp/smtp_params.c @@ -117,6 +117,7 @@ VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, #endif + VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth, VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr, VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 9bf9926ff..e55110895 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -324,6 +324,20 @@ int smtp_helo(SMTP_STATE *state) #endif const char *NOCLOBBER where; + /* + * Skip the plaintext SMTP handshake when connecting in SMTPS mode. + */ +#ifdef USE_TLS + if (var_smtp_tls_wrappermode + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } +#endif + /* * Prepare for disaster. */ @@ -336,7 +350,8 @@ int smtp_helo(SMTP_STATE *state) * If not recursing after STARTTLS, examine the server greeting banner * and decide if we are going to send EHLO as the next command. */ - if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + if (var_smtp_tls_wrappermode + || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { /* * Read and parse the server's SMTP greeting banner. diff --git a/postfix/src/smtp/smtp_sasl_auth_cache.c b/postfix/src/smtp/smtp_sasl_auth_cache.c index 29ad938cb..b0ae35730 100644 --- a/postfix/src/smtp/smtp_sasl_auth_cache.c +++ b/postfix/src/smtp/smtp_sasl_auth_cache.c @@ -131,7 +131,7 @@ SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl) * dict_proxy module one level down in the build dependency hierachy. */ #define CACHE_DICT_OPEN_FLAGS \ - (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE) + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST) #define PROXY_COLON DICT_TYPE_PROXY ":" #define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c index 951369466..d2c1c3c59 100644 --- a/postfix/src/smtp/smtp_sasl_glue.c +++ b/postfix/src/smtp/smtp_sasl_glue.c @@ -234,9 +234,10 @@ void smtp_sasl_initialize(void) * Open the per-host password table and initialize the SASL library. Use * shared locks for reading, just in case someone updates the table. */ - smtp_sasl_passwd_map = maps_create("smtp_sasl_passwd", + smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD), var_smtp_sasl_passwd, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type, var_smtp_sasl_path)) == 0) msg_fatal("SASL library initialization"); @@ -245,7 +246,8 @@ void smtp_sasl_initialize(void) * Initialize optional supported mechanism matchlist */ if (*var_smtp_sasl_mechs) - smtp_sasl_mechs = string_list_init(MATCH_FLAG_NONE, + smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS, + MATCH_FLAG_NONE, var_smtp_sasl_mechs); /* @@ -258,7 +260,7 @@ void smtp_sasl_initialize(void) var_smtp_sasl_auth_cache_time); #else msg_warn("not compiled with TLS support -- " - "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); + "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); #endif } } diff --git a/postfix/src/smtp/smtp_tls_policy.c b/postfix/src/smtp/smtp_tls_policy.c index 07e3c4a6f..22b76ab49 100644 --- a/postfix/src/smtp/smtp_tls_policy.c +++ b/postfix/src/smtp/smtp_tls_policy.c @@ -132,7 +132,8 @@ void smtp_tls_list_init(void) if (*var_smtp_tls_policy) { tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY), var_smtp_tls_policy, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); if (*var_smtp_tls_per_site) msg_warn("%s ignored when %s is not empty.", VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY)); @@ -141,7 +142,8 @@ void smtp_tls_list_init(void) if (*var_smtp_tls_per_site) { tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE), var_smtp_tls_per_site, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); } } diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 13c6e6f09..4aee827e2 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -329,7 +329,7 @@ smtpd_check.o: ../../include/mail_stream.h smtpd_check.o: ../../include/maps.h smtpd_check.o: ../../include/match_list.h smtpd_check.o: ../../include/match_parent_style.h -smtpd_check.o: ../../include/midna.h +smtpd_check.o: ../../include/midna_domain.h smtpd_check.o: ../../include/milter.h smtpd_check.o: ../../include/msg.h smtpd_check.o: ../../include/msg_stats.h diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 35aae24b6..566bc3b2f 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -3653,7 +3653,8 @@ static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) * As an extension to RFC 1985 we also allow an RFC 2821 address literal * enclosed in []. * - * XXX EAI: Convert to ASCII and use that form internally. + * XXX There does not appear to be an ETRN parameter to indicate that the + * domain name is UTF-8. */ if (!valid_hostname(argv[1].strval, DONT_GRIPE) && !valid_mailhost_literal(argv[1].strval, DONT_GRIPE)) { @@ -4948,6 +4949,14 @@ static void smtpd_proto(SMTPD_STATE *state) } watchdog_pat(); smtpd_chat_query(state); + /* Safety: protect internal interfaces against malformed UTF-8. */ + if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer), + LEN(state->buffer)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax"); + state->error_count++; + continue; + } /* Move into smtpd_chat_query() and update session transcript. */ if (smtpd_cmd_filter != 0) { for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++) @@ -5237,12 +5246,18 @@ static void pre_jail_init(char *unused_name, char **unused_argv) * Initialize blacklist/etc. patterns before entering the chroot jail, in * case they specify a filename pattern. */ - smtpd_noop_cmds = string_list_init(MATCH_FLAG_RETURN, var_smtpd_noop_cmds); - smtpd_forbid_cmds = string_list_init(MATCH_FLAG_RETURN, var_smtpd_forbid_cmds); - verp_clients = namadr_list_init(MATCH_FLAG_RETURN, var_verp_clients); - xclient_hosts = namadr_list_init(MATCH_FLAG_RETURN, var_xclient_hosts); - xforward_hosts = namadr_list_init(MATCH_FLAG_RETURN, var_xforward_hosts); - hogger_list = namadr_list_init(MATCH_FLAG_RETURN + smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN, + var_smtpd_noop_cmds); + smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS, + MATCH_FLAG_RETURN, + var_smtpd_forbid_cmds); + verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN, + var_verp_clients); + xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN, + var_xclient_hosts); + xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN, + var_xforward_hosts); + hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN | match_parent_style(VAR_SMTPD_HOGGERS), var_smtpd_hoggers); @@ -5267,7 +5282,8 @@ static void pre_jail_init(char *unused_name, char **unused_argv) if (*var_smtpd_sasl_exceptions_networks) sasl_exceptions_networks = - namadr_list_init(MATCH_FLAG_RETURN, + namadr_list_init(VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, + MATCH_FLAG_RETURN, var_smtpd_sasl_exceptions_networks); #else msg_warn("%s is true, but SASL support is not compiled in", diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index fd114c0d1..5286ba86d 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -206,7 +206,7 @@ #include #include #include -#include +#include #include /* DNS library. */ @@ -607,7 +607,8 @@ static ARGV *smtpd_check_parse(int flags, const char *checks) else if ((flags & SMTPD_CHECK_PARSE_MAPS) && strchr(name, ':') && dict_handle(name) == 0) { dict_register(name, dict_open(name, O_RDONLY, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX)); + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST)); } last = name; } @@ -705,16 +706,17 @@ void smtpd_check_init(void) * Pre-open access control lists before going to jail. */ mynetworks_curr = - namadr_list_init(MATCH_FLAG_RETURN | match_parent_style(VAR_MYNETWORKS), - var_mynetworks); + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), var_mynetworks); mynetworks_new = - namadr_list_init(MATCH_FLAG_RETURN | match_parent_style(VAR_MYNETWORKS), - mynetworks_host()); + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), mynetworks_host()); relay_domains = - domain_list_init(match_parent_style(VAR_RELAY_DOMAINS), + domain_list_init(VAR_RELAY_DOMAINS, + match_parent_style(VAR_RELAY_DOMAINS), var_relay_domains); perm_mx_networks = - namadr_list_init(MATCH_FLAG_RETURN + namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN | match_parent_style(VAR_PERM_MX_NETWORKS), var_perm_mx_networks); #ifdef USE_TLS @@ -726,22 +728,30 @@ void smtpd_check_init(void) * Pre-parse and pre-open the recipient maps. */ local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); virt_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); relay_rcpt_maps = maps_create(VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); #ifdef TEST - virt_alias_doms = string_list_init(MATCH_FLAG_NONE, var_virt_alias_doms); - virt_mailbox_doms = string_list_init(MATCH_FLAG_NONE, var_virt_mailbox_doms); + virt_alias_doms = string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_NONE, + var_virt_alias_doms); + virt_mailbox_doms = string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_NONE, + var_virt_mailbox_doms); #endif access_parent_style = match_parent_style(SMTPD_ACCESS_MAPS); @@ -750,14 +760,16 @@ void smtpd_check_init(void) * Templates for RBL rejection replies. */ rbl_reply_maps = maps_create(VAR_RBL_REPLY_MAPS, var_rbl_reply_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); /* * Sender to login name mapping. */ smtpd_sender_login_maps = maps_create(VAR_SMTPD_SND_AUTH_MAPS, var_smtpd_snd_auth_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); /* * error_text is used for returning error responses. @@ -880,7 +892,8 @@ void smtpd_check_init(void) /* * Optional permit logging. */ - smtpd_acl_perm_log = string_list_init(MATCH_FLAG_RETURN, + smtpd_acl_perm_log = string_list_init(VAR_SMTPD_ACL_PERM_LOG, + MATCH_FLAG_RETURN, var_smtpd_acl_perm_log); } @@ -1117,11 +1130,29 @@ static const char *check_mail_addr_find(SMTPD_STATE *state, if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0) return (result); if (maps->error == DICT_ERR_RETRY) + /* Warning is already logged. */ reject_dict_retry(state, reply_name); else reject_server_error(state); } +/* check_dict_get - reject with temporary failure if dict lookup fails */ + +static const char *check_dict_get(SMTPD_STATE *state, const char *table, + const char *reply_name, + DICT *dict, const char *key) +{ + const char *result; + + if ((result = dict_get(dict, key)) != 0 || dict->error == 0) + return (result); + if (dict->error == DICT_ERR_RETRY) { + msg_warn("%s: table lookup problem", table); + reject_dict_retry(state, reply_name); + } else + reject_server_error(state); +} + /* reject_unknown_reverse_name - fail if reverse client hostname is unknown */ static int reject_unknown_reverse_name(SMTPD_STATE *state) @@ -1417,7 +1448,7 @@ static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, * Fix 20140924: convert domain to ASCII. */ #ifndef NO_EAI - if (!allascii(name) && (aname = midna_to_ascii(name)) != 0) { + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", name, aname); name = aname; @@ -1916,7 +1947,7 @@ static int permit_mx_backup(SMTPD_STATE *state, const char *recipient, * Fix 20140924: convert domain to ASCII. */ #ifndef NO_EAI - if (!allascii(domain) && (adomain = midna_to_ascii(domain)) != 0) { + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", domain, adomain); domain = adomain; @@ -2661,23 +2692,13 @@ static int check_access(SMTPD_STATE *state, const char *table, const char *name, if ((dict = dict_handle(table)) == 0) { msg_warn("%s: unexpected dictionary: %s", myname, table); - value = "451 4.3.5 Server configuration error"; - CHK_ACCESS_RETURN(check_table_result(state, table, value, name, - reply_name, reply_class, - def_acl), FOUND); + reject_server_error(state); } if (flags == 0 || (flags & dict->flags) != 0) { - if ((value = dict_get(dict, name)) != 0) + if ((value = check_dict_get(state, table, reply_name, dict, name)) != 0) CHK_ACCESS_RETURN(check_table_result(state, table, value, name, reply_name, reply_class, def_acl), FOUND); - if (dict->error != 0) { - msg_warn("%s: table lookup problem", table); - value = "451 4.3.5 Server configuration error"; - CHK_ACCESS_RETURN(check_table_result(state, table, value, name, - reply_name, reply_class, - def_acl), FOUND); - } } CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED); } @@ -2711,24 +2732,15 @@ static int check_domain_access(SMTPD_STATE *state, const char *table, if ((dict = dict_handle(table)) == 0) { msg_warn("%s: unexpected dictionary: %s", myname, table); - value = "451 4.3.5 Server configuration error"; - CHK_DOMAIN_RETURN(check_table_result(state, table, value, - domain, reply_name, reply_class, - def_acl), FOUND); + reject_server_error(state); } for (name = domain; *name != 0; name = next) { if (flags == 0 || (flags & dict->flags) != 0) { - if ((value = dict_get(dict, name)) != 0) + if ((value = check_dict_get(state, table, reply_name, + dict, name)) != 0) CHK_DOMAIN_RETURN(check_table_result(state, table, value, domain, reply_name, reply_class, def_acl), FOUND); - if (dict->error != 0) { - msg_warn("%s: table lookup problem", table); - value = "451 4.3.5 Server configuration error"; - CHK_DOMAIN_RETURN(check_table_result(state, table, value, - domain, reply_name, reply_class, - def_acl), FOUND); - } } /* Don't apply subdomain magic to numerical hostnames. */ if (maybe_numerical @@ -2775,24 +2787,15 @@ static int check_addr_access(SMTPD_STATE *state, const char *table, if ((dict = dict_handle(table)) == 0) { msg_warn("%s: unexpected dictionary: %s", myname, table); - value = "451 4.3.5 Server configuration error"; - CHK_ADDR_RETURN(check_table_result(state, table, value, address, - reply_name, reply_class, - def_acl), FOUND); + reject_server_error(state); } do { if (flags == 0 || (flags & dict->flags) != 0) { - if ((value = dict_get(dict, addr)) != 0) + if ((value = check_dict_get(state, table, reply_name, + dict, addr)) != 0) CHK_ADDR_RETURN(check_table_result(state, table, value, address, reply_name, reply_class, def_acl), FOUND); - if (dict->error != 0) { - msg_warn("%s: table lookup problem", table); - value = "451 4.3.5 Server configuration error"; - CHK_ADDR_RETURN(check_table_result(state, table, value, address, - reply_name, reply_class, - def_acl), FOUND); - } } flags = PARTIAL; } while (split_at_right(addr, delim)); @@ -2914,7 +2917,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table, * Fix 20140924: convert domain to ASCII. */ #ifndef NO_EAI - if (!allascii(domain) && (adomain = midna_to_ascii(domain)) != 0) { + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", domain, adomain); domain = adomain; @@ -3634,7 +3637,7 @@ static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state, * Fix 20140706: convert domain to ASCII. */ #ifndef NO_EAI - if (!allascii(domain) && (adomain = midna_to_ascii(domain)) != 0) { + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", domain, adomain); domain = adomain; @@ -3816,6 +3819,18 @@ static int reject_unauth_sender_login_mismatch(SMTPD_STATE *state, const char *s #endif +/* valid_utf8_action - validate UTF-8 policy server response */ + +static int valid_utf8_action(const char *server, const char *action) +{ + int retval; + + if ((retval = valid_utf8_string(action, strlen(action))) == 0) + msg_warn("malformed UTF-8 in policy server %s response: \"%s\"", + server, action); + return (retval); +} + /* check_policy_service - check delegated policy service */ static int check_policy_service(SMTPD_STATE *state, const char *server, @@ -3926,7 +3941,8 @@ static int check_policy_service(SMTPD_STATE *state, const char *server, ATTR_TYPE_END, ATTR_FLAG_MISSING, /* Reply attributes. */ RECV_ATTR_STR(MAIL_ATTR_ACTION, action), - ATTR_TYPE_END) != 1) { + ATTR_TYPE_END) != 1 + || (var_smtputf8_enable && valid_utf8_action(server, STR(action)) == 0)) { NOCLOBBER static int nesting_level = 0; jmp_buf savebuf; int status; @@ -5963,9 +5979,9 @@ int main(int argc, char **argv) #define UPDATE_MAPS(ptr, var, val, lock) \ { if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); } -#define UPDATE_LIST(ptr, val) \ +#define UPDATE_LIST(ptr, var, val) \ { if (ptr) string_list_free(ptr); \ - ptr = string_list_init(MATCH_FLAG_NONE, val); } + ptr = string_list_init(var, MATCH_FLAG_NONE, val); } case 2: if (strcasecmp(args->argv[0], VAR_MYDEST) == 0) { @@ -5979,13 +5995,14 @@ int main(int argc, char **argv) UPDATE_STRING(var_virt_alias_maps, args->argv[1]); UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) { UPDATE_STRING(var_virt_alias_doms, args->argv[1]); - UPDATE_LIST(virt_alias_doms, var_virt_alias_doms); + UPDATE_LIST(virt_alias_doms, VAR_VIRT_ALIAS_DOMS, + var_virt_alias_doms); smtpd_resolve_init(100); resp = 0; break; @@ -5994,13 +6011,14 @@ int main(int argc, char **argv) UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]); UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) { UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]); - UPDATE_LIST(virt_mailbox_doms, var_virt_mailbox_doms); + UPDATE_LIST(virt_mailbox_doms, VAR_VIRT_MAILBOX_DOMS, + var_virt_mailbox_doms); smtpd_resolve_init(100); resp = 0; break; @@ -6009,7 +6027,7 @@ int main(int argc, char **argv) UPDATE_STRING(var_local_rcpt_maps, args->argv[1]); UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } @@ -6017,7 +6035,7 @@ int main(int argc, char **argv) UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]); UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } @@ -6025,7 +6043,7 @@ int main(int argc, char **argv) UPDATE_STRING(var_canonical_maps, args->argv[1]); UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS, var_canonical_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } @@ -6033,7 +6051,7 @@ int main(int argc, char **argv) UPDATE_STRING(var_rbl_reply_maps, args->argv[1]); UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS, var_rbl_reply_maps, DICT_FLAG_LOCK - | DICT_FLAG_FOLD_FIX); + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); resp = 0; break; } @@ -6041,7 +6059,7 @@ int main(int argc, char **argv) /* NOT: UPDATE_STRING */ namadr_list_free(mynetworks_curr); mynetworks_curr = - namadr_list_init(MATCH_FLAG_RETURN + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN | match_parent_style(VAR_MYNETWORKS), args->argv[1]); smtpd_resolve_init(100); @@ -6052,7 +6070,8 @@ int main(int argc, char **argv) /* NOT: UPDATE_STRING */ domain_list_free(relay_domains); relay_domains = - domain_list_init(match_parent_style(VAR_RELAY_DOMAINS), + domain_list_init(VAR_RELAY_DOMAINS, + match_parent_style(VAR_RELAY_DOMAINS), args->argv[1]); smtpd_resolve_init(100); resp = 0; @@ -6062,7 +6081,7 @@ int main(int argc, char **argv) UPDATE_STRING(var_perm_mx_networks, args->argv[1]); domain_list_free(perm_mx_networks); perm_mx_networks = - namadr_list_init(MATCH_FLAG_RETURN + namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN | match_parent_style(VAR_PERM_MX_NETWORKS), args->argv[1]); resp = 0; diff --git a/postfix/src/smtpd/smtpd_error.ref b/postfix/src/smtpd/smtpd_error.ref index 44675cb2e..0f2e2fac3 100644 --- a/postfix/src/smtpd/smtpd_error.ref +++ b/postfix/src/smtpd/smtpd_error.ref @@ -11,8 +11,8 @@ OK >>> # Expect: REJECT (temporary lookup failure) >>> helo foobar ./smtpd_check: warning: fail:1_helo_access: table lookup problem -./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 451 4.3.5 : Helo command rejected: Server configuration error; proto=SMTP helo= -451 4.3.5 : Helo command rejected: Server configuration error +./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 451 4.3.0 : Temporary lookup failure; proto=SMTP helo= +451 4.3.0 : Temporary lookup failure >>> # >>> # Test check_namadr_access() >>> # @@ -21,8 +21,8 @@ OK >>> # Expect: REJECT (temporary lookup failure) >>> client foo.dunno.com 131.155.210.17 ./smtpd_check: warning: fail:1_client_access: table lookup problem -./smtpd_check: : reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Client host rejected: Server configuration error; proto=SMTP helo= -451 4.3.5 : Client host rejected: Server configuration error +./smtpd_check: : reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; proto=SMTP helo= +451 4.3.0 : Temporary lookup failure >>> # >>> # Test check_mail_access() >>> # @@ -31,8 +31,8 @@ OK >>> # Expect: REJECT (temporary lookup failure) >>> mail reject@dunno.domain ./smtpd_check: warning: fail:1_sender_access: table lookup problem -./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Sender address rejected: Server configuration error; from= proto=SMTP helo= -451 4.3.5 : Sender address rejected: Server configuration error +./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure >>> # >>> # Test check_rcpt_access() >>> # @@ -41,8 +41,8 @@ OK >>> # Expect: REJECT (temporary lookup failure) >>> rcpt reject@dunno.domain ./smtpd_check: warning: fail:1_rcpt_access: table lookup problem -./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Recipient address rejected: Server configuration error; from= to= proto=SMTP helo= -451 4.3.5 : Recipient address rejected: Server configuration error +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure >>> # Expect: OK >>> rcpt postmaster OK @@ -57,7 +57,7 @@ OK >>> recipient_restrictions permit_mynetworks OK >>> rcpt reject@dunno.domain -./smtpd_check: warning: fail:1_mynetworks: table lookup problem +./smtpd_check: warning: mynetworks: fail:1_mynetworks: table lookup problem ./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= 451 4.3.0 : Temporary lookup failure >>> # @@ -69,7 +69,7 @@ OK >>> # Expect REJECT (server configuration error) >>> # >>> rcpt reject@dunno.domain -./smtpd_check: warning: non-null host address bits in "168.100.189.1/27", perhaps you should use "168.100.189.0/27" instead +./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.189.1/27", perhaps you should use "168.100.189.0/27" instead ./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= 451 4.3.0 : Temporary lookup failure >>> # @@ -79,8 +79,8 @@ OK OK >>> mail <> ./smtpd_check: warning: fail:1_sender_access: table lookup problem -./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <>: Sender address rejected: Server configuration error; from=<> proto=SMTP helo= -451 4.3.5 <>: Sender address rejected: Server configuration error +./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.0 <>: Temporary lookup failure; from=<> proto=SMTP helo= +451 4.3.0 <>: Temporary lookup failure >>> # >>> # Test permit_tls_client_certs in generic_restrictions >>> # @@ -119,7 +119,7 @@ OK >>> mydestination fail:1_mydestination OK >>> rcpt user@example.com -./smtpd_check: warning: fail:1_mydestination: table lookup problem +./smtpd_check: warning: mydestination: fail:1_mydestination: table lookup problem ./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= 451 4.3.0 : Temporary lookup failure >>> # diff --git a/postfix/src/tls/Makefile.in b/postfix/src/tls/Makefile.in index cddb4915b..1a4896053 100644 --- a/postfix/src/tls/Makefile.in +++ b/postfix/src/tls/Makefile.in @@ -140,7 +140,7 @@ tls_client.o: ../../include/dict.h tls_client.o: ../../include/dns.h tls_client.o: ../../include/iostuff.h tls_client.o: ../../include/mail_params.h -tls_client.o: ../../include/midna.h +tls_client.o: ../../include/midna_domain.h tls_client.o: ../../include/msg.h tls_client.o: ../../include/myaddrinfo.h tls_client.o: ../../include/myflock.h diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index 664faec46..9e5f3dc8b 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -296,23 +296,61 @@ extern void tls_param_init(void); * Protocol selection. */ #define TLS_PROTOCOL_INVALID (~0) /* All protocol bits masked */ + +#ifdef SSL_TXT_SSLV2 #define TLS_PROTOCOL_SSLv2 (1<<0) /* SSLv2 */ +#else +#define SSL_TXT_SSLV2 "SSLv2" +#define TLS_PROTOCOL_SSLv2 0 /* Unknown */ +#undef SSL_OP_NO_SSLv2 +#define SSL_OP_NO_SSLv2 0L /* Noop */ +#endif + +#ifdef SSL_TXT_SSLV3 #define TLS_PROTOCOL_SSLv3 (1<<1) /* SSLv3 */ +#else +#define SSL_TXT_SSLV3 "SSLv3" +#define TLS_PROTOCOL_SSLv3 0 /* Unknown */ +#undef SSL_OP_NO_SSLv3 +#define SSL_OP_NO_SSLv3 0L /* Noop */ +#endif + +#ifdef SSL_TXT_TLSV1 #define TLS_PROTOCOL_TLSv1 (1<<2) /* TLSv1 */ +#else +#define SSL_TXT_TLSV1 "TLSv1" +#define TLS_PROTOCOL_TLSv1 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1 +#define SSL_OP_NO_TLSv1 0L /* Noop */ +#endif + #ifdef SSL_TXT_TLSV1_1 #define TLS_PROTOCOL_TLSv1_1 (1<<3) /* TLSv1_1 */ #else +#define SSL_TXT_TLSV1_1 "TLSv1.1" #define TLS_PROTOCOL_TLSv1_1 0 /* Unknown */ #undef SSL_OP_NO_TLSv1_1 #define SSL_OP_NO_TLSv1_1 0L /* Noop */ #endif + #ifdef SSL_TXT_TLSV1_2 #define TLS_PROTOCOL_TLSv1_2 (1<<4) /* TLSv1_2 */ #else +#define SSL_TXT_TLSV1_2 "TLSv1.2" #define TLS_PROTOCOL_TLSv1_2 0 /* Unknown */ #undef SSL_OP_NO_TLSv1_2 #define SSL_OP_NO_TLSv1_2 0L /* Noop */ #endif + +#ifdef SSL_TXT_TLSV1_3 +#define TLS_PROTOCOL_TLSv1_3 (1<<5) /* TLSv1_3 */ +#else +#define SSL_TXT_TLSV1_3 "TLSv1.3" +#define TLS_PROTOCOL_TLSv1_3 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1_3 +#define SSL_OP_NO_TLSv1_3 0L /* Noop */ +#endif + #define TLS_KNOWN_PROTOCOLS \ ( TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3 | TLS_PROTOCOL_TLSv1 \ | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2 ) @@ -321,7 +359,8 @@ extern void tls_param_init(void); | (((m) & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) \ | (((m) & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) \ | (((m) & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) \ - | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L)) + | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) \ + | (((m) & TLS_PROTOCOL_TLSv1_3) ? SSL_OP_NO_TLSv1_3 : 0L)) /* * SSL options that are managed via dedicated Postfix features, rather than diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index f30f1e3ae..8211e2602 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -140,7 +140,7 @@ #include #include #include /* non-blocking */ -#include +#include /* Global library. */ @@ -535,7 +535,7 @@ static int match_servername(const char *certid, */ if (!allascii(certid)) return (0); - if (!allascii(nexthop) && (aname = midna_to_ascii(nexthop)) != 0) { + if (!allascii(nexthop) && (aname = midna_domain_to_ascii(nexthop)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", nexthop, aname); nexthop = aname; @@ -565,13 +565,19 @@ static int match_servername(const char *certid, #ifndef NO_EAI /* - * IDNA allows labels to be separated by any of the additional - * characters U+3002, U+FF0E, and U+FF61; that are Unicode - * variants. Their UTF-8 encodings are: E38082, EFBC8E and - * EFBDA1. + * Besides U+002E (full stop) IDNA2003 allows labels to be + * separated by any of the Unicode variants U+3002 (ideographic + * full stop), U+FF0E (fullwidth full stop), and U+FF61 + * (halfwidth ideographic full stop). Their respective UTF-8 + * encodings are: E38082, EFBC8E and EFBDA1. * - * It is not clear whether the IDNA to_ASCII conversion allows empty - * leading labels, so we handle these explicitly here. + * IDNA2008 does not permit (upper) case and other variant + * differences in U-labels. The midna_domain_to_ascii() function, + * based on UTS46, midna_domain_to_ascii() normalizes the + * differences away. + * + * The IDNA to_ASCII conversion does not allow empty leading labels, + * so we handle these explicitly here. */ else { unsigned char *cp = (unsigned char *) domain; @@ -586,7 +592,7 @@ static int match_servername(const char *certid, } } if (!allascii(domain) - && (aname = midna_to_ascii(domain)) != 0) { + && (aname = midna_domain_to_ascii(domain)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", domain, aname); domain = aname; diff --git a/postfix/src/tls/tls_dh.c b/postfix/src/tls/tls_dh.c index da17be73a..be126c9a1 100644 --- a/postfix/src/tls/tls_dh.c +++ b/postfix/src/tls/tls_dh.c @@ -82,6 +82,7 @@ #define TLS_INTERNAL #include +#include /* Application-specific. */ diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index 1a3590f28..3497014ed 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -252,12 +252,9 @@ static const NAME_CODE protocol_table[] = { SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2, SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3, SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1, -#ifdef SSL_TXT_TLSV1_1 SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1, -#endif -#ifdef SSL_TXT_TLSV1_2 SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2, -#endif + SSL_TXT_TLSV1_3, TLS_PROTOCOL_TLSv1_3, 0, TLS_PROTOCOL_INVALID, }; diff --git a/postfix/src/tls/tls_rsa.c b/postfix/src/tls/tls_rsa.c index 1dfe266b3..aba4142d2 100644 --- a/postfix/src/tls/tls_rsa.c +++ b/postfix/src/tls/tls_rsa.c @@ -52,6 +52,7 @@ #define TLS_INTERNAL #include +#include /* tls_tmp_rsa_cb - call-back to generate ephemeral RSA key */ diff --git a/postfix/src/tls/tls_scache.c b/postfix/src/tls/tls_scache.c index 1c1a29e5f..ca86cc258 100644 --- a/postfix/src/tls/tls_scache.c +++ b/postfix/src/tls/tls_scache.c @@ -481,10 +481,12 @@ TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label, * opening a damaged file after some process terminated abnormally. */ #ifdef SINGLE_UPDATER -#define DICT_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK) +#define DICT_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK \ + | DICT_FLAG_UTF8_REQUEST) #else #define DICT_FLAGS \ - (DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE) + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE \ + | DICT_FLAG_UTF8_REQUEST) #endif dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS); diff --git a/postfix/src/tls/tls_server.c b/postfix/src/tls/tls_server.c index aa943a018..b74c32736 100644 --- a/postfix/src/tls/tls_server.c +++ b/postfix/src/tls/tls_server.c @@ -498,12 +498,7 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props) * Global protocol selection. */ if (protomask != 0) - SSL_CTX_set_options(server_ctx, - ((protomask & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) - | ((protomask & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) - | ((protomask & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) - | ((protomask & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) - | ((protomask & TLS_PROTOCOL_SSLv2) ? SSL_OP_NO_SSLv2 : 0L)); + SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask)); /* * Some sites may want to give the client less rope. On the other hand, diff --git a/postfix/src/trivial-rewrite/resolve.c b/postfix/src/trivial-rewrite/resolve.c index 8ffb196ef..f7c5a6a63 100644 --- a/postfix/src/trivial-rewrite/resolve.c +++ b/postfix/src/trivial-rewrite/resolve.c @@ -128,6 +128,7 @@ */ #define STR vstring_str +#define LEN VSTRING_LEN /* * Some of the lists that define the address domain classes. @@ -414,11 +415,14 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, */ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL); rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; - if (rcpt_domain == 0) + if (rcpt_domain == (char *) 1) msg_panic("no @ in address: \"%s\"", STR(nextrcpt)); if (*rcpt_domain == '[') { if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE)) *flags |= RESOLVE_FLAG_ERROR; + } else if (var_smtputf8_enable + && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) { + *flags |= RESOLVE_FLAG_ERROR; } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain, DONT_GRIPE)) { if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) { @@ -791,20 +795,23 @@ void resolve_init(void) if (*var_virt_alias_doms) virt_alias_doms = - string_list_init(MATCH_FLAG_RETURN, var_virt_alias_doms); + string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_RETURN, + var_virt_alias_doms); if (*var_virt_mailbox_doms) virt_mailbox_doms = - string_list_init(MATCH_FLAG_RETURN, var_virt_mailbox_doms); + string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_RETURN, + var_virt_mailbox_doms); if (*var_relay_domains) relay_domains = - domain_list_init(MATCH_FLAG_RETURN + domain_list_init(VAR_RELAY_DOMAINS, MATCH_FLAG_RETURN | match_parent_style(VAR_RELAY_DOMAINS), var_relay_domains); if (*var_relocated_maps) relocated_maps = maps_create(VAR_RELOCATED_MAPS, var_relocated_maps, - DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); } diff --git a/postfix/src/trivial-rewrite/transport.c b/postfix/src/trivial-rewrite/transport.c index ec571cdb5..f270b8dde 100644 --- a/postfix/src/trivial-rewrite/transport.c +++ b/postfix/src/trivial-rewrite/transport.c @@ -99,7 +99,8 @@ TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name, tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp)); tp->transport_path = maps_create(transport_maps_name, transport_maps, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX - | DICT_FLAG_NO_REGSUB); + | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); tp->wildcard_channel = tp->wildcard_nexthop = 0; tp->wildcard_errno = 0; tp->expire = 0; diff --git a/postfix/src/trivial-rewrite/trivial-rewrite.c b/postfix/src/trivial-rewrite/trivial-rewrite.c index 4a71ec0e9..31ae822e3 100644 --- a/postfix/src/trivial-rewrite/trivial-rewrite.c +++ b/postfix/src/trivial-rewrite/trivial-rewrite.c @@ -542,25 +542,25 @@ static void pre_jail_init(char *unused_name, char **unused_argv) maps_create(resolve_regular.snd_relay_maps_name, RES_PARAM_VALUE(resolve_regular.snd_relay_maps), DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX - | DICT_FLAG_NO_REGSUB); + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); if (*RES_PARAM_VALUE(resolve_verify.snd_relay_maps)) resolve_verify.snd_relay_info = maps_create(resolve_verify.snd_relay_maps_name, RES_PARAM_VALUE(resolve_verify.snd_relay_maps), DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX - | DICT_FLAG_NO_REGSUB); + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); if (*RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps)) resolve_regular.snd_def_xp_info = maps_create(resolve_regular.snd_def_xp_maps_name, RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps), DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX - | DICT_FLAG_NO_REGSUB); + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); if (*RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps)) resolve_verify.snd_def_xp_info = maps_create(resolve_verify.snd_def_xp_maps_name, RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps), DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX - | DICT_FLAG_NO_REGSUB); + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); } /* post_jail_init - initialize after entering chroot jail */ diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index b634d30ad..4dee5e51f 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -38,8 +38,8 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \ dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \ poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \ - valid_utf8_hostname.c midna.c argv_splitq.c balpar.c dict_union.c \ - extpar.c dict_inline.c + valid_utf8_hostname.c midna_domain.c argv_splitq.c balpar.c dict_union.c \ + extpar.c dict_inline.c casefold.c dict_utf8.c OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ @@ -79,8 +79,8 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \ dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \ poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \ - valid_utf8_hostname.o midna.o argv_splitq.o balpar.o dict_union.o \ - extpar.o dict_inline.o + valid_utf8_hostname.o midna_domain.o argv_splitq.o balpar.o dict_union.o \ + extpar.o dict_inline.o casefold.o dict_utf8.o # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), # otherwise it sets the PLUGIN_* macros. @@ -109,7 +109,7 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \ dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \ slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \ - valid_utf8_hostname.h midna.h dict_union.h dict_inline.h check_arg.h + valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h check_arg.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c DEFS = -I. -D$(SYSTYPE) @@ -128,7 +128,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \ myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \ valid_utf8_string ip_match base32_code msg_rate_delay netstring \ - vstream timecmp dict_cache midna + vstream timecmp dict_cache midna_domain casefold PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) LIB_DIR = ../../lib @@ -504,7 +504,12 @@ dict_cache: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -midna: $(LIB) +midna_domain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +casefold: $(LIB) mv $@.o junk $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o @@ -515,7 +520,8 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ dict_cidr_test attr_scan_plain_test htable_test hex_code_test \ myaddrinfo_test format_tv_test ip_match_test name_mask_tests \ base32_code_test dict_thash_test surrogate_test timecmp_test \ - dict_static_test dict_inline_test midna_test + dict_static_test dict_inline_test midna_domain_test casefold_test \ + dict_utf8_test root_tests: @@ -705,8 +711,8 @@ base32_code_test: base32_code $(SHLIB_ENV) ./base32_code dict_thash_test: ../postmap/postmap dict_thash.map - $(SHLIB_ENV) ../postmap/postmap -s texthash:dict_thash.map >dict_thash.tmp 2>&1 - sort dict_thash.tmp | diff -b dict_thash.map - + $(SHLIB_ENV) ../postmap/postmap -s texthash:dict_thash.map | sort >dict_thash.tmp 2>&1 + tr '[A-Z]' '[a-z]' dict_inline.tmp 2>&1 diff dict_inline.ref dict_inline.tmp rm -f dict_inline.tmp -midna_test: midna midna_test.in midna_test.ref - $(SHLIB_ENV) ./midna midna_test.tmp 2>&1 - diff midna_test.ref midna_test.tmp - rm -f midna_test.tmp +midna_domain_test: midna_domain midna_domain_test.in midna_domain_test.ref + $(SHLIB_ENV) ./midna_domain midna_domain_test.tmp 2>&1 + diff midna_domain_test.ref midna_domain_test.tmp + rm -f midna_domain_test.tmp + +casefold_test: casefold casefold_test.in casefold_test.ref + $(SHLIB_ENV) ./casefold casefold_test.tmp 2>&1 + diff casefold_test.ref casefold_test.tmp + rm -f casefold_test.tmp + +dict_utf8_test: dict_open dict_utf8_test.in dict_utf8_test.ref + $(SHLIB_ENV) sh dict_utf8_test.in >dict_utf8_test.tmp 2>&1 + diff dict_utf8_test.ref dict_utf8_test.tmp + rm -f dict_utf8_test.tmp depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ @@ -945,6 +963,13 @@ binhash.o: binhash.h binhash.o: msg.h binhash.o: mymalloc.h binhash.o: sys_defs.h +casefold.o: casefold.c +casefold.o: check_arg.h +casefold.o: msg.h +casefold.o: stringops.h +casefold.o: sys_defs.h +casefold.o: vbuf.h +casefold.o: vstring.h chroot_uid.o: chroot_uid.c chroot_uid.o: chroot_uid.h chroot_uid.o: msg.h @@ -1132,6 +1157,7 @@ dict_ht.o: vstring.h dict_inline.o: argv.h dict_inline.o: check_arg.h dict_inline.o: dict.h +dict_inline.o: dict_ht.h dict_inline.o: dict_inline.c dict_inline.o: dict_inline.h dict_inline.o: htable.h @@ -1380,20 +1406,19 @@ dict_test.o: vstring_vstream.h dict_thash.o: argv.h dict_thash.o: check_arg.h dict_thash.o: dict.h +dict_thash.o: dict_ht.h dict_thash.o: dict_thash.c dict_thash.o: dict_thash.h dict_thash.o: htable.h dict_thash.o: iostuff.h dict_thash.o: msg.h dict_thash.o: myflock.h -dict_thash.o: mymalloc.h dict_thash.o: readlline.h dict_thash.o: stringops.h dict_thash.o: sys_defs.h dict_thash.o: vbuf.h dict_thash.o: vstream.h dict_thash.o: vstring.h -dict_thash.o: warn_stat.h dict_union.o: argv.h dict_union.o: check_arg.h dict_union.o: dict.h @@ -1421,6 +1446,18 @@ dict_unix.o: sys_defs.h dict_unix.o: vbuf.h dict_unix.o: vstream.h dict_unix.o: vstring.h +dict_utf8.o: argv.h +dict_utf8.o: check_arg.h +dict_utf8.o: dict.h +dict_utf8.o: dict_utf8.c +dict_utf8.o: msg.h +dict_utf8.o: myflock.h +dict_utf8.o: mymalloc.h +dict_utf8.o: stringops.h +dict_utf8.o: sys_defs.h +dict_utf8.o: vbuf.h +dict_utf8.o: vstream.h +dict_utf8.o: vstring.h dir_forest.o: check_arg.h dir_forest.o: dir_forest.c dir_forest.o: dir_forest.h @@ -1678,8 +1715,6 @@ load_file.o: vbuf.h load_file.o: vstream.h load_file.o: warn_stat.h load_lib.o: load_lib.c -load_lib.o: load_lib.h -load_lib.o: msg.h load_lib.o: sys_defs.h lowercase.o: check_arg.h lowercase.o: lowercase.c @@ -1755,17 +1790,17 @@ match_ops.o: sys_defs.h match_ops.o: vbuf.h match_ops.o: vstream.h match_ops.o: vstring.h -midna.o: check_arg.h -midna.o: ctable.h -midna.o: midna.c -midna.o: midna.h -midna.o: msg.h -midna.o: mymalloc.h -midna.o: stringops.h -midna.o: sys_defs.h -midna.o: valid_hostname.h -midna.o: vbuf.h -midna.o: vstring.h +midna_domain.o: check_arg.h +midna_domain.o: ctable.h +midna_domain.o: midna_domain.c +midna_domain.o: midna_domain.h +midna_domain.o: msg.h +midna_domain.o: mymalloc.h +midna_domain.o: stringops.h +midna_domain.o: sys_defs.h +midna_domain.o: valid_hostname.h +midna_domain.o: vbuf.h +midna_domain.o: vstring.h msg.o: msg.c msg.o: msg.h msg.o: msg_output.h @@ -2223,7 +2258,7 @@ valid_hostname.o: valid_hostname.h valid_hostname.o: vbuf.h valid_hostname.o: vstring.h valid_utf8_hostname.o: check_arg.h -valid_utf8_hostname.o: midna.h +valid_utf8_hostname.o: midna_domain.h valid_utf8_hostname.o: msg.h valid_utf8_hostname.o: mymalloc.h valid_utf8_hostname.o: stringops.h diff --git a/postfix/src/util/casefold.c b/postfix/src/util/casefold.c new file mode 100644 index 000000000..c1260c7b8 --- /dev/null +++ b/postfix/src/util/casefold.c @@ -0,0 +1,293 @@ +/*++ +/* NAME +/* casefold 3 +/* SUMMARY +/* casefold text for caseless comparison +/* SYNOPSIS +/* #include +/* +/* char *casefold( +/* int utf8_request, +/* VSTRING *src, +/* const char *src, +/* CONST_CHAR_STAR *err) +/* DESCRIPTION +/* casefold() converts text to a form that is suitable for +/* caseless comparison, rather than presentation to humans. +/* +/* When compiled without EAI support, casefold() implements +/* ASCII case folding, leaving non-ASCII byte values unchanged. +/* This mode has no error returns. +/* +/* When compiled with EAI support, casefold() implements UTF-8 +/* case folding using the en_US locale, as recommended when +/* the conversion result is not meant to be presented to humans. +/* When conversion fails the result is null, and the pointer +/* referenced by err is updated. +/* +/* With the ICU 4.8 library, there is no casefold error for +/* UTF-8 code points U+0000..U+10FFFF (including surrogate +/* range), not even when running inside an empty chroot jail. +/* +/* Arguments: +/* .IP utf8_request +/* Boolean parameter that enables UTF-8 case folding instead +/* of folding only ASCII characters. This flag is ignored when +/* compiled without EAI support. +/* .IP src +/* Null-terminated input string. +/* .IP dest +/* Output buffer, null-terminated if the function completes +/* without reporting an error. +/* .IP err +/* Null pointer, or pointer to "const char *". for descriptive +/* text about errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#ifndef NO_EAI +#include +#include +#include +#endif + +/* Utility library. */ + +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* casefold - casefold an UTF-8 string */ + +char *casefold(int utf8_req, VSTRING *dest, const char *src, + CONST_CHAR_STAR *err) +{ +#ifdef NO_EAI + + /* + * ASCII mode only. + */ + vstring_strcpy(dest, src); + return (lowercase(STR(dest))); +#else + + /* + * Unicode mode. + */ + static UCaseMap *csm = 0; + UErrorCode error; + ssize_t space_needed; + int n; + + /* + * All-ASCII input, or ASCII mode only. + */ + if (utf8_req == 0 || allascii(src)) { + vstring_strcpy(dest, src); + return (lowercase(STR(dest))); + } + + /* + * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax + * errors. XXX Is this behavior guaranteed or accidental? We don't know, + * therefore must check it here. + */ + if (valid_utf8_string(src, strlen(src)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + + /* + * One-time initialization. With ICU 4.8 this works while chrooted. + */ + if (csm == 0) { + error = U_ZERO_ERROR; + csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error); + if (U_SUCCESS(error) == 0) + msg_fatal("ucasemap_open error: %s", u_errorName(error)); + } + + /* + * Fold the input, adjusting the buffer size if needed. Safety: don't + * loop forever. + */ + VSTRING_RESET(dest); + for (n = 0; n < 3; n++) { + error = U_ZERO_ERROR; + space_needed = + ucasemap_utf8FoldCase(csm, STR(dest), vstring_avail(dest), + src, strlen(src), &error); + if (error == U_BUFFER_OVERFLOW_ERROR) { + VSTRING_SPACE(dest, space_needed); + } else { + break; + } + } + + /* + * Report the result. With ICU 4.8, there are no casefolding errors for + * the entire RFC 3629 Unicode range (code points U+0000..U+10FFFF + * including surrogates), nor are there casefolding errors for bad UTF-8 + * input. XXX Is this behavior guaranteed or accidental? We don't know, + * therefore we have the UTF-8 syntax check (and range check) above. + */ + if (U_SUCCESS(error) == 0) { + if (err) + *err = u_errorName(error); + return (0); + } else { + /* Position the write pointer at the null terminator. */ + VSTRING_AT_OFFSET(dest, space_needed - 1); + return (STR(dest)); + } +#endif /* NO_EAI */ +} + +#ifdef TEST + +static void encode_utf8(VSTRING *buffer, int codepoint) +{ + const char myname[] = "encode_utf8"; + + VSTRING_RESET(buffer); + if (codepoint < 0x80) { + VSTRING_ADDCH(buffer, codepoint); + } else if (codepoint < 0x800) { + VSTRING_ADDCH(buffer, 0xc0 | (codepoint >> 6)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0x10000) { + VSTRING_ADDCH(buffer, 0xe0 | (codepoint >> 12)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint <= 0x10FFFF) { + VSTRING_ADDCH(buffer, 0xf0 | (codepoint >> 18)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 12) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else { + msg_panic("%s: out-of-range codepoint U+%X", myname, codepoint); + } + VSTRING_TERMINATE(buffer); +} + +#include +#include +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + VSTRING *dest = vstring_alloc(1); + char *bp; + char *conv_res; + const char *fold_err; + char *cmd; + int codepoint, first, last, utf8_req; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + + utf8_req = util_utf8_enable = 1; + + VSTRING_SPACE(buffer, 256); /* chroot pathname */ + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + vstream_printf("> %s\n", bp); + cmd = mystrtok(&bp, CHARS_SPACE); + if (cmd == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + + /* + * Null-terminated string. + */ + if (strcmp(cmd, "fold") == 0) { + if ((conv_res = casefold(utf8_req, dest, bp, &fold_err)) != 0) + vstream_printf("\"%s\" ->fold \"%s\"\n", bp, conv_res); + else + vstream_printf("cannot casefold \"%s\": %s\n", bp, fold_err); + } + + /* + * Codepoint range. + */ + else if (strcmp(cmd, "range") == 0 + && sscanf(bp, "%i %i", &first, &last) == 2 + && first <= last) { + for (codepoint = first; codepoint <= last; codepoint++) { + if (codepoint >= 0xD800 && codepoint <= 0xDFFF) { + vstream_printf("skipping surrogate range\n"); + codepoint = 0xDFFF; + } else { + encode_utf8(buffer, codepoint); + if (msg_verbose) + vstream_printf("U+%X -> %s\n", codepoint, STR(buffer)); + if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0) + msg_fatal("bad utf-8 encoding for U+%X\n", codepoint); + if (casefold(utf8_req, dest, STR(buffer), &fold_err) == 0) + vstream_printf("casefold error for U+%X: %s\n", + codepoint, fold_err); + } + } + vstream_printf("range completed: 0x%x..0x%x\n", first, last); + } + + /* + * Chroot directory. + */ + else if (strcmp(cmd, "chroot") == 0 + && sscanf(bp, "%255s", STR(buffer)) == 1) { + if (geteuid() == 0) { + if (chdir(STR(buffer)) < 0) + msg_fatal("chdir(%s): %m\n", STR(buffer)); + if (chroot(STR(buffer)) < 0) + msg_fatal("chroot(%s): %m\n", STR(buffer)); + vstream_printf("chroot %s completed\n", STR(buffer)); + } + } + + /* + * Verbose. + */ + else if (strcmp(cmd, "verbose") == 0 + && sscanf(bp, "%i", &msg_verbose) == 1) { + /* void */ ; + } + + /* + * Usage + */ + else { + vstream_printf("Usage: %s chroot | fold | range | verbose \n", + argv[0]); + } + vstream_fflush(VSTREAM_OUT); + } + exit(0); +} + +#endif /* TEST */ diff --git a/postfix/src/util/casefold_test.in b/postfix/src/util/casefold_test.in new file mode 100644 index 000000000..9b50e22e2 --- /dev/null +++ b/postfix/src/util/casefold_test.in @@ -0,0 +1,22 @@ +# Ignored when not running as root. +chroot /tmp +# Casefold U+0000 .. U+10FFFF excluding surrogates. +range 0x0 0xD7FF +range 0xD800 0xD800 +range 0xDFFF 0xDFFF +range 0xE000 0x10FFFF +# Demonstrate that range is not a noop. +verbose 1 +range 0xE000 0xE007 +verbose 0 +# Upper-case greek -> lower-case greek. +fold Δημοσθένους.example.com +# Upper-case ASCII -> lower-case ASCII. +fold HeLlO.ExAmPlE.CoM +# Folding does not change aliases for '.'. +fold x。example.com +fold x.example.com +fold x。example.com +# Bad UTF-8 +fold yyy€€€ +fold €€€xxx diff --git a/postfix/src/util/casefold_test.ref b/postfix/src/util/casefold_test.ref new file mode 100644 index 000000000..00d6f8d1f --- /dev/null +++ b/postfix/src/util/casefold_test.ref @@ -0,0 +1,44 @@ +> # Ignored when not running as root. +> chroot /tmp +> # Casefold U+0000 .. U+10FFFF excluding surrogates. +> range 0x0 0xD7FF +range completed: 0x0..0xd7ff +> range 0xD800 0xD800 +skipping surrogate range +range completed: 0xd800..0xd800 +> range 0xDFFF 0xDFFF +skipping surrogate range +range completed: 0xdfff..0xdfff +> range 0xE000 0x10FFFF +range completed: 0xe000..0x10ffff +> # Demonstrate that range is not a noop. +> verbose 1 +> range 0xE000 0xE007 +U+E000 ->  +U+E001 -> î€ +U+E002 ->  +U+E003 ->  +U+E004 ->  +U+E005 ->  +U+E006 ->  +U+E007 ->  +range completed: 0xe000..0xe007 +> verbose 0 +> # Upper-case greek -> lower-case greek. +> fold Δημοσθένους.example.com +"Δημοσθένους.example.com" ->fold "δημοσθένουσ.example.com" +> # Upper-case ASCII -> lower-case ASCII. +> fold HeLlO.ExAmPlE.CoM +"HeLlO.ExAmPlE.CoM" ->fold "hello.example.com" +> # Folding does not change aliases for '.'. +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> fold x.example.com +"x.example.com" ->fold "x.example.com" +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> # Bad UTF-8 +> fold yyy€€€ +cannot casefold "yyy€€€": malformed UTF-8 or invalid codepoint +> fold €€€xxx +cannot casefold "€€€xxx": malformed UTF-8 or invalid codepoint diff --git a/postfix/src/util/dict.c b/postfix/src/util/dict.c index df583179e..75fd94972 100644 --- a/postfix/src/util/dict.c +++ b/postfix/src/util/dict.c @@ -637,6 +637,8 @@ static const NAME_MASK dict_mask[] = { "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */ "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */ "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */ + "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */ + "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */ 0, }; diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h index 104996ae5..0c3c464a6 100644 --- a/postfix/src/util/dict.h +++ b/postfix/src/util/dict.h @@ -92,6 +92,7 @@ typedef struct DICT { VSTRING *fold_buf; /* key folding buffer */ DICT_OWNER owner; /* provenance */ int error; /* last operation only */ + ssize_t size; /* size of this thing */ DICT_JMP_BUF *jbuf; /* exception handling */ } DICT; @@ -126,6 +127,10 @@ extern DICT *dict_debug(DICT *); #define DICT_FLAG_OPEN_LOCK (1<<16) /* perm lock if not multi-writer safe */ #define DICT_FLAG_BULK_UPDATE (1<<17) /* optimize for bulk updates */ #define DICT_FLAG_MULTI_WRITER (1<<18) /* multi-writer safe map */ +#define DICT_FLAG_UTF8_REQUEST (1<<19) /* activate UTF-8 if possible */ +#define DICT_FLAG_UTF8_ACTIVE (1<<20) /* UTF-8 proxy layer is present */ + +#define DICT_FLAG_UTF8_MASK (DICT_FLAG_UTF8_REQUEST) /* IMPORTANT: Update the dict_mask[] table when the above changes */ @@ -157,9 +162,15 @@ extern DICT *dict_debug(DICT *); #define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \ DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \ DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \ - DICT_FLAG_PARANOID) + DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK) #define DICT_FLAG_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK) + /* + * Feature tests. + */ +#define DICT_NEED_UTF8_ACTIVATION(enable, flags) \ + ((enable) && ((flags) & DICT_FLAG_UTF8_MASK)) + /* * dict->error values. Errors must be negative; smtpd_check depends on this. */ @@ -233,6 +244,14 @@ extern int dict_changed(void); extern const char *dict_changed_name(void); extern const char *dict_flags_str(int); extern int dict_flags_mask(const char *); +extern void dict_type_override(DICT *, const char *); + + /* + * Check and convert UTF-8 keys and values. + */ +extern DICT *dict_utf8_activate(DICT *); +extern char *dict_utf8_check_fold(DICT *, const char *, CONST_CHAR_STAR *); +extern int dict_utf8_check(const char *, CONST_CHAR_STAR *); /* * Driver for interactive or scripted tests. @@ -250,6 +269,7 @@ extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, in * This name is reserved for matchlist error handling. */ #define DICT_TYPE_NOFILE "non-existent" +#define DICT_TYPE_NOUTF8 "non-UTF-8" /* * Duplicated from vstream(3). This should probably be abstracted out. @@ -262,13 +282,13 @@ extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, in * systems have bugs in their implementation. */ #ifdef NO_SIGSETJMP -#define dict_setjmp(stream) setjmp((stream)->jbuf[0]) -#define dict_longjmp(stream, val) longjmp((stream)->jbuf[0], (val)) +#define dict_setjmp(dict) setjmp((dict)->jbuf[0]) +#define dict_longjmp(dict, val) longjmp((dict)->jbuf[0], (val)) #else -#define dict_setjmp(stream) sigsetjmp((stream)->jbuf[0], 1) -#define dict_longjmp(stream, val) siglongjmp((stream)->jbuf[0], (val)) +#define dict_setjmp(dict) sigsetjmp((dict)->jbuf[0], 1) +#define dict_longjmp(dict, val) siglongjmp((dict)->jbuf[0], (val)) #endif -#define dict_isjmp(stream) ((stream)->jbuf != 0) +#define dict_isjmp(dict) ((dict)->jbuf != 0) /* * Temporary API. If exception handling proves to be useful, diff --git a/postfix/src/util/dict_alloc.c b/postfix/src/util/dict_alloc.c index f370569a8..094e2193a 100644 --- a/postfix/src/util/dict_alloc.c +++ b/postfix/src/util/dict_alloc.c @@ -153,6 +153,7 @@ DICT *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size) dict->owner.status = DICT_OWNER_UNKNOWN; dict->owner.uid = INT_MAX; dict->error = DICT_ERR_NONE; + dict->size = size; dict->jbuf = 0; return dict; } diff --git a/postfix/src/util/dict_cdb.c b/postfix/src/util/dict_cdb.c index dbd4bd9ad..85e49a4ca 100644 --- a/postfix/src/util/dict_cdb.c +++ b/postfix/src/util/dict_cdb.c @@ -195,7 +195,7 @@ static DICT *dict_cdbq_open(const char *path, int dict_flags) if ((fd = open(cdb_path, O_RDONLY)) < 0) DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, - O_RDONLY, dict_flags, + O_RDONLY, dict_flags, "open database %s: %m", cdb_path)); dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, diff --git a/postfix/src/util/dict_debug.c b/postfix/src/util/dict_debug.c index 3d9a44325..46634d40c 100644 --- a/postfix/src/util/dict_debug.c +++ b/postfix/src/util/dict_debug.c @@ -59,7 +59,9 @@ static const char *dict_debug_lookup(DICT *dict, const char *key) DICT *real_dict = dict_debug->real_dict; const char *result; + real_dict->flags = dict->flags; result = dict_get(real_dict, key); + dict->flags = real_dict->flags; msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key, result ? result : real_dict->error ? "error" : "not_found"); DICT_ERR_VAL_RETURN(dict, real_dict->error, result); @@ -73,7 +75,9 @@ static int dict_debug_update(DICT *dict, const char *key, const char *value) DICT *real_dict = dict_debug->real_dict; int result; + real_dict->flags = dict->flags; result = dict_put(real_dict, key, value); + dict->flags = real_dict->flags; msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name, key, value, result == 0 ? "success" : real_dict->error ? "error" : "failed"); @@ -88,7 +92,9 @@ static int dict_debug_delete(DICT *dict, const char *key) DICT *real_dict = dict_debug->real_dict; int result; + real_dict->flags = dict->flags; result = dict_del(real_dict, key); + dict->flags = real_dict->flags; msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key, result == 0 ? "success" : real_dict->error ? "error" : "failed"); @@ -104,7 +110,9 @@ static int dict_debug_sequence(DICT *dict, int function, DICT *real_dict = dict_debug->real_dict; int result; + real_dict->flags = dict->flags; result = dict_seq(real_dict, function, key, value); + dict->flags = real_dict->flags; if (result == 0) msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name, *key, *value); diff --git a/postfix/src/util/dict_inline.c b/postfix/src/util/dict_inline.c index af646007c..c6202a6ac 100644 --- a/postfix/src/util/dict_inline.c +++ b/postfix/src/util/dict_inline.c @@ -31,114 +31,30 @@ /* System library. */ #include +#include /* Utility library. */ #include #include -#include #include #include +#include #include /* Application-specific. */ -typedef struct { - DICT dict; /* generic members */ - HTABLE *table; /* lookup table */ - HTABLE_INFO **info; /* for iterator */ - HTABLE_INFO **cursor; /* ditto */ -} DICT_INLINE; - -/* dict_inline_lookup - search inline table */ - -static const char *dict_inline_lookup(DICT *dict, const char *name) -{ - DICT_INLINE *dict_inline = (DICT_INLINE *) dict; - const char *result = 0; - - /* - * Optionally fold the key. - */ - if (dict->flags & DICT_FLAG_FOLD_FIX) { - if (dict->fold_buf == 0) - dict->fold_buf = vstring_alloc(10); - vstring_strcpy(dict->fold_buf, name); - name = lowercase(vstring_str(dict->fold_buf)); - } - - /* - * Look up the value. - */ - result = htable_find(dict_inline->table, name); - - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, result); -} - -/* dict_inline_sequence - traverse the dictionary */ - -static int dict_inline_sequence(DICT *dict, int function, - const char **key, const char **value) -{ - const char *myname = "dict_inline_sequence"; - DICT_INLINE *dict_inline = (DICT_INLINE *) dict; - - /* - * Determine and execute the seek function. - */ - switch (function) { - case DICT_SEQ_FUN_FIRST: - if (dict_inline->info == 0) - dict_inline->info = htable_list(dict_inline->table); - dict_inline->cursor = dict_inline->info; - break; - case DICT_SEQ_FUN_NEXT: - if (dict_inline->cursor[0]) - dict_inline->cursor += 1; - break; - default: - msg_panic("%s: invalid function: %d", myname, function); - } - - /* - * Return the entry under the cursor. - */ - if (dict_inline->cursor[0]) { - *key = dict_inline->cursor[0]->key; - *value = dict_inline->cursor[0]->value; - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); - } else { - *key = 0; - *value = 0; - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); - } -} - -/* dict_inline_close - disassociate from inline table */ - -static void dict_inline_close(DICT *dict) -{ - DICT_INLINE *dict_inline = (DICT_INLINE *) dict; - - htable_free(dict_inline->table, myfree); - if (dict_inline->info) - myfree((void *) dict_inline->info); - if (dict->fold_buf) - vstring_free(dict->fold_buf); - dict_free(dict); -} - /* dict_inline_open - open inline table */ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) { - DICT_INLINE *dict_inline; + DICT *dict; char *cp, *saved_name = 0; size_t len; - HTABLE *table = 0; char *nameval, *vname, *value; const char *err = 0; char *xperr = 0; + int count = 0; /* * Clarity first. Let the optimizer worry about redundant code. @@ -149,8 +65,6 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) myfree(saved_name); \ if (xperr != 0) \ myfree(xperr); \ - if (table != 0) \ - htable_free(table, myfree); \ return (__d); \ } while (0) @@ -163,6 +77,19 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) "%s:%s map requires O_RDONLY access mode", DICT_TYPE_INLINE, name)); + /* + * UTF-8 syntax check. + */ + if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags) + && allascii(name) == 0 + && valid_utf8_string(name, strlen(name)) == 0) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "bad UTF-8 syntax: \"%s:%s\"; " + "need \"%s:{name=value...}\"", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE)); + /* * Parse the table into its constituent name=value pairs. */ @@ -175,15 +102,23 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) DICT_TYPE_INLINE, name, DICT_TYPE_INLINE)); - table = htable_create(5); + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, name, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_INLINE); while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { if ((nameval[0] != CHARS_BRACE[0] || (err = xperr = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0) && (err = split_nameval(nameval, &vname, &value)) != 0) break; - (void) htable_enter(table, vname, mystrdup(value)); + + /* No duplicate checks. See comments in dict_thash.c. */ + dict->update(dict, vname, value); + count += 1; } - if (err != 0 || table->used == 0) + if (err != 0 || count == 0) { + dict->close(dict); DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, open_flags, dict_flags, "%s: \"%s:%s\"; " @@ -191,21 +126,8 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) err != 0 ? err : "empty table", DICT_TYPE_INLINE, name, DICT_TYPE_INLINE)); + } + dict->owner.status = DICT_OWNER_TRUSTED; - /* - * Bundle up the result. - */ - dict_inline = (DICT_INLINE *) - dict_alloc(DICT_TYPE_INLINE, name, sizeof(*dict_inline)); - dict_inline->dict.lookup = dict_inline_lookup; - dict_inline->dict.sequence = dict_inline_sequence; - dict_inline->dict.close = dict_inline_close; - dict_inline->dict.flags = dict_flags | DICT_FLAG_FIXED; - dict_inline->dict.owner.status = DICT_OWNER_TRUSTED; - if (dict_flags & DICT_FLAG_FOLD_FIX) - dict_inline->dict.fold_buf = vstring_alloc(10); - dict_inline->info = 0; - dict_inline->table = table; - table = 0; - DICT_INLINE_RETURN(DICT_DEBUG (&dict_inline->dict)); + DICT_INLINE_RETURN(DICT_DEBUG (dict)); } diff --git a/postfix/src/util/dict_inline.ref b/postfix/src/util/dict_inline.ref index 771ca9f6a..e64e6d040 100644 --- a/postfix/src/util/dict_inline.ref +++ b/postfix/src/util/dict_inline.ref @@ -10,7 +10,14 @@ owner=trusted (uid=2147483647) owner=trusted (uid=2147483647) owner=trusted (uid=2147483647) > get foo -foo=xx +foo=XX +> get bar +bar=lotsa stuff +> get baz +baz: not found +owner=trusted (uid=2147483647) +> get foo +foo=XX > get bar bar=lotsa stuff > get baz diff --git a/postfix/src/util/dict_lmdb.c b/postfix/src/util/dict_lmdb.c index 3cbea12f9..50f5c7090 100644 --- a/postfix/src/util/dict_lmdb.c +++ b/postfix/src/util/dict_lmdb.c @@ -235,7 +235,6 @@ static int dict_lmdb_update(DICT *dict, const char *name, const char *value) name = lowercase(vstring_str(dict->fold_buf)); } mdb_key.mv_data = (void *) name; - mdb_value.mv_data = (void *) value; mdb_key.mv_size = strlen(name); mdb_value.mv_size = strlen(value); @@ -435,6 +434,8 @@ static int dict_lmdb_sequence(DICT *dict, int function, if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0) *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, mdb_value.mv_size); + else + *value = ""; /* XXX */ break; /* diff --git a/postfix/src/util/dict_open.c b/postfix/src/util/dict_open.c index 0880b95cd..a18a6ce34 100644 --- a/postfix/src/util/dict_open.c +++ b/postfix/src/util/dict_open.c @@ -66,6 +66,10 @@ /* int dict_longjmp(dict, val) /* DICT *dict; /* int val; +/* +/* void dict_type_override(dict, type) +/* DICT *dict; +/* const char *type; /* DESCRIPTION /* This module implements a low-level interface to multiple /* physical dictionary types. @@ -119,11 +123,14 @@ /* With databases whose lookup fields are fixed-case strings, /* fold the search string to lower case before accessing the /* database. This includes hash:, cdb:, dbm:. nis:, ldap:, -/* *sql. +/* *sql. WARNING: case folding is supported only for ASCII or +/* valid UTF-8. /* .IP DICT_FLAG_FOLD_MUL /* With databases where one lookup field can match both upper /* and lower case, fold the search key to lower case before -/* accessing the database. This includes regexp: and pcre: +/* accessing the database. This includes regexp: and pcre:. +/* WARNING: case folding is supported only for ASCII or valid +/* UTF-8. /* .IP DICT_FLAG_FOLD_ANY /* Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL). /* .IP DICT_FLAG_SYNC_UPDATE @@ -149,6 +156,12 @@ /* and must trap exceptions from the database client with dict_setjmp(). /* .IP DICT_FLAG_DEBUG /* Enable additional logging. +/* .IP DICT_FLAG_UTF8_ENABLE +/* With util_utf8_enable != 0, require that lookup/update/delete +/* keys and values are valid UTF-8. Skip a lookup/update/delete +/* request with a non-UTF-8 key, skip an update request with +/* a non-UTF-8 value, and fail a lookup request with a non-UTF-8 +/* value. /* .PP /* Specify DICT_FLAG_NONE for no special processing. /* @@ -179,6 +192,10 @@ /* dict_open3() takes separate arguments for dictionary type and /* name, but otherwise performs the same functions as dict_open(). /* +/* The dict_get(), dict_put(), dict_del(), and dict_seq() +/* macros evaluate their first argument multiple times. +/* These names should have been in uppercase. +/* /* dict_get() retrieves the value stored in the named dictionary /* under the given key. A null pointer means the value was not found. /* As with dict_lookup(), the result is owned by the lookup table @@ -226,6 +243,10 @@ /* NB: non-local jumps such as dict_longjmp() are not safe for /* jumping out of any routine that manipulates DICT data. /* longjmp() like calls are best avoided in signal handlers. +/* +/* dict_type_override() changes the symbolic dictionary type. +/* This is used by dictionaries whose internals are based on +/* some other dictionary type. /* DIAGNOSTICS /* Fatal error: open error, unsupported dictionary type, attempt to /* update non-writable dictionary. @@ -457,6 +478,10 @@ DICT *dict_open3(const char *dict_type, const char *dict_name, msg_fatal("%s:%s: unable to get exclusive lock: %m", dict_type, dict_name); } + /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)) + dict = dict_utf8_activate(dict); return (dict); } @@ -532,6 +557,14 @@ DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb) return (old_cb); } +/* dict_type_override - disguise a dictionary type */ + +void dict_type_override(DICT *dict, const char *type) +{ + myfree(dict->type); + dict->type = mystrdup(type); +} + #ifdef TEST /* diff --git a/postfix/src/util/dict_test.c b/postfix/src/util/dict_test.c index 0fb0133bf..5f394ff81 100644 --- a/postfix/src/util/dict_test.c +++ b/postfix/src/util/dict_test.c @@ -76,9 +76,11 @@ void dict_test(int argc, char **argv) dict_flags |= DICT_FLAG_LOCK; if ((dict_flags & (DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE)) == 0) dict_flags |= DICT_FLAG_DUP_REPLACE; + dict_flags |= DICT_FLAG_UTF8_REQUEST; vstream_fflush(VSTREAM_OUT); dict_name = argv[optind]; dict_allow_surrogate = 1; + util_utf8_enable = 1; dict = dict_open(dict_name, open_flags, dict_flags); dict_register(dict_name, dict); vstream_printf("owner=%s (uid=%ld)\n", @@ -125,8 +127,6 @@ void dict_test(int argc, char **argv) if (dict_put(dict, key, value) != 0) vstream_printf("%s: %s\n", key, dict->error ? "error" : "not updated"); - else - vstream_printf("%s=%s\n", key, value); } else if (strcmp(cmd, "first") == 0 && !key && !value) { if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0) vstream_printf("%s=%s\n", key, value); diff --git a/postfix/src/util/dict_test.ref b/postfix/src/util/dict_test.ref index 0872fb09a..54e91f88d 100644 --- a/postfix/src/util/dict_test.ref +++ b/postfix/src/util/dict_test.ref @@ -12,7 +12,6 @@ foo=fooval > del foo foo: deleted > put baz bazval -baz=bazval > get baz baz=bazval > del baz diff --git a/postfix/src/util/dict_thash.c b/postfix/src/util/dict_thash.c index aaef6613f..cad181cfd 100644 --- a/postfix/src/util/dict_thash.c +++ b/postfix/src/util/dict_thash.c @@ -41,121 +41,33 @@ /* Utility library. */ #include -#include -#include #include #include #include #include #include +#include #include -#include /* Application-specific. */ -typedef struct { - DICT dict; /* generic members */ - HTABLE *table; /* in-memory hash */ - HTABLE_INFO **info; /* for iterator */ - HTABLE_INFO **cursor; /* ditto */ -} DICT_THASH; - #define STR vstring_str - -/* dict_thash_lookup - find database entry */ - -static const char *dict_thash_lookup(DICT *dict, const char *name) -{ - DICT_THASH *dict_thash = (DICT_THASH *) dict; - const char *result = 0; - - /* - * Optionally fold the key. - */ - if (dict->flags & DICT_FLAG_FOLD_FIX) { - if (dict->fold_buf == 0) - dict->fold_buf = vstring_alloc(10); - vstring_strcpy(dict->fold_buf, name); - name = lowercase(vstring_str(dict->fold_buf)); - } - - /* - * Look up the value. - */ - result = htable_find(dict_thash->table, name); - - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, result); -} - -/* dict_thash_sequence - traverse the dictionary */ - -static int dict_thash_sequence(DICT *dict, int function, - const char **key, const char **value) -{ - const char *myname = "dict_thash_sequence"; - DICT_THASH *dict_thash = (DICT_THASH *) dict; - - /* - * Determine and execute the seek function. - */ - switch (function) { - case DICT_SEQ_FUN_FIRST: - if (dict_thash->info == 0) - dict_thash->info = htable_list(dict_thash->table); - dict_thash->cursor = dict_thash->info; - break; - case DICT_SEQ_FUN_NEXT: - if (dict_thash->cursor[0]) - dict_thash->cursor += 1; - break; - default: - msg_panic("%s: invalid function: %d", myname, function); - } - - /* - * Return the entry under the cursor. - */ - if (dict_thash->cursor[0]) { - *key = dict_thash->cursor[0]->key; - *value = dict_thash->cursor[0]->value; - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); - } else { - *key = 0; - *value = 0; - DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); - } -} - -/* dict_thash_close - disassociate from data base */ - -static void dict_thash_close(DICT *dict) -{ - DICT_THASH *dict_thash = (DICT_THASH *) dict; - - htable_free(dict_thash->table, myfree); - if (dict_thash->info) - myfree((void *) dict_thash->info); - if (dict->fold_buf) - vstring_free(dict->fold_buf); - dict_free(dict); -} +#define LEN VSTRING_LEN /* dict_thash_open - open flat text data base */ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) { - DICT_THASH *dict_thash; - VSTREAM *fp = 0; + DICT *dict; + VSTREAM *fp = 0; /* DICT_THASH_OPEN_RETURN() */ struct stat st; time_t before; time_t after; - VSTRING *line_buffer = 0; + VSTRING *line_buffer = 0; /* DICT_THASH_OPEN_RETURN() */ int lineno; int last_line; char *key; char *value; - HTABLE *table; - HTABLE_INFO *ht; /* * Let the optimizer worry about eliminating redundant code. @@ -187,13 +99,31 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags, "open database %s: %m", path)); - } + } + + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_THASH); + if (line_buffer == 0) line_buffer = vstring_alloc(100); last_line = 0; - table = htable_create(13); while (readllines(line_buffer, fp, &last_line, &lineno)) { + /* + * First some UTF-8 checks sans casefolding. + */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) + && allascii(STR(line_buffer)) == 0 + && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } + /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. @@ -220,31 +150,31 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) msg_warn("%s, line %d: record is in \"key: value\" format;" " is this an alias file?", path, lineno); - /* - * Optionally fold the key. - */ - if (dict_flags & DICT_FLAG_FOLD_FIX) - lowercase(key); - /* * Store the value under the key. Handle duplicates - * appropriately. + * appropriately. XXX Move this into dict_ht, but 1) that map + * ignores duplicates by default and we would have to check that + * we won't break existing code that depends on such benavior; 2) + * by inlining the checks here we can degrade gracefully instead + * of terminating with a fatal error. See comment in dict_inline.c. */ - if ((ht = htable_locate(table, key)) != 0) { + if (dict->lookup(dict, key) != 0) { if (dict_flags & DICT_FLAG_DUP_IGNORE) { /* void */ ; } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { - myfree(ht->value); - ht->value = mystrdup(value); + dict->update(dict, key, value); } else if (dict_flags & DICT_FLAG_DUP_WARN) { msg_warn("%s, line %d: duplicate entry: \"%s\"", path, lineno, key); } else { - msg_fatal("%s, line %d: duplicate entry: \"%s\"", - path, lineno, key); + dict->close(dict); + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "%s, line %d: duplicate entry: \"%s\"", + path, lineno, key)); } } else { - htable_enter(table, key, mystrdup(value)); + dict->update(dict, key, value); } } @@ -263,27 +193,14 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) /* * Yes, it is hot. Discard the result and read the file again. */ - htable_free(table, myfree); + dict->close(dict); if (msg_verbose > 1) msg_info("pausing to let file %s cool down", path); doze(300000); } - /* - * Create the in-memory table. - */ - dict_thash = (DICT_THASH *) - dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash)); - dict_thash->dict.lookup = dict_thash_lookup; - dict_thash->dict.sequence = dict_thash_sequence; - dict_thash->dict.close = dict_thash_close; - dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED; - if (dict_flags & DICT_FLAG_FOLD_FIX) - dict_thash->dict.fold_buf = vstring_alloc(10); - dict_thash->info = 0; - dict_thash->table = table; - dict_thash->dict.owner.uid = st.st_uid; - dict_thash->dict.owner.status = (st.st_uid != 0); + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); - DICT_THASH_OPEN_RETURN(DICT_DEBUG (&dict_thash->dict)); + DICT_THASH_OPEN_RETURN(DICT_DEBUG (dict)); } diff --git a/postfix/src/util/dict_thash.map b/postfix/src/util/dict_thash.map index 67e75784f..95b54fd82 100644 --- a/postfix/src/util/dict_thash.map +++ b/postfix/src/util/dict_thash.map @@ -12,3 +12,4 @@ attr_scan0.c 15454 attr_scan64.c 17256 attr_scan_plain.c 16924 auto_clnt.c 9819 +ABCDEF 012345 diff --git a/postfix/src/util/dict_utf8.c b/postfix/src/util/dict_utf8.c new file mode 100644 index 000000000..17fe06131 --- /dev/null +++ b/postfix/src/util/dict_utf8.c @@ -0,0 +1,327 @@ +/*++ +/* NAME +/* dict_utf8 3 +/* SUMMARY +/* dictionary UTF-8 helpers +/* SYNOPSIS +/* #include +/* +/* DICT *dict_utf8_activate( +/* DICT *dict) +/* AUXILIARY FUNCTIONS +/* char *dict_utf8_check_fold( +/* DICT *dict, +/* const char *string, +/* CONST_CHAR_STAR *err, +/* int fold_flag) +/* +/* int dict_utf8_check( +/* const char *string, +/* CONST_CHAR_STAR *err) +/* DESCRIPTION +/* dict_utf8_activate() wraps a dictionary's lookup/update/delete +/* methods with code that enforces UTF-8 checks on keys and +/* values, and that logs a warning when incorrect UTF-8 is +/* encountered. The original dictionary handle becomes invalid. +/* +/* The wrapper code enforces a policy that maximizes application +/* robustness (it avoids the need for new error-handling code +/* paths in application code). Attempts to store non-UTF-8 +/* keys or values are skipped while reporting a non-error +/* status, attempts to look up or delete non-UTF-8 keys are +/* skipped while reporting a non-error status, and lookup +/* results that contain a non-UTF-8 value are blocked while +/* reporting a configuration error. +/* +/* The dict_utf8_check* functions may be invoked to perform +/* UTF-8 validity checks when util_utf8_enable is non-zero. +/* +/* dict_utf8_check_fold() optionally folds a string, and checks +/* it for UTF-8 validity. The result is the possibly-folded +/* string, or a null pointer in case of error. +/* +/* dict_utf8_check() checks a string for UTF-8 validity. The +/* result is zero in case of error. +/* BUGS +/* dict_utf8_activate() does not nest. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Backed-up accessor function pointers. + */ +typedef struct { + const char *(*lookup) (struct DICT *, const char *); + int (*update) (struct DICT *, const char *, const char *); + int (*delete) (struct DICT *, const char *); +} DICT_UTF8_BACKUP; + + /* + * The goal is to maximize robustness: bad UTF-8 should not appear in keys, + * because those are derived from controlled inputs, and values should be + * printable before they are stored. But if we failed to check something + * then it should not result in fatal errors and thus open up the system for + * a denial-of-service attack. + * + * Proposed over-all policy: skip attempts to store invalid UTF-8 lookup keys + * or values. Rationale: some storage may not permit malformed UTF-8. This + * maximizes program robustness. If we get an invalid lookup result, report + * a configuration error. + * + * LOOKUP + * + * If the key is invalid, log a warning and skip the request. Rationale: the + * item cannot exist. + * + * If the lookup result is invalid, log a warning and return a configuration + * error. + * + * UPDATE + * + * If the key is invalid, then log a warning and skip the request. Rationale: + * the item cannot exist. + * + * If the value is invalid, log a warning and skip the request. Rationale: + * storage may not permit malformed UTF-8. This maximizes program + * robustness. + * + * DELETE + * + * If the key is invalid, then skip the request. Rationale: the item cannot + * exist. + */ + +/* dict_utf8_check_fold - casefold or validate string */ + +char *dict_utf8_check_fold(DICT *dict, const char *string, + CONST_CHAR_STAR *err) +{ + int fold_flag = (dict->flags & DICT_FLAG_FOLD_ANY); + + /* + * Casefold and implicitly validate UTF-8. + */ + if (fold_flag != 0 && (fold_flag & (dict->flags & DICT_FLAG_FIXED) ? + DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL)) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + return (casefold(dict->flags & DICT_FLAG_UTF8_ACTIVE, + dict->fold_buf, string, err)); + } + + /* + * Validate UTF-8 without casefolding. + */ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + return ((char *) string); +} + +/* dict_utf8_check validate UTF-8 string */ + +int dict_utf8_check(const char *string, CONST_CHAR_STAR *err) +{ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + return (1); +} + +/* dict_utf8_lookup - UTF-8 lookup method wrapper */ + +static const char *dict_utf8_lookup(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + const char *value; + int saved_flags; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (0); + } + + /* + * Proxy the request with casefolding turned off. + */ + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = (void *) dict + dict->size; + value = backup->lookup(dict, fold_res); + dict->flags |= saved_flags; + + /* + * Validate the result, and if invalid fail the request. + */ + if (value != 0 && dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_CONFIG; + return (0); + } else { + return (value); + } +} + +/* dict_utf8_update - UTF-8 update method wrapper */ + +static int dict_utf8_update(DICT *dict, const char *key, const char *value) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate or fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Validate the value, and if invalid skip the request. + */ + else if (dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = (void *) dict + dict->size; + status = backup->update(dict, fold_res, value); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_delete - UTF-8 delete method wrapper */ + +static int dict_utf8_delete(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = (void *) dict + dict->size; + status = backup->delete(dict, fold_res); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_activate - wrap a legacy dict object for UTF-8 processing */ + +DICT *dict_utf8_activate(DICT *dict) +{ + const char myname[] = "dict_utf8_activate"; + DICT_UTF8_BACKUP *backup; + + /* + * Sanity check. + */ + if (util_utf8_enable == 0) + msg_panic("%s: Unicode support is not available", myname); + if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0) + msg_panic("%s: %s:%s does not request Unicode support", + myname, dict->type, dict->name); + if (dict->flags & DICT_FLAG_UTF8_ACTIVE) + msg_panic("%s: %s:%s Unicode support is already activated", + myname, dict->type, dict->name); + + /* + * Unlike dict_debug(3) we do not put a proxy dict object in front of the + * encapsulated object, because then we would have to bidirectionally + * propagate changes in the data members (errors, flags, jbuf, and so on) + * between proxy object and encapsulated object. + * + * Instead we append ourselves to the encapsulated dict object itself, and + * redirect some function pointers. This approach does not yet generalize + * to arbitrary levels of encapsulation. That is, it does not co-exist + * with dict_debug(3) which is broken for the reasons stated above. + */ + dict = myrealloc(dict, dict->size + sizeof(*backup)); + backup = (void *) dict + dict->size; + + /* + * Interpose on the lookup/update/delete methods. It is a conscious + * decision not to tinker with the iterator or destructor. + */ + backup->lookup = dict->lookup; + backup->update = dict->update; + backup->delete = dict->delete; + + dict->lookup = dict_utf8_lookup; + dict->update = dict_utf8_update; + dict->delete = dict_utf8_delete; + + /* + * Leave our mark. See sanity check above. + */ + dict->flags |= DICT_FLAG_UTF8_ACTIVE; + + return (dict); +} diff --git a/postfix/src/util/dict_utf8_test.in b/postfix/src/util/dict_utf8_test.in new file mode 100644 index 000000000..748d25b36 --- /dev/null +++ b/postfix/src/util/dict_utf8_test.in @@ -0,0 +1,12 @@ +#!/bin/sh + +awk 'BEGIN { + print "flags" + print "verbose" + printf "get foo\n" + printf "put %c%c%c xxx\n", 128, 128, 128 + printf "get %c%c%c\n", 128, 128, 128 + printf "put xxx %c%c%c\n", 128, 128, 128 + printf "get xxx\n" + exit +}' | ./dict_open internal:whatever write utf8_request diff --git a/postfix/src/util/dict_utf8_test.ref b/postfix/src/util/dict_utf8_test.ref new file mode 100644 index 000000000..c29a336cf --- /dev/null +++ b/postfix/src/util/dict_utf8_test.ref @@ -0,0 +1,15 @@ +owner=trusted (uid=2147483647) +> flags +dict flags fixed|lock|replace|utf8_request|utf8_active +> verbose +> get foo +foo: not found +> put €€€ xxx +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +> get €€€ +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +€€€: not found +> put xxx €€€ +./dict_open: warning: internal:whatever: key "xxx": non-UTF-8 value "???": malformed UTF-8 or invalid codepoint +> get xxx +xxx: not found diff --git a/postfix/src/util/host_port.c b/postfix/src/util/host_port.c index defa3b455..c4e86166d 100644 --- a/postfix/src/util/host_port.c +++ b/postfix/src/util/host_port.c @@ -91,7 +91,7 @@ #include #include -#include /* XXX temp_utf8_kludge */ +#include /* XXX util_utf8_enable */ #include /* Global library. */ @@ -158,7 +158,7 @@ const char *host_port(char *buf, char **host, char *def_host, * network addresses instead of requiring proper [ipaddress] forms. */ if (*host != def_host - && !valid_utf8_hostname(temp_utf8_kludge, *host, DONT_GRIPE) + && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE) && !valid_hostaddr(*host, DONT_GRIPE)) return ("valid hostname or network address required"); if (*port != def_service && ISDIGIT(**port) && !alldig(*port)) diff --git a/postfix/src/util/match_list.c b/postfix/src/util/match_list.c index 7bf88bd58..36ee0902a 100644 --- a/postfix/src/util/match_list.c +++ b/postfix/src/util/match_list.c @@ -6,7 +6,8 @@ /* SYNOPSIS /* #include /* -/* MATCH_LIST *match_list_init(flags, pattern_list, count, func,...) +/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...) +/* const char *pname; /* int flags; /* const char *pattern_list; /* int count; @@ -19,40 +20,59 @@ /* void match_list_free(list) /* MATCH_LIST *list; /* DESCRIPTION -/* This module implements a framework for tests for list membership. -/* The actual tests are done by user-supplied functions. +/* This module implements a framework for tests for list +/* membership. The actual tests are done by user-supplied +/* functions. /* /* Patterns are separated by whitespace and/or commas. A pattern /* is either a string, a file name (in which case the contents /* of the file are substituted for the file name) or a type:name -/* lookup table specification. In order to reverse the result of -/* a pattern match, precede a pattern with an exclamation point (!). +/* lookup table specification. In order to reverse the result +/* of a pattern match, precede a pattern with an exclamation +/* point (!). /* -/* match_list_init() performs initializations. The flags argument -/* specifies the bit-wise OR of zero or more of the following: +/* match_list_init() performs initializations. When the global +/* util_utf8_enable variable is non-zero, and when the code +/* is compiled with EAI support, string comparison will use +/* caseless UTF-8 mode. Otherwise, only ASCII characters will +/* be casefolded. +/* +/* match_list_match() matches strings against the specified +/* pattern list, passing the first string to the first function +/* given to match_list_init(), the second string to the second +/* function, and so on. +/* +/* match_list_free() releases storage allocated by match_list_init(). +/* +/* Arguments: +/* .IP pname +/* Parameter name or other identiying information that is +/* prepended to error messages. +/* .IP flags +/* Specifies the bit-wise OR of zero or more of the following: /* .RS /* .IP MATCH_FLAG_PARENT -/* The hostname pattern foo.com matches any name within the domain -/* foo.com. If this flag is cleared, foo.com matches itself -/* only, and .foo.com matches any name below the domain foo.com. +/* The hostname pattern foo.com matches any name within the +/* domain foo.com. If this flag is cleared, foo.com matches +/* itself only, and .foo.com matches any name below the domain +/* foo.com. /* .IP MATCH_FLAG_RETURN /* Request that match_list_match() logs a warning and returns /* zero (with list->error set to a non-zero dictionary error /* code) instead of raising a fatal run-time error. /* .RE /* Specify MATCH_FLAG_NONE to request none of the above. -/* The pattern_list argument specifies a list of patterns. The third -/* argument specifies how many match functions follow. -/* -/* match_list_match() matches strings against the specified pattern -/* list, passing the first string to the first function given to -/* match_list_init(), the second string to the second function, and -/* so on. -/* -/* match_list_free() releases storage allocated by match_list_init(). +/* .IP pattern_list +/* A list of patterns. +/* .IP count +/* Specifies how many match functions follow. +/* .IP list +/* Pattern list produced by match_list_init(). +/* .IP string +/* Search string. /* DIAGNOSTICS /* Fatal error: unable to open or read a match_list file; invalid -/* match_list pattern. +/* match_list pattern; casefold error (UTF-8 mode only). /* SEE ALSO /* host_match(3) match hosts by name or by address /* LICENSE @@ -94,7 +114,8 @@ /* match_list_parse - parse buffer, destroy buffer */ -static ARGV *match_list_parse(ARGV *list, char *string, int init_match) +static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list, + char *string, int init_match) { const char *myname = "match_list_parse"; VSTRING *buf = vstring_alloc(10); @@ -105,39 +126,52 @@ static ARGV *match_list_parse(ARGV *list, char *string, int init_match) char *item; char *map_type_name_flags; int match; + const char *utf8_err; + /* + * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search + * string at the beginning of a search, and we use strcmp() for string + * comparison. This works because string patterns are casefolded during + * match_list initialization, and databases are supposed to fold case + * upon creation. + */ #define OPEN_FLAGS O_RDONLY -#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX) +#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST) #define STR(x) vstring_str(x) /* * /filename contents are expanded in-line. To support !/filename we * prepend the negation operator to each item from the file. + * + * If there is an error, implement graceful degradation by inserting a + * pseudo table whose lookups fail with a warning message. */ while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) { if (*start == '#') { msg_warn("%s: comment at end of line is not supported: %s %s", - myname, start, bp); + match_list->pname, start, bp); break; } for (match = init_match, item = start; *item == '!'; item++) match = !match; if (*item == 0) - msg_fatal("%s: no pattern after '!'", myname); + /* No graceful degradation for this... */ + msg_fatal("%s: no pattern after '!'", match_list->pname); if (*item == '/') { /* /file/name */ if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { + /* Replace unusable pattern with pseudo table. */ vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); - /* XXX Should increment existing map refcount. */ if (dict_handle(STR(buf)) == 0) dict_register(STR(buf), dict_surrogate(DICT_TYPE_NOFILE, item, OPEN_FLAGS, DICT_FLAGS, "open file %s: %m", item)); - argv_add(list, STR(buf), (char *) 0); + argv_add(pat_list, STR(buf), (char *) 0); } else { while (vstring_fgets(buf, fp)) if (vstring_str(buf)[0] != '#') - list = match_list_parse(list, vstring_str(buf), match); + pat_list = match_list_parse(match_list, pat_list, + vstring_str(buf), match); if (vstream_fclose(fp)) msg_fatal("%s: read file %s: %m", myname, item); } @@ -145,23 +179,35 @@ static ARGV *match_list_parse(ARGV *list, char *string, int init_match) vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); map_type_name_flags = STR(buf) + (match == 0); - /* XXX Should increment existing map refcount. */ if (dict_handle(map_type_name_flags) == 0) dict_register(map_type_name_flags, dict_open(item, OPEN_FLAGS, DICT_FLAGS)); - argv_add(list, STR(buf), (char *) 0); + argv_add(pat_list, STR(buf), (char *) 0); } else { /* other pattern */ - argv_add(list, match ? item : - STR(vstring_sprintf(buf, "!%s", item)), (char *) 0); + if (casefold(util_utf8_enable, match_list->fold_buf, match ? + item : STR(vstring_sprintf(buf, "!%s", item)), + &utf8_err) == 0) { + /* Replace unusable pattern with pseudo table. */ + vstring_sprintf(match_list->fold_buf, "%s:%s", + DICT_TYPE_NOUTF8, item); + if (dict_handle(STR(match_list->fold_buf)) == 0) + dict_register(STR(match_list->fold_buf), + dict_surrogate(DICT_TYPE_NOUTF8, item, + OPEN_FLAGS, DICT_FLAGS, + "casefold error: %s", + utf8_err)); + } + argv_add(pat_list, STR(match_list->fold_buf), (char *) 0); } } vstring_free(buf); - return (list); + return (pat_list); } /* match_list_init - initialize pattern list */ -MATCH_LIST *match_list_init(int flags, const char *patterns, int match_count,...) +MATCH_LIST *match_list_init(const char *pname, int flags, + const char *patterns, int match_count,...) { MATCH_LIST *list; char *saved_patterns; @@ -172,6 +218,7 @@ MATCH_LIST *match_list_init(int flags, const char *patterns, int match_count,... msg_panic("match_list_init: bad flags 0x%x", flags); list = (MATCH_LIST *) mymalloc(sizeof(*list)); + list->pname = mystrdup(pname); list->flags = flags; list->match_count = match_count; list->match_func = @@ -183,11 +230,13 @@ MATCH_LIST *match_list_init(int flags, const char *patterns, int match_count,... list->match_func[i] = va_arg(ap, MATCH_LIST_FN); va_end(ap); list->error = 0; + list->fold_buf = vstring_alloc(20); #define DO_MATCH 1 saved_patterns = mystrdup(patterns); - list->patterns = match_list_parse(argv_alloc(1), saved_patterns, DO_MATCH); + list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns, + DO_MATCH); argv_terminate(list->patterns); myfree(saved_patterns); return (list); @@ -203,6 +252,7 @@ int match_list_match(MATCH_LIST *list,...) int match; int i; va_list ap; + const char *utf8_err; /* * Iterate over all patterns in the list, stop at the first match. @@ -216,11 +266,18 @@ int match_list_match(MATCH_LIST *list,...) for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) { for (match = 1; *pat == '!'; pat++) match = !match; - for (i = 0; i < list->match_count; i++) - if (list->match_func[i] (list, list->match_args[i], pat)) + for (i = 0; i < list->match_count; i++) { + if (casefold(util_utf8_enable, list->fold_buf, + list->match_args[i], &utf8_err) == 0) { + msg_warn("%s: casefold error for \"%s\": %s", + myname, list->match_args[i], utf8_err); + continue; + } + if (list->match_func[i] (list, STR(list->fold_buf), pat)) return (match); else if (list->error != 0) return (0); + } } if (msg_verbose) for (i = 0; i < list->match_count; i++) @@ -233,8 +290,10 @@ int match_list_match(MATCH_LIST *list,...) void match_list_free(MATCH_LIST *list) { /* XXX Should decrement map refcounts. */ + myfree(list->pname); argv_free(list->patterns); myfree((void *) list->match_func); myfree((void *) list->match_args); + vstring_free(list->fold_buf); myfree((void *) list); } diff --git a/postfix/src/util/match_list.h b/postfix/src/util/match_list.h index d51b6e831..d8b779430 100644 --- a/postfix/src/util/match_list.h +++ b/postfix/src/util/match_list.h @@ -15,6 +15,7 @@ * Utility library. */ #include +#include /* * External interface. @@ -24,11 +25,13 @@ typedef struct MATCH_LIST MATCH_LIST; typedef int (*MATCH_LIST_FN) (MATCH_LIST *, const char *, const char *); struct MATCH_LIST { + char *pname; /* used in error messages */ int flags; /* processing options */ ARGV *patterns; /* one pattern each */ int match_count; /* match function/argument count */ MATCH_LIST_FN *match_func; /* match functions */ const char **match_args; /* match arguments */ + VSTRING *fold_buf; /* case-folded pattern string */ int error; /* last operation */ }; @@ -37,7 +40,7 @@ struct MATCH_LIST { #define MATCH_FLAG_RETURN (1<<1) #define MATCH_FLAG_ALL (MATCH_FLAG_PARENT | MATCH_FLAG_RETURN) -extern MATCH_LIST *match_list_init(int, const char *, int,...); +extern MATCH_LIST *match_list_init(const char *, int, const char *, int,...); extern int match_list_match(MATCH_LIST *,...); extern void match_list_free(MATCH_LIST *); @@ -61,4 +64,3 @@ extern int match_hostaddr(MATCH_LIST *, const char *, const char *); /*--*/ #endif - diff --git a/postfix/src/util/match_ops.c b/postfix/src/util/match_ops.c index f978d2629..6542f4910 100644 --- a/postfix/src/util/match_ops.c +++ b/postfix/src/util/match_ops.c @@ -99,9 +99,9 @@ static int match_error(MATCH_LIST *list, const char *fmt,...) vstring_vsprintf(buf, fmt, ap); va_end(ap); if (list->flags & MATCH_FLAG_RETURN) { - msg_warn("%s", vstring_str(buf)); + msg_warn("%s: %s", list->pname, vstring_str(buf)); } else { - msg_fatal("%s", vstring_str(buf)); + msg_fatal("%s: %s", list->pname, vstring_str(buf)); } vstring_free(buf); return (0); @@ -115,7 +115,7 @@ int match_string(MATCH_LIST *list, const char *string, const char *pattern) DICT *dict; if (msg_verbose) - msg_info("%s: %s ~? %s", myname, string, pattern); + msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern); /* * Try dictionary lookup: exact match. @@ -132,9 +132,10 @@ int match_string(MATCH_LIST *list, const char *string, const char *pattern) } /* - * Try an exact string match. + * Try an exact string match. Note that the string and pattern are + * already casefolded. */ - if (strcasecmp(string, pattern) == 0) { + if (strcmp(string, pattern) == 0) { return (1); } @@ -156,7 +157,7 @@ int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) DICT *dict; if (msg_verbose) - msg_info("%s: %s ~? %s", myname, name, pattern); + msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern); /* * Try dictionary lookup: exact match and parent domains. @@ -171,9 +172,9 @@ int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) if (entry == name || (dict->flags & DICT_FLAG_FIXED)) { match = (dict_get(dict, entry) != 0); if (msg_verbose > 1) - msg_info("%s: lookup %s:%s %s: %s", - myname, dict->type, dict->name, entry, - match ? "found" : "notfound"); + msg_info("%s: %s: lookup %s:%s %s: %s", + myname, list->pname, dict->type, dict->name, + entry, match ? "found" : "notfound"); if (match != 0) break; if ((list->error = dict->error) != 0) @@ -189,23 +190,25 @@ int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) } /* - * Try an exact match with the host name. + * Try an exact match with the host name. Note that the name and the + * pattern are already casefolded. */ - if (strcasecmp(name, pattern) == 0) { + if (strcmp(name, pattern) == 0) { return (1); } /* - * See if the pattern is a parent domain of the hostname. + * See if the pattern is a parent domain of the hostname. Note that the + * name and the pattern are already casefolded. */ else { if (list->flags & MATCH_FLAG_PARENT) { pd = name + strlen(name) - strlen(pattern); - if (pd > name && pd[-1] == '.' && strcasecmp(pd, pattern) == 0) + if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0) return (1); } else if (pattern[0] == '.') { pd = name + strlen(name) - strlen(pattern); - if (pd > name && strcasecmp(pd, pattern) == 0) + if (pd > name && strcmp(pd, pattern) == 0) return (1); } } @@ -224,7 +227,7 @@ int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) int rc; if (msg_verbose) - msg_info("%s: %s ~? %s", myname, addr, pattern); + msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern); #define V4_ADDR_STRING_CHARS "01234567890." #define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:" @@ -247,15 +250,16 @@ int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) } /* - * Try an exact match with the host address. + * Try an exact match with the host address. Note that the address and + * pattern are already casefolded. */ if (pattern[0] != '[') { - if (strcasecmp(addr, pattern) == 0) + if (strcmp(addr, pattern) == 0) return (1); } else { size_t addr_len = strlen(addr); - if (strncasecmp(addr, pattern + 1, addr_len) == 0 + if (strncmp(addr, pattern + 1, addr_len) == 0 && strcmp(pattern + 1 + addr_len, "]") == 0) return (1); } @@ -271,7 +275,7 @@ int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) * - Don't bother unless the pattern is either an IPv6 address or net/mask. * * We can safely skip IPv4 address patterns because their form is - * unambiguous and they did not match in the strcasecmp() calls above. + * unambiguous and they did not match in the strcmp() calls above. * * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST * input, to avoid triggering false cidr_match_parse() errors. diff --git a/postfix/src/util/midna.c b/postfix/src/util/midna.c deleted file mode 100644 index 0f4118ee1..000000000 --- a/postfix/src/util/midna.c +++ /dev/null @@ -1,335 +0,0 @@ -/*++ -/* NAME -/* midna 3 -/* SUMMARY -/* Postfix domain name conversion -/* SYNOPSIS -/* #include -/* -/* int midna_cache_size; -/* -/* const char *midna_to_ascii( -/* const char *name) -/* -/* const char *midna_to_utf8( -/* const char *name) -/* -/* const char *midna_suffix_to_ascii( -/* const char *name) -/* -/* const char *midna_suffix_to_utf8( -/* const char *name) -/* DESCRIPTION -/* The functions in this module transform domain names from -/* or to IDNA form. The result is cached to avoid repeated -/* conversion of the same name. -/* -/* midna_to_ascii() converts an UTF-8 or ASCII domain name to -/* ASCII. The result is a null pointer in case of error. This -/* function verifies that the result is a valid ASCII domainname. -/* -/* midna_to_utf8() converts an UTF-8 or ASCII domain name to -/* UTF-8. The result is a null pointer in case of error. This -/* function verifies that the result converts to a valid ASCII -/* domainname. -/* -/* midna_suffix_to_ascii() and midna_suffix_to_utf8() take a -/* name that starts with '.' and otherwise perform the same -/* operations as midna_to_ascii() and midna_to_utf8(). -/* -/* midna_cache_size specifies the size of the conversion result -/* cache. This value is used only once, upon the first lookup -/* request. -/* SEE ALSO -/* msg(3) diagnostics interface -/* DIAGNOSTICS -/* Fatal errors: memory allocation problem. -/* Warnings: conversion error or result validation error. -/* LICENSE -/* .ad -/* .fi -/* The Secure Mailer license must be distributed with this software. -/* AUTHOR(S) -/* Arnt Gulbrandsen -/* -/* Wietse Venema -/* IBM T.J. Watson Research -/* P.O. Box 704 -/* Yorktown Heights, NY 10598, USA -/*--*/ - - /* - * System library. - */ -#include -#include -#include - -#ifndef NO_EAI -#include - - /* - * Utility library. - */ -#include -#include -#include -#include -#include -#include - - /* - * Application-specific. - */ -#define DEF_MIDNA_CACHE_SIZE 256 - -int midna_cache_size = DEF_MIDNA_CACHE_SIZE; -static VSTRING *midna_buf; /* x.suffix */ - -#define STR(x) vstring_str(x) - -/* midna_to_ascii_create - convert domain to ASCII */ - -static void *midna_to_ascii_create(const char *name, void *unused_context) -{ - static const char myname[] = "midna_to_ascii_create"; - char buf[1024]; /* XXX */ - UErrorCode error = U_ZERO_ERROR; - UIDNAInfo info = UIDNA_INFO_INITIALIZER; - UIDNA *idna; - int anl; - - /* - * Paranoia: do not expose uidna_*() to unfiltered network data. - */ - if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { - msg_warn("%s: Problem translating domain \"%s\" to ASCII form: %s", - myname, name, "malformed UTF-8"); - return (0); - } - - /* - * Perform the requested conversion. - */ - idna = uidna_openUTS46(UIDNA_DEFAULT, &error); - anl = uidna_nameToASCII_UTF8(idna, - name, strlen(name), - buf, sizeof(buf), - &info, - &error); - uidna_close(idna); - - /* - * Paranoia: verify that the result is a valid ASCII domain name. A quick - * check shows that the UTS46 implementation will reject labels that - * start or end in '-' or that are over-long, but let's play safe here. - */ - if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { - buf[anl] = 0; /* XXX */ - if (!valid_hostname(buf, DONT_GRIPE)) { - msg_warn("%s: Problem translating domain \"%s\" to ASCII form: %s", - myname, name, "malformed ASCII label(s)"); - return (0); - } - return (mystrndup(buf, anl)); - } else { - msg_warn("%s: Problem translating domain \"%s\" to ASCII form: %s", - myname, name, u_errorName(error)); - return (0); - } -} - -/* midna_to_utf8_create - convert domain to UTF8 */ - -static void *midna_to_utf8_create(const char *name, void *unused_context) -{ - static const char myname[] = "midna_to_utf8_create"; - char buf[1024]; /* XXX */ - UErrorCode error = U_ZERO_ERROR; - UIDNAInfo info = UIDNA_INFO_INITIALIZER; - UIDNA *idna; - int anl; - - /* - * Paranoia: do not expose uidna_*() to unfiltered network data. - */ - if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { - msg_warn("%s: Problem translating domain \"%s\" to UTF-8 form: %s", - myname, name, "malformed UTF-8"); - return (0); - } - - /* - * Perform the requested conversion. - */ - idna = uidna_openUTS46(UIDNA_DEFAULT, &error); - anl = uidna_nameToUnicodeUTF8(idna, - name, strlen(name), - buf, sizeof(buf), - &info, - &error); - uidna_close(idna); - - /* - * Paranoia: UTS46 toUTF8 will accept and produce a name that does not - * convert to a valid ASCII domain name. So we enforce sanity here. - */ - if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { - buf[anl] = 0; /* XXX */ - if (midna_to_ascii(buf) == 0) - return (0); - return (mystrndup(buf, anl)); - } else { - msg_warn("%s: Problem translating domain \"%s\" to UTF8 form: %s", - myname, name, u_errorName(error)); - return (0); - } -} - -/* midna_cache_free - cache element destructor */ - -static void midna_cache_free(void *value, void *unused_context) -{ - if (value) - myfree(value); -} - -/* midna_to_ascii - convert name to ASCII */ - -const char *midna_to_ascii(const char *name) -{ - static CTABLE *midna_to_ascii_cache = 0; - - if (midna_to_ascii_cache == 0) - midna_to_ascii_cache = ctable_create(midna_cache_size, - midna_to_ascii_create, - midna_cache_free, - (void *) 0); - return (ctable_locate(midna_to_ascii_cache, name)); -} - -/* midna_to_utf8 - convert name to UTF8 */ - -const char *midna_to_utf8(const char *name) -{ - static CTABLE *midna_to_utf8_cache = 0; - - if (midna_to_utf8_cache == 0) - midna_to_utf8_cache = ctable_create(midna_cache_size, - midna_to_utf8_create, - midna_cache_free, - (void *) 0); - return (ctable_locate(midna_to_utf8_cache, name)); -} - -/* midna_suffix_to_ascii - convert .name to ASCII */ - -const char *midna_suffix_to_ascii(const char *suffix) -{ - const char *cache_res; - - /* - * If prepending x to .name causes the result to become too long, then - * the suffix is bad. - */ - if (midna_buf == 0) - midna_buf = vstring_alloc(100); - vstring_sprintf(midna_buf, "x%s", suffix); - if ((cache_res = midna_to_ascii(STR(midna_buf))) == 0) - return (0); - else - return (cache_res + 1); -} - -/* midna_suffix_to_utf8 - convert .name to UTF8 */ - -const char *midna_suffix_to_utf8(const char *name) -{ - const char *cache_res; - - /* - * If prepending x to .name causes the result to become too long, then - * the suffix is bad. - */ - if (midna_buf == 0) - midna_buf = vstring_alloc(100); - vstring_sprintf(midna_buf, "x%s", name); - if ((cache_res = midna_to_utf8(STR(midna_buf))) == 0) - return (0); - else - return (cache_res + 1); -} - -#ifdef TEST - - /* - * Test program - reads names from stdin, reports invalid names to stderr. - */ -#include -#include - -#include /* XXX temp_utf8_kludge */ -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - VSTRING *buffer = vstring_alloc(1); - const char *bp; - const char *ascii; - const char *utf8; - - if (setlocale(LC_ALL, "C") == 0) - msg_fatal("setlocale(LC_ALL, C) failed: %m"); - - msg_vstream_init(argv[0], VSTREAM_ERR); - msg_verbose = 1; - temp_utf8_kludge = 1; - - while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { - bp = STR(buffer); - msg_info("> %s", bp); - while (ISSPACE(*bp)) - bp++; - if (*bp == '#' || *bp == 0) - continue; - if (!allascii(bp)) { - utf8 = midna_to_utf8(bp); - if (utf8 != 0) - msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8); - ascii = midna_to_ascii(bp); - if (ascii != 0) { - msg_info("\"%s\" ->ascii \"%s\"", bp, ascii); - utf8 = midna_to_utf8(ascii); - if (utf8 != 0) { - msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"", - bp, ascii, utf8); - if (strcmp(utf8, bp) != 0) - msg_warn("\"%s\" != \"%s\"", bp, utf8); - } - } - } else { - ascii = midna_to_ascii(bp); - if (ascii != 0) - msg_info("\"%s\" ->ascii \"%s\"", bp, ascii); - utf8 = midna_to_utf8(bp); - if (utf8 != 0) { - msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8); - ascii = midna_to_ascii(utf8); - if (ascii != 0) { - msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"", - bp, utf8, ascii); - if (strcmp(ascii, bp) != 0) - msg_warn("\"%s\" != \"%s\"", bp, ascii); - } - } - } - } - exit(0); -} - -#endif /* TEST */ - -#endif /* NO_EAI */ diff --git a/postfix/src/util/midna_domain.c b/postfix/src/util/midna_domain.c new file mode 100644 index 000000000..1563cac0e --- /dev/null +++ b/postfix/src/util/midna_domain.c @@ -0,0 +1,345 @@ +/*++ +/* NAME +/* midna_domain 3 +/* SUMMARY +/* ASCII/UTF-8 domain name conversion +/* SYNOPSIS +/* #include +/* +/* int midna_domain_cache_size; +/* +/* const char *midna_domain_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_to_utf8( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_utf8( +/* const char *name) +/* DESCRIPTION +/* The functions in this module transform domain names from/to +/* ASCII and UTF-8 form. The result is cached to avoid repeated +/* conversion. +/* +/* This module builds on the ICU library implementation of the +/* UTS #46 specification, using default ICU library options +/* because those are likely best tested: with transitional +/* processing, with case mapping, with normalization, with +/* limited IDNA2003 compatibility, without STD3 ASCII rules. +/* +/* midna_domain_to_ascii() converts an UTF-8 or ASCII domain +/* name to ASCII. The result is a null pointer in case of +/* error. This function verifies that the result passes +/* valid_hostname(). +/* +/* midna_domain_to_utf8() converts an UTF-8 or ASCII domain +/* name to UTF-8. The result is a null pointer in case of +/* error. This function verifies that the result, after +/* conversion to ASCII, passes valid_hostname(). +/* +/* midna_domain_suffix_to_ascii() and midna_domain_suffix_to_utf8() +/* take a name that starts with '.' and otherwise perform the +/* same operations as midna_domain_to_ascii() and +/* midna_domain_to_utf8(). +/* +/* midna_domain_cache_size specifies the size of the conversion +/* result cache. This value is used only once, upon the first +/* lookup +/* request. +/* SEE ALSO +/* http://unicode.org/reports/tr46/ Unicode IDNA Compatibility processing +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: conversion error or result validation error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Arnt Gulbrandsen +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + +#ifndef NO_EAI +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ +#define DEF_MIDNA_CACHE_SIZE 256 + +int midna_domain_cache_size = DEF_MIDNA_CACHE_SIZE; +static VSTRING *midna_domain_buf; /* x.suffix */ + +#define STR(x) vstring_str(x) + +/* midna_domain_to_ascii_create - convert domain to ASCII */ + +static void *midna_domain_to_ascii_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_ascii_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(UIDNA_DEFAULT, &error);/* XXX check error */ + anl = uidna_nameToASCII_UTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: verify that the result passes valid_hostname(). A quick + * check shows that UTS46 ToASCII by default rejects inputs with labels + * that start or end in '-', with names or labels that are over-long, or + * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on + * valid_hostname() on the output side just to be sure. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (!valid_hostname(buf, DONT_GRIPE)) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed ASCII label(s)"); + return (0); + } + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, u_errorName(info.errors)); + return (0); + } +} + +/* midna_domain_to_utf8_create - convert domain to UTF8 */ + +static void *midna_domain_to_utf8_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_utf8_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(UIDNA_DEFAULT, &error);/* XXX check error */ + anl = uidna_nameToUnicodeUTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: UTS46 toUTF8 by default accepts and produces an over-long + * name or a name that contains an over-long NR-LDH label (and perhaps + * other invalid forms that are not covered in UTS 46, section 4.1). We + * rely on midna_domain_to_ascii() to validate the output. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (midna_domain_to_ascii(buf) == 0) + return (0); + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF8 form: %s", + myname, name, u_errorName(info.errors)); + return (0); + } +} + +/* midna_domain_cache_free - cache element destructor */ + +static void midna_domain_cache_free(void *value, void *unused_context) +{ + if (value) + myfree(value); +} + +/* midna_domain_to_ascii - convert name to ASCII */ + +const char *midna_domain_to_ascii(const char *name) +{ + static CTABLE *midna_domain_to_ascii_cache = 0; + + if (midna_domain_to_ascii_cache == 0) + midna_domain_to_ascii_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_ascii_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_ascii_cache, name)); +} + +/* midna_domain_to_utf8 - convert name to UTF8 */ + +const char *midna_domain_to_utf8(const char *name) +{ + static CTABLE *midna_domain_to_utf8_cache = 0; + + if (midna_domain_to_utf8_cache == 0) + midna_domain_to_utf8_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_utf8_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_utf8_cache, name)); +} + +/* midna_domain_suffix_to_ascii - convert .name to ASCII */ + +const char *midna_domain_suffix_to_ascii(const char *suffix) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", suffix); + if ((cache_res = midna_domain_to_ascii(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +/* midna_domain_suffix_to_utf8 - convert .name to UTF8 */ + +const char *midna_domain_suffix_to_utf8(const char *name) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", name); + if ((cache_res = midna_domain_to_utf8(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +#ifdef TEST + + /* + * Test program - reads names from stdin, reports invalid names to stderr. + */ +#include +#include + +#include /* XXX util_utf8_enable */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + const char *bp; + const char *ascii; + const char *utf8; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + /* msg_verbose = 1; */ + util_utf8_enable = 1; + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + msg_info("> %s", bp); + while (ISSPACE(*bp)) + bp++; + if (*bp == '#' || *bp == 0) + continue; + msg_info("unconditional conversions:"); + utf8 = midna_domain_to_utf8(bp); + msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8 ? utf8 : "(error)"); + ascii = midna_domain_to_ascii(bp); + msg_info("\"%s\" ->ascii \"%s\"", bp, ascii ? ascii : "(error)"); + msg_info("conditional conversions:"); + if (!allascii(bp)) { + if (ascii != 0) { + utf8 = midna_domain_to_utf8(ascii); + msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"", + bp, ascii, utf8 ? utf8 : "(error)"); + if (utf8 != 0) { + if (strcmp(utf8, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, utf8); + } + } + } else { + if (utf8 != 0) { + ascii = midna_domain_to_ascii(utf8); + msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"", + bp, utf8, ascii ? ascii : "(error)"); + if (ascii != 0) { + if (strcmp(ascii, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, ascii); + } + } + } + } + exit(0); +} + +#endif /* TEST */ + +#endif /* NO_EAI */ diff --git a/postfix/src/util/midna.h b/postfix/src/util/midna_domain.h similarity index 54% rename from postfix/src/util/midna.h rename to postfix/src/util/midna_domain.h index 599097832..29cfc8c84 100644 --- a/postfix/src/util/midna.h +++ b/postfix/src/util/midna_domain.h @@ -3,21 +3,21 @@ /*++ /* NAME -/* mail_idna 3h +/* midna_domain 3h /* SUMMARY -/* domain name conversion +/* ASCII/UTF-8 domain name conversion /* SYNOPSIS -/* #include +/* #include /* DESCRIPTION /* .nf /* * External interface. */ -extern const char *midna_to_ascii(const char *); -extern const char *midna_to_utf8(const char *); -extern const char *midna_suffix_to_ascii(const char *); -extern const char *midna_suffix_to_utf8(const char *); +extern const char *midna_domain_to_ascii(const char *); +extern const char *midna_domain_to_utf8(const char *); +extern const char *midna_domain_suffix_to_ascii(const char *); +extern const char *midna_domain_suffix_to_utf8(const char *); /* LICENSE /* .ad diff --git a/postfix/src/util/midna_domain_test.in b/postfix/src/util/midna_domain_test.in new file mode 100644 index 000000000..55491e41f --- /dev/null +++ b/postfix/src/util/midna_domain_test.in @@ -0,0 +1,21 @@ +# Upper-case greek -> lower-case greek. +Δημοσθένους.example.com +# Upper-case ASCII -> lower-case ASCII. +Hello.example.com +# Invalid LDH label('-' at begin or end). +bad-.example.com +-bad.example.com +# Invalid LDH (label > 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +# Valid LDH label (label <= 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +# Invalid name (length > 255 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +# Aliases for '.' -> '.'. +x。example.com +x.example.com +x。example.com +# Good a-label. +xn--mumble.example.com +# Bad a-label. +xn--123456.example.com diff --git a/postfix/src/util/midna_domain_test.ref b/postfix/src/util/midna_domain_test.ref new file mode 100644 index 000000000..17e4fcc68 --- /dev/null +++ b/postfix/src/util/midna_domain_test.ref @@ -0,0 +1,89 @@ +./midna_domain: > # Upper-case greek -> lower-case greek. +./midna_domain: > Δημοσθένους.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->utf8 "δημοσθÎνουσ.example.com" +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild6aev.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild6aev.example.com" ->utf8 "δημοσθÎνουσ.example.com" +./midna_domain: warning: "Δημοσθένους.example.com" != "δημοσθÎνουσ.example.com" +./midna_domain: > # Upper-case ASCII -> lower-case ASCII. +./midna_domain: > Hello.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" +./midna_domain: "Hello.example.com" ->ascii "hello.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com" +./midna_domain: warning: "Hello.example.com" != "hello.example.com" +./midna_domain: > # Invalid LDH label('-' at begin or end). +./midna_domain: > bad-.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "bad-.example.com" to UTF8 form: U_UNSUPPORTED_ERROR +./midna_domain: "bad-.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "bad-.example.com" to ASCII form: U_UNSUPPORTED_ERROR +./midna_domain: "bad-.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > -bad.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "-bad.example.com" to UTF8 form: U_INDEX_OUTOFBOUNDS_ERROR +./midna_domain: "-bad.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "-bad.example.com" to ASCII form: U_INDEX_OUTOFBOUNDS_ERROR +./midna_domain: "-bad.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Invalid LDH (label > 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" to ASCII form: U_MISSING_RESOURCE_ERROR +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Valid LDH label (label <= 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +./midna_domain: unconditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: conditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: > # Invalid name (length > 255 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcde" to ASCII form: U_FILE_ACCESS_ERROR +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Aliases for '.' -> '.'. +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > x.example.com +./midna_domain: unconditional conversions: +./midna_domain: "x.example.com" ->utf8 "x.example.com" +./midna_domain: "x.example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x.example.com" != "x.example.com" +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > # Good a-label. +./midna_domain: > xn--mumble.example.com +./midna_domain: unconditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" +./midna_domain: "xn--mumble.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: conditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: > # Bad a-label. +./midna_domain: > xn--123456.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "xn--123456.example.com" to UTF8 form: [BOGUS UErrorCode] +./midna_domain: "xn--123456.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "xn--123456.example.com" to ASCII form: [BOGUS UErrorCode] +./midna_domain: "xn--123456.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: diff --git a/postfix/src/util/midna_test.in b/postfix/src/util/midna_test.in deleted file mode 100644 index b309be965..000000000 --- a/postfix/src/util/midna_test.in +++ /dev/null @@ -1,14 +0,0 @@ -# Upper-case greek -> lower-case greek. -Δημοσθένους.example.com -# Upper-case ASCII -> lower-case ASCII. -Hello.example.com -# Invalid domain name ('-' at begin or end). --bad-.example.com -# Invalid domain name (label > 62 bytes). -abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com -# Valid domain name. -abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -# Aliases for '.' -> '.'. -x。example.com -x.example.com -x。example.com diff --git a/postfix/src/util/midna_test.ref b/postfix/src/util/midna_test.ref deleted file mode 100644 index 1df1eb61e..000000000 --- a/postfix/src/util/midna_test.ref +++ /dev/null @@ -1,71 +0,0 @@ -./midna: > # Upper-case greek -> lower-case greek. -./midna: > Δημοσθένους.example.com -./midna: ctable_locate: install entry key δημοσθÎνουσ.example.com -./midna: ctable_locate: install entry key Δημοσθένους.example.com -./midna: "Δημοσθένους.example.com" ->utf8 "δημοσθÎνουσ.example.com" -./midna: ctable_locate: install entry key Δημοσθένους.example.com -./midna: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild6aev.example.com" -./midna: ctable_locate: move existing entry key δημοσθÎνουσ.example.com -./midna: ctable_locate: install entry key xn--ixanjetild6aev.example.com -./midna: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild6aev.example.com" ->utf8 "δημοσθÎνουσ.example.com" -./midna: warning: "Δημοσθένους.example.com" != "δημοσθÎνουσ.example.com" -./midna: > # Upper-case ASCII -> lower-case ASCII. -./midna: > Hello.example.com -./midna: ctable_locate: install entry key Hello.example.com -./midna: "Hello.example.com" ->ascii "hello.example.com" -./midna: ctable_locate: install entry key hello.example.com -./midna: ctable_locate: install entry key Hello.example.com -./midna: "Hello.example.com" ->utf8 "hello.example.com" -./midna: ctable_locate: leave existing entry key hello.example.com -./midna: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com" -./midna: warning: "Hello.example.com" != "hello.example.com" -./midna: > # Invalid domain name ('-' at begin or end). -./midna: > -bad-.example.com -./midna: warning: midna_to_ascii_create: Problem translating domain "-bad-.example.com" to ASCII form: U_ZERO_ERROR -./midna: ctable_locate: install entry key -bad-.example.com -./midna: warning: midna_to_utf8_create: Problem translating domain "-bad-.example.com" to UTF8 form: U_ZERO_ERROR -./midna: ctable_locate: install entry key -bad-.example.com -./midna: > # Invalid domain name (label > 62 bytes). -./midna: > abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com -./midna: warning: midna_to_ascii_create: Problem translating domain "abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com" to ASCII form: U_ZERO_ERROR -./midna: ctable_locate: install entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com -./midna: ctable_locate: leave existing entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com -./midna: ctable_locate: install entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456789.example.com -./midna: > # Valid domain name. -./midna: > abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -./midna: ctable_locate: install entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -./midna: "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" ->ascii "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" -./midna: ctable_locate: leave existing entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -./midna: ctable_locate: install entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -./midna: "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" ->utf8 "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" -./midna: ctable_locate: leave existing entry key abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com -./midna: "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" ->utf8 "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" ->ascii "abcdef01234567890abcdef01234567890abcdef01234567890abcdef012345.example.com" -./midna: > # Aliases for '.' -> '.'. -./midna: > x。example.com -./midna: ctable_locate: install entry key x.example.com -./midna: ctable_locate: install entry key x。example.com -./midna: "x。example.com" ->utf8 "x.example.com" -./midna: ctable_locate: install entry key x。example.com -./midna: "x。example.com" ->ascii "x.example.com" -./midna: ctable_locate: move existing entry key x.example.com -./midna: ctable_locate: install entry key x.example.com -./midna: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" -./midna: warning: "x。example.com" != "x.example.com" -./midna: > x.example.com -./midna: ctable_locate: leave existing entry key x.example.com -./midna: ctable_locate: install entry key x.example.com -./midna: "x.example.com" ->utf8 "x.example.com" -./midna: ctable_locate: install entry key x.example.com -./midna: "x.example.com" ->ascii "x.example.com" -./midna: ctable_locate: move existing entry key x.example.com -./midna: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com" -./midna: warning: "x.example.com" != "x.example.com" -./midna: > x。example.com -./midna: ctable_locate: move existing entry key x.example.com -./midna: ctable_locate: install entry key x。example.com -./midna: "x。example.com" ->utf8 "x.example.com" -./midna: ctable_locate: install entry key x。example.com -./midna: "x。example.com" ->ascii "x.example.com" -./midna: ctable_locate: move existing entry key x.example.com -./midna: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" -./midna: warning: "x。example.com" != "x.example.com" diff --git a/postfix/src/util/printable.c b/postfix/src/util/printable.c index 1ba0731c7..a37ff6306 100644 --- a/postfix/src/util/printable.c +++ b/postfix/src/util/printable.c @@ -6,7 +6,7 @@ /* SYNOPSIS /* #include /* -/* int temp_utf8_kludge; +/* int util_utf8_enable; /* /* char *printable(buffer, replacement) /* char *buffer; @@ -15,8 +15,8 @@ /* printable() replaces non-printable characters /* in its input with the given replacement. /* -/* temp_utf8_kludge controls whether UTF8 is considered printable. -/* By default, non-ASCII text is replaced. +/* util_utf8_enable controls whether UTF8 is considered printable. +/* With util_utf8_enable equal to zero, non-ASCII text is replaced. /* /* Arguments: /* .IP buffer @@ -44,7 +44,7 @@ #include "stringops.h" -int temp_utf8_kludge = 0; +int util_utf8_enable = 0; char *printable(char *string, int replacement) { @@ -59,7 +59,7 @@ char *printable(char *string, int replacement) while ((ch = *cp) != 0) { if (ISASCII(ch) && ISPRINT(ch)) { /* ok */ - } else if (temp_utf8_kludge && ch >= 194 && ch <= 254 + } else if (util_utf8_enable && ch >= 194 && ch <= 254 && cp[1] >= 128 && cp[1] < 192) { /* UTF8; skip the rest of the bytes in the character. */ while (cp[1] >= 128 && cp[1] < 192) diff --git a/postfix/src/util/stringops.h b/postfix/src/util/stringops.h index 3d4d3b869..52938ca30 100644 --- a/postfix/src/util/stringops.h +++ b/postfix/src/util/stringops.h @@ -19,10 +19,11 @@ /* * External interface. */ -extern int temp_utf8_kludge; +extern int util_utf8_enable; extern char *printable(char *, int); extern char *neuter(char *, const char *, int); extern char *lowercase(char *); +extern char *casefold(int, VSTRING *, const char *, CONST_CHAR_STAR *); extern char *uppercase(char *); extern char *skipblanks(const char *); extern char *trimblanks(char *, ssize_t); diff --git a/postfix/src/util/valid_utf8_hostname.c b/postfix/src/util/valid_utf8_hostname.c index f42e21f53..3d6922aa9 100644 --- a/postfix/src/util/valid_utf8_hostname.c +++ b/postfix/src/util/valid_utf8_hostname.c @@ -8,8 +8,8 @@ /* /* int valid_utf8_hostname( /* int enable_utf8, -/* const char *domain, -/* int gripe) +/* const char *domain, +/* int gripe) /* DESCRIPTION /* valid_utf8_hostname() is a wrapper around valid_hostname(). /* If EAI support is compiled in, and enable_utf8 is true, the @@ -43,7 +43,7 @@ #include #include #include -#include +#include #include /* valid_utf8_hostname - validate internationalized domain name */ @@ -51,8 +51,6 @@ int valid_utf8_hostname(int enable_utf8, const char *name, int gripe) { static const char myname[] = "valid_utf8_hostname"; - const char *aname; - int ret; /* * Trivial cases first. @@ -64,23 +62,26 @@ int valid_utf8_hostname(int enable_utf8, const char *name, int gripe) } /* - * Convert domain name to ASCII form. + * Convert non-ASCII domain name to ASCII and validate the result per + * STD3. midna_domain_to_ascii() applies valid_hostname() to the result. + * Propagate the gripe parameter for better diagnostics (note that + * midna_domain_to_ascii() logs a problem only when the result is not + * cached). */ #ifndef NO_EAI if (enable_utf8 && !allascii(name)) { - if ((aname = midna_to_ascii(name)) == 0) { + if (midna_domain_to_ascii(name) == 0) { if (gripe) msg_warn("%s: malformed UTF-8 domain name", myname); return (0); + } else { + return (1); } - } else + } #endif - aname = name; /* - * Validate the name per STD3 (if the IDNA routines didn't already). + * Validate ASCII name per STD3. */ - ret = valid_hostname(aname, gripe); - - return (ret); + return (valid_hostname(name, gripe)); } diff --git a/postfix/src/verify/verify.c b/postfix/src/verify/verify.c index fb36540b0..009396ef9 100644 --- a/postfix/src/verify/verify.c +++ b/postfix/src/verify/verify.c @@ -680,7 +680,7 @@ static void pre_jail_init(char *unused_name, char **unused_argv) * Start the cache cleanup thread after permanently dropping privileges. */ #define VERIFY_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE \ - | DICT_FLAG_OPEN_LOCK) + | DICT_FLAG_OPEN_LOCK | DICT_FLAG_UTF8_REQUEST) saved_mask = umask(022); verify_map = diff --git a/postfix/src/virtual/virtual.c b/postfix/src/virtual/virtual.c index 69252c308..487b3a2d7 100644 --- a/postfix/src/virtual/virtual.c +++ b/postfix/src/virtual/virtual.c @@ -457,15 +457,18 @@ static void post_init(char *unused_name, char **unused_argv) */ virtual_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps, - DICT_FLAG_LOCK | DICT_FLAG_PARANOID); + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); virtual_uid_maps = maps_create(VAR_VIRT_UID_MAPS, var_virt_uid_maps, - DICT_FLAG_LOCK | DICT_FLAG_PARANOID); + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); virtual_gid_maps = maps_create(VAR_VIRT_GID_MAPS, var_virt_gid_maps, - DICT_FLAG_LOCK | DICT_FLAG_PARANOID); + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); virtual_mbox_lock_mask = mbox_lock_mask(var_virt_mailbox_lock); }