-TMAC_EXP_OP_INFO
-TMAC_HEAD
-TMAC_PARSE
+-TMAIL_ADDR_MAP_TEST
-TMAIL_PRINT
-TMAIL_SCAN
-TMAIL_STREAM
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.
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.
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 <i>key</i> specifies email address information, the localpart needs
+ to be enclosed with double quotes if required by <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a> and if the
+ <i>key</i> is used in <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>, *<a href="postconf.5.html#canonical_maps">canonical_maps</a>, or smtp_generic
+ maps. For example, an address localpart that contains space or ';'
+ characters needs to be quoted. The <a href="postmap.1.html"><b>postmap</b>(1)</a> command supports spaces
+ in the <i>key</i> 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 <a href="DATABASE_README.html#types">btree</a>:, <a href="DATABASE_README.html#types">dbm</a>: or
<a href="DATABASE_README.html#types">hash</a>:. 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
- <a href="regexp_table.5.html">regexp</a>: and <a href="pcre_table.5.html">pcre</a>:. This resulted in loss of information with $<i>number</i>
+ where a lookup field can match both upper and lower case text, such as
+ <a href="regexp_table.5.html">regexp</a>: and <a href="pcre_table.5.html">pcre</a>:. This resulted in loss of information with $<i>number</i>
substitutions.
<b>COMMAND-LINE ARGUMENTS</b>
- <b>-b</b> Enable message body query mode. When reading lookup keys from
- standard input with "<b>-q -</b>", process the input as if it is an
- email message in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a> format. Each line of body content
+ <b>-b</b> Enable message body query mode. When reading lookup keys from
+ standard input with "<b>-q -</b>", process the input as if it is an
+ email message in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a> format. Each line of body content
becomes one lookup key.
- By default, the <b>-b</b> option starts generating lookup keys at the
- first non-header line, and stops when the end of the message is
- reached. To simulate <a href="header_checks.5.html"><b>body_checks</b>(5)</a> processing, enable MIME
- parsing with <b>-m</b>. With this, the <b>-b</b> option generates no
- body-style lookup keys for attachment MIME headers and for
+ By default, the <b>-b</b> option starts generating lookup keys at the
+ first non-header line, and stops when the end of the message is
+ reached. To simulate <a href="header_checks.5.html"><b>body_checks</b>(5)</a> processing, enable MIME
+ parsing with <b>-m</b>. With this, the <b>-b</b> option generates no
+ body-style lookup keys for attachment MIME headers and for
attached message/* headers.
- NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option option dis-
- ables UTF-8 syntax checks on query keys and lookup results.
+ NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option option dis-
+ ables UTF-8 syntax checks on query keys and lookup results.
Specify the <b>-U</b> option to force UTF-8 syntax checks anyway.
This feature is available in Postfix version 2.6 and later.
<b>-c</b> <i>config</i><b>_</b><i>dir</i>
- Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
instead of the default configuration directory.
- <b>-d</b> <i>key</i> Search the specified maps for <i>key</i> and remove one entry per map.
- The exit status is zero when the requested information was
+ <b>-d</b> <i>key</i> Search the specified maps for <i>key</i> and remove one entry per map.
+ The exit status is zero when the requested information was
found.
- If a key value of <b>-</b> is specified, the program reads key values
- from the standard input stream. The exit status is zero when at
+ If a key value of <b>-</b> 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.
- <b>-f</b> Do not fold the lookup key to lower case while creating or
+ <b>-f</b> 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.
- <b>-h</b> Enable message header query mode. When reading lookup keys from
- standard input with "<b>-q -</b>", process the input as if it is an
- email message in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a> format. Each logical header line
- becomes one lookup key. A multi-line header becomes one lookup
+ <b>-h</b> Enable message header query mode. When reading lookup keys from
+ standard input with "<b>-q -</b>", process the input as if it is an
+ email message in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a> 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 <b>-h</b> option generates lookup keys until the first
- non-header line is reached. To simulate <a href="header_checks.5.html"><b>header_checks</b>(5)</a> pro-
- cessing, enable MIME parsing with <b>-m</b>. With this, the <b>-h</b> option
- also generates header-style lookup keys for attachment MIME
+ By default, the <b>-h</b> option generates lookup keys until the first
+ non-header line is reached. To simulate <a href="header_checks.5.html"><b>header_checks</b>(5)</a> pro-
+ cessing, enable MIME parsing with <b>-m</b>. With this, the <b>-h</b> option
+ also generates header-style lookup keys for attachment MIME
headers and for attached message/* headers.
- NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option option dis-
- ables UTF-8 syntax checks on query keys and lookup results.
+ NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option option dis-
+ ables UTF-8 syntax checks on query keys and lookup results.
Specify the <b>-U</b> option to force UTF-8 syntax checks anyway.
This feature is available in Postfix version 2.6 and later.
- <b>-i</b> Incremental mode. Read entries from standard input and do not
- truncate an existing database. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> creates a
+ <b>-i</b> Incremental mode. Read entries from standard input and do not
+ truncate an existing database. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> creates a
new database from the entries in <b>file_name</b>.
<b>-m</b> Enable MIME parsing with "<b>-b</b>" and "<b>-h</b>".
This feature is available in Postfix version 2.6 and later.
- <b>-N</b> Include the terminating null character that terminates lookup
- keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is the
+ <b>-N</b> Include the terminating null character that terminates lookup
+ keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is the
default for the host operating system.
- <b>-n</b> Don't include the terminating null character that terminates
- lookup keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is
+ <b>-n</b> Don't include the terminating null character that terminates
+ lookup keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is
the default for the host operating system.
- <b>-o</b> Do not release root privileges when processing a non-root input
- file. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> drops root privileges and runs as
+ <b>-o</b> Do not release root privileges when processing a non-root input
+ file. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> drops root privileges and runs as
the source file owner instead.
- <b>-p</b> Do not inherit the file access permissions from the input file
- when creating a new file. Instead, create a new file with
+ <b>-p</b> 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).
- <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> and write the first value
- found to the standard output stream. The exit status is zero
+ <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> 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 <b>-</b> is specified, the program reads key values
- from the standard input stream and writes one line of <i>key value</i>
+ If a key value of <b>-</b> is specified, the program reads key values
+ from the standard input stream and writes one line of <i>key value</i>
output for each key that was found. The exit status is zero when
at least one of the requested keys was found.
- <b>-r</b> When updating a table, do not complain about attempts to update
+ <b>-r</b> When updating a table, do not complain about attempts to update
existing entries, and make those updates anyway.
- <b>-s</b> Retrieve all database elements, and write one line of <i>key value</i>
- output for each element. The elements are printed in database
- order, which is not necessarily the same as the original input
+ <b>-s</b> Retrieve all database elements, and write one line of <i>key value</i>
+ 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.
- <b>-u</b> Disable UTF-8 support. UTF-8 support is enabled by default when
- "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys and values are
+ <b>-u</b> Disable UTF-8 support. UTF-8 support is enabled by default when
+ "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys and values are
valid UTF-8 strings.
<b>-U</b> With "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", force UTF-8 syntax checks with the
<b>-b</b> and <b>-h</b> options.
- <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
options make the software increasingly verbose.
- <b>-w</b> When updating a table, do not complain about attempts to update
+ <b>-w</b> When updating a table, do not complain about attempts to update
existing entries, and ignore those attempts.
Arguments:
The <a href="postmap.1.html"><b>postmap</b>(1)</a> command can query any supported file type, but it
can create only the following file types:
- <b>btree</b> The output file is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
- This is available on systems with support for <b>db</b> data-
+ <b>btree</b> The output file is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
+ This is available on systems with support for <b>db</b> data-
bases.
- <b>cdb</b> The output consists of one file, named <i>file</i><b>_</b><i>name</i><b>.cdb</b>.
- This is available on systems with support for <b>cdb</b> data-
+ <b>cdb</b> The output consists of one file, named <i>file</i><b>_</b><i>name</i><b>.cdb</b>.
+ This is available on systems with support for <b>cdb</b> data-
bases.
<b>dbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
for <b>dbm</b> databases.
- <b>hash</b> The output file is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
- This is available on systems with support for <b>db</b> data-
+ <b>hash</b> The output file is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
+ This is available on systems with support for <b>db</b> data-
bases.
- <b>fail</b> A table that reliably fails all requests. The lookup ta-
- ble name is used for logging only. This table exists to
+ <b>fail</b> 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.
<b>sdbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
<i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
for <b>sdbm</b> databases.
- When no <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
- type specified via the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ When no <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
+ type specified via the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
parameter.
<i>file</i><b>_</b><i>name</i>
<b>DIAGNOSTICS</b>
Problems are logged to the standard error stream and to <b>syslogd</b>(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.
<a href="postmap.1.html"><b>postmap</b>(1)</a> terminates with zero exit status in case of success (includ-
- ing successful "<b>postmap -q</b>" lookup) and terminates with non-zero exit
+ ing successful "<b>postmap -q</b>" lookup) and terminates with non-zero exit
status in case of failure.
<b>ENVIRONMENT</b>
Enable verbose logging for debugging purposes.
<b>CONFIGURATION PARAMETERS</b>
- The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
- gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
<a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
<b><a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (16777216)</b>
- 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.
<b><a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (131072)</b>
hash or btree tables.
<b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
- The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
figuration files.
<b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
<b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
- Enable preliminary SMTPUTF8 support for the protocols described
+ Enable preliminary SMTPUTF8 support for the protocols described
in <a href="http://tools.ietf.org/html/rfc6531">RFC 6531</a>..6533.
<b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
The syslog facility of Postfix logging.
<b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
- 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".
<b>SEE ALSO</b>
# 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"
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
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
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
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
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 -- "
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 -- "
* 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));
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) {
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);
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 \
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.
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)
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
$(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
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
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
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
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
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
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
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
/* SYNOPSIS
/* #include <mail_addr_crunch.h>
/*
-/* 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
#include <tok822.h>
#include <canon_addr.h>
+#include <quote_822_local.h>
#include <mail_addr_crunch.h>
/* 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);
#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
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);
}
#include <mail_conf.h>
#include <mail_params.h>
+/* 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);
}
*/
#include <argv.h>
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+#include <argv.h>
+
/*
* 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
--- /dev/null
+#!/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
--- /dev/null
+==== 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|
/* SYNOPSIS
/* #include <mail_addr_find.h>
/*
-/* 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.
/*
/* 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.
#include <sys_defs.h>
#include <string.h>
+#include <unistd.h>
/* Utility library. */
#include <strip_addr.h>
#include <mail_addr_find.h>
#include <resolve_local.h>
+#include <quote_822_local.h>
/* 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);
}
/*
#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;
* 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;
}
* 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.
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);
}
*/
#include <vstream.h>
#include <vstring_vstream.h>
+#include <name_code.h>
#include <mail_conf.h>
+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);
vstring_free(buffer);
maps_free(path);
- return (0);
+ return (errs != 0);
}
#endif
/*
* Global library.
*/
+#include <mail_addr_form.h>
#include <maps.h>
/*
* 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
--- /dev/null
+#!/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
--- /dev/null
+==== 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)
--- /dev/null
+/*++
+/* NAME
+/* mail_addr_form 3
+/* SUMMARY
+/* predicate if string is all numerical
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* 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 <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_addr_form.h>
+
+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));
+}
--- /dev/null
+#ifndef _MAIL_ADDR_FORM_H_INCLUDED_
+#define _MAIL_ADDR_FORM_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_addr_form 3h
+/* SUMMARY
+/* mail address formats
+/* SYNOPSIS
+/* #include <mail_addr_form.h>
+/* 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
/* SYNOPSIS
/* #include <mail_addr_map.h>
/*
-/* 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.
/*
/* Global library. */
+#include <quote_822_local.h>
#include <mail_addr_find.h>
#include <mail_addr_crunch.h>
#include <mail_addr_map.h>
/* 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";
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
*/
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]);
*/
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 <unistd.h>
-#include <mail_conf.h>
-#include <vstream.h>
-#include <vstring_vstream.h>
-#include <mail_params.h>
-
-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
/*
* Global library.
*/
+#include <mail_addr_form.h>
#include <maps.h>
/*
* 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
--- /dev/null
+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
--- /dev/null
+/*++
+/* 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 <sys_defs.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <canon_addr.h>
+#include <mail_addr_map.h>
+#include <mail_conf.h> /* XXX eliminate main.cf dependency */
+#include <mail_params.h>
+
+/* 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);
+}
* 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
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);
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);
}
* Proof-of-concept test program. Read an unquoted address from stdin, and
* show the quoted and unquoted results.
*/
+#include <ctype.h>
+#include <string.h>
+
+#include <msg.h>
+#include <stringops.h>
#include <vstream.h>
#include <vstring_vstream.h>
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);
}
--- /dev/null
+quote a@b@c@d
+unquote "a@b@c"@d
+unquote "a@b@c"
+unquote "a@b@c"@d@e
--- /dev/null
+'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'
/* SYNOPSIS
/* #include <split_addr.h>
/*
-/* 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
#include <mail_addr.h>
#include <split_addr.h>
-/* 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;
/* External interface. */
-extern char *split_addr(char *, const char *);
+extern char *split_addr_internal(char *, const char *);
/* LICENSE
/* .ad
/* SYNOPSIS
/* #include <strip_addr.h>
/*
-/* 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.
/* Global library. */
+#include <quote_822_local.h>
#include <split_addr.h>
#include <strip_addr.h>
+#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;
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)];
* 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)
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)
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)
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);
}
/* 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
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
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);
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);
: 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));
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));
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);
}
../../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
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
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
/* 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
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.
}
/*
- * 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.
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)
--- /dev/null
+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
--- /dev/null
+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
: 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));
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
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
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
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
foo@example.com bar@com.example
bar@example.com bar
baz@example.com @com.example
+splitme@example.com "split me"@com.example
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];
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);
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
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
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
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)
#include <ehlo_mask.h>
#include <maps.h>
#include <tok822.h>
-#include <mail_addr_map.h>
#include <ext_prop.h>
#include <namadr_list.h>
#include <match_parent_style.h>
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,
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
{
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. */
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
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;
}
}
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) \
*/
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);
* 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);
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);
* 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);
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 \
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.
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
$(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 \
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:
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
$(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]' <dict_thash.map | sort | diff -b dict_thash.tmp -
- rm -f dict_thash.tmp
+ $(SHLIB_ENV) ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
+ LANG=C sort | diff dict_thash.map -
surrogate_test: dict_open surrogate.ref
cp /dev/null surrogate.tmp
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
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
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. */
DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
open_flags, dict_flags,
"open database %s: %m", path));
- }
+ }
/*
* Reuse the "internal" dictionary type.
dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags);
dict_type_override(dict, DICT_TYPE_THASH);
+ /*
+ * 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));
* 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.
* 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) {
-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
--- /dev/null
+/*++
+/* NAME
+/* split_qnameval 3
+/* SUMMARY
+/* name-value splitter
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* 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 <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <mymalloc.h>
+
+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
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);
{
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')
*/
#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);
/*
* 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);
/*
* 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);