From: Wietse Venema Date: Sun, 8 Jan 2017 05:00:00 +0000 (-0500) Subject: postfix-3.2-20170108-nonprod X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=990a60f79f95516748a98d3d9a37dc305a7322a7;p=thirdparty%2Fpostfix.git postfix-3.2-20170108-nonprod --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 0b5723cf5..6a98ec87e 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -179,6 +179,7 @@ -TMAC_EXP_OP_INFO -TMAC_HEAD -TMAC_PARSE +-TMAIL_ADDR_MAP_TEST -TMAIL_PRINT -TMAIL_SCAN -TMAIL_STREAM diff --git a/postfix/HISTORY b/postfix/HISTORY index c5437ab1c..d9428c0db 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -22749,3 +22749,39 @@ Apologies for any names omitted. Portability: compatibility macros for SSLv23_client_method() etc. deprecation. Files: tls/tls.h, tls/tls_client.c, tls/tls_dane.c, tls_server.c. + +201606-20170108 + + Cleanup: handling of address extensions with email addresses + that contain spaces. The virtual_alias_maps, canonical_maps, + and smtp_generic_maps features now correctly propagate an + address extension from "aa bb+ext"@example.com to "cc + dd+ext"@other.example, instead of producing broken output. + + Files updated to support conversion between unquoted and + quoted address forms, as required for addresses that contain + spaces: global/mail_addr_map.*, global/mail_addr_find.* and + global/mail_addr_crunch.*. + + Files updated to enable these address conversions to correctly + propagate address extensions: cleanup/cleanup_map11.c + (canonical_maps), cleanup/cleanup_map1n.c (virtual_alias_maps), + and smtp/smtp_generic.c (smtp_generic_maps). + + Files updated to rename functions to better reflect their + input and output forms: global/split_addr.*, global/strip_addr.*. + + Files updated to support quoted lookup keys: util/dict_inline.c, + util/dict_thash.c, postmap/postmap.c. + + Files updated to invoke a backwards-compatible mail_addr_find() + version that disables quoted/unquoted address conversions: + smtp/smtp/smtp_sasl_glue.c (smtp_sasl_password_maps), + smtpd/smtpd_check.c (SMTP server address validation), + cleanup/cleanup_addr.c (sender_bcc_maps and recipient_bcc_maps), + virtual/mailbox.c (user-related table lookups), + trivial-rewrite/transport.c (transport_maps), + trivial-rewrite/resolve.c (sender_dependent_mumble_maps, + relocated_maps). These features may be migrated later to + enable quoted-form address lookup keys, for consistency + with other Postfix features. diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 43ab56aa1..109e8c82c 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -6,6 +6,8 @@ Wish list: Disable -DSNAPSHOT and -DNONPROD in makedefs. + Document RFC5321 localpart quoting in DATABASE_README. + In the bounce daemon, set util_utf8_enable if returning an SMTPUTF8 message. diff --git a/postfix/html/postmap.1.html b/postfix/html/postmap.1.html index f99c0520f..40aeaca1c 100644 --- a/postfix/html/postmap.1.html +++ b/postfix/html/postmap.1.html @@ -45,125 +45,132 @@ POSTMAP(1) POSTMAP(1) not be used to protect lookup keys that contain special characters such as `#' or whitespace. - By default the lookup key is mapped to lowercase to make the lookups + When the key specifies email address information, the localpart needs + to be enclosed with double quotes if required by RFC 5322 and if the + key is used in virtual_alias_maps, *canonical_maps, or smtp_generic + maps. For example, an address localpart that contains space or ';' + characters needs to be quoted. The postmap(1) command supports spaces + in the key as of Postfix version 3.2. + + By default the lookup key is mapped to lowercase to make the lookups case insensitive; as of Postfix 2.3 this case folding happens only with tables whose lookup keys are fixed-case strings such as btree:, dbm: or hash:. With earlier versions, the lookup key is folded even with tables - where a lookup field can match both upper and lower case text, such as - regexp: and pcre:. This resulted in loss of information with $number + where a lookup field can match both upper and lower case text, such as + regexp: and pcre:. This resulted in loss of information with $number substitutions. COMMAND-LINE ARGUMENTS - -b Enable message body query mode. When reading lookup keys from - standard input with "-q -", process the input as if it is an - email message in RFC 2822 format. Each line of body content + -b Enable message body query mode. When reading lookup keys from + standard input with "-q -", process the input as if it is an + email message in RFC 2822 format. Each line of body content becomes one lookup key. - By default, the -b option starts generating lookup keys at the - first non-header line, and stops when the end of the message is - reached. To simulate body_checks(5) processing, enable MIME - parsing with -m. With this, the -b option generates no - body-style lookup keys for attachment MIME headers and for + By default, the -b option starts generating lookup keys at the + first non-header line, and stops when the end of the message is + reached. To simulate body_checks(5) processing, enable MIME + parsing with -m. With this, the -b option generates no + body-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. + 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 - Read the main.cf configuration file in the named directory + Read the main.cf configuration file in the named directory instead of the default configuration directory. - -d key Search the specified maps for key and remove one entry per map. - The exit status is zero when the requested information was + -d key Search the specified maps for key and remove one entry per map. + The exit status is zero when the requested information was found. - If a key value of - is specified, the program reads key values - from the standard input stream. The exit status is zero when at + If a key value of - is specified, the program reads key values + from the standard input stream. The exit status is zero when at least one of the requested keys was found. - -f Do not fold the lookup key to lower case while creating or + -f Do not fold the lookup key to lower case while creating or querying a table. - With Postfix version 2.3 and later, this option has no effect + With Postfix version 2.3 and later, this option has no effect for regular expression tables. There, case folding is controlled by appending a flag to a pattern. - -h Enable message header query mode. When reading lookup keys from - standard input with "-q -", process the input as if it is an - email message in RFC 2822 format. Each logical header line - becomes one lookup key. A multi-line header becomes one lookup + -h Enable message header query mode. When reading lookup keys from + standard input with "-q -", process the input as if it is an + email message in RFC 2822 format. Each logical header line + becomes one lookup key. A multi-line header becomes one lookup key with one or more embedded newline characters. - By default, the -h option generates lookup keys until the first - non-header line is reached. To simulate header_checks(5) pro- - cessing, enable MIME parsing with -m. With this, the -h option - also generates header-style lookup keys for attachment MIME + By default, the -h option generates lookup keys until the first + non-header line is reached. To simulate header_checks(5) pro- + cessing, enable MIME parsing with -m. With this, the -h option + 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. + 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 - truncate an existing database. By default, postmap(1) creates a + -i Incremental mode. Read entries from standard input and do not + truncate an existing database. By default, postmap(1) creates a new database from the entries in file_name. -m Enable MIME parsing with "-b" and "-h". This feature is available in Postfix version 2.6 and later. - -N Include the terminating null character that terminates lookup - keys and values. By default, postmap(1) does whatever is the + -N Include the terminating null character that terminates lookup + keys and values. By default, postmap(1) does whatever is the default for the host operating system. - -n Don't include the terminating null character that terminates - lookup keys and values. By default, postmap(1) does whatever is + -n Don't include the terminating null character that terminates + lookup keys and values. By default, postmap(1) does whatever is the default for the host operating system. - -o Do not release root privileges when processing a non-root input - file. By default, postmap(1) drops root privileges and runs as + -o Do not release root privileges when processing a non-root input + file. By default, postmap(1) drops root privileges and runs as the source file owner instead. - -p Do not inherit the file access permissions from the input file - when creating a new file. Instead, create a new file with + -p Do not inherit the file access permissions from the input file + when creating a new file. Instead, create a new file with default access permissions (mode 0644). - -q key Search the specified maps for key and write the first value - found to the standard output stream. The exit status is zero + -q key Search the specified maps for key and write the first value + found to the standard output stream. The exit status is zero when the requested information was found. - If a key value of - is specified, the program reads key values - from the standard input stream and writes one line of key value + If a key value of - is specified, the program reads key values + from the standard input stream and writes one line of key value output for each key that was found. The exit status is zero when at least one of the requested keys was found. - -r When updating a table, do not complain about attempts to update + -r When updating a table, do not complain about attempts to update existing entries, and make those updates anyway. - -s Retrieve all database elements, and write one line of key value - output for each element. The elements are printed in database - order, which is not necessarily the same as the original input + -s Retrieve all database elements, and write one line of key value + output for each element. The elements are 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 + 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 + -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 + -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: @@ -175,32 +182,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 @@ -209,11 +216,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 @@ -224,12 +231,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) @@ -237,7 +244,7 @@ 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) @@ -245,14 +252,14 @@ POSTMAP(1) POSTMAP(1) and postmap(1) commands. smtputf8_enable (yes) - Enable preliminary SMTPUTF8 support for the protocols described + Enable preliminary 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) - A prefix that is prepended to the process name in syslog + A prefix that is prepended to the process name in syslog records, so that, for example, "smtpd" becomes "prefix/smtpd". SEE ALSO diff --git a/postfix/makedefs b/postfix/makedefs index 1152270c7..5de7bc614 100644 --- a/postfix/makedefs +++ b/postfix/makedefs @@ -862,7 +862,7 @@ CCARGS="$CCARGS -DSNAPSHOT" # Non-production: needs thorough testing, or major changes are still # needed before the code stabilizes. -#CCARGS="$CCARGS -DNONPROD" +CCARGS="$CCARGS -DNONPROD" # Workaround: prepend Postfix include files before other include files. CCARGS="-I. -I../../include $CCARGS" diff --git a/postfix/man/man1/postmap.1 b/postfix/man/man1/postmap.1 index 668709728..1a4f887a9 100644 --- a/postfix/man/man1/postmap.1 +++ b/postfix/man/man1/postmap.1 @@ -54,6 +54,14 @@ surrounding white space is stripped off. Unlike with Postfix alias databases, quotes cannot be used to protect lookup keys that contain special characters such as `#' or whitespace. +When the \fIkey\fR specifies email address information, the +localpart needs to be enclosed with double quotes if required +by RFC 5322 and if the \fIkey\fR is used in virtual_alias_maps, +*canonical_maps, or smtp_generic maps. For example, an +address localpart that contains space or ';' characters +needs to be quoted. The \fBpostmap\fR(1) command supports +spaces in the \fIkey\fR as of Postfix version 3.2. + By default the lookup key is mapped to lowercase to make the lookups case insensitive; as of Postfix 2.3 this case folding happens only with tables whose lookup keys are diff --git a/postfix/src/cleanup/Makefile.in b/postfix/src/cleanup/Makefile.in index e425f3ec2..902c07c96 100644 --- a/postfix/src/cleanup/Makefile.in +++ b/postfix/src/cleanup/Makefile.in @@ -642,6 +642,7 @@ cleanup_addr.o: ../../include/htable.h cleanup_addr.o: ../../include/iostuff.h cleanup_addr.o: ../../include/mail_addr.h cleanup_addr.o: ../../include/mail_addr_find.h +cleanup_addr.o: ../../include/mail_addr_form.h cleanup_addr.o: ../../include/mail_conf.h cleanup_addr.o: ../../include/mail_params.h cleanup_addr.o: ../../include/mail_proto.h @@ -941,6 +942,7 @@ cleanup_map11.o: ../../include/dsn_mask.h cleanup_map11.o: ../../include/header_body_checks.h cleanup_map11.o: ../../include/header_opts.h cleanup_map11.o: ../../include/htable.h +cleanup_map11.o: ../../include/mail_addr_form.h cleanup_map11.o: ../../include/mail_addr_map.h cleanup_map11.o: ../../include/mail_conf.h cleanup_map11.o: ../../include/mail_stream.h @@ -974,6 +976,7 @@ cleanup_map1n.o: ../../include/dsn_mask.h cleanup_map1n.o: ../../include/header_body_checks.h cleanup_map1n.o: ../../include/header_opts.h cleanup_map1n.o: ../../include/htable.h +cleanup_map1n.o: ../../include/mail_addr_form.h cleanup_map1n.o: ../../include/mail_addr_map.h cleanup_map1n.o: ../../include/mail_conf.h cleanup_map1n.o: ../../include/mail_params.h diff --git a/postfix/src/cleanup/cleanup_addr.c b/postfix/src/cleanup/cleanup_addr.c index b6396ada8..842d0d924 100644 --- a/postfix/src/cleanup/cleanup_addr.c +++ b/postfix/src/cleanup/cleanup_addr.c @@ -165,8 +165,9 @@ off_t cleanup_addr_sender(CLEANUP_STATE *state, const char *buf) if ((state->flags & CLEANUP_FLAG_BCC_OK) && *STR(clean_addr) && cleanup_send_bcc_maps) { - if ((bcc = mail_addr_find(cleanup_send_bcc_maps, STR(clean_addr), - IGNORE_EXTENSION)) != 0) { + if ((bcc = mail_addr_find_noconv(cleanup_send_bcc_maps, + STR(clean_addr), + IGNORE_EXTENSION)) != 0) { cleanup_addr_bcc(state, bcc); } else if (cleanup_send_bcc_maps->error) { msg_warn("%s: %s map lookup problem -- " @@ -228,8 +229,9 @@ void cleanup_addr_recipient(CLEANUP_STATE *state, const char *buf) if ((state->flags & CLEANUP_FLAG_BCC_OK) && *STR(clean_addr) && cleanup_rcpt_bcc_maps) { - if ((bcc = mail_addr_find(cleanup_rcpt_bcc_maps, STR(clean_addr), - IGNORE_EXTENSION)) != 0) { + if ((bcc = mail_addr_find_noconv(cleanup_rcpt_bcc_maps, + STR(clean_addr), + IGNORE_EXTENSION)) != 0) { cleanup_addr_bcc(state, bcc); } else if (cleanup_rcpt_bcc_maps->error) { msg_warn("%s: %s map lookup problem -- " diff --git a/postfix/src/cleanup/cleanup_map11.c b/postfix/src/cleanup/cleanup_map11.c index 0f4f25bd2..6e835b0e2 100644 --- a/postfix/src/cleanup/cleanup_map11.c +++ b/postfix/src/cleanup/cleanup_map11.c @@ -104,7 +104,9 @@ int cleanup_map11_external(CLEANUP_STATE *state, VSTRING *addr, * the place. */ for (count = 0; count < MAX_RECURSION; count++) { - if ((new_addr = mail_addr_map(maps, STR(addr), propagate)) != 0) { + if ((new_addr = mail_addr_map(maps, STR(addr), propagate, + MAIL_ADDR_FORM_EXTERNAL, + MAIL_ADDR_FORM_EXTERNAL)) != 0) { if (new_addr->argc > 1) msg_warn("%s: multi-valued %s entry for %s", state->queue_id, maps->title, STR(addr)); diff --git a/postfix/src/cleanup/cleanup_map1n.c b/postfix/src/cleanup/cleanup_map1n.c index 65a03fc12..3ec776c81 100644 --- a/postfix/src/cleanup/cleanup_map1n.c +++ b/postfix/src/cleanup/cleanup_map1n.c @@ -132,8 +132,8 @@ ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, UNEXPAND(argv, addr); RETURN(argv); } - quote_822_local(state->temp1, argv->argv[arg]); - if ((lookup = mail_addr_map(maps, STR(state->temp1), propagate)) != 0) { + if ((lookup = mail_addr_map_internal(maps, argv->argv[arg], + propagate)) != 0) { saved_lhs = mystrdup(argv->argv[arg]); for (i = 0; i < lookup->argc; i++) { if (strlen(lookup->argv[i]) > var_virt_addrlen_limit) { @@ -145,18 +145,17 @@ ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, UNEXPAND(argv, addr); RETURN(argv); } - unquote_822_local(state->temp1, lookup->argv[i]); if (i == 0) { - UPDATE(argv->argv[arg], STR(state->temp1)); + UPDATE(argv->argv[arg], lookup->argv[i]); } else { - argv_add(argv, STR(state->temp1), ARGV_END); + argv_add(argv, lookup->argv[i], ARGV_END); argv_terminate(argv); } /* * Allow an address to expand into itself once. */ - if (strcasecmp_utf8(saved_lhs, STR(state->temp1)) == 0) + if (strcasecmp_utf8(saved_lhs, lookup->argv[i]) == 0) been_here_fixed(been_here, saved_lhs); } myfree(saved_lhs); diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 9de1ce84f..dd7a18265 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -33,7 +33,8 @@ 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 midna_adomain.c + smtputf8.c mail_conf_over.c mail_parm_split.c midna_adomain.c \ + mail_addr_form.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 \ @@ -69,7 +70,7 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.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 midna_adomain.o \ - $(NON_PLUGIN_MAP_OBJ) + $(NON_PLUGIN_MAP_OBJ) mail_addr_form.o # 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. @@ -102,7 +103,7 @@ HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.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 + attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h TESTSRC = rec2stream.c stream2rec.c recdump.c DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) @@ -117,7 +118,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \ valid_mailhost_addr own_inet_addr header_body_checks \ data_redirect addr_match_list safe_ultostr verify_sender_addr \ mail_version mail_dict server_acl uxtext mail_parm_split \ - fold_addr smtp_reply_footer + fold_addr smtp_reply_footer mail_addr_map_tester LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) LIB_DIR = ../../lib @@ -248,10 +249,8 @@ off_cvt: $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) mv junk $@.o -mail_addr_map: $(LIB) $(LIBS) - mv $@.o junk +mail_addr_map_tester: mail_addr_map_tester.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - mv junk $@.o mail_addr_find: $(LIB) $(LIBS) mv $@.o junk @@ -381,7 +380,8 @@ tests: tok822_test mime_tests strip_addr_test tok822_limit_test \ namadr_list_test mail_conf_time_test header_body_checks_tests \ mail_version_test server_acl_test resolve_local_test maps_test \ safe_ultostr_test mail_parm_split_test fold_addr_test \ - smtp_reply_footer_test off_cvt_test + smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \ + mail_addr_find_test mail_addr_map_test quote_822_local_test mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \ mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4 @@ -578,7 +578,7 @@ ehlo_mask_test: ehlo_mask ehlo_mask.in ehlo_mask.ref rm -f ehlo_mask.tmp namadr_list_test: namadr_list namadr_list.in namadr_list.ref - -sh namadr_list.in >namadr_list.tmp 2>&1 + -$(SHLIB_ENV) sh namadr_list.in >namadr_list.tmp 2>&1 diff namadr_list.ref namadr_list.tmp rm -f namadr_list.tmp @@ -673,6 +673,27 @@ off_cvt_test: off_cvt off_cvt.in off_cvt.ref diff off_cvt.ref off_cvt.tmp rm -f off_cvt.tmp +mail_addr_crunch_test: mail_addr_crunch mail_addr_crunch.in mail_addr_crunch.ref + -$(SHLIB_ENV) sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1 + diff mail_addr_crunch.ref mail_addr_crunch.tmp + rm -f mail_addr_crunch.tmp + +mail_addr_find_test: mail_addr_find mail_addr_find.in mail_addr_find.ref + -$(SHLIB_ENV) sh mail_addr_find.in >mail_addr_find.tmp 2>&1 + diff mail_addr_find.ref mail_addr_find.tmp + rm -f mail_addr_find.tmp + +mail_addr_map_test: update mail_addr_map_tester mail_addr_map.ref + -$(SHLIB_ENV) ./mail_addr_map_tester pass_tests + -$(SHLIB_ENV) ./mail_addr_map_tester fail_tests >mail_addr_map.tmp 2>&1 + diff mail_addr_map.ref mail_addr_map.tmp + rm -f mail_addr_map.tmp + +quote_822_local_test: update quote_822_local quote_822_local.in quote_822_local.ref + -$(SHLIB_ENV) ./quote_822_local < quote_822_local.in >quote_822_local.tmp 2>&1 + diff quote_822_local.ref quote_822_local.tmp + rm -f quote_822_local.tmp + printfck: $(OBJS) $(PROG) rm -rf printfck mkdir printfck @@ -1458,6 +1479,9 @@ mail_addr_crunch.o: ../../include/vstring.h mail_addr_crunch.o: canon_addr.h mail_addr_crunch.o: mail_addr_crunch.c mail_addr_crunch.o: mail_addr_crunch.h +mail_addr_crunch.o: mail_addr_form.h +mail_addr_crunch.o: quote_822_local.h +mail_addr_crunch.o: quote_flags.h mail_addr_crunch.o: resolve_clnt.h mail_addr_crunch.o: tok822.h mail_addr_find.o: ../../include/argv.h @@ -1473,10 +1497,17 @@ mail_addr_find.o: ../../include/vstream.h mail_addr_find.o: ../../include/vstring.h mail_addr_find.o: mail_addr_find.c mail_addr_find.o: mail_addr_find.h +mail_addr_find.o: mail_addr_form.h mail_addr_find.o: mail_params.h mail_addr_find.o: maps.h +mail_addr_find.o: quote_822_local.h +mail_addr_find.o: quote_flags.h mail_addr_find.o: resolve_local.h mail_addr_find.o: strip_addr.h +mail_addr_form.o: ../../include/name_code.h +mail_addr_form.o: ../../include/sys_defs.h +mail_addr_form.o: mail_addr_form.c +mail_addr_form.o: mail_addr_form.h mail_addr_map.o: ../../include/argv.h mail_addr_map.o: ../../include/check_arg.h mail_addr_map.o: ../../include/dict.h @@ -1489,9 +1520,32 @@ mail_addr_map.o: ../../include/vstream.h mail_addr_map.o: ../../include/vstring.h mail_addr_map.o: mail_addr_crunch.h mail_addr_map.o: mail_addr_find.h +mail_addr_map.o: mail_addr_form.h mail_addr_map.o: mail_addr_map.c mail_addr_map.o: mail_addr_map.h mail_addr_map.o: maps.h +mail_addr_map.o: quote_822_local.h +mail_addr_map.o: quote_flags.h +mail_addr_map_tester.o: ../../include/argv.h +mail_addr_map_tester.o: ../../include/check_arg.h +mail_addr_map_tester.o: ../../include/dict.h +mail_addr_map_tester.o: ../../include/msg.h +mail_addr_map_tester.o: ../../include/myflock.h +mail_addr_map_tester.o: ../../include/mymalloc.h +mail_addr_map_tester.o: ../../include/readlline.h +mail_addr_map_tester.o: ../../include/stringops.h +mail_addr_map_tester.o: ../../include/sys_defs.h +mail_addr_map_tester.o: ../../include/vbuf.h +mail_addr_map_tester.o: ../../include/vstream.h +mail_addr_map_tester.o: ../../include/vstring.h +mail_addr_map_tester.o: ../../include/vstring_vstream.h +mail_addr_map_tester.o: canon_addr.h +mail_addr_map_tester.o: mail_addr_form.h +mail_addr_map_tester.o: mail_addr_map.h +mail_addr_map_tester.o: mail_addr_map_tester.c +mail_addr_map_tester.o: mail_conf.h +mail_addr_map_tester.o: mail_params.h +mail_addr_map_tester.o: maps.h mail_command_client.o: ../../include/attr.h mail_command_client.o: ../../include/check_arg.h mail_command_client.o: ../../include/htable.h @@ -2575,8 +2629,13 @@ 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/check_arg.h strip_addr.o: ../../include/mymalloc.h strip_addr.o: ../../include/sys_defs.h +strip_addr.o: ../../include/vbuf.h +strip_addr.o: ../../include/vstring.h +strip_addr.o: quote_822_local.h +strip_addr.o: quote_flags.h strip_addr.o: split_addr.h strip_addr.o: strip_addr.c strip_addr.o: strip_addr.h diff --git a/postfix/src/global/mail_addr_crunch.c b/postfix/src/global/mail_addr_crunch.c index 9cbf0d932..7e7db1a06 100644 --- a/postfix/src/global/mail_addr_crunch.c +++ b/postfix/src/global/mail_addr_crunch.c @@ -6,22 +6,46 @@ /* SYNOPSIS /* #include /* -/* ARGV *mail_addr_crunch(string, extension) +/* ARGV *mail_addr_crunch_ext_to_int(string, extension) +/* const char *string; +/* const char *extension; +/* +/* ARGV *mail_addr_crunch(string, extension, in_form, out_form) +/* const char *string; +/* const char *extension; +/* int in_form; +/* int out_form; +/* LEGACY SUPPORT +/* ARGV *mail_addr_crunch_noconv(string, extension) /* const char *string; /* const char *extension; /* DESCRIPTION -/* mail_addr_crunch() parses a string with zero or more addresses, -/* rewrites each address to canonical form, and optionally applies -/* an address extension to each resulting address. Input and result -/* are in external (quoted) format. The caller is expected to pass -/* the result to argv_free(). +/* mail_addr_crunch_ext_to_int() parses a string with zero or +/* more email addresses, rewrites each address to canonical form, +/* and optionally applies an address extension to each resulting +/* address. The string is in external form, and the result is +/* in internal form. This API minimizes the number of conversions +/* between internal and external forms. The caller is expected +/* to pass the result to argv_free(). +/* +/* mail_addr_crunch() gives more control, at the cost of +/* additional conversions between internal and external forms. +/* +/* mail_addr_crunch_noconv() is used by legacy code and performs +/* no conversion between internal and external forms. /* /* Arguments: /* .IP string -/* A string with zero or more addresses in RFC 822 (external) format. +/* A string with zero or more addresses in external (quoted) +/* form, or in the form specified with the in_form argument. /* .IP extension /* A null pointer, or an address extension (including the recipient /* address delimiter) that is propagated to all result addresses. +/* This is in internal (unquoted) form. +/* .IP in_form +/* .IP out_form +/* Input and output address forms, either MAIL_ADDR_FORM_INTERNAL +/* (unquoted form) or MAIL_ADDR_FORM_EXTERNAL (quoted form). /* DIAGNOSTICS /* Fatal error: out of memory. /* SEE ALSO @@ -53,12 +77,15 @@ #include #include +#include #include /* mail_addr_crunch - break string into addresses, optionally add extension */ -ARGV *mail_addr_crunch(const char *string, const char *extension) +ARGV *mail_addr_crunch(const char *string, const char *extension, + int in_form, int out_form) { + VSTRING *intern_addr = vstring_alloc(100); VSTRING *extern_addr = vstring_alloc(100); VSTRING *canon_addr = vstring_alloc(100); ARGV *argv = argv_alloc(1); @@ -73,6 +100,14 @@ ARGV *mail_addr_crunch(const char *string, const char *extension) #define STR(x) vstring_str(x) + /* + * Optionally convert input from internal form. + */ + if (in_form == MAIL_ADDR_FORM_INTERNAL) { + quote_822_local(extern_addr, string); + string = STR(extern_addr); + } + /* * Parse the string, rewrite each address to canonical form, and convert * the result to external (quoted) form. Optionally apply the extension @@ -84,27 +119,37 @@ ARGV *mail_addr_crunch(const char *string, const char *extension) if (*string == 0 || strcmp(string, "<>") == 0) string = "\"\""; tree = tok822_parse(string); + /* string->extern_addr would be invalidated by tok822_externalize() */ + string = 0; addr_list = tok822_grep(tree, TOK822_ADDR); for (tpp = addr_list; *tpp; tpp++) { tok822_externalize(extern_addr, tpp[0]->head, TOK822_STR_DEFL); canon_addr_external(canon_addr, STR(extern_addr)); - if (extension) { - VSTRING_SPACE(canon_addr, extlen + 1); - if ((ratsign = strrchr(STR(canon_addr), '@')) == 0) { - vstring_strcat(canon_addr, extension); + unquote_822_local(intern_addr, STR(canon_addr)); + if (extension && strchr(STR(intern_addr), *extension) == 0) { + VSTRING_SPACE(intern_addr, extlen + 1); + if ((ratsign = strrchr(STR(intern_addr), '@')) == 0) { + vstring_strcat(intern_addr, extension); } else { memmove(ratsign + extlen, ratsign, strlen(ratsign) + 1); memcpy(ratsign, extension, extlen); - VSTRING_SKIP(canon_addr); + VSTRING_SKIP(intern_addr); } } - argv_add(argv, STR(canon_addr), ARGV_END); + /* Optionally convert output to external form. */ + if (out_form == MAIL_ADDR_FORM_EXTERNAL) { + quote_822_local(extern_addr, STR(intern_addr)); + argv_add(argv, STR(extern_addr), ARGV_END); + } else { + argv_add(argv, STR(intern_addr), ARGV_END); + } } argv_terminate(argv); myfree((void *) addr_list); tok822_free_tree(tree); vstring_free(canon_addr); vstring_free(extern_addr); + vstring_free(intern_addr); return (argv); } @@ -121,30 +166,66 @@ ARGV *mail_addr_crunch(const char *string, const char *extension) #include #include +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + return (vstring_strcpy(result, addr)); +} + +static int get_addr_form(const char *prompt, VSTRING *buf) +{ + int addr_form; + + if (prompt) { + vstream_printf("%s: ", prompt); + vstream_fflush(VSTREAM_OUT); + } + if (vstring_get_nonl(buf, VSTREAM_IN) == VSTREAM_EOF) + exit(0); + if ((addr_form = mail_addr_form_from_string(STR(buf))) < 0) + msg_fatal("bad address form: %s", STR(buf)); + return (addr_form); +} + int main(int unused_argc, char **unused_argv) { VSTRING *extension = vstring_alloc(1); VSTRING *buf = vstring_alloc(1); ARGV *argv; char **cpp; + int do_prompt = isatty(0); + int in_form; + int out_form; mail_conf_read(); if (chdir(var_queue_dir) < 0) msg_fatal("chdir %s: %m", var_queue_dir); - vstream_printf("extension: (CR for none): "); - vstream_fflush(VSTREAM_OUT); + in_form = get_addr_form(do_prompt ? "input form" : 0, buf); + out_form = get_addr_form(do_prompt ? "output form" : 0, buf); + if (do_prompt) { + vstream_printf("extension: (CR for none): "); + vstream_fflush(VSTREAM_OUT); + } if (vstring_get_nonl(extension, VSTREAM_IN) == VSTREAM_EOF) exit(0); - vstream_printf("print strings to be translated, one per line\n"); - vstream_fflush(VSTREAM_OUT); + if (do_prompt) { + vstream_printf("print strings to be translated, one per line\n"); + vstream_fflush(VSTREAM_OUT); + } while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { - argv = mail_addr_crunch(STR(buf), VSTRING_LEN(extension) ? STR(extension) : 0); + argv = mail_addr_crunch(STR(buf), (VSTRING_LEN(extension) ? + STR(extension) : 0), + in_form, out_form); for (cpp = argv->argv; *cpp; cpp++) - vstream_printf(" %s\n", *cpp); + vstream_printf("|%s|\n", *cpp); vstream_fflush(VSTREAM_OUT); + argv_free(argv); } + vstring_free(extension); + vstring_free(buf); return (0); } diff --git a/postfix/src/global/mail_addr_crunch.h b/postfix/src/global/mail_addr_crunch.h index 2c5eb3375..3ee39fff1 100644 --- a/postfix/src/global/mail_addr_crunch.h +++ b/postfix/src/global/mail_addr_crunch.h @@ -16,10 +16,26 @@ */ #include + /* + * Global library. + */ +#include +#include + /* * External interface. */ -extern ARGV *mail_addr_crunch(const char *, const char *); +extern ARGV *mail_addr_crunch(const char *, const char *, int, int); + + /* The least-overhead form. */ +#define mail_addr_crunch_ext_to_int(string, extension) \ + mail_addr_crunch((string), (extension), MAIL_ADDR_FORM_EXTERNAL, \ + MAIL_ADDR_FORM_INTERNAL) + + /* The legacy form. */ +#define mail_addr_crunch_noconv(string, extension) \ + mail_addr_crunch((string), (extension), MAIL_ADDR_FORM_NOCONV, \ + MAIL_ADDR_FORM_NOCONV) /* LICENSE /* .ad diff --git a/postfix/src/global/mail_addr_crunch.in b/postfix/src/global/mail_addr_crunch.in new file mode 100644 index 000000000..bf25737e5 --- /dev/null +++ b/postfix/src/global/mail_addr_crunch.in @@ -0,0 +1,51 @@ +#!/bin/sh + +echo ==== external to internal, with extension +$VALGRIND ./mail_addr_crunch <<'EOF' +external +internal ++extension +foo@example.com, "foo bar"@example.com, foo+ext@example.com +EOF + +echo ==== external to internal, without extension +$VALGRIND ./mail_addr_crunch <<'EOF' +external +internal + +foo@example.com, "foo bar"@example.com, foo+ext@example.com +EOF + +echo ==== external to external, with extension +$VALGRIND ./mail_addr_crunch <<'EOF' +external +external ++extension +foo@example.com, "foo bar"@example.com, foo+ext@example.com +EOF + +echo ==== external to external, without extension +$VALGRIND ./mail_addr_crunch <<'EOF' +external +external + +foo@example.com, "foo bar"@example.com, foo+ext@example.com +EOF + +echo ==== internal to internal, with extension +$VALGRIND ./mail_addr_crunch <<'EOF' +internal +internal ++extension +foo@example.com +foo+ext@example.com +EOF + +echo ==== internal to internal, without extension +$VALGRIND ./mail_addr_crunch <<'EOF' +internal +internal + +foo@example.com +foo+ext@example.com +EOF diff --git a/postfix/src/global/mail_addr_crunch.ref b/postfix/src/global/mail_addr_crunch.ref new file mode 100644 index 000000000..ec95edfe6 --- /dev/null +++ b/postfix/src/global/mail_addr_crunch.ref @@ -0,0 +1,22 @@ +==== external to internal, with extension +|foo+extension@example.com| +|foo bar+extension@example.com| +|foo+ext@example.com| +==== external to internal, without extension +|foo@example.com| +|foo bar@example.com| +|foo+ext@example.com| +==== external to external, with extension +|foo+extension@example.com| +|"foo bar+extension"@example.com| +|foo+ext@example.com| +==== external to external, without extension +|foo@example.com| +|"foo bar"@example.com| +|foo+ext@example.com| +==== internal to internal, with extension +|foo+extension@example.com| +|foo+ext@example.com| +==== internal to internal, without extension +|foo@example.com| +|foo+ext@example.com| diff --git a/postfix/src/global/mail_addr_find.c b/postfix/src/global/mail_addr_find.c index 34e77a15a..7adf59330 100644 --- a/postfix/src/global/mail_addr_find.c +++ b/postfix/src/global/mail_addr_find.c @@ -6,16 +6,52 @@ /* SYNOPSIS /* #include /* -/* const char *mail_addr_find(maps, address, extension) +/* const char *mail_addr_find_int_to_ext(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* +/* const char *mail_addr_find(maps, address, extension, in_form, out_form) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* int in_form; +/* int out_form; +/* LEGACY SUPPORT +/* const char *mail_addr_find_noconv(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* +/* const char *mail_addr_find_trans(maps, address, extension) /* MAPS *maps; /* const char *address; /* char **extension; /* DESCRIPTION -/* mail_addr_find() searches the specified maps for an entry with as -/* key the specified address, and derivations from that address. -/* It is up to the caller to specify its case sensitivity -/* preferences when it opens the maps. -/* The result is overwritten upon each call. +/* mail_addr_find_int_to_ext() searches the specified maps for +/* an entry with as key the specified address, and derivations +/* from that address. It is up to the caller to specify its +/* case sensitivity preferences when it opens the maps. The +/* search address is in internal (unquoted) form. The result +/* is in the form found in the table (it is not necessarily +/* an email address). This version avoids internal/external +/* (unquoted/quoted) conversions of the query, extension, or +/* result. +/* +/* mail_addr_find() gives more control, at the cost of +/* additional conversions between internal and external forms. +/* In particular, the output conversion to internal form assumes +/* that the lookup result is an email address. +/* +/* mail_addr_find_noconv() is used by legacy code that is not +/* yet aware of internal versus external addres formats. +/* +/* mail_addr_find_trans() implements transitional functionality. +/* It behaves like mail_addr_find(...INTERNAL, ...NOCONV) and +/* searches a table with the quoted form of the address, but +/* if the lookup produces no result, and the quoted address +/* differs from the unquoted form, it also tries +/* mail_addr_find(...NOCONV, ...NOCONV). /* /* An address that is in the form \fIuser\fR matches itself. /* @@ -44,7 +80,12 @@ /* the address of a dynamic memory copy of the address extension /* that had to be chopped off in order to match the lookup tables. /* The copy includes the recipient address delimiter. +/* The copy is in internal (unquoted) form. /* The caller is expected to pass the copy to myfree(). +/* .IP in_form +/* .IP out_form +/* Input and output address forms, either MAIL_ADDR_FORM_INTERNAL +/* (unquoted form) or MAIL_ADDR_FORM_EXTERNAL (quoted form). /* DIAGNOSTICS /* The maps->error value is non-zero when the lookup /* should be tried again. @@ -66,6 +107,7 @@ #include #include +#include /* Utility library. */ @@ -81,31 +123,89 @@ #include #include #include +#include /* Application-specific. */ #define STR vstring_str +/* mail_addr_find_trans - transitional support (migration tool) */ + +const char *mail_addr_find_trans(MAPS *path, const char *address, char **extp) +{ + const char *result; + static VSTRING *quoted_addr; + + /* + * First, let mail_addr_find() search with the address converted to + * external form. Fall back to a search with the address in internal + * (unconverted) form, if no match was found and the internal and + * external forms differ. + */ + if ((result = mail_addr_find(path, address, extp, + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_NOCONV)) == 0) { + if (quoted_addr == 0) + quoted_addr = vstring_alloc(100); + quote_822_local(quoted_addr, address); + if (strcmp(STR(quoted_addr), address) != 0) + result = mail_addr_find(path, address, extp, + MAIL_ADDR_FORM_NOCONV, MAIL_ADDR_FORM_NOCONV); + } + return (result); +} + +/* find_addr - helper to search map with external-form address */ + +static const char *find_addr(MAPS *path, const char *address, int flags, + int in_form, VSTRING *ext_addr_buf) +{ + if (in_form == MAIL_ADDR_FORM_INTERNAL) { + quote_822_local(ext_addr_buf, address); + address = STR(ext_addr_buf); + } + return (maps_find(path, address, flags)); +} + /* mail_addr_find - map a canonical address */ -const char *mail_addr_find(MAPS *path, const char *address, char **extp) +const char *mail_addr_find(MAPS *path, const char *address, char **extp, + int in_form, int out_form) { const char *myname = "mail_addr_find"; + VSTRING *ext_addr_buf = 0; + VSTRING *int_addr_buf = 0; + const char *int_addr; + static VSTRING *int_result = 0; const char *result; char *ratsign = 0; - char *full_key; - char *bare_key; + char *int_full_key; + char *int_bare_key; char *saved_ext; int rc = 0; + /* + * Optionally convert input from external form. + */ + if (in_form == MAIL_ADDR_FORM_EXTERNAL) { + int_addr_buf = vstring_alloc(100); + unquote_822_local(int_addr_buf, address); + int_addr = STR(int_addr_buf); + in_form = MAIL_ADDR_FORM_INTERNAL; + } else { + int_addr = address; + } + if (in_form == MAIL_ADDR_FORM_INTERNAL) + ext_addr_buf = vstring_alloc(100); + /* * Initialize. */ - full_key = mystrdup(address); + int_full_key = mystrdup(int_addr); if (*var_rcpt_delim == 0) { - bare_key = saved_ext = 0; + int_bare_key = saved_ext = 0; } else { - bare_key = strip_addr(full_key, &saved_ext, var_rcpt_delim); + int_bare_key = + strip_addr_internal(int_full_key, &saved_ext, var_rcpt_delim); } /* @@ -117,8 +217,11 @@ const char *mail_addr_find(MAPS *path, const char *address, char **extp) #define FULL 0 #define PARTIAL DICT_FLAG_FIXED - if ((result = maps_find(path, full_key, FULL)) == 0 && path->error == 0 - && bare_key != 0 && (result = maps_find(path, bare_key, PARTIAL)) != 0 + if ((result = find_addr(path, int_full_key, FULL, + in_form, ext_addr_buf)) == 0 + && path->error == 0 && int_bare_key != 0 + && (result = find_addr(path, int_bare_key, PARTIAL, + in_form, ext_addr_buf)) != 0 && extp != 0) { *extp = saved_ext; saved_ext = 0; @@ -129,16 +232,18 @@ const char *mail_addr_find(MAPS *path, const char *address, char **extp) * user+foo@[${proxy,inet}_interfaces]. Then try with +foo stripped off. */ if (result == 0 && path->error == 0 - && (ratsign = strrchr(full_key, '@')) != 0 + && (ratsign = strrchr(int_full_key, '@')) != 0 && (strcasecmp_utf8(ratsign + 1, var_myorigin) == 0 || (rc = resolve_local(ratsign + 1)) > 0)) { *ratsign = 0; - result = maps_find(path, full_key, PARTIAL); - if (result == 0 && path->error == 0 && bare_key != 0) { - if ((ratsign = strrchr(bare_key, '@')) == 0) + result = find_addr(path, int_full_key, PARTIAL, in_form, ext_addr_buf); + if (result == 0 && path->error == 0 && int_bare_key != 0) { + if ((ratsign = strrchr(int_bare_key, '@')) == 0) msg_panic("%s: bare key botch", myname); *ratsign = 0; - if ((result = maps_find(path, bare_key, PARTIAL)) != 0 && extp != 0) { + if ((result = find_addr(path, int_bare_key, PARTIAL, + in_form, ext_addr_buf)) != 0 + && extp != 0) { *extp = saved_ext; saved_ext = 0; } @@ -151,7 +256,18 @@ const char *mail_addr_find(MAPS *path, const char *address, char **extp) * Try @domain. */ if (result == 0 && path->error == 0 && ratsign) - result = maps_find(path, ratsign, PARTIAL); + result = maps_find(path, ratsign, PARTIAL); /* addr form is OK */ + + /* + * Optionally convert the result to internal form. The lookup result is + * supposed to be in external form. + */ + if (result != 0 && out_form == MAIL_ADDR_FORM_INTERNAL) { + if (int_result == 0) + int_result = vstring_alloc(100); + unquote_822_local(int_result, result); + result = STR(int_result); + } /* * Clean up. @@ -161,12 +277,15 @@ const char *mail_addr_find(MAPS *path, const char *address, char **extp) result ? result : path->error ? "(try again)" : "(not found)"); - myfree(full_key); - if (bare_key) - myfree(bare_key); + myfree(int_full_key); + if (int_bare_key) + myfree(int_bare_key); if (saved_ext) myfree(saved_ext); - + if (int_addr_buf) + vstring_free(int_addr_buf); + if (ext_addr_buf) + vstring_free(ext_addr_buf); return (result); } @@ -178,34 +297,102 @@ const char *mail_addr_find(MAPS *path, const char *address, char **extp) */ #include #include +#include #include +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v] database", progname); +} + int main(int argc, char **argv) { VSTRING *buffer = vstring_alloc(100); + char *bp; MAPS *path; const char *result; char *extent; + char *in_field; + char *out_field; + char *key_field; + char *expect_res; + char *expect_ext; + int in_form; + int out_form; + int ch; + int errs = 0; /* * Parse JCL. */ - if (argc != 2) - msg_fatal("usage: %s database", argv[0]); - msg_verbose = 1; + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind + 1) + usage(argv[0]); /* * Initialize. */ - mail_conf_read(); - path = maps_create(argv[0], argv[1], DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX \ - |DICT_FLAG_UTF8_REQUEST); + mail_conf_read(); /* XXX eliminate dependency. */ + myfree(var_rcpt_delim); + var_rcpt_delim = mystrdup("+"); + myfree(var_myorigin); + var_myorigin = mystrdup("localhost.localdomain"); + myfree(var_mydest); + var_mydest = mystrdup("localhost.localdomain"); + path = maps_create(argv[0], argv[optind], DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + if ((in_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no input form"); + if ((in_form = mail_addr_form_from_string(in_field)) < 0) + msg_fatal("bad input form: '%s'", in_field); + if ((out_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no output form"); + if ((out_form = mail_addr_form_from_string(out_field)) < 0) + msg_fatal("bad output form: '%s'", out_field); + if ((key_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no search key"); + expect_res = mystrtok(&bp, ":"); + expect_ext = mystrtok(&bp, ":"); extent = 0; - result = mail_addr_find(path, STR(buffer), &extent); - vstream_printf("%s -> %s (%s)\n", STR(buffer), result ? result : + result = mail_addr_find(path, key_field, &extent, in_form, out_form); + vstream_printf("%s:%s -> %s:%s (%s)\n", + in_field, key_field, out_field, result ? result : path->error ? "(try again)" : "(not found)", extent ? extent : "null extension"); + if (expect_res && result) { + if (strcmp(expect_res, result) != 0) { + msg_warn("expect result '%s' but got '%s'", expect_res, result); + errs = 1; + if (expect_ext && extent) { + if (strcmp(expect_ext, extent) != 0) + msg_warn("expect extension '%s' but got '%s'", + expect_ext, extent); + errs = 1; + } else if (expect_ext && !extent) { + msg_warn("expect extension '%s' but got none", expect_ext); + errs = 1; + } else if (!expect_ext && extent) { + msg_warn("expect no extension but got '%s'", extent); + errs = 1; + } + } + } else if (expect_res && !result) { + msg_warn("expect result '%s' but got none", expect_res); + errs = 1; + } else if (!expect_res && result) { + msg_warn("expected no result but got '%s'", result); + errs = 1; + } vstream_fflush(VSTREAM_OUT); if (extent) myfree(extent); @@ -213,7 +400,7 @@ int main(int argc, char **argv) vstring_free(buffer); maps_free(path); - return (0); + return (errs != 0); } #endif diff --git a/postfix/src/global/mail_addr_find.h b/postfix/src/global/mail_addr_find.h index a82246a87..aa3a97450 100644 --- a/postfix/src/global/mail_addr_find.h +++ b/postfix/src/global/mail_addr_find.h @@ -14,12 +14,23 @@ /* * Global library. */ +#include #include /* * External interface. */ -extern const char *mail_addr_find(MAPS *, const char *, char **); +extern const char *mail_addr_find(MAPS *, const char *, char **, int, int); + + /* The least-overhead form. */ +#define mail_addr_find_int_to_ext(maps, address, extension) \ + mail_addr_find((maps), (address), (extension), \ + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_EXTERNAL) + + /* The legacy form. */ +#define mail_addr_find_noconv(maps, address, extension) \ + mail_addr_find((maps), (address), (extension), \ + MAIL_ADDR_FORM_NOCONV, MAIL_ADDR_FORM_NOCONV) /* LICENSE /* .ad diff --git a/postfix/src/global/mail_addr_find.in b/postfix/src/global/mail_addr_find.in new file mode 100644 index 000000000..09bccf0cb --- /dev/null +++ b/postfix/src/global/mail_addr_find.in @@ -0,0 +1,26 @@ +#!/bin/sh + +# Format: input form:output form:query:expected result:expected extension +# The last fields are optional. + +echo ==== no search string extension +$VALGRIND ./mail_addr_find 'inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}' <<'EOF' +internal:external:plain1@1.example:plain2@2.example +internal:external:aa bb@cc.example:"dd ee"@dd.example +external:external:"aa bb"@cc.example:"dd ee"@dd.example +external:internal:"aa bb"@cc.example:dd ee@dd.example +noconv:noconv:plain1@1.example:plain2@2.example +noconv:noconv:aa bb@cc.example +noconv:noconv:"aa bb"@cc.example:"dd ee"@dd.example +EOF + +echo ==== with search string extension +$VALGRIND ./mail_addr_find 'inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}' <<'EOF' +internal:external:plain1+ext@1.example:plain2@2.example:+ext +internal:external:aa bb+ax bx@cc.example:"dd ee"@dd.example:+ax bx +external:external:"aa bb+ax bx"@cc.example:"dd ee"@dd.example:+ax bx +external:internal:"aa bb+ax bx"@cc.example:dd ee@dd.example:+ax bx +noconv:noconv:plain1+ext@1.example:plain2@2.example:+ext +noconv:noconv:"aa bb+ax bx"@cc.example +noconv:noconv:"aa bb"+ax bx@cc.example:"dd ee"@dd.example:+ax bx +EOF diff --git a/postfix/src/global/mail_addr_find.ref b/postfix/src/global/mail_addr_find.ref new file mode 100644 index 000000000..b39d64e5b --- /dev/null +++ b/postfix/src/global/mail_addr_find.ref @@ -0,0 +1,16 @@ +==== no search string extension +internal:plain1@1.example -> external:plain2@2.example (null extension) +internal:aa bb@cc.example -> external:"dd ee"@dd.example (null extension) +external:"aa bb"@cc.example -> external:"dd ee"@dd.example (null extension) +external:"aa bb"@cc.example -> internal:dd ee@dd.example (null extension) +noconv:plain1@1.example -> noconv:plain2@2.example (null extension) +noconv:aa bb@cc.example -> noconv:(not found) (null extension) +noconv:"aa bb"@cc.example -> noconv:"dd ee"@dd.example (null extension) +==== with search string extension +internal:plain1+ext@1.example -> external:plain2@2.example (+ext) +internal:aa bb+ax bx@cc.example -> external:"dd ee"@dd.example (+ax bx) +external:"aa bb+ax bx"@cc.example -> external:"dd ee"@dd.example (+ax bx) +external:"aa bb+ax bx"@cc.example -> internal:dd ee@dd.example (+ax bx) +noconv:plain1+ext@1.example -> noconv:plain2@2.example (+ext) +noconv:"aa bb+ax bx"@cc.example -> noconv:(not found) (null extension) +noconv:"aa bb"+ax bx@cc.example -> noconv:"dd ee"@dd.example (+ax bx) diff --git a/postfix/src/global/mail_addr_form.c b/postfix/src/global/mail_addr_form.c new file mode 100644 index 000000000..115838f6a --- /dev/null +++ b/postfix/src/global/mail_addr_form.c @@ -0,0 +1,64 @@ +/*++ +/* NAME +/* mail_addr_form 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include +/* +/* int mail_addr_form_from_string(const char *addr_form_name) +/* +/* int mail_addr_form_to_string(int addr_form) +/* DESCRIPTION +/* mail_addr_form_from_string() converts a symbolic mail address +/* form name ("internal", "external", "noconv") into the +/* corresponding internal code. The result is -1 if an +/* unrecognized name was specified. +/* +/* mail_addr_form_to_string() converts from internal code +/* to the corresponding symbolic name. The result is null if +/* an unrecognized code was specified. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + +static const NAME_CODE addr_form_table[] = { + "external", MAIL_ADDR_FORM_EXTERNAL, + "internal", MAIL_ADDR_FORM_INTERNAL, + "noconv", MAIL_ADDR_FORM_NOCONV, + 0, -1, +}; + +/* mail_addr_form_from_string - symbolic mail address to internal form */ + +int mail_addr_form_from_string(const char *addr_form_name) +{ + return (name_code(addr_form_table, NAME_CODE_FLAG_NONE, addr_form_name)); +} + +const char *mail_addr_form_to_string(int addr_form) +{ + return (str_name_code(addr_form_table, addr_form)); +} diff --git a/postfix/src/global/mail_addr_form.h b/postfix/src/global/mail_addr_form.h new file mode 100644 index 000000000..f263ac511 --- /dev/null +++ b/postfix/src/global/mail_addr_form.h @@ -0,0 +1,36 @@ +#ifndef _MAIL_ADDR_FORM_H_INCLUDED_ +#define _MAIL_ADDR_FORM_H_INCLUDED_ + +/*++ +/* NAME +/* mail_addr_form 3h +/* SUMMARY +/* mail address formats +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. The MAIL_ADDR_FORM_NOCONV is for legacy code that + * hasn't yet been converted to external-form address lookups. + */ +#define MAIL_ADDR_FORM_NOCONV 0 /* do not convert */ +#define MAIL_ADDR_FORM_INTERNAL 1 /* unquoted form */ +#define MAIL_ADDR_FORM_EXTERNAL 2 /* quoted form */ + +extern int mail_addr_form_from_string(const char *); +extern const char *mail_addr_form_to_string(int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/global/mail_addr_map.c b/postfix/src/global/mail_addr_map.c index 6a0479643..fafb05dd7 100644 --- a/postfix/src/global/mail_addr_map.c +++ b/postfix/src/global/mail_addr_map.c @@ -6,29 +6,50 @@ /* SYNOPSIS /* #include /* -/* ARGV *mail_addr_map(path, address, propagate) +/* ARGV *mail_addr_map_internal(path, address, propagate) /* MAPS *path; /* const char *address; /* int propagate; +/* +/* ARGV *mail_addr_map(path, address, propagate, in_form, out_form) +/* MAPS *path; +/* const char *address; +/* int propagate; +/* int how; /* DESCRIPTION -/* mail_addr_map() returns the translation for the named address, -/* or a null pointer if none is found. The result is in canonical -/* external (quoted) form. The search is case insensitive. +/* mail_addr_map_internal() returns the translation for the +/* named address, or a null pointer if none is found. The +/* search address and results are in internal (unquoted) form. +/* +/* mail_addr_map() gives more control, at the cost of additional +/* conversions between internal and external forms. /* /* When the \fBpropagate\fR argument is non-zero, /* address extensions that aren't explicitly matched in the lookup /* table are propagated to the result addresses. The caller is /* expected to pass the result to argv_free(). /* -/* Lookups are performed by mail_addr_find(). When the result has the -/* form \fI@otherdomain\fR, the result is the original user in +/* Lookups are performed by mail_addr_find_internal(). When +/* the result has the form \fI@otherdomain\fR, the result is +/* the original user in /* \fIotherdomain\fR. /* +/* mail_addr_map() gives additional control over whether the +/* input is in internal (unquoted) or external (quoted) form. +/* to internal form and invokes mail_addr_map_int_to_ext(). +/* This may introduce additional unqoute822_local() and +/* quote_833_local() calls. +/* /* Arguments: /* .IP path /* Dictionary search path (see maps(3)). /* .IP address -/* The address to be looked up. +/* The address to be looked up in external (quoted) form, or +/* in the form specified with the in_form argument. +/* .IP in_form +/* .IP out_form +/* Input and output address forms, either MAIL_ADDR_FORM_INTERNAL +/* (unquoted form) or MAIL_ADDR_FORM_EXTERNAL (quoted form). /* DIAGNOSTICS /* Warnings: map lookup returns a non-address result. /* @@ -63,6 +84,7 @@ /* Global library. */ +#include #include #include #include @@ -74,7 +96,8 @@ /* mail_addr_map - map a canonical address */ -ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) +ARGV *mail_addr_map(MAPS *path, const char *address, int propagate, + int in_form, int out_form) { VSTRING *buffer = 0; const char *myname = "mail_addr_map"; @@ -83,12 +106,37 @@ ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) char *extension = 0; ARGV *argv = 0; int i; + VSTRING *int_address = 0; + VSTRING *ext_address = 0; + const char *int_addr; + + /* Crutch until we can retire MAIL_ADDR_FORM_NOCONV. */ + int mid_form = (out_form == MAIL_ADDR_FORM_NOCONV ? + MAIL_ADDR_FORM_NOCONV : MAIL_ADDR_FORM_EXTERNAL); + + /* + * Optionally convert input from external form. We prefer internal-form + * input to avoid an unnecessary input conversion in mail_addr_find(). + * But the consequence is that we have to convert the internal-form + * input's localpart to external form when mapping @domain -> @domain. + */ + if (in_form == MAIL_ADDR_FORM_EXTERNAL) { + int_address = vstring_alloc(100); + unquote_822_local(int_address, address); + int_addr = STR(int_address); + in_form = MAIL_ADDR_FORM_INTERNAL; + } else { + int_addr = address; + } /* * Look up the full address; if no match is found, look up the address * with the extension stripped off, and remember the unmatched extension. + * We explicitly call the mail_addr_find() variant that does not convert + * the lookup result. */ - if ((string = mail_addr_find(path, address, &extension)) != 0) { + if ((string = mail_addr_find(path, int_addr, &extension, + in_form, mid_form)) != 0) { /* * Prepend the original user to @otherdomain, but do not propagate @@ -96,23 +144,28 @@ ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) */ if (*string == '@') { buffer = vstring_alloc(100); - if ((ratsign = strrchr(address, '@')) != 0) - vstring_strncpy(buffer, address, ratsign - address); + if ((ratsign = strrchr(int_addr, '@')) != 0) + vstring_strncpy(buffer, int_addr, ratsign - int_addr); else - vstring_strcpy(buffer, address); + vstring_strcpy(buffer, int_addr); if (extension) vstring_truncate(buffer, LEN(buffer) - strlen(extension)); - vstring_strcat(buffer, string); - string = STR(buffer); + ext_address = vstring_alloc(100); + quote_822_local(ext_address, STR(buffer)); + vstring_strcat(ext_address, string); + string = STR(ext_address); } /* - * Canonicalize and externalize the result, and propagate the - * unmatched extension to each address found. + * Canonicalize the result, and propagate the unmatched extension to + * each address found. */ - argv = mail_addr_crunch(string, propagate ? extension : 0); + argv = mail_addr_crunch(string, propagate ? extension : 0, + mid_form, out_form); if (buffer) vstring_free(buffer); + if (ext_address) + vstring_free(ext_address); if (msg_verbose) for (i = 0; i < argv->argc; i++) msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]); @@ -138,61 +191,8 @@ ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) */ if (extension) myfree(extension); + if (int_address) + vstring_free(int_address); return (argv); } - -#ifdef TEST - - /* - * Proof-of-concept test program. Read an address from stdin, and spit out - * the lookup result. - */ -#include -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - VSTRING *buffer = vstring_alloc(100); - MAPS *path; - ARGV *result; - - /* - * Parse JCL. - */ - if (argc != 2) - msg_fatal("usage: %s database", argv[0]); - - /* - * Initialize. - */ -#define UPDATE(dst, src) { myfree(dst); dst = mystrdup(src); } - - mail_conf_read(); - 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 \ - | DICT_FLAGS_UTF8_REQUEST); - while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { - msg_info("=== Address extension on, extension propagation on ==="); - UPDATE(var_rcpt_delim, "+"); - if ((result = mail_addr_map(path, STR(buffer), 1)) != 0) - argv_free(result); - msg_info("=== Address extension on, extension propagation off ==="); - if ((result = mail_addr_map(path, STR(buffer), 0)) != 0) - argv_free(result); - msg_info("=== Address extension off ==="); - UPDATE(var_rcpt_delim, ""); - if ((result = mail_addr_map(path, STR(buffer), 1)) != 0) - argv_free(result); - } - vstring_free(buffer); - maps_free(path); - return (0); -} - -#endif diff --git a/postfix/src/global/mail_addr_map.h b/postfix/src/global/mail_addr_map.h index f887c611a..87f926850 100644 --- a/postfix/src/global/mail_addr_map.h +++ b/postfix/src/global/mail_addr_map.h @@ -19,12 +19,18 @@ /* * Global library. */ +#include #include /* * External interface. */ -extern ARGV *mail_addr_map(MAPS *, const char *, int); +extern ARGV *mail_addr_map(MAPS *, const char *, int, int, int); + + /* The least-overhead form. */ +#define mail_addr_map_internal(path, address, propagate) \ + mail_addr_map((path), (address), (propagate), \ + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL) /* LICENSE /* .ad diff --git a/postfix/src/global/mail_addr_map.ref b/postfix/src/global/mail_addr_map.ref new file mode 100644 index 000000000..c4b3f0535 --- /dev/null +++ b/postfix/src/global/mail_addr_map.ref @@ -0,0 +1,23 @@ +unknown: warning: fail test selftest 1 external to external, no extension, quoted: expect[0]='"bXb"@example.com', result[0]='"b b"@example.com' +unknown: database = inline:{ {"a a"@example.com="b b"@example.com} } +unknown: propagate = 1 +unknown: delimiter = '+' +unknown: in_form = external +unknown: out_form = external +unknown: address = "a a"@example.com +unknown: warning: fail test selftest 2 external to external, no extension, quoted: expects 1 results but there were 0 +unknown: no result to match expect[0]='"b b"@example.com' +unknown: database = inline:{ {"a a"@example.com="b b"@example.com} } +unknown: propagate = 1 +unknown: delimiter = '+' +unknown: in_form = external +unknown: out_form = external +unknown: address = "aXa"@example.com +unknown: warning: fail test selftest 3 external to external, no extension, quoted: expects 0 results but there were 1 +unknown: no expect to match result[0]='"b b"@example.com' +unknown: database = inline:{ {"a a"@example.com="b b"@example.com} } +unknown: propagate = 1 +unknown: delimiter = '+' +unknown: in_form = external +unknown: out_form = external +unknown: address = "a a"@example.com diff --git a/postfix/src/global/mail_addr_map_tester.c b/postfix/src/global/mail_addr_map_tester.c new file mode 100644 index 000000000..4bdd4bba7 --- /dev/null +++ b/postfix/src/global/mail_addr_map_tester.c @@ -0,0 +1,309 @@ +/*++ +/* NAME +/* mail_addr_map_tester 1 +/* SUMMARY +/* mail_addr_map test program +/* SYNOPSIS +/* mail_addr_map pass_tests | fail_tests +/* DESCRIPTION +/* mail_addr_map performs the specified set of built-in +/* unit tests. With 'pass_tests', all tests must pass, and +/* with 'fail_tests' all tests must fail. +/* DIAGNOSTICS +/* When a unit test fails, the program prints details of the +/* failed test. +/* +/* The program terminates with a non-zero exit status when at +/* least one test does not pass with 'pass_tests', or when at +/* least one test does not fail with 'fail_tests'. +/* SEE ALSO +/* mail_addr_map(3), generic address mapping +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include /* XXX eliminate main.cf dependency */ +#include + +/* Application-specific. */ + +#define STR vstring_str + +typedef struct { + const char *testname; + const char *database; + int propagate; + const char *delimiter; + int in_form; + int out_form; + const char *address; + const char *expect_argv[2]; + int expect_argc; +} MAIL_ADDR_MAP_TEST; + +#define DONT_PROPAGATE_UNMATCHED_EXTENSION 0 +#define DO_PROPAGATE_UNMATCHED_EXTENSION 1 +#define NO_RECIPIENT_DELIMITER "" +#define PLUS_RECIPIENT_DELIMITER "+" + + /* + * All these tests must pass, so that we know that mail_addr_map() works as + * intended. + */ +static MAIL_ADDR_MAP_TEST pass_tests[] = { + { + "1 external to external, no extension", + "inline:{ aa@example.com=bb@example.com }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "aa@example.com", + {"bb@example.com"}, 1, + }, + { + "2 external to external, extension, propagation", + "inline:{ aa@example.com=bb@example.com }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "aa+ext@example.com", + {"bb+ext@example.com"}, 1, + }, + { + "3 external to external, extension, no propagation, no match", + "inline:{ aa@example.com=bb@example.com }", + DONT_PROPAGATE_UNMATCHED_EXTENSION, NO_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "aa+ext@example.com", + {0}, 0, + }, + { + "4 external to external, extension, full match", + "inline:{{cc+ext@example.com=dd@example.com,ee@example.com}}", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "cc+ext@example.com", + {"dd@example.com", "ee@example.com"}, 2, + }, + { + "5 external to external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "\"a a\"@example.com", + {"\"b b\"@example.com"}, 1, + }, + { + "6 external to external, extension, propagation, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "\"a a+ext\"@example.com", + {"\"b b+ext\"@example.com"}, 1, + }, + { + "7 internal to internal, no extension, propagation, embedded space", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL, + "a a@example.com", + {"b b@example.com"}, 1, + }, + { + "8 internal to internal, extension, propagation, embedded space", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL, + "a a+ext@example.com", + {"b b+ext@example.com"}, 1, + }, + { + "9 noconv to noconv, no extension, propagation, embedded space", + "inline:{ {a_a@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL, + "a_a@example.com", + {"b b@example.com"}, 1, + }, + { + "10 noconv to noconv, extension, propagation, embedded space", + "inline:{ {a_a@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL, + "a_a+ext@example.com", + {"b b+ext@example.com"}, 1, + }, + 0, +}; + + /* + * All these tests must fail, so that we know that the tests work. + */ +static MAIL_ADDR_MAP_TEST fail_tests[] = { + { + "selftest 1 external to external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "\"a a\"@example.com", + {"\"bXb\"@example.com"}, 1, + }, + { + "selftest 2 external to external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "\"aXa\"@example.com", + {"\"b b\"@example.com"}, 1, + }, + { + "selftest 3 external to external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL, + "\"a a\"@example.com", + {0}, 0, + }, + 0, +}; + +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + return (vstring_strcpy(result, addr)); +} + +static int compare(const char *testname, + const char **expect_argv, int expect_argc, + char **result_argv, int result_argc) +{ + int n; + int err = 0; + + if (expect_argc != 0 && result_argc != 0) { + for (n = 0; n < expect_argc && n < result_argc; n++) { + if (strcmp(expect_argv[n], result_argv[n]) != 0) { + msg_warn("fail test %s: expect[%d]='%s', result[%d]='%s'", + testname, n, expect_argv[n], n, result_argv[n]); + err = 1; + } + } + } + if (expect_argc != result_argc) { + msg_warn("fail test %s: expects %d results but there were %d", + testname, expect_argc, result_argc); + for (n = expect_argc; n < result_argc; n++) + msg_info("no expect to match result[%d]='%s'", n, result_argv[n]); + for (n = result_argc; n < expect_argc; n++) + msg_info("no result to match expect[%d]='%s'", n, expect_argv[n]); + err = 1; + } + return (err); +} + +static char *progname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s pass_test | fail_test", progname); +} + +int main(int argc, char **argv) +{ + MAIL_ADDR_MAP_TEST *test; + MAIL_ADDR_MAP_TEST *tests; + int errs = 0; + +#define UPDATE(dst, src) { if (dst) myfree(dst); dst = mystrdup(src); } + + /* + * Parse JCL. + */ + progname = argv[0]; + if (argc != 2) { + usage(); + } else if (strcmp(argv[1], "pass_tests") == 0) { + tests = pass_tests; + } else if (strcmp(argv[1], "fail_tests") == 0) { + tests = fail_tests; + } else { + usage(); + } + + /* + * Initialize. + */ + mail_conf_read(); /* XXX eliminate */ + + /* + * A read-eval-print loop, because specifying C strings with quotes and + * backslashes is painful. + */ + for (test = tests; test->testname; test++) { + ARGV *result; + int fail = 0; + + if (mail_addr_form_to_string(test->in_form) == 0) { + msg_warn("test %s: bad in_form field: %d", + test->testname, test->in_form); + fail = 1; + continue; + } + if (mail_addr_form_to_string(test->out_form) == 0) { + msg_warn("test %s: bad out_form field: %d", + test->testname, test->out_form); + fail = 1; + continue; + } + MAPS *maps = maps_create("test", test->database, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + + UPDATE(var_rcpt_delim, test->delimiter); + result = mail_addr_map(maps, test->address, test->propagate, + test->in_form, test->out_form); + if (compare(test->testname, test->expect_argv, test->expect_argc, + result ? result->argv : 0, result ? result->argc : 0) != 0) { + msg_info("database = %s", test->database); + msg_info("propagate = %d", test->propagate); + msg_info("delimiter = '%s'", var_rcpt_delim); + msg_info("in_form = %s", mail_addr_form_to_string(test->in_form)); + msg_info("out_form = %s", mail_addr_form_to_string(test->out_form)); + msg_info("address = %s", test->address); + fail = 1; + } + maps_free(maps); + if (result) + argv_free(result); + + /* + * It is an error if a test does not pass or fail as intended. + */ + errs += (tests == pass_tests ? fail : !fail); + } + return (errs != 0); +} diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 14d547d5a..ced90d0bf 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 "20170101" +#define MAIL_RELEASE_DATE "20170108" #define MAIL_VERSION_NUMBER "3.2" #ifdef SNAPSHOT diff --git a/postfix/src/global/quote_822_local.c b/postfix/src/global/quote_822_local.c index 9292252b4..bf8b82b55 100644 --- a/postfix/src/global/quote_822_local.c +++ b/postfix/src/global/quote_822_local.c @@ -178,10 +178,13 @@ VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags) VSTRING *unquote_822_local(VSTRING *dst, const char *mbox) { const char *start; /* first byte of localpart */ - const char *end; /* first byte after localpart */ const char *colon; const char *cp; + int in_quote = 0; + const char *bare_at_src; + int bare_at_dst_pos = -1; + /* Don't unquote a routing prefix. Is this still possible? */ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) { start = colon + 1; vstring_strncpy(dst, mbox, start - mbox); @@ -189,21 +192,28 @@ VSTRING *unquote_822_local(VSTRING *dst, const char *mbox) start = mbox; VSTRING_RESET(dst); } - if ((end = strrchr(start, '@')) == 0) - end = start + strlen(start); - for (cp = start; cp < end; cp++) { - if (*cp == '"') + /* Locate the last unquoted '@'. */ + for (cp = start; *cp; cp++) { + if (*cp == '"') { + in_quote = !in_quote; continue; - if (*cp == '\\') { + } else if (*cp == '@') { + if (!in_quote) { + bare_at_dst_pos = VSTRING_LEN(dst); + bare_at_src = cp; + } + } else if (*cp == '\\') { if (cp[1] == 0) continue; cp++; } VSTRING_ADDCH(dst, *cp); } - if (*end) - vstring_strcat(dst, end); - else + /* Don't unquote text after the last unquoted '@'. */ + if (bare_at_dst_pos >= 0) { + vstring_truncate(dst, bare_at_dst_pos); + vstring_strcat(dst, bare_at_src); + } else VSTRING_TERMINATE(dst); return (dst); } @@ -214,6 +224,11 @@ VSTRING *unquote_822_local(VSTRING *dst, const char *mbox) * Proof-of-concept test program. Read an unquoted address from stdin, and * show the quoted and unquoted results. */ +#include +#include + +#include +#include #include #include @@ -221,20 +236,32 @@ VSTRING *unquote_822_local(VSTRING *dst, const char *mbox) int main(int unused_argc, char **unused_argv) { - VSTRING *raw = vstring_alloc(100); - VSTRING *quoted = vstring_alloc(100); - VSTRING *unquoted = vstring_alloc(100); - - while (vstring_fgets_nonl(raw, VSTREAM_IN)) { - quote_822_local(quoted, STR(raw)); - vstream_printf("quoted: %s\n", STR(quoted)); - unquote_822_local(unquoted, STR(quoted)); - vstream_printf("unquoted: %s\n", STR(unquoted)); - vstream_fflush(VSTREAM_OUT); + VSTRING *in = vstring_alloc(100); + VSTRING *out = vstring_alloc(100); + char *cmd; + char *bp; + + while (vstring_fgets_nonl(in, VSTREAM_IN)) { + bp = STR(in); + if ((cmd = mystrtok(&bp, CHARS_SPACE)) != 0) { + while (ISSPACE(*bp)) + bp++; + if (*bp == 0) { + msg_warn("missing argument"); + } else if (strcmp(cmd, "quote") == 0) { + quote_822_local(out, bp); + vstream_printf("'%s' quoted '%s'\n", bp, STR(out)); + } else if (strcmp(cmd, "unquote") == 0) { + unquote_822_local(out, bp); + vstream_printf("'%s' unquoted '%s'\n", bp, STR(out)); + } else { + msg_warn("unknown command: %s", cmd); + } + vstream_fflush(VSTREAM_OUT); + } } - vstring_free(unquoted); - vstring_free(quoted); - vstring_free(raw); + vstring_free(in); + vstring_free(out); return (0); } diff --git a/postfix/src/global/quote_822_local.in b/postfix/src/global/quote_822_local.in new file mode 100644 index 000000000..f99ebc676 --- /dev/null +++ b/postfix/src/global/quote_822_local.in @@ -0,0 +1,4 @@ +quote a@b@c@d +unquote "a@b@c"@d +unquote "a@b@c" +unquote "a@b@c"@d@e diff --git a/postfix/src/global/quote_822_local.ref b/postfix/src/global/quote_822_local.ref new file mode 100644 index 000000000..2e6ec5949 --- /dev/null +++ b/postfix/src/global/quote_822_local.ref @@ -0,0 +1,4 @@ +'a@b@c@d' quoted '"a@b@c"@d' +'"a@b@c"@d' unquoted 'a@b@c@d' +'"a@b@c"' unquoted 'a@b@c' +'"a@b@c"@d@e' unquoted 'a@b@c@d@e' diff --git a/postfix/src/global/split_addr.c b/postfix/src/global/split_addr.c index 435f38c95..58b8da799 100644 --- a/postfix/src/global/split_addr.c +++ b/postfix/src/global/split_addr.c @@ -6,13 +6,14 @@ /* SYNOPSIS /* #include /* -/* char *split_addr(localpart, delimiter_set) +/* char *split_addr_internal(localpart, delimiter_set) /* char *localpart; /* const char *delimiter_set; /* DESCRIPTION -/* split_addr() null-terminates \fIlocalpart\fR at the first -/* occurrence of the \fIdelimiter\fR character(s) found, and -/* returns a pointer to the remainder. +/* split_addr_internal() null-terminates \fIlocalpart\fR at +/* the first occurrence of the \fIdelimiter\fR character(s) +/* found, and returns a pointer to the remainder. The address +/* must be in internal (unquoted) form. /* /* Reserved addresses are not split: postmaster, mailer-daemon, /* double-bounce. Addresses that begin with owner-, or addresses @@ -49,9 +50,9 @@ #include #include -/* split_addr - split address with extreme prejudice */ +/* split_addr_internal - split address with extreme prejudice */ -char *split_addr(char *localpart, const char *delimiter_set) +char *split_addr_internal(char *localpart, const char *delimiter_set) { ssize_t len; diff --git a/postfix/src/global/split_addr.h b/postfix/src/global/split_addr.h index fa89faeae..e278ee2cd 100644 --- a/postfix/src/global/split_addr.h +++ b/postfix/src/global/split_addr.h @@ -13,7 +13,7 @@ /* External interface. */ -extern char *split_addr(char *, const char *); +extern char *split_addr_internal(char *, const char *); /* LICENSE /* .ad diff --git a/postfix/src/global/strip_addr.c b/postfix/src/global/strip_addr.c index 95365c202..da6670df6 100644 --- a/postfix/src/global/strip_addr.c +++ b/postfix/src/global/strip_addr.c @@ -6,24 +6,26 @@ /* SYNOPSIS /* #include /* -/* char *strip_addr(address, extension, delimiter_set) +/* char *strip_addr_internal(address, extension, delimiter_set) /* const char *address; /* char **extension; /* const char *delimiter_set; /* DESCRIPTION -/* strip_addr() takes an address and either returns a null -/* pointer when the address contains no address extension, +/* strip_addr_internal() takes an address and either returns +/* a null pointer when the address contains no address extension, /* or returns a copy of the address without address extension. /* The caller is expected to pass the copy to myfree(). +/* The input and result are in internal form. /* /* Arguments: /* .IP address -/* Address localpart or user@domain form. +/* Address localpart or user@domain form in internal form. /* .IP extension /* A null pointer, or the address of a pointer that is set to /* the address of a dynamic memory copy of the address extension /* that had to be chopped off. -/* The copy includes the recipient address delimiter. +/* The copy includes the recipient address delimiter, and is +/* always in internal (unquoted) form. /* The caller is expected to pass the copy to myfree(). /* .IP delimiter_set /* Set of recipient address delimiter characters. @@ -51,12 +53,16 @@ /* Global library. */ +#include #include #include +#define STR(x) vstring_str(x) + /* strip_addr - strip extension from address */ -char *strip_addr(const char *full, char **extension, const char *delimiter_set) +char *strip_addr_internal(const char *full, char **extension, + const char *delimiter_set) { char *ratsign; char *extent; @@ -72,7 +78,7 @@ char *strip_addr(const char *full, char **extension, const char *delimiter_set stripped = mystrdup(full); if ((ratsign = strrchr(stripped, '@')) != 0) *ratsign = 0; - if ((extent = split_addr(stripped, delimiter_set)) != 0) { + if ((extent = split_addr_internal(stripped, delimiter_set)) != 0) { extent -= 1; if (extension) { *extent = full[strlen(stripped)]; @@ -113,64 +119,64 @@ int main(int unused_argc, char **unused_argv) * Incredible. This function takes only three arguments, and the tests * already take more lines of code than the code being tested. */ - stripped = strip_addr("foo", (char **) 0, NO_DELIM); + stripped = strip_addr_internal("foo", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 1"); - stripped = strip_addr("foo", &extension, NO_DELIM); + stripped = strip_addr_internal("foo", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 2"); if (extension != 0) msg_panic("strip_addr botch 3"); - stripped = strip_addr("foo", (char **) 0, delim); + stripped = strip_addr_internal("foo", (char **) 0, delim); if (stripped != 0) msg_panic("strip_addr botch 4"); - stripped = strip_addr("foo", &extension, delim); + stripped = strip_addr_internal("foo", &extension, delim); if (stripped != 0) msg_panic("strip_addr botch 5"); if (extension != 0) msg_panic("strip_addr botch 6"); - stripped = strip_addr("foo@bar", (char **) 0, NO_DELIM); + stripped = strip_addr_internal("foo@bar", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 7"); - stripped = strip_addr("foo@bar", &extension, NO_DELIM); + stripped = strip_addr_internal("foo@bar", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 8"); if (extension != 0) msg_panic("strip_addr botch 9"); - stripped = strip_addr("foo@bar", (char **) 0, delim); + stripped = strip_addr_internal("foo@bar", (char **) 0, delim); if (stripped != 0) msg_panic("strip_addr botch 10"); - stripped = strip_addr("foo@bar", &extension, delim); + stripped = strip_addr_internal("foo@bar", &extension, delim); if (stripped != 0) msg_panic("strip_addr botch 11"); if (extension != 0) msg_panic("strip_addr botch 12"); - stripped = strip_addr("foo-ext", (char **) 0, NO_DELIM); + stripped = strip_addr_internal("foo-ext", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 13"); - stripped = strip_addr("foo-ext", &extension, NO_DELIM); + stripped = strip_addr_internal("foo-ext", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 14"); if (extension != 0) msg_panic("strip_addr botch 15"); - stripped = strip_addr("foo-ext", (char **) 0, delim); + stripped = strip_addr_internal("foo-ext", (char **) 0, delim); if (stripped == 0) msg_panic("strip_addr botch 16"); msg_info("wanted: foo-ext -> %s", "foo"); msg_info("strip_addr foo-ext -> %s", stripped); myfree(stripped); - stripped = strip_addr("foo-ext", &extension, delim); + stripped = strip_addr_internal("foo-ext", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 17"); if (extension == 0) @@ -180,24 +186,24 @@ int main(int unused_argc, char **unused_argv) myfree(stripped); myfree(extension); - stripped = strip_addr("foo-ext@bar", (char **) 0, NO_DELIM); + stripped = strip_addr_internal("foo-ext@bar", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 19"); - stripped = strip_addr("foo-ext@bar", &extension, NO_DELIM); + stripped = strip_addr_internal("foo-ext@bar", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 20"); if (extension != 0) msg_panic("strip_addr botch 21"); - stripped = strip_addr("foo-ext@bar", (char **) 0, delim); + stripped = strip_addr_internal("foo-ext@bar", (char **) 0, delim); if (stripped == 0) msg_panic("strip_addr botch 22"); msg_info("wanted: foo-ext@bar -> %s", "foo@bar"); msg_info("strip_addr foo-ext@bar -> %s", stripped); myfree(stripped); - stripped = strip_addr("foo-ext@bar", &extension, delim); + stripped = strip_addr_internal("foo-ext@bar", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 23"); if (extension == 0) @@ -207,7 +213,7 @@ int main(int unused_argc, char **unused_argv) myfree(stripped); myfree(extension); - stripped = strip_addr("foo+ext@bar", &extension, delim); + stripped = strip_addr_internal("foo+ext@bar", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 25"); if (extension == 0) @@ -217,6 +223,16 @@ int main(int unused_argc, char **unused_argv) myfree(stripped); myfree(extension); + stripped = strip_addr_internal("foo bar+ext", &extension, delim); + if (stripped == 0) + msg_panic("strip_addr botch 27"); + if (extension == 0) + msg_panic("strip_addr botch 28"); + msg_info("wanted: foo bar+ext -> %s %s", "foo bar", "+ext"); + msg_info("strip_addr foo bar+ext -> %s %s", stripped, extension); + myfree(stripped); + myfree(extension); + return (0); } diff --git a/postfix/src/global/strip_addr.h b/postfix/src/global/strip_addr.h index 19530e1ba..e482ee47a 100644 --- a/postfix/src/global/strip_addr.h +++ b/postfix/src/global/strip_addr.h @@ -11,9 +11,10 @@ /* DESCRIPTION /* .nf - /* External interface. */ - -extern char *strip_addr(const char *, char **, const char *); + /* + * External interface. + */ +extern char * strip_addr_internal(const char *, char **, const char *); /* LICENSE /* .ad diff --git a/postfix/src/global/strip_addr.ref b/postfix/src/global/strip_addr.ref index af218e5c0..1a2839513 100644 --- a/postfix/src/global/strip_addr.ref +++ b/postfix/src/global/strip_addr.ref @@ -8,3 +8,5 @@ unknown: wanted: foo-ext@bar -> foo@bar -ext unknown: strip_addr foo-ext@bar -> foo@bar -ext unknown: wanted: foo+ext@bar -> foo@bar +ext unknown: strip_addr foo+ext@bar -> foo@bar +ext +unknown: wanted: foo bar+ext -> foo bar +ext +unknown: strip_addr foo bar+ext -> foo bar +ext diff --git a/postfix/src/local/bounce_workaround.c b/postfix/src/local/bounce_workaround.c index 7fe4aaa14..027c0ec95 100644 --- a/postfix/src/local/bounce_workaround.c +++ b/postfix/src/local/bounce_workaround.c @@ -108,9 +108,10 @@ int bounce_workaround(LOCAL_STATE state) FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address); if (alias_maps->error == 0 && owner_expansion == 0 - && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address, - (char **) 0, - var_rcpt_delim)) != 0) { + && (stripped_recipient = + strip_addr_internal(state.msg_attr.rcpt.address, + (char **) 0, + var_rcpt_delim)) != 0) { myfree(owner_alias); FIND_OWNER(owner_alias, owner_expansion, stripped_recipient); myfree(stripped_recipient); diff --git a/postfix/src/local/recipient.c b/postfix/src/local/recipient.c index e3f4d1ceb..7f083b548 100644 --- a/postfix/src/local/recipient.c +++ b/postfix/src/local/recipient.c @@ -267,7 +267,7 @@ int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) state.msg_attr.user = mystrdup(state.msg_attr.local); if (*var_rcpt_delim) { state.msg_attr.extension = - split_addr(state.msg_attr.user, var_rcpt_delim); + split_addr_internal(state.msg_attr.user, var_rcpt_delim); if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { msg_warn("%s: address with illegal extension: %s", state.msg_attr.queue_id, state.msg_attr.local); diff --git a/postfix/src/oqmgr/qmgr_message.c b/postfix/src/oqmgr/qmgr_message.c index 26d9bd37e..dcc948ec0 100644 --- a/postfix/src/oqmgr/qmgr_message.c +++ b/postfix/src/oqmgr/qmgr_message.c @@ -1204,7 +1204,8 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) : strlen(STR(reply.recipient))); vstring_strncpy(queue_name, STR(reply.recipient), len); /* Remove the address extension from the recipient localpart. */ - if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim)) + if (*var_rcpt_delim + && split_addr_internal(STR(queue_name), var_rcpt_delim)) vstring_truncate(queue_name, strlen(STR(queue_name))); /* Assume the recipient domain is equivalent to nexthop. */ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); diff --git a/postfix/src/pipe/pipe.c b/postfix/src/pipe/pipe.c index dd995132d..649d83469 100644 --- a/postfix/src/pipe/pipe.c +++ b/postfix/src/pipe/pipe.c @@ -754,7 +754,7 @@ static ARGV *expand_argv(const char *service, char **argv, msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim) - split_addr(STR(buf), var_rcpt_delim); + split_addr_internal(STR(buf), var_rcpt_delim); if (*STR(buf) == 0) continue; dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); @@ -772,7 +772,8 @@ static ARGV *expand_argv(const char *service, char **argv, msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim == 0 - || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0) + || (ext = split_addr_internal(STR(buf), + var_rcpt_delim)) == 0) ext = ""; /* insert null arg */ dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); } diff --git a/postfix/src/postmap/Makefile.in b/postfix/src/postmap/Makefile.in index 75cbb0362..ab1571aca 100644 --- a/postfix/src/postmap/Makefile.in +++ b/postfix/src/postmap/Makefile.in @@ -26,42 +26,49 @@ update: ../../bin/$(PROG) ../../bin/$(PROG): $(PROG) cp $(PROG) ../../bin -tests: test1 test2 fail_test +tests: test1 test2 fail_test quote_test root_tests: test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref - ./$(PROG) map.in + $(SHLIB_ENV) ./$(PROG) map.in for key in abc ghi; \ do \ - ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ + $(SHLIB_ENV) ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ done - ./$(PROG) -f map.in + $(SHLIB_ENV) ./$(PROG) -f map.in for key in ABC; \ do \ - ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \ + $(SHLIB_ENV) ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \ done rm -f map.in.db test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref - ./$(PROG) map.in + $(SHLIB_ENV) ./$(PROG) map.in for key in abc ghi; \ do \ - echo $${key} | ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ + echo $${key} | $(SHLIB_ENV) ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ done - ./$(PROG) -f map.in + $(SHLIB_ENV) ./$(PROG) -f map.in for key in ABC; \ do \ - echo $${key} | ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \ + echo $${key} | $(SHLIB_ENV) ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \ done rm -f map.in.db fail_test: $(PROG) aliases fail_test.in fail_test.ref - -(sh fail_test.in || exit 0) 2>&1 | \ - sed 's/No error:/Unknown error:/' > fail_test.tmp + -($(SHLIB_ENV) sh fail_test.in || exit 0) 2>&1 | \ + sed -e 's/No error:/Unknown error:/' \ + -e 's/Success/Unknown error: 0/' > fail_test.tmp diff fail_test.ref fail_test.tmp rm -f fail_test.tmp +quote_test: $(PROG) aliases quote_test.in quote_test.ref + rm -f quote_test_map.* + $(SHLIB_ENV) sh quote_test.in >quote_test.tmp 2>&1 + diff quote_test.ref quote_test.tmp + rm -f quote_test.tmp quote_test_map.* + printfck: $(OBJS) $(PROG) rm -rf printfck mkdir printfck @@ -73,7 +80,7 @@ lint: lint $(DEFS) $(SRCS) $(LINTFIX) clean: - rm -f *.o *core $(PROG) $(TESTPROG) junk map.in.db + rm -f *.o *core $(PROG) $(TESTPROG) *.tmp junk *.db rm -rf printfck tidy: clean @@ -105,6 +112,8 @@ postmap.o: ../../include/msg_syslog.h postmap.o: ../../include/msg_vstream.h postmap.o: ../../include/myflock.h postmap.o: ../../include/mymalloc.h +postmap.o: ../../include/quote_822_local.h +postmap.o: ../../include/quote_flags.h postmap.o: ../../include/readlline.h postmap.o: ../../include/rec_type.h postmap.o: ../../include/set_eugid.h diff --git a/postfix/src/postmap/postmap.c b/postfix/src/postmap/postmap.c index 389ae0a13..198fe1ee4 100644 --- a/postfix/src/postmap/postmap.c +++ b/postfix/src/postmap/postmap.c @@ -46,6 +46,14 @@ /* databases, quotes cannot be used to protect lookup keys that contain /* special characters such as `#' or whitespace. /* +/* When the \fIkey\fR specifies email address information, the +/* localpart needs to be enclosed with double quotes if required +/* by RFC 5322 and if the \fIkey\fR is used in virtual_alias_maps, +/* *canonical_maps, or smtp_generic maps. For example, an +/* address localpart that contains space or ';' characters +/* needs to be quoted. The \fBpostmap\fR(1) command supports +/* spaces in the \fIkey\fR as of Postfix version 3.2. +/* /* By default the lookup key is mapped to lowercase to make /* the lookups case insensitive; as of Postfix 2.3 this case /* folding happens only with tables whose lookup keys are @@ -421,10 +429,12 @@ static void postmap(char *map_type, char *path_name, int postmap_flags, msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* - * Add records to the database. + * Add records to the database. XXX This duplicates the parser in + * dict_thash.c. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { + int in_quotes = 0; /* * First some UTF-8 checks sans casefolding. @@ -439,18 +449,40 @@ static void postmap(char *map_type, char *path_name, int postmap_flags, } /* - * Split on the first whitespace character, then trim leading and - * trailing whitespace from key and value. + * Terminate the key on the first unquoted whitespace character, + * then trim leading and trailing whitespace from the value. */ - key = STR(line_buffer); - value = key + strcspn(key, CHARS_SPACE); + for (value = STR(line_buffer); *value; value++) { + if (*value == '\\') { + if (*++value == 0) + break; + } else if (ISSPACE(*value)) { + if (!in_quotes) + break; + } else if (*value == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + msg_warn("%s, line %d: unbalanced '\"' in '%s'" + " -- ignoring this line", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } if (*value) *value++ = 0; while (ISSPACE(*value)) value++; - trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; + /* + * Leave the key in quoted form, because 1) postmap cannot assume + * that a string without @ contains an email address localpart, + * and 2) an address localpart may require quoting even when the + * quoted form contains no backslash or ". + */ + key = STR(line_buffer); + /* * Enforce the "key whitespace value" format. Disallow missing * keys or missing values. @@ -465,7 +497,8 @@ static void postmap(char *map_type, char *path_name, int postmap_flags, VSTREAM_PATH(source_fp), lineno); /* - * Store the value under a case-insensitive key. + * Store the value under a (possibly case-insensitive) key, as + * specified with open_flags. */ mkmap_append(mkmap, key, value); if (mkmap->dict->error) diff --git a/postfix/src/postmap/quote_test.in b/postfix/src/postmap/quote_test.in new file mode 100644 index 000000000..14ae42dde --- /dev/null +++ b/postfix/src/postmap/quote_test.in @@ -0,0 +1,8 @@ +echo '"aa bb" cc' | ./postmap -i quote_test_map || exit 1 +echo '"dd ee ff' | ./postmap -i quote_test_map || exit 1 +echo 'gg\ hh ii' | ./postmap -i quote_test_map || exit 1 +echo '"gg\"hh" ii' | ./postmap -i quote_test_map || exit 1 +echo '"jj@kk" ll' | ./postmap -i quote_test_map || exit 1 +echo 'mm@nn@oo pp' | ./postmap -i quote_test_map || exit 1 +echo '@oo pp' | ./postmap -i quote_test_map || exit 1 +./postmap -s quote_test_map | LC_ALL=C sort diff --git a/postfix/src/postmap/quote_test.ref b/postfix/src/postmap/quote_test.ref new file mode 100644 index 000000000..164324470 --- /dev/null +++ b/postfix/src/postmap/quote_test.ref @@ -0,0 +1,7 @@ +postmap: warning: stdin, line 1: unbalanced '"' in '"dd ee ff' -- ignoring this line +"aa bb" cc +"gg\"hh" ii +"jj@kk" ll +@oo pp +gg\ hh ii +mm@nn@oo pp diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c index 495d52d93..ce1a57d4a 100644 --- a/postfix/src/qmgr/qmgr_message.c +++ b/postfix/src/qmgr/qmgr_message.c @@ -1263,7 +1263,8 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) : strlen(STR(reply.recipient))); vstring_strncpy(queue_name, STR(reply.recipient), len); /* Remove the address extension from the recipient localpart. */ - if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim)) + if (*var_rcpt_delim + && split_addr_internal(STR(queue_name), var_rcpt_delim)) vstring_truncate(queue_name, strlen(STR(queue_name))); /* Assume the recipient domain is equivalent to nexthop. */ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); diff --git a/postfix/src/smtp/Makefile.in b/postfix/src/smtp/Makefile.in index 6e77038f4..551534101 100644 --- a/postfix/src/smtp/Makefile.in +++ b/postfix/src/smtp/Makefile.in @@ -75,7 +75,7 @@ smtp_map11: smtp_map11.c $(LIBS) smtp_map11_test: smtp_map11 map11_map smtp_map11.ref $(SHLIB_ENV) ../postmap/postmap map11_map $(SHLIB_ENV) ./smtp_map11 hash:map11_map foo@example.com bar@example.com \ - baz@example.com foo@example.net >smtp_map11.tmp 2>&1 + baz@example.com foo@example.net splitme@example.com splitme+ext@example.com >smtp_map11.tmp 2>&1 sed -e "s/MYDOMAIN/`postconf -h mydomain`/" \ -e "s/MYHOSTNAME/`postconf -h myhostname`/" smtp_map11.ref | \ diff - smtp_map11.tmp @@ -327,6 +327,7 @@ smtp_map11.o: ../../include/dsn_buf.h smtp_map11.o: ../../include/header_body_checks.h smtp_map11.o: ../../include/header_opts.h smtp_map11.o: ../../include/htable.h +smtp_map11.o: ../../include/mail_addr_form.h smtp_map11.o: ../../include/mail_addr_map.h smtp_map11.o: ../../include/maps.h smtp_map11.o: ../../include/match_list.h @@ -373,7 +374,6 @@ smtp_proto.o: ../../include/header_opts.h smtp_proto.o: ../../include/htable.h smtp_proto.o: ../../include/iostuff.h smtp_proto.o: ../../include/lex_822.h -smtp_proto.o: ../../include/mail_addr_map.h smtp_proto.o: ../../include/mail_params.h smtp_proto.o: ../../include/mail_proto.h smtp_proto.o: ../../include/mail_queue.h @@ -549,6 +549,7 @@ smtp_sasl_glue.o: ../../include/header_body_checks.h smtp_sasl_glue.o: ../../include/header_opts.h smtp_sasl_glue.o: ../../include/htable.h smtp_sasl_glue.o: ../../include/mail_addr_find.h +smtp_sasl_glue.o: ../../include/mail_addr_form.h smtp_sasl_glue.o: ../../include/mail_params.h smtp_sasl_glue.o: ../../include/maps.h smtp_sasl_glue.o: ../../include/match_list.h diff --git a/postfix/src/smtp/map11_map b/postfix/src/smtp/map11_map index 5b35f1e54..9850972c6 100644 --- a/postfix/src/smtp/map11_map +++ b/postfix/src/smtp/map11_map @@ -1,3 +1,4 @@ foo@example.com bar@com.example bar@example.com bar baz@example.com @com.example +splitme@example.com "split me"@com.example diff --git a/postfix/src/smtp/smtp_map11.c b/postfix/src/smtp/smtp_map11.c index 8579f915e..f49b9122d 100644 --- a/postfix/src/smtp/smtp_map11.c +++ b/postfix/src/smtp/smtp_map11.c @@ -80,7 +80,8 @@ int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate) ARGV *new_addr; const char *result; - if ((new_addr = mail_addr_map(maps, STR(addr), propagate)) != 0) { + if ((new_addr = mail_addr_map(maps, STR(addr), propagate, + MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL)) != 0) { if (new_addr->argc > 1) msg_warn("multi-valued %s result for %s", maps->title, STR(addr)); result = new_addr->argv[0]; @@ -151,6 +152,8 @@ int main(int argc, char **argv) argv += 1; msg_verbose = 1; + myfree(var_rcpt_delim); + var_rcpt_delim = mystrdup("+"); while (--argc && *++argv) { msg_info("-- start %s --", *argv); smtp_map11_external(vstring_strcpy(buf, *argv), maps, 1); diff --git a/postfix/src/smtp/smtp_map11.ref b/postfix/src/smtp/smtp_map11.ref index e5820c75e..828785a81 100644 --- a/postfix/src/smtp/smtp_map11.ref +++ b/postfix/src/smtp/smtp_map11.ref @@ -1,5 +1,5 @@ smtp_map11: -- start foo@example.com -- -smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix): foo@example.com = bar@com.example +smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix|utf8_request): foo@example.com = bar@com.example smtp_map11: mail_addr_find: foo@example.com -> bar@com.example smtp_map11: connect to subsystem private/rewrite smtp_map11: send attr request = rewrite @@ -18,7 +18,7 @@ smtp_map11: mail_addr_map: foo@example.com -> 0: bar@com.example smtp_map11: smtp_map11_external: foo@example.com -> bar@com.example smtp_map11: -- end foo@example.com -- smtp_map11: -- start bar@example.com -- -smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix): bar@example.com = bar +smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix|utf8_request): bar@example.com = bar smtp_map11: mail_addr_find: bar@example.com -> bar smtp_map11: send attr request = rewrite smtp_map11: send attr rule = local @@ -36,7 +36,7 @@ smtp_map11: mail_addr_map: bar@example.com -> 0: bar@MYDOMAIN smtp_map11: smtp_map11_external: bar@example.com -> bar@MYDOMAIN smtp_map11: -- end bar@example.com -- smtp_map11: -- start baz@example.com -- -smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix): baz@example.com = @com.example +smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix|utf8_request): baz@example.com = @com.example smtp_map11: mail_addr_find: baz@example.com -> @com.example smtp_map11: send attr request = rewrite smtp_map11: send attr rule = local @@ -55,13 +55,39 @@ smtp_map11: smtp_map11_external: baz@example.com -> baz@com.example smtp_map11: -- end baz@example.com -- smtp_map11: -- start foo@example.net -- smtp_map11: maps_find: hash:map11_map: foo@example.net: not found -smtp_map11: match_string: example.net ~? MYHOSTNAME -smtp_map11: match_string: example.net ~? localhost.MYDOMAIN -smtp_map11: match_string: example.net ~? localhost +smtp_map11: match_string: mydestination: example.net ~? MYHOSTNAME +smtp_map11: match_string: mydestination: example.net ~? localhost.MYDOMAIN +smtp_map11: match_string: mydestination: example.net ~? localhost smtp_map11: match_list_match: example.net: no match smtp_map11: maps_find: hash:map11_map: @example.net: not found smtp_map11: mail_addr_find: foo@example.net -> (not found) smtp_map11: mail_addr_map: foo@example.net -> (not found) smtp_map11: smtp_map11_external: foo@example.net not found smtp_map11: -- end foo@example.net -- -smtp_map11: maps_free: hash:map11_map(0,fold_fix) +smtp_map11: -- start splitme@example.com -- +smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix|utf8_request): splitme@example.com = "split me"@com.example +smtp_map11: mail_addr_find: splitme@example.com -> "split me"@com.example +smtp_map11: send attr request = rewrite +smtp_map11: send attr rule = local +smtp_map11: send attr address = "split me"@com.example +smtp_map11: private/rewrite socket: wanted attribute: flags +smtp_map11: input attribute name: flags +smtp_map11: input attribute value: 0 +smtp_map11: private/rewrite socket: wanted attribute: address +smtp_map11: input attribute name: address +smtp_map11: input attribute value: "split me"@com.example +smtp_map11: private/rewrite socket: wanted attribute: (list terminator) +smtp_map11: input attribute name: (end) +smtp_map11: rewrite_clnt: local: "split me"@com.example -> "split me"@com.example +smtp_map11: mail_addr_map: splitme@example.com -> 0: "split me"@com.example +smtp_map11: smtp_map11_external: splitme@example.com -> "split me"@com.example +smtp_map11: -- end splitme@example.com -- +smtp_map11: -- start splitme+ext@example.com -- +smtp_map11: maps_find: hash:map11_map: splitme+ext@example.com: not found +smtp_map11: maps_find: hash:map11_map: hash:map11_map(0,fold_fix|utf8_request): splitme@example.com = "split me"@com.example +smtp_map11: mail_addr_find: splitme+ext@example.com -> "split me"@com.example +smtp_map11: rewrite_clnt: cached: local: "split me"@com.example -> "split me"@com.example +smtp_map11: mail_addr_map: splitme+ext@example.com -> 0: "split me+ext"@com.example +smtp_map11: smtp_map11_external: splitme+ext@example.com -> "split me+ext"@com.example +smtp_map11: -- end splitme+ext@example.com -- +smtp_map11: maps_free: hash:map11_map(0,fold_fix|utf8_request) diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 89a3aab94..328ad9275 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -142,7 +142,6 @@ #include #include #include -#include #include #include #include diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c index d2c1c3c59..257c72ac2 100644 --- a/postfix/src/smtp/smtp_sasl_glue.c +++ b/postfix/src/smtp/smtp_sasl_glue.c @@ -184,7 +184,7 @@ int smtp_sasl_passwd_lookup(SMTP_SESSION *session) smtp_sasl_passwd_map->error = 0; if ((smtp_mode && var_smtp_sender_auth && state->request->sender[0] - && (value = mail_addr_find(smtp_sasl_passwd_map, + && (value = mail_addr_find_noconv(smtp_sasl_passwd_map, state->request->sender, (char **) 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 814a15332..eac2547d2 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -324,6 +324,7 @@ smtpd_check.o: ../../include/mac_expand.h smtpd_check.o: ../../include/mac_parse.h smtpd_check.o: ../../include/mail_addr.h smtpd_check.o: ../../include/mail_addr_find.h +smtpd_check.o: ../../include/mail_addr_form.h smtpd_check.o: ../../include/mail_conf.h smtpd_check.o: ../../include/mail_error.h smtpd_check.o: ../../include/mail_params.h diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 7bf7f1516..052f20f22 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -1155,7 +1155,8 @@ static const char *check_mail_addr_find(SMTPD_STATE *state, { const char *result; - if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0) + if ((result = mail_addr_find_noconv(maps, key, ext)) != 0 + || maps->error == 0) return (result); if (maps->error == DICT_ERR_RETRY) /* Warning is already logged. */ @@ -2525,7 +2526,7 @@ static int check_table_result(SMTPD_STATE *state, const char *table, reply_name, reply_class, cmd_text); log_whatsup(state, "bcc", STR(error_text)); #ifndef TEST - if (state->saved_bcc == 0) + if (state->saved_bcc == 0) state->saved_bcc = argv_alloc(1); argv_add(state->saved_bcc, cmd_text, (char *) 0); #endif @@ -3001,7 +3002,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table, domain += 1; dns_status = dns_lookup(domain, type, 0, &server_list, (VSTRING *) 0, (VSTRING *) 0); - if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */) + if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */ ) break; } } @@ -3186,7 +3187,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table, if (*var_rcpt_delim == 0) { bare_addr = 0; } else { - bare_addr = strip_addr(addr, (char **) 0, var_rcpt_delim); + bare_addr = strip_addr_internal(addr, (char **) 0, var_rcpt_delim); } #define CHECK_MAIL_ACCESS_RETURN(x) \ diff --git a/postfix/src/trivial-rewrite/resolve.c b/postfix/src/trivial-rewrite/resolve.c index 70c3fdcaa..69bd2c4fa 100644 --- a/postfix/src/trivial-rewrite/resolve.c +++ b/postfix/src/trivial-rewrite/resolve.c @@ -560,10 +560,10 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, */ else { if (rp->snd_def_xp_info - && (xport = mail_addr_find(rp->snd_def_xp_info, + && (xport = mail_addr_find_noconv(rp->snd_def_xp_info, sender_key = (*sender ? sender : var_null_def_xport_maps_key), - (char **) 0)) != 0) { + (char **) 0)) != 0) { if (*xport == 0) { msg_warn("%s: ignoring null lookup result for %s", rp->snd_def_xp_maps_name, sender_key); @@ -589,10 +589,10 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, * override the recipient domain. */ if (rp->snd_relay_info - && (relay = mail_addr_find(rp->snd_relay_info, - sender_key = (*sender ? sender : + && (relay = mail_addr_find_noconv(rp->snd_relay_info, + sender_key = (*sender ? sender : var_null_relay_maps_key), - (char **) 0)) != 0) { + (char **) 0)) != 0) { if (*relay == 0) { msg_warn("%s: ignoring null lookup result for %s", rp->snd_relay_maps_name, sender_key); @@ -716,8 +716,8 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, if (relocated_maps != 0) { const char *newloc; - if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt), - IGNORE_ADDR_EXTENSION)) != 0) { + if ((newloc = mail_addr_find_noconv(relocated_maps, STR(nextrcpt), + IGNORE_ADDR_EXTENSION)) != 0) { vstring_strcpy(channel, MAIL_SERVICE_ERROR); /* 5.1.6 is the closest match, but not perfect. */ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc); diff --git a/postfix/src/trivial-rewrite/transport.c b/postfix/src/trivial-rewrite/transport.c index f270b8dde..e9fc071c0 100644 --- a/postfix/src/trivial-rewrite/transport.c +++ b/postfix/src/trivial-rewrite/transport.c @@ -287,8 +287,8 @@ int transport_lookup(TRANSPORT_INFO *tp, const char *addr, * look up the stripped address with the PARTIAL flag to avoid matching * partial lookup keys with regular expressions. */ - if ((stripped_addr = strip_addr(addr, DISCARD_EXTENSION, - var_rcpt_delim)) != 0) { + if ((stripped_addr = strip_addr_internal(addr, DISCARD_EXTENSION, + var_rcpt_delim)) != 0) { found = find_transport_entry(tp, stripped_addr, rcpt_domain, PARTIAL, channel, nexthop); diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index cfc8610d1..377fa1b7a 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -39,7 +39,8 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.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_domain.c argv_splitq.c balpar.c dict_union.c \ - extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c + extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \ + split_qnameval.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 \ @@ -80,7 +81,8 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.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_domain.o argv_splitq.o balpar.o dict_union.o \ - extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o + extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \ + split_qnameval.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. @@ -129,7 +131,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \ valid_utf8_string ip_match base32_code msg_rate_delay netstring \ vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \ - vbuf_print + vbuf_print split_qnameval PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) LIB_DIR = ../../lib @@ -525,6 +527,11 @@ vbuf_print: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o +split_qnameval: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ hex_quote_test ctable_test inet_addr_list_test base64_code_test \ attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \ @@ -534,7 +541,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ dict_static_test dict_inline_test midna_domain_test casefold_test \ dict_utf8_test strcasecmp_utf8_test vbuf_print_test dict_regexp_test \ dict_union_test dict_pipe_test miss_endif_cidr_test \ - miss_endif_pcre_test miss_endif_regexp_test + miss_endif_pcre_test miss_endif_regexp_test split_qnameval_test root_tests: @@ -631,6 +638,9 @@ miss_endif_regexp_test: dict_open miss_endif_re.map miss_endif_regexp.ref diff miss_endif_regexp.ref dict_regexp.tmp rm -f dict_regexp.tmp +split_qnameval_test: split_qnameval update + $(SHLIB_ENV) ./split_qnameval + dict_seq_test: dict_open testdb dict_seq.in dict_seq.ref rm -f testdb.db testdb.dir testdb.pag $(SHLIB_ENV) ./dict_open hash:testdb create sync < dict_seq.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' > dict_seq.tmp @@ -739,9 +749,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 | sort >dict_thash.tmp 2>&1 - tr '[A-Z]' '[a-z]' &1 | \ + LANG=C sort | diff dict_thash.map - surrogate_test: dict_open surrogate.ref cp /dev/null surrogate.tmp @@ -2167,6 +2176,13 @@ split_nameval.o: stringops.h split_nameval.o: sys_defs.h split_nameval.o: vbuf.h split_nameval.o: vstring.h +split_qnameval.o: check_arg.h +split_qnameval.o: msg.h +split_qnameval.o: split_qnameval.c +split_qnameval.o: stringops.h +split_qnameval.o: sys_defs.h +split_qnameval.o: vbuf.h +split_qnameval.o: vstring.h stat_as.o: msg.h stat_as.o: set_eugid.h stat_as.o: stat_as.c @@ -2219,6 +2235,7 @@ stream_trigger.o: mymalloc.h stream_trigger.o: stream_trigger.c stream_trigger.o: sys_defs.h stream_trigger.o: trigger.h +sys_compat.o: iostuff.h sys_compat.o: sys_compat.c sys_compat.o: sys_defs.h timecmp.o: timecmp.c diff --git a/postfix/src/util/dict_inline.c b/postfix/src/util/dict_inline.c index c6202a6ac..667ae8f9d 100644 --- a/postfix/src/util/dict_inline.c +++ b/postfix/src/util/dict_inline.c @@ -110,7 +110,7 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) 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) + && (err = split_qnameval(nameval, &vname, &value)) != 0) break; /* No duplicate checks. See comments in dict_thash.c. */ diff --git a/postfix/src/util/dict_thash.c b/postfix/src/util/dict_thash.c index cad181cfd..f5257c40f 100644 --- a/postfix/src/util/dict_thash.c +++ b/postfix/src/util/dict_thash.c @@ -99,7 +99,7 @@ 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. @@ -107,17 +107,21 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags); dict_type_override(dict, DICT_TYPE_THASH); + /* + * XXX This duplicates the parser in postmap.c. + */ if (line_buffer == 0) line_buffer = vstring_alloc(100); last_line = 0; while (readllines(line_buffer, fp, &last_line, &lineno)) { + int in_quotes = 0; /* * 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) { + && 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)); @@ -128,15 +132,35 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ - key = STR(line_buffer); - value = key + strcspn(key, CHARS_SPACE); + for (value = STR(line_buffer); *value; value++) { + if (*value == '\\') { + if (*++value == 0) + break; + } else if (ISSPACE(*value)) { + if (!in_quotes) + break; + } else if (*value == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + msg_warn("%s, line %d: unbalanced '\"' in '%s'" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } if (*value) *value++ = 0; while (ISSPACE(*value)) value++; - trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; + /* + * Leave the key in quoted form, for consistency with postmap.c + * and dict_inline.c. + */ + key = STR(line_buffer); + /* * Enforce the "key whitespace value" format. Disallow missing * keys or missing values. @@ -156,7 +180,8 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) * 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. + * of terminating with a fatal error. See comment in + * dict_inline.c. */ if (dict->lookup(dict, key) != 0) { if (dict_flags & DICT_FLAG_DUP_IGNORE) { diff --git a/postfix/src/util/dict_thash.map b/postfix/src/util/dict_thash.map index 95b54fd82..cce1144ea 100644 --- a/postfix/src/util/dict_thash.map +++ b/postfix/src/util/dict_thash.map @@ -1,15 +1,17 @@ -allascii.c 915 -alldig.c 928 -allprint.c 943 -allspace.c 931 -argv.c 5271 -argv_split.c 2780 -attr_clnt.c 5813 -attr_print0.c 7243 -attr_print64.c 8104 -attr_print_plain.c 7086 -attr_scan0.c 15454 -attr_scan64.c 17256 -attr_scan_plain.c 16924 -auto_clnt.c 9819 -ABCDEF 012345 +"the answer is" 42 +ABCDEF 012345 +allascii.c 915 +alldig.c 928 +allprint.c 943 +allspace.c 931 +argv.c 5271 +argv_split.c 2780 +attr_clnt.c 5813 +attr_print0.c 7243 +attr_print64.c 8104 +attr_print_plain.c 7086 +attr_scan0.c 15454 +attr_scan64.c 17256 +attr_scan_plain.c 16924 +auto_clnt.c 9819 +the answer is 42 diff --git a/postfix/src/util/split_qnameval.c b/postfix/src/util/split_qnameval.c new file mode 100644 index 000000000..7cdffe423 --- /dev/null +++ b/postfix/src/util/split_qnameval.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* split_qnameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include +/* +/* const char *split_qnameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_qnameval() expects text of the form "key = value" +/* or "key =", where the key may be quoted with backslash or +/* double quotes. The buffer argument is broken up into the key +/* and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP key +/* Upon successful completion, this is set to the key +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* SEE ALSO +/* split_nameval(3) name-value splitter +/* BUGS +/* DIAGNOSTICS +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* split_qnameval - split "key = value", support quoted key */ + +const char *split_qnameval(char *buf, char **pkey, char **pvalue) +{ + int in_quotes = 0; + char *key; + char *key_end; + char *value; + + for (key = buf; *key && ISSPACE(*key); key++) + /* void */ ; + if (*key == 0) + return ("no key found; expected format: key = value"); + + for (key_end = key; *key_end; key_end++) { + if (*key_end == '\\') { + if (*++key_end == 0) + break; + } else if (ISSPACE(*key_end) || *key_end == '=') { + if (!in_quotes) + break; + } else if (*key_end == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + return ("unbalanced '\"\'"); + } + value = key_end; + while (ISSPACE(*value)) + value++; + if (*value != '=') + return ("missing '=' after attribute name"); + *key_end = 0; + *value++ = 0; + while (ISSPACE(*value)) + value++; + trimblanks(value, 0)[0] = 0; + *pkey = key; + *pvalue = value; + return (0); +} + +#ifdef TEST + +#include +#include +#include + +#include + +static int compare(int test_number, const char *what, + const char *expect, const char *real) +{ + if ((expect == 0 && real == 0) + || (expect != 0 && real != 0 && strcmp(expect, real) == 0)) { + return (0); + } else { + msg_warn("test %d: %s mis-match: expect='%s', real='%s'", + test_number, what, expect ? expect : "(null)", + real ? real : "(null)"); + return (1); + } +} + +int main(int argc, char **argv) +{ + struct test_info { + const char *input; + const char *expect_result; + const char *expect_key; + const char *expect_value; + }; + static const struct test_info test_info[] = { + /* Unquoted keys. */ + {"xx = yy", 0, "xx", "yy"}, + {"xx=yy", 0, "xx", "yy"}, + {"xx =", 0, "xx", ""}, + {"xx=", 0, "xx", ""}, + {"xx", "missing '=' after attribute name", 0, 0}, + /* Quoted keys. */ + {"\"xx \" = yy", 0, "\"xx \"", "yy"}, + {"\"xx \"= yy", 0, "\"xx \"", "yy"}, + {"\"xx \" =", 0, "\"xx \"", ""}, + {"\"xx \"=", 0, "\"xx \"", ""}, + {"\"xx \"", "missing '=' after attribute name", 0, 0}, + {"\"xx ", "unbalanced '\"'", 0, 0}, + /* Backslash escapes. */ + {"\"\\\"xx \" = yy", 0, "\"\\\"xx \"", "yy"}, + {0,}, + }; + + int errs = 0; + const struct test_info *tp; + + for (tp = test_info; tp->input != 0; tp++) { + const char *result; + char *key = 0; + char *value = 0; + char *buf = mystrdup(tp->input); + int test_number = (int) (tp - test_info); + + result = split_qnameval(buf, &key, &value); + errs += compare(test_number, "result", tp->expect_result, result); + errs += compare(test_number, "key", tp->expect_key, key); + errs += compare(test_number, "value", tp->expect_value, value); + myfree(buf); + } + exit(errs); +} + +#endif diff --git a/postfix/src/util/stringops.h b/postfix/src/util/stringops.h index de2ecc739..32b1c7754 100644 --- a/postfix/src/util/stringops.h +++ b/postfix/src/util/stringops.h @@ -46,6 +46,7 @@ extern int allprint(const char *); extern int allspace(const char *); extern int allascii_len(const char *, ssize_t); extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **); +extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **); extern int valid_utf8_string(const char *, ssize_t); extern size_t balpar(const char *, const char *); extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int); diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c index cb29927d7..aaf499b35 100644 --- a/postfix/src/util/vstream.c +++ b/postfix/src/util/vstream.c @@ -1600,8 +1600,8 @@ static void copy_line(ssize_t bufsize) { int c; - vstream_control(VSTREAM_IN, VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); - vstream_control(VSTREAM_OUT, VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) { VSTREAM_PUTC(c, VSTREAM_OUT); if (c == '\n') diff --git a/postfix/src/virtual/mailbox.c b/postfix/src/virtual/mailbox.c index 51e646de7..fce73b152 100644 --- a/postfix/src/virtual/mailbox.c +++ b/postfix/src/virtual/mailbox.c @@ -193,8 +193,9 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) */ #define IGNORE_EXTENSION ((char **) 0) - mailbox_res = mail_addr_find(virtual_mailbox_maps, state.msg_attr.user, - IGNORE_EXTENSION); + mailbox_res = mail_addr_find_noconv(virtual_mailbox_maps, + state.msg_attr.user, + IGNORE_EXTENSION); if (mailbox_res == 0) { if (virtual_mailbox_maps->error == 0) return (NO); @@ -213,8 +214,8 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) /* * Look up the mailbox owner rights. Defer in case of trouble. */ - uid_res = mail_addr_find(virtual_uid_maps, state.msg_attr.user, - IGNORE_EXTENSION); + uid_res = mail_addr_find_noconv(virtual_uid_maps, state.msg_attr.user, + IGNORE_EXTENSION); if (uid_res == 0) { msg_warn("recipient %s: not found in %s", state.msg_attr.user, virtual_uid_maps->title); @@ -236,8 +237,8 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) /* * Look up the mailbox group rights. Defer in case of trouble. */ - gid_res = mail_addr_find(virtual_gid_maps, state.msg_attr.user, - IGNORE_EXTENSION); + gid_res = mail_addr_find_noconv(virtual_gid_maps, state.msg_attr.user, + IGNORE_EXTENSION); if (gid_res == 0) { msg_warn("recipient %s: not found in %s", state.msg_attr.user, virtual_gid_maps->title);