]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.2-20170122
authorWietse Venema <wietse@porcupine.org>
Sun, 22 Jan 2017 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Mon, 23 Jan 2017 23:32:07 +0000 (18:32 -0500)
23 files changed:
postfix/HISTORY
postfix/WISHLIST
postfix/html/postmap.1.html
postfix/man/man1/postmap.1
postfix/src/cleanup/cleanup_addr.c
postfix/src/cleanup/cleanup_map11.c
postfix/src/global/Makefile.in
postfix/src/global/mail_addr_find.c
postfix/src/global/mail_addr_find.h
postfix/src/global/mail_addr_find.in
postfix/src/global/mail_addr_find.ref
postfix/src/global/mail_addr_form.c
postfix/src/global/mail_addr_form.h
postfix/src/global/mail_addr_map.c
postfix/src/global/mail_addr_map.h
postfix/src/global/mail_addr_map.ref
postfix/src/global/mail_addr_map_tester.c [deleted file]
postfix/src/global/mail_version.h
postfix/src/postmap/postmap.c
postfix/src/smtp/smtp_map11.c
postfix/src/smtpd/Makefile.in
postfix/src/smtpd/smtpd_check.c
postfix/src/trivial-rewrite/transport.c

index 0e672df4f844806e7f97c199cd8158f28265172a..a6d71241da8ce0a95fb00ac47677a1bec8ca81ba 100644 (file)
@@ -22845,3 +22845,21 @@ Apologies for any names omitted.
        compatibility, for check_sender_access and check_recipient_access.
        This now uses 'user@' lookup support in the mail_addr_find()
        engine.  File: global/mail_addr_find.*, smtpd/smtpd_check.c.
+
+20170122
+
+       Cleanup: separated the database query form from the address
+       form that is input to mail_addr_find_() or mail_addr_map*(),
+       in attempt to make code more obviously correct. Files:
+       global/mail_addr_find.c, global/mail_addr_map.c.
+
+       Abandoned an experiment that used internal-form queries for
+       all maps, because it would be very difficult to test. The
+       tests inputs would have to compensate for multiple levels
+       of unquoting by postmap, C compilers, or shell interpreters.
+
+       Cleanup: moved the backwards-compatibility lookup strategy
+       (try the external address form first, then the internal
+       address form if it is different) inside the loop that
+       iterates over full and partial address forms. File:
+       global/mail_addr_find.c.
index 167140d49f0e5ef8c9a9ce7c043a5bf13fecc476..f015bdd790a190e5fe7f65a4504bdc05eac1bb57 100644 (file)
@@ -8,15 +8,11 @@ Wish list:
 
        Enable external-form lookups for smtpd access maps.
 
-       Enable caching in quote_822_local.
-
-       Quoting: in smtp_map11, don't convert to external.
-
-       Make the address substring generation ("strategy") suitable
-       for dict-based applications such as access(5) maps or local
-       aliases.  Either that, or do some serious surgery on the
-       dict-based mechanisms. For access(5) maps this requires a
-       localpart[+ext]@ query that comes after the domain queries.
+       Convert postalias(1) to store external-form keys, and convert
+       aliases(5) to perform external-first lookup with fallback to
+       internal form, to make it consistent with the rest of Postfix.
+       In several years we may remove the internal-form fallbacks
+       with a compatibility_level safety net.
 
        In the bounce daemon, set util_utf8_enable if returning an
        SMTPUTF8 message.
@@ -37,18 +33,6 @@ Wish list:
        RHS. This will not preserve trailing comments in lines that
        are modified with "postconf -e" and the like.
 
-       The cleanup daemon searches canonical_maps and virtual_alias_maps
-       with quoted address forms. The address local part should
-       be in unquoted form before it is split into name and
-       extension. Note, however, that although quoting is done
-       over the entire localpart, unquoting is not simply a matter
-       of removing the outer quotes. The fix will require careful
-       consideration of the responsibilities of mail_addr_map(),
-       mail_addr_find(), and mail_addr_crunch(), and making sure
-       that the callers can handle quoted results. For example,
-       sender_bcc_maps and recipient_bcc_maps invoke mail_addr_find()
-       with unquoted forms and expects an unquoted result, and so on.
-
        Maintainability: replace lengthy libmilter-API argument lists
        with named parameters, as with the libtls API.
        
index 82a72025560c10f2f34528a57daf3124a6d45d2c..2d3361348b6092fe076a27336a4789bc2c74aa1f 100644 (file)
@@ -46,127 +46,128 @@ POSTMAP(1)                                                          POSTMAP(1)
 
        When the <i>key</i> specifies email address information, the localpart  should
        be enclosed with double quotes if required by <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>. For example, an
-       address localpart that contains
+       address localpart that contains ";", or a localpart that starts or ends
+       with ".".
 
-       By default the lookup key is mapped to lowercase to  make  the  lookups
+       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/rfc5322">RFC  5322</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/rfc5322">RFC  5322</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:
@@ -178,32 +179,32 @@ POSTMAP(1)                                                          POSTMAP(1)
               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>
@@ -212,11 +213,11 @@ POSTMAP(1)                                                          POSTMAP(1)
 
 <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>
@@ -227,12 +228,12 @@ POSTMAP(1)                                                          POSTMAP(1)
               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>
@@ -240,7 +241,7 @@ POSTMAP(1)                                                          POSTMAP(1)
               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>
@@ -248,14 +249,14 @@ POSTMAP(1)                                                          POSTMAP(1)
               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>
index f0f8e2beeaeb01f2ff6def7923aabb4d826ffd16..862438ed2ff6bc2f1305f52cec208e553c529239 100644 (file)
@@ -56,7 +56,7 @@ keys is supported as of Postfix 3.2.
 When the \fIkey\fR specifies email address information, the
 localpart should be enclosed with double quotes if required
 by RFC 5322. For example, an address localpart that contains
-';' or that ends on '.'.
+";", or a localpart that starts or ends with ".".
 
 By default the lookup key is mapped to lowercase to make
 the lookups case insensitive; as of Postfix 2.3 this case
@@ -74,7 +74,7 @@ information with $\fInumber\fR substitutions.
 .IP \fB\-b\fR
 Enable message body query mode. When reading lookup keys
 from standard input with "\fB\-q \-\fR", process the input
-as if it is an email message in RFC 2822 format.  Each line
+as if it is an email message in RFC 5322 format.  Each line
 of body content becomes one lookup key.
 .sp
 By default, the \fB\-b\fR option starts generating lookup
@@ -111,7 +111,7 @@ is controlled by appending a flag to a pattern.
 .IP \fB\-h\fR
 Enable message header query mode. When reading lookup keys
 from standard input with "\fB\-q \-\fR", process the input
-as if it is an email message in RFC 2822 format.  Each
+as if it is an email message in RFC 5322 format.  Each
 logical header line becomes one lookup key. A multi\-line
 header becomes one lookup key with one or more embedded
 newline characters.
index b6396ada860e8b6840458a1db7552c20be95af0f..73b72edc49f5dcb615f279d5a308174310850c7c 100644 (file)
@@ -165,8 +165,9 @@ off_t   cleanup_addr_sender(CLEANUP_STATE *state, const char *buf)
     if ((state->flags & CLEANUP_FLAG_BCC_OK)
        && *STR(clean_addr)
        && cleanup_send_bcc_maps) {
-       if ((bcc = mail_addr_find(cleanup_send_bcc_maps, STR(clean_addr),
-                                 IGNORE_EXTENSION)) != 0) {
+       if ((bcc = mail_addr_find_to_internal(cleanup_send_bcc_maps,
+                                             STR(clean_addr),
+                                             IGNORE_EXTENSION)) != 0) {
            cleanup_addr_bcc(state, bcc);
        } else if (cleanup_send_bcc_maps->error) {
            msg_warn("%s: %s map lookup problem -- "
@@ -228,8 +229,9 @@ void    cleanup_addr_recipient(CLEANUP_STATE *state, const char *buf)
     if ((state->flags & CLEANUP_FLAG_BCC_OK)
        && *STR(clean_addr)
        && cleanup_rcpt_bcc_maps) {
-       if ((bcc = mail_addr_find(cleanup_rcpt_bcc_maps, STR(clean_addr),
-                                 IGNORE_EXTENSION)) != 0) {
+       if ((bcc = mail_addr_find_to_internal(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 -- "
index 941e156f23130a707c241a9718b2b0fa017865a1..fd9cddcf16c9d9dc9b6b087a4d1ec9c7b0f9d971 100644 (file)
@@ -105,6 +105,7 @@ int     cleanup_map11_external(CLEANUP_STATE *state, VSTRING *addr,
      */
     for (count = 0; count < MAX_RECURSION; count++) {
        if ((new_addr = mail_addr_map_opt(maps, STR(addr), propagate,
+                                         MAIL_ADDR_FORM_EXTERNAL,
                                          MAIL_ADDR_FORM_EXTERNAL,
                                          MAIL_ADDR_FORM_EXTERNAL)) != 0) {
            if (new_addr->argc > 1)
index 4bafabeb4d3059b914d7b5eddaf9d7144d058147..722517dbce1cce6eefe195814a8774326a57f92c 100644 (file)
@@ -118,7 +118,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
        valid_mailhost_addr own_inet_addr header_body_checks \
        data_redirect addr_match_list safe_ultostr verify_sender_addr \
        mail_version mail_dict server_acl uxtext mail_parm_split \
-       fold_addr smtp_reply_footer mail_addr_map_tester
+       fold_addr smtp_reply_footer mail_addr_map
 
 LIBS   = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 LIB_DIR        = ../../lib
@@ -249,8 +249,10 @@ off_cvt: $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
        mv junk $@.o
 
-mail_addr_map_tester: mail_addr_map_tester.c $(LIB) $(LIBS)
+mail_addr_map: mail_addr_map.c $(LIB) $(LIBS)
+       mv $@.o junk
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+       mv junk $@.o
 
 mail_addr_find: $(LIB) $(LIBS)
        mv $@.o junk
@@ -683,9 +685,9 @@ mail_addr_find_test: update mail_addr_find mail_addr_find.in mail_addr_find.ref
        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) $(VALGRIND) ./mail_addr_map_tester pass_tests
-       -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_map_tester fail_tests >mail_addr_map.tmp 2>&1
+mail_addr_map_test: update mail_addr_map mail_addr_map.ref
+       -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_map pass_tests
+       -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_map fail_tests >mail_addr_map.tmp 2>&1
        diff mail_addr_map.ref mail_addr_map.tmp
        rm -f mail_addr_map.tmp
 
@@ -1490,6 +1492,7 @@ mail_addr_find.o: ../../include/dict.h
 mail_addr_find.o: ../../include/msg.h
 mail_addr_find.o: ../../include/myflock.h
 mail_addr_find.o: ../../include/mymalloc.h
+mail_addr_find.o: ../../include/name_mask.h
 mail_addr_find.o: ../../include/stringops.h
 mail_addr_find.o: ../../include/sys_defs.h
 mail_addr_find.o: ../../include/vbuf.h
@@ -1526,23 +1529,6 @@ 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/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: 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
index 56907acc71e1baad5d2bf62ada22056a6be24080..5934db5c16d013eb142f50590e7d11e0eb3687b5 100644 (file)
 /*     const char *address;
 /*     char    **extension;
 /*
-/*     const char *mail_addr_find_opt(maps, address, extension,
-/*                                     in_form, out_form, strategy)
+/*     const char *mail_addr_find_opt(maps, address, extension, in_form,
+/*                                     query_form, out_form, strategy)
 /*     MAPS    *maps;
 /*     const char *address;
 /*     char    **extension;
 /*     int     in_form;
+/*     int     in_form;
 /*     int     out_form;
 /*     int     strategy;
 /* LEGACY SUPPORT
 /*     const char *address;
 /*     char    **extension;
 /*
+/*     const char *mail_addr_find_to_internal(maps, address, extension)
+/*     MAPS    *maps;
+/*     const char *address;
+/*     char    **extension;
+/*
 /*     const char *mail_addr_find_strategy(maps, address, extension)
 /*     MAPS    *maps;
 /*     const char *address;
 /*     preferences when it opens the maps.
 /*     The result is overwritten upon each call.
 /*
-/*     The table key and value are expected to be in external
-/*     (quoted) form. Override these assumptions with the in_form
+/*     In the lookup table, the key is expected to be in external
+/*     form (as produced with the postmap command) and the value is
+/*     expected to be in external (quoted) form if it is an email
+/*     address. Override these assumptions with the query_form
 /*     and out_form arguments.
 /*
 /*     With mail_addr_find_int_to_ext(), the specified 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 minimizes internal/external (unquoted/quoted)
-/*     conversions of the query, extension, or result.
+/*     internal (unquoted) form, the query is made in external (quoted)
+/*     form, and the result is in the form found in the table (it is
+/*     not necessarily an email address). This version minimizes
+/*     internal/external (unquoted/quoted) conversions of the input,
+/*     query, extension, or result.
 /*
 /*     mail_addr_find_opt() gives more control, at the cost of
 /*     additional conversions between internal and external forms.
-/*     In particular, the output conversion to internal form assumes
+/*     In particular, output conversion to internal form assumes
 /*     that the lookup result is an email address.
 /*
-/*     mail_addr_find() is used by legacy code that historically
-/*     searched with internal-form keys.  The lookup strategy is
-/*     to first look up with (in_form, out_form) of (INTERNAL,
-/*     NOCONV), which converts the key to external form.  If no
-/*     result is found, and the internal and external key forms
-/*     differ, there is a backwards-compatibility lookup with
-/*     (in_form, out_form) of (NOCONV, NOCONV).
+/*     mail_addr_find() is used by legacy code that historically searched
+/*     with internal-form queries. The input is in internal form. It
+/*     searches with external-form queries first, and falls back to
+/*     internal-form queries if no result was found and the external
+/*     and internal forms differ. The result is external form (i.e. no
+/*     conversion).
 /*
-/*     mail_addr_find_strategy() overrides the default search
-/*     strategy for full and partial addresses.
+/*     mail_addr_find_to_internal() is like mail_addr_find() but assumes
+/*     that the lookup result is one external-form email address,
+/*     and converts it to internal form.
 /*
-/*     An address that is in the form \fIuser\fR matches itself.
+/*     mail_addr_find_strategy() is like mail_addr_find() but overrides
+/*     the default search strategy for full and partial addresses.
 /*
 /*     Arguments:
 /* .IP maps
 /*     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 query_form
+/*     The address form to use for database queries: one of
+/*     MAIL_ADDR_FORM_INTERNAL (unquoted form), MAIL_ADDR_FORM_EXTERNAL
+/*     (quoted form), or MAIL_ADDR_FORM_EXTERNAL_FIRST (external form,
+/*     then internal form if the external and internal forms differ).
 /* .IP in_form
 /* .IP out_form
-/*     Input and output address forms, either MAIL_ADDR_FORM_INTERNAL
-/*     (unquoted form), MAIL_ADDR_FORM_EXTERNAL (quoted form), or
-/*     MAIL_ADDR_FORM_NOCONV (don't convert between unquoted and
-/*     quoted form).
+/*     Input and output address forms, one of MAIL_ADDR_FORM_INTERNAL
+/*     (unquoted form), or MAIL_ADDR_FORM_EXTERNAL (quoted form).
 /* .IP strategy
 /*     The lookup strategy for full and partial addresses, specified
-/*     as the binary OR of one or more of the following. These
-/*     lookups are implemented in the order as listed below.
+/*     as the binary OR of one or more of the following. These lookups
+/*     are implemented in the order as listed below.
 /* .RS
-/* .IP MAIL_ADDR_FIND_FULL
+/* .IP MAF_STRATEGY_DEFAULT
+/*     A convenience alias for (MAF_STRATEGY_FULL | MAF_STRATEGY_NOEXT |
+/*     MAF_STRATEGY_LOCALPART_IF_LOCAL | MAF_STRATEGY_AT_DOMAIN).
+/* .IP MAF_STRATEGY_FULL
 /*     Look up the full email address.
-/* .IP MAIL_ADDR_FIND_NOEXT
-/*     If no match was found, and the address has an extension,
-/*     look up the address after removing the address extension.
-/* .IP MAIL_ADDR_FIND_LOCALPART_IF_LOCAL
+/* .IP MAF_STRATEGY_NOEXT
+/*     If no match was found, and the address has a localpart extension,
+/*     look up the address after removing the extension.
+/* .IP MAF_STRATEGY_LOCALPART_IF_LOCAL
 /*     If no match was found, and the domain matches myorigin,
-/*     mydestination, or any proxy_interfaces IP address, look up
-/*     the localpart.  If no match was found, and the address has
-/*     an extension, repeat the same query after removing the
-/*     address extension unless MAIL_ADDR_FIND_NOEXT is specified.
-/* .IP MAIL_ADDR_FIND_LOCALPART_AT_IF_LOCAL
+/*     mydestination, or any inet_interfaces or proxy_interfaces IP
+/*     address, look up the localpart.  If no match was found, and the
+/*     address has a localpart extension, repeat the same query after
+/*     removing the extension unless MAF_STRATEGY_NOEXT is specified.
+/* .IP MAF_STRATEGY_LOCALPART_AT_IF_LOCAL
 /*     As above, but using the localpart@ instead.
-/* .IP MAIL_ADDR_FIND_ATDOMAIN
+/* .IP MAF_STRATEGY_AT_DOMAIN
 /*     If no match was found, look up the @domain without localpart.
-/* .IP MAIL_ADDR_FIND_DOMAIN
+/* .IP MAF_STRATEGY_DOMAIN
 /*     If no match was found, look up the domain without localpart.
-/* .IP MAIL_ADDR_FIND_PMS
-/*     When used with MAIL_ADDR_FIND_DOMAIN, also matches subdomains.
-/* .IP MAIL_ADDR_FIND_PMDS
-/*     When used with MAIL_ADDR_FIND_DOMAIN, also matches dot-subdomains.
-/* .IP MAIL_ADDR_FIND_LOCALPART_AT
-/*     If no match was found, look up the localpart@, regardless
-/*     of the domain content.
+/* .IP MAF_STRATEGY_PMS
+/*     When used with MAF_STRATEGY_DOMAIN, also matches subdomains.
+/* .IP MAF_STRATEGY_PMDS
+/*     When used with MAF_STRATEGY_DOMAIN, also matches dot-subdomains.
+/* .IP MAF_STRATEGY_LOCALPART_AT
+/*     If no match was found, look up the localpart@, regardless of
+/*     the domain content.
 /* .RE
 /* DIAGNOSTICS
-/*     The maps->error value is non-zero when the lookup
-/*     should be tried again.
+/*     The maps->error value is non-zero when the lookup should be
+/*     tried again.
 /* SEE ALSO
-/*     maps(3), multi-dictionary search
-/*     resolve_local(3), recognize local system
+/*     maps(3), multi-dictionary search resolve_local(3), recognize
+/*     local system
 /* LICENSE
 /* .ad
 /* .fi
 
 #define STR    vstring_str
 
+#ifdef TEST
+
 static const NAME_MASK strategy_table[] = {
-    "full", MAIL_ADDR_FIND_FULL,
-    "noext", MAIL_ADDR_FIND_NOEXT,
-    "localpart_if_local", MAIL_ADDR_FIND_LOCALPART_IF_LOCAL,
-    "localpart_at_if_local", MAIL_ADDR_FIND_LOCALPART_AT_IF_LOCAL,
-    "atdomain", MAIL_ADDR_FIND_ATDOMAIN,
-    "domain", MAIL_ADDR_FIND_DOMAIN,
-    "pms", MAIL_ADDR_FIND_PMS,
-    "pmds", MAIL_ADDR_FIND_PMDS,
-    "localpartat", MAIL_ADDR_FIND_LOCALPART_AT,
-    "default", MAIL_ADDR_FIND_DEFAULT,
+    "full", MAF_STRATEGY_FULL,
+    "noext", MAF_STRATEGY_NOEXT,
+    "localpart_if_local", MAF_STRATEGY_LOCALPART_IF_LOCAL,
+    "localpart_at_if_local", MAF_STRATEGY_LOCALPART_AT_IF_LOCAL,
+    "atdomain", MAF_STRATEGY_AT_DOMAIN,
+    "domain", MAF_STRATEGY_DOMAIN,
+    "pms", MAF_STRATEGY_PMS,
+    "pmds", MAF_STRATEGY_PMDS,
+    "localpart_at", MAF_STRATEGY_LOCALPART_AT,
+    "default", MAF_STRATEGY_DEFAULT,
     0, -1,
 };
 
- /*
-  * Specify what keys are partial or full, to avoid matching partial
-  * addresses with regular expressions.
-  */
-#define FULL   0
-#define PARTIAL        DICT_FLAG_FIXED
-
 /* strategy_from_string - symbolic strategy flags to internal form */
 
 static int strategy_from_string(const char *strategy_string)
@@ -199,29 +210,63 @@ static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask)
                              NAME_MASK_WARN | NAME_MASK_PIPE));
 }
 
-/* find_addr - helper to search map with external-form address */
+#endif
+
+ /*
+  * Specify what keys are partial or full, to avoid matching partial
+  * addresses with regular expressions.
+  */
+#define FULL   0
+#define PARTIAL        DICT_FLAG_FIXED
+
+/* find_addr - helper to search maps with the right query form */
 
 static const char *find_addr(MAPS *path, const char *address, int flags,
-                     int with_domain, int find_form, VSTRING *ext_addr_buf)
+                    int with_domain, int query_form, VSTRING *ext_addr_buf)
 {
+    const char *result;
 
 #define SANS_DOMAIN    0
 #define WITH_DOMAIN    1
 
-    if (find_form == MAIL_ADDR_FORM_INTERNAL) {
+    switch (query_form) {
+
+       /*
+        * Query with external-form (quoted) address.
+        */
+    case MAIL_ADDR_FORM_EXTERNAL:
+    case MAIL_ADDR_FORM_EXTERNAL_FIRST:
        quote_822_local_flags(ext_addr_buf, address,
                              with_domain ? QUOTE_FLAG_DEFAULT :
                            QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART);
-       address = STR(ext_addr_buf);
+       result = maps_find(path, STR(ext_addr_buf), flags);
+       if (result != 0 || path->error != 0
+           || query_form == MAIL_ADDR_FORM_EXTERNAL
+           || strcmp(address, STR(ext_addr_buf)) == 0)
+           break;
+       /* FALLTHROUGH */
+
+       /*
+        * Query with internal-form (unquoted) address.
+        */
+    case MAIL_ADDR_FORM_INTERNAL:
+       result = maps_find(path, address, flags);
+       break;
+
+       /*
+        * Can't happen.
+        */
+    default:
+       msg_panic("mail_addr_find: bad query_form: %d", query_form);
     }
-    return (maps_find(path, address, flags));
+    return (result);
 }
 
 /* find_local - search on localpart info */
 
 static const char *find_local(MAPS *path, char *ratsign, int rats_offs,
                                      char *int_full_key, char *int_bare_key,
-                              int find_form, char **extp, char **saved_ext,
+                             int query_form, char **extp, char **saved_ext,
                                      VSTRING *ext_addr_buf)
 {
     const char *myname = "mail_addr_find";
@@ -230,15 +275,15 @@ static const char *find_local(MAPS *path, char *ratsign, int rats_offs,
     int     saved_ch;
 
     /*
-     * This was ripped from the middle of a function so it can be reused,
-     * that's why the interface makes no sense.
+     * This code was ripped from the middle of a function so that it can be
+     * reused multiple times, that's why the interface makes little sense.
      */
     with_domain = rats_offs ? WITH_DOMAIN : SANS_DOMAIN;
 
     saved_ch = *(unsigned char *) (ratsign + rats_offs);
     *(ratsign + rats_offs) = 0;
     result = find_addr(path, int_full_key, PARTIAL, with_domain,
-                      find_form, ext_addr_buf);
+                      query_form, ext_addr_buf);
     *(ratsign + rats_offs) = saved_ch;
     if (result == 0 && path->error == 0 && int_bare_key != 0) {
        if ((ratsign = strrchr(int_bare_key, '@')) == 0)
@@ -246,7 +291,7 @@ static const char *find_local(MAPS *path, char *ratsign, int rats_offs,
        saved_ch = *(unsigned char *) (ratsign + rats_offs);
        *(ratsign + rats_offs) = 0;
        if ((result = find_addr(path, int_bare_key, PARTIAL, with_domain,
-                               find_form, ext_addr_buf)) != 0
+                               query_form, ext_addr_buf)) != 0
            && extp != 0) {
            *extp = *saved_ext;
            *saved_ext = 0;
@@ -259,11 +304,11 @@ static const char *find_local(MAPS *path, char *ratsign, int rats_offs,
 /* mail_addr_find_opt - map a canonical address */
 
 const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
-                                   int in_form, int out_form, int strategy)
+                                      int in_form, int query_form,
+                                      int out_form, int strategy)
 {
     const char *myname = "mail_addr_find";
     VSTRING *ext_addr_buf = 0;
-    int     find_form;
     VSTRING *int_addr_buf = 0;
     const char *int_addr;
     static VSTRING *int_result = 0;
@@ -275,27 +320,27 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
     int     rc = 0;
 
     /*
-     * Optionally convert input from external form.
+     * Optionally convert the address 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);
-       find_form = MAIL_ADDR_FORM_INTERNAL;
     } else {
        int_addr = address;
-       find_form = in_form;
     }
-    if (find_form == MAIL_ADDR_FORM_INTERNAL)
+    if (query_form == MAIL_ADDR_FORM_EXTERNAL_FIRST
+       || query_form == MAIL_ADDR_FORM_EXTERNAL)
        ext_addr_buf = vstring_alloc(100);
 
     /*
      * Initialize.
      */
     int_full_key = mystrdup(int_addr);
-    if (*var_rcpt_delim == 0 || (strategy & MAIL_ADDR_FIND_NOEXT) == 0) {
+    if (*var_rcpt_delim == 0 || (strategy & MAF_STRATEGY_NOEXT) == 0) {
        int_bare_key = saved_ext = 0;
     } else {
+       /* XXX This could be done after user+foo@domain fails. */
        int_bare_key =
            strip_addr_internal(int_full_key, &saved_ext, var_rcpt_delim);
     }
@@ -303,9 +348,9 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
     /*
      * Try user+foo@domain and user@domain.
      */
-    if ((strategy & MAIL_ADDR_FIND_FULL) != 0) {
+    if ((strategy & MAF_STRATEGY_FULL) != 0) {
        result = find_addr(path, int_full_key, FULL, WITH_DOMAIN,
-                          find_form, ext_addr_buf);
+                          query_form, ext_addr_buf);
     } else {
        result = 0;
        path->error = 0;
@@ -313,31 +358,31 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
 
     if (result == 0 && path->error == 0 && int_bare_key != 0
        && (result = find_addr(path, int_bare_key, PARTIAL, WITH_DOMAIN,
-                              find_form, ext_addr_buf)) != 0
+                              query_form, ext_addr_buf)) != 0
        && extp != 0) {
        *extp = saved_ext;
        saved_ext = 0;
     }
 
     /*
-     * Try user+foo, if the domain matches user+foo@$myorigin,
+     * Try user+foo if the domain matches user+foo@$myorigin,
      * user+foo@$mydestination or user+foo@[${proxy,inet}_interfaces]. Then
      * try with +foo stripped off.
      */
     if (result == 0 && path->error == 0
        && (ratsign = strrchr(int_full_key, '@')) != 0
-       && (strategy & (MAIL_ADDR_FIND_LOCALPART_IF_LOCAL
-                       | MAIL_ADDR_FIND_LOCALPART_AT_IF_LOCAL)) != 0) {
+       && (strategy & (MAF_STRATEGY_LOCALPART_IF_LOCAL
+                       | MAF_STRATEGY_LOCALPART_AT_IF_LOCAL)) != 0) {
        if (strcasecmp_utf8(ratsign + 1, var_myorigin) == 0
            || (rc = resolve_local(ratsign + 1)) > 0) {
-           if ((strategy & MAIL_ADDR_FIND_LOCALPART_IF_LOCAL) != 0)
+           if ((strategy & MAF_STRATEGY_LOCALPART_IF_LOCAL) != 0)
                result = find_local(path, ratsign, 0, int_full_key,
-                                 int_bare_key, find_form, extp, &saved_ext,
+                                int_bare_key, query_form, extp, &saved_ext,
                                    ext_addr_buf);
            if (result == 0 && path->error == 0
-               && (strategy & MAIL_ADDR_FIND_LOCALPART_AT_IF_LOCAL) != 0)
+               && (strategy & MAF_STRATEGY_LOCALPART_AT_IF_LOCAL) != 0)
                result = find_local(path, ratsign, 1, int_full_key,
-                                 int_bare_key, find_form, extp, &saved_ext,
+                                int_bare_key, query_form, extp, &saved_ext,
                                    ext_addr_buf);
        } else if (rc < 0)
            path->error = rc;
@@ -347,40 +392,40 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
      * Try @domain.
      */
     if (result == 0 && path->error == 0 && ratsign != 0
-       && (strategy & MAIL_ADDR_FIND_ATDOMAIN) != 0)
+       && (strategy & MAF_STRATEGY_AT_DOMAIN) != 0)
        result = maps_find(path, ratsign, PARTIAL);
 
     /*
      * Try domain (optionally, subdomains).
      */
     if (result == 0 && path->error == 0 && ratsign != 0
-       && (strategy & MAIL_ADDR_FIND_DOMAIN) != 0) {
+       && (strategy & MAF_STRATEGY_DOMAIN) != 0) {
        const char *name;
        const char *next;
 
        for (name = ratsign + 1; *name != 0; name = next) {
            if ((result = maps_find(path, name, PARTIAL)) != 0
                || path->error != 0
-            || (strategy & (MAIL_ADDR_FIND_PMS | MAIL_ADDR_FIND_PMDS)) == 0
+               || (strategy & (MAF_STRATEGY_PMS | MAF_STRATEGY_PMDS)) == 0
                || (next = strchr(name + 1, '.')) == 0)
                break;
-           if ((strategy & MAIL_ADDR_FIND_PMDS) == 0)
+           if ((strategy & MAF_STRATEGY_PMDS) == 0)
                next++;
        }
     }
 
     /*
-     * Try localpart@ even if not local.
+     * Try localpart@ even if the domain is not local.
      */
-    if ((strategy & MAIL_ADDR_FIND_LOCALPART_AT) != 0 \
+    if ((strategy & MAF_STRATEGY_LOCALPART_AT) != 0 \
        &&result == 0 && path->error == 0)
        result = find_local(path, ratsign, 1, int_full_key,
-                           int_bare_key, find_form, extp, &saved_ext,
+                           int_bare_key, query_form, extp, &saved_ext,
                            ext_addr_buf);
 
     /*
      * Optionally convert the result to internal form. The lookup result is
-     * supposed to be in external form.
+     * supposed to be one external-form email address.
      */
     if (result != 0 && out_form == MAIL_ADDR_FORM_INTERNAL) {
        if (int_result == 0)
@@ -409,40 +454,6 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
     return (result);
 }
 
-/* mail_addr_find_strategy - map a canonical address */
-
-const char *mail_addr_find_strategy(MAPS *path, const char *address,
-                                           char **extp, int strategy)
-{
-    static VSTRING *ext_addr_buf;
-    const char *result;
-
-    /*
-     * The future: look up with MAIL_ADDR_FORM_INTERNAL, which converts keys
-     * to external form.
-     */
-    if ((result = mail_addr_find_opt(path, address, extp,
-                                    MAIL_ADDR_FORM_INTERNAL,
-                                    MAIL_ADDR_FORM_NOCONV,
-                                    strategy)) != 0
-       || path->error != 0)
-       return (result);
-
-    /*
-     * The past: look up with MAIL_ADDR_FORM_NOCONV, which leaves keys in
-     * internal form.
-     */
-    if (ext_addr_buf == 0)
-       ext_addr_buf = vstring_alloc(100);
-    quote_822_local(ext_addr_buf, address);
-    if (strcmp(STR(ext_addr_buf), address) != 0)
-       result = mail_addr_find_opt(path, address, extp,
-                                   MAIL_ADDR_FORM_NOCONV,
-                                   MAIL_ADDR_FORM_NOCONV,
-                                   strategy);
-    return (result);
-}
-
 #ifdef TEST
 
  /*
@@ -455,23 +466,25 @@ const char *mail_addr_find_strategy(MAPS *path, const char *address,
 
 static NORETURN usage(const char *progname)
 {
-    msg_fatal("usage: %s [-v] database", progname);
+    msg_fatal("usage: %s [-v]", progname);
 }
 
 int     main(int argc, char **argv)
 {
     VSTRING *buffer = vstring_alloc(100);
     char   *bp;
-    MAPS   *path;
+    MAPS   *path = 0;
     const char *result;
     char   *extent;
     char   *in_field;
+    char   *query_field;
     char   *out_field;
     char   *strategy_field;
     char   *key_field;
     char   *expect_res;
     char   *expect_ext;
     int     in_form;
+    int     query_form;
     int     out_form;
     int     strategy_flags;
     int     ch;
@@ -489,7 +502,7 @@ int     main(int argc, char **argv)
            usage(argv[0]);
        }
     }
-    if (argc != optind + 1)
+    if (argc != optind)
        usage(argv[0]);
 
     /*
@@ -502,26 +515,38 @@ int     main(int argc, char **argv)
     UPDATE(var_mydomain, "localdomain");
     UPDATE(var_myorigin, "localdomain");
     UPDATE(var_mydest, "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 (msg_verbose)
+           msg_info("> %s", bp);
+
+       /*
+        * Quick and dirty.
+        */
+       if (path == 0) {
+           path = maps_create(argv[0], bp, DICT_FLAG_LOCK
+                            | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+           vstream_printf("%s\n", bp);
+           continue;
+       }
 
        /*
         * Parse the input and expectations.
         */
-       /* internal, external, noconv, or compat. */
+       /* internal, external. */
        if ((in_field = mystrtok(&bp, ":")) == 0)
            msg_fatal("no input form");
-       if ((in_form = mail_addr_form_from_string(in_field)) < 0
-           && strcmp(in_field, "compat") != 0)
+       if ((in_form = mail_addr_form_from_string(in_field)) < 0)
            msg_fatal("bad input form: '%s'", in_field);
+       if ((query_field = mystrtok(&bp, ":")) == 0)
+           msg_fatal("no query form");
+       /* internal, external, external-first. */
+       if ((query_form = mail_addr_form_from_string(query_field)) < 0)
+           msg_fatal("bad query form: '%s'", query_field);
        if ((out_field = mystrtok(&bp, ":")) == 0)
            msg_fatal("no output form");
-       /* internal, external, noconv, or compat, depending on in_form. */
-       if (((out_form = mail_addr_form_from_string(out_field)) < 0
-            && strcmp(out_field, "compat") != 0)
-           || ((in_form >= 0) != (out_form >= 0)))
+       /* internal, external. */
+       if ((out_form = mail_addr_form_from_string(out_field)) < 0)
            msg_fatal("bad output form: '%s'", out_field);
        if ((strategy_field = mystrtok(&bp, ":")) == 0)
            msg_fatal("no strategy field");
@@ -538,18 +563,11 @@ int     main(int argc, char **argv)
         * Lookups.
         */
        extent = 0;
-       if (in_form >= 0 && out_form >= 0) {
-           /* It's the future. */
-           result = mail_addr_find_opt(path, key_field, &extent,
-                                       in_form, out_form,
-                                       strategy_flags);
-       } else {
-           /* Backwards compatibility. */
-           result = mail_addr_find_strategy(path, key_field, &extent,
-                                            strategy_flags);
-       }
-       vstream_printf("%s:%s -> %s:%s (%s)\n",
-                      in_field, key_field, out_field, result ? result :
+       result = mail_addr_find_opt(path, key_field, &extent,
+                                   in_form, query_form, out_form,
+                                   strategy_flags);
+       vstream_printf("%s:%s -%s-> %s:%s (%s)\n",
+             in_field, key_field, query_field, out_field, result ? result :
                       path->error ? "(try again)" :
                       "(not found)", extent ? extent : "null extension");
        vstream_fflush(VSTREAM_OUT);
index 9c6546d9f7186ab996ea795792c24e869949843d..9afccf59403b378d4f827ece90abd6d707bae3c6 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _MAIL_ADDR_FIND_H_INCLUDED_
-#define _MAIL_ADDR_FIND_H_INCLUDED_
+#ifndef _MAF_STRATEGY_H_INCLUDED_
+#define _MAF_STRATEGY_H_INCLUDED_
 
 /*++
 /* NAME
  /*
   * External interface.
   */
-extern const char *mail_addr_find_opt(MAPS *, const char *, char **, 
-       int, int, int);
+extern const char *mail_addr_find_opt(MAPS *, const char *, char **,
+                                             int, int, int, int);
 
-#define MAIL_ADDR_FIND_FULL    (1<<0)  /* localpart+ext@domain */
-#define MAIL_ADDR_FIND_NOEXT   (1<<1)  /* localpart@domain */
-#define MAIL_ADDR_FIND_LOCALPART_IF_LOCAL \
+#define MAF_STRATEGY_FULL      (1<<0)  /* localpart+ext@domain */
+#define MAF_STRATEGY_NOEXT     (1<<1)  /* localpart@domain */
+#define MAF_STRATEGY_LOCALPART_IF_LOCAL \
                                (1<<2)  /* localpart (maybe localpart+ext) */
-#define MAIL_ADDR_FIND_LOCALPART_AT_IF_LOCAL \
+#define MAF_STRATEGY_LOCALPART_AT_IF_LOCAL \
                                (1<<3)  /* ditto, with @ at end */
-#define MAIL_ADDR_FIND_ATDOMAIN        (1<<4)  /* @domain */
-#define MAIL_ADDR_FIND_DOMAIN  (1<<5)  /* domain */
-#define MAIL_ADDR_FIND_PMS     (1<<6)  /* parent matches subdomain */
-#define MAIL_ADDR_FIND_PMDS    (1<<7)  /* parent matches dot-subdomain */
-#define MAIL_ADDR_FIND_LOCALPART_AT    \
+#define MAF_STRATEGY_AT_DOMAIN (1<<4)  /* @domain */
+#define MAF_STRATEGY_DOMAIN    (1<<5)  /* domain */
+#define MAF_STRATEGY_PMS       (1<<6)  /* parent matches subdomain */
+#define MAF_STRATEGY_PMDS      (1<<7)  /* parent matches dot-subdomain */
+#define MAF_STRATEGY_LOCALPART_AT      \
                                (1<<8)  /* localpart@ (maybe localpart+ext@) */
 
-#define MAIL_ADDR_FIND_DEFAULT  (MAIL_ADDR_FIND_FULL | MAIL_ADDR_FIND_NOEXT \
-                               | MAIL_ADDR_FIND_LOCALPART_IF_LOCAL \
-                               | MAIL_ADDR_FIND_ATDOMAIN)
+#define MAF_STRATEGY_DEFAULT   (MAF_STRATEGY_FULL | MAF_STRATEGY_NOEXT \
+                               | MAF_STRATEGY_LOCALPART_IF_LOCAL \
+                               | MAF_STRATEGY_AT_DOMAIN)
 
  /* The least-overhead form. */
 #define mail_addr_find_int_to_ext(maps, address, extension) \
        mail_addr_find_opt((maps), (address), (extension), \
            MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_EXTERNAL, \
-       MAIL_ADDR_FIND_DEFAULT)
+           MAIL_ADDR_FORM_EXTERNAL, MAF_STRATEGY_DEFAULT)
 
- /* The legacy form. */
-extern const char *mail_addr_find_strategy(MAPS *, const char *, char **, int);
+ /* The legacy forms. */
+#define MAF_FORM_LEGACY \
+       MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_EXTERNAL_FIRST, \
+           MAIL_ADDR_FORM_EXTERNAL
+
+#define mail_addr_find_strategy(maps, address, extension, strategy) \
+       mail_addr_find_opt((maps), (address), (extension), \
+           MAF_FORM_LEGACY, (strategy))
 
 #define mail_addr_find(maps, address, extension) \
        mail_addr_find_strategy((maps), (address), (extension), \
-           MAIL_ADDR_FIND_DEFAULT)
+           MAF_STRATEGY_DEFAULT)
+
+#define mail_addr_find_to_internal(maps, address, extension) \
+       mail_addr_find_opt((maps), (address), (extension), \
+           MAF_FORM_LEGACY, MAF_STRATEGY_DEFAULT)
 
 /* LICENSE
 /* .ad
index 36c1cfc5d6927f9eeb86039e7a0c829db19b8b9a..b8278594ffe22a319bca81576042aa039634ad6a 100644 (file)
@@ -4,78 +4,94 @@
 # 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:default:plain1@1.example:plain2@2.example
-internal:external:default:aa bb@cc.example:"dd ee"@dd.example
-external:external:default:"aa bb"@cc.example:"dd ee"@dd.example
-external:internal:default:"aa bb"@cc.example:dd ee@dd.example
-noconv:noconv:default:plain1@1.example:plain2@2.example
-noconv:noconv:default:aa bb@cc.example
-noconv:noconv:default:"aa bb"@cc.example:"dd ee"@dd.example
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:external:external:default:plain1@1.example:plain2@2.example
+internal:external:external:default:aa bb@cc.example:"dd ee"@dd.example
+external:external:external:default:"aa bb"@cc.example:"dd ee"@dd.example
+external:external:internal:default:"aa bb"@cc.example:dd ee@dd.example
+internal:internal:external:default:plain1@1.example:plain2@2.example
+internal:internal:external:default:aa bb@cc.example
+internal:internal:external:default:"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:default:plain1+ext@1.example:plain2@2.example:+ext
-internal:external:default:aa bb+ax bx@cc.example:"dd ee"@dd.example:+ax bx
-external:external:default:"aa bb+ax bx"@cc.example:"dd ee"@dd.example:+ax bx
-external:internal:default:"aa bb+ax bx"@cc.example:dd ee@dd.example:+ax bx
-noconv:noconv:default:plain1+ext@1.example:plain2@2.example:+ext
-noconv:noconv:default:"aa bb+ax bx"@cc.example
-noconv:noconv:default:"aa bb"+ax bx@cc.example:"dd ee"@dd.example:+ax bx
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:external:external:default:plain1+ext@1.example:plain2@2.example:+ext
+internal:external:external:default:aa bb+ax bx@cc.example:"dd ee"@dd.example:+ax bx
+external:external:external:default:"aa bb+ax bx"@cc.example:"dd ee"@dd.example:+ax bx
+external:external:internal:default:"aa bb+ax bx"@cc.example:dd ee@dd.example:+ax bx
+internal:internal:external:default:plain1+ext@1.example:plain2@2.example:+ext
+internal:internal:external:default:"aa bb+ax bx"@cc.example
+internal:internal:external:default:"aa bb"+ax bx@cc.example:"dd ee"@dd.example:+ax bx
 EOF
 
 echo ==== at in localpart
-$VALGRIND ./mail_addr_find 'inline:{"a@b"=foo@example,"a.b."=bar@example}'  <<'EOF'
-external:external:default:"a@b"@localhost.localdomain:foo@example
-external:external:default:"a@b+ext"@localhost.localdomain:foo@example:+ext
-external:external:default:"a.b."@localhost.localdomain:bar@example
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{"a@b"=foo@example,"a.b."=bar@example}
+external:external:external:default:"a@b"@localhost.localdomain:foo@example
+external:external:external:default:"a@b+ext"@localhost.localdomain:foo@example:+ext
+external:external:external:default:"a.b."@localhost.localdomain:bar@example
 EOF
 
 echo ==== legacy support
-$VALGRIND ./mail_addr_find 'inline:{"a@b"=extern-1@example,a@b=intern-1@example,a.b.=intern-2@example}'  <<'EOF'
-compat:compat:default:a@b@localhost.localdomain:extern-1@example
-compat:compat:default:a.b.@localhost.localdomain:intern-2@example
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{"a@b"=extern-1@example,a@b=intern-1@example,a.b.=intern-2@example}
+internal:external-first:external:default:a@b@localhost.localdomain:extern-1@example
+internal:external-first:external:default:a.b.@localhost.localdomain:intern-2@example
 EOF
 
-echo ==== atdomain test
-$VALGRIND ./mail_addr_find 'inline:{plain1@1.example=plain2@2.example,@3.example=plain4@4.example,plain5@3.example=plain6@6.example}'  <<'EOF'
-external:external:default:plain1+ext@1.example:plain2@2.example:+ext
-external:external:default:plain2@2.example:
-external:external:default:plain3@3.example:plain4@4.example
-external:external:default:plain5@3.example:plain6@6.example
+echo ==== at_domain test
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{plain1@1.example=plain2@2.example,@3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:external:external:default:plain1+ext@1.example:plain2@2.example:+ext
+external:external:external:default:plain2@2.example:
+external:external:external:default:plain3@3.example:plain4@4.example
+external:external:external:default:plain5@3.example:plain6@6.example
 EOF
 
 echo ==== domain test
-$VALGRIND ./mail_addr_find 'inline:{plain1@1.example=plain2@2.example,3.example=plain4@4.example,plain5@3.example=plain6@6.example}'  <<'EOF'
-external:external:full|noext|domain:plain1+ext@1.example:plain2@2.example:+ext
-external:external:full|noext|domain:plain2@2.example:
-external:external:full|noext|domain:plain3@3.example:plain4@4.example
-external:external:full|noext|domain:plain5@3.example:plain6@6.example
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{plain1@1.example=plain2@2.example,3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:external:external:full|noext|domain:plain1+ext@1.example:plain2@2.example:+ext
+external:external:external:full|noext|domain:plain2@2.example:
+external:external:external:full|noext|domain:plain3@3.example:plain4@4.example
+external:external:external:full|noext|domain:plain5@3.example:plain6@6.example
 EOF
 
-echo ==== atdomain for local domain
-$VALGRIND ./mail_addr_find 'inline:{ab=foo@example,@localhost.localdomain=@bar.example}' <<'EOF'
-external:external:default:ab@localhost.localdomain:foo@example:
-external:external:default:cd@localhost.localdomain:@bar.example
+echo ==== at_domain for local domain
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{ab=foo@example,@localhost.localdomain=@bar.example}
+external:external:external:default:ab@localhost.localdomain:foo@example:
+external:external:external:default:cd@localhost.localdomain:@bar.example
 EOF
 
-echo ==== localat and domain test
-$VALGRIND ./mail_addr_find 'inline:{ab@=foo@example,localhost.localdomain=@bar.example}' <<'EOF'
-internal:external:localpart_at_if_local|domain:ab@localhost.localdomain:foo@example:
-internal:external:localpart_at_if_local|noext|domain:ab+ext@localhost.localdomain:foo@example:+ext
-internal:external:localpart_at_if_local|domain:cd@localhost.localdomain:@bar.example
+echo ==== localpart_at_if_local and domain test
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+internal:external:external:localpart_at_if_local|domain:ab@localhost.localdomain:foo@example:
+internal:external:external:localpart_at_if_local|noext|domain:ab+ext@localhost.localdomain:foo@example:+ext
+internal:external:external:localpart_at_if_local|domain:cd@localhost.localdomain:@bar.example
+EOF
+
+echo ==== localpart_at has less precedence than domain test
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+external:external:external:localpart_at|domain:ab@localhost.localdomain:@bar.example:
+external:external:external:localpart_at|domain:ab@foo:foo@example
 EOF
 
 echo ==== domain and subdomain test
-$VALGRIND ./mail_addr_find 'inline:{example=example-result,.example=dot-example-result}'  <<'EOF'
-external:external:domain:plain1+ext@1.example
-external:external:domain:foo@sub.example
-external:external:domain:foo@example:example-result
-external:external:domain|pms:foo@example:example-result
-external:external:domain|pms:foo@sub.example:example-result
-external:external:domain|pms:foo@sub.sub.example:example-result
-external:external:domain|pmds:foo@example:example-result
-external:external:domain|pmds:foo@sub.example:dot-example-result
-external:external:domain|pmds:foo@sub.sub.example:dot-example-result
+$VALGRIND ./mail_addr_find <<'EOF'
+inline:{example=example-result,.example=dot-example-result}
+external:external:external:domain:plain1+ext@1.example
+external:external:external:domain:foo@sub.example
+external:external:external:domain:foo@example:example-result
+external:external:external:domain|pms:foo@example:example-result
+external:external:external:domain|pms:foo@sub.example:example-result
+external:external:external:domain|pms:foo@sub.sub.example:example-result
+external:external:external:domain|pmds:foo@example:example-result
+external:external:external:domain|pmds:foo@sub.example:dot-example-result
+external:external:external:domain|pmds:foo@sub.sub.example:dot-example-result
 EOF
index 102b807d2a7e02ad442c81e7d47c0aba7c4201e1..6833eecc5d7f179ebbae2906d53a88429fef5dbd 100644 (file)
@@ -1,50 +1,63 @@
 ==== 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)
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:plain1@1.example -external-> external:plain2@2.example (null extension)
+internal:aa bb@cc.example -external-> external:"dd ee"@dd.example (null extension)
+external:"aa bb"@cc.example -external-> external:"dd ee"@dd.example (null extension)
+external:"aa bb"@cc.example -external-> internal:dd ee@dd.example (null extension)
+internal:plain1@1.example -internal-> external:plain2@2.example (null extension)
+internal:aa bb@cc.example -internal-> external:(not found) (null extension)
+internal:"aa bb"@cc.example -internal-> external:"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)
+inline:{plain1@1.example=plain2@2.example,{"aa bb"@cc.example="dd ee"@dd.example}}
+internal:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+internal:aa bb+ax bx@cc.example -external-> external:"dd ee"@dd.example (+ax bx)
+external:"aa bb+ax bx"@cc.example -external-> external:"dd ee"@dd.example (+ax bx)
+external:"aa bb+ax bx"@cc.example -external-> internal:dd ee@dd.example (+ax bx)
+internal:plain1+ext@1.example -internal-> external:plain2@2.example (+ext)
+internal:"aa bb+ax bx"@cc.example -internal-> external:(not found) (null extension)
+internal:"aa bb"+ax bx@cc.example -internal-> external:"dd ee"@dd.example (+ax bx)
 ==== at in localpart
-external:"a@b"@localhost.localdomain -> external:foo@example (null extension)
-external:"a@b+ext"@localhost.localdomain -> external:foo@example (+ext)
-external:"a.b."@localhost.localdomain -> external:bar@example (null extension)
+inline:{"a@b"=foo@example,"a.b."=bar@example}
+external:"a@b"@localhost.localdomain -external-> external:foo@example (null extension)
+external:"a@b+ext"@localhost.localdomain -external-> external:foo@example (+ext)
+external:"a.b."@localhost.localdomain -external-> external:bar@example (null extension)
 ==== legacy support
-compat:a@b@localhost.localdomain -> compat:extern-1@example (null extension)
-compat:a.b.@localhost.localdomain -> compat:intern-2@example (null extension)
-==== atdomain test
-external:plain1+ext@1.example -> external:plain2@2.example (+ext)
-external:plain2@2.example -> external:(not found) (null extension)
-external:plain3@3.example -> external:plain4@4.example (null extension)
-external:plain5@3.example -> external:plain6@6.example (null extension)
+inline:{"a@b"=extern-1@example,a@b=intern-1@example,a.b.=intern-2@example}
+internal:a@b@localhost.localdomain -external-first-> external:extern-1@example (null extension)
+internal:a.b.@localhost.localdomain -external-first-> external:intern-2@example (null extension)
+==== at_domain test
+inline:{plain1@1.example=plain2@2.example,@3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+external:plain2@2.example -external-> external:(not found) (null extension)
+external:plain3@3.example -external-> external:plain4@4.example (null extension)
+external:plain5@3.example -external-> external:plain6@6.example (null extension)
 ==== domain test
-external:plain1+ext@1.example -> external:plain2@2.example (+ext)
-external:plain2@2.example -> external:(not found) (null extension)
-external:plain3@3.example -> external:plain4@4.example (null extension)
-external:plain5@3.example -> external:plain6@6.example (null extension)
-==== atdomain for local domain
-external:ab@localhost.localdomain -> external:foo@example (null extension)
-external:cd@localhost.localdomain -> external:@bar.example (null extension)
-==== localat and domain test
-internal:ab@localhost.localdomain -> external:foo@example (null extension)
-internal:ab+ext@localhost.localdomain -> external:foo@example (+ext)
-internal:cd@localhost.localdomain -> external:@bar.example (null extension)
+inline:{plain1@1.example=plain2@2.example,3.example=plain4@4.example,plain5@3.example=plain6@6.example}
+external:plain1+ext@1.example -external-> external:plain2@2.example (+ext)
+external:plain2@2.example -external-> external:(not found) (null extension)
+external:plain3@3.example -external-> external:plain4@4.example (null extension)
+external:plain5@3.example -external-> external:plain6@6.example (null extension)
+==== at_domain for local domain
+inline:{ab=foo@example,@localhost.localdomain=@bar.example}
+external:ab@localhost.localdomain -external-> external:foo@example (null extension)
+external:cd@localhost.localdomain -external-> external:@bar.example (null extension)
+==== localpart_at_if_local and domain test
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+internal:ab@localhost.localdomain -external-> external:foo@example (null extension)
+internal:ab+ext@localhost.localdomain -external-> external:foo@example (+ext)
+internal:cd@localhost.localdomain -external-> external:@bar.example (null extension)
+==== localpart_at has less precedence than domain test
+inline:{ab@=foo@example,localhost.localdomain=@bar.example}
+external:ab@localhost.localdomain -external-> external:@bar.example (null extension)
+external:ab@foo -external-> external:foo@example (null extension)
 ==== domain and subdomain test
-external:plain1+ext@1.example -> external:(not found) (null extension)
-external:foo@sub.example -> external:(not found) (null extension)
-external:foo@example -> external:example-result (null extension)
-external:foo@example -> external:example-result (null extension)
-external:foo@sub.example -> external:example-result (null extension)
-external:foo@sub.sub.example -> external:example-result (null extension)
-external:foo@example -> external:example-result (null extension)
-external:foo@sub.example -> external:dot-example-result (null extension)
-external:foo@sub.sub.example -> external:dot-example-result (null extension)
+inline:{example=example-result,.example=dot-example-result}
+external:plain1+ext@1.example -external-> external:(not found) (null extension)
+external:foo@sub.example -external-> external:(not found) (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@sub.example -external-> external:example-result (null extension)
+external:foo@sub.sub.example -external-> external:example-result (null extension)
+external:foo@example -external-> external:example-result (null extension)
+external:foo@sub.example -external-> external:dot-example-result (null extension)
+external:foo@sub.sub.example -external-> external:dot-example-result (null extension)
index 8a1e94261979dcd4df6badb5914f0aee98d69ec1..ba81ecdef089333dca87db4fe654772c431308d9 100644 (file)
@@ -11,9 +11,9 @@
 /*     const char *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.
+/*     form name ("internal", "external", "internal-first") 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
@@ -47,7 +47,7 @@
 static const NAME_CODE addr_form_table[] = {
     "external", MAIL_ADDR_FORM_EXTERNAL,
     "internal", MAIL_ADDR_FORM_INTERNAL,
-    "noconv", MAIL_ADDR_FORM_NOCONV,
+    "external-first", MAIL_ADDR_FORM_EXTERNAL_FIRST,
     0, -1,
 };
 
index f263ac5119e6402b84081693eca1e89f212fa9f2..a547e7877b2a96a88ff9e6fdee352f67634be591 100644 (file)
 /* .nf
 
  /*
-  * External interface. The MAIL_ADDR_FORM_NOCONV is for legacy code that
-  * hasn't yet been converted to external-form address lookups.
+  * External interface.
   */
-#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 */
+#define MAIL_ADDR_FORM_EXTERNAL_FIRST 3        /* quoted form, then unquoted */
 
 extern int mail_addr_form_from_string(const char *);
 extern const char *mail_addr_form_to_string(int);
index 40b4b9bdc77efe09316061bf6368b3570a3455d3..7caa92466245c470a5363cc154f71d8f4e43d63d 100644 (file)
 /*     const char *address;
 /*     int     propagate;
 /*
-/*     ARGV    *mail_addr_map_opt(path, address, propagate, in_form, out_form)
+/*     ARGV    *mail_addr_map_opt(path, address, propagate, in_form,
+/*                                     query_form, out_form)
 /*     MAPS    *path;
 /*     const char *address;
 /*     int     propagate;
-/*     int     how;
+/*     int     in_form;
+/*     int     query_form;
+/*     int     out_form;
 /* DESCRIPTION
 /*     mail_addr_map_*() returns the translation for the named address,
 /*     or a null pointer if none is found.
 /*     form \fI@otherdomain\fR, the result is the original user in
 /*     \fIotherdomain\fR.
 /*
-/*     mail_addr_map_opt() 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 in external (quoted) form, or
 /*     in the form specified with the in_form argument.
+/* .IP query_form
+/*     Database query address forms, either MAIL_ADDR_FORM_INTERNAL
+/*     (unquoted form), MAIL_ADDR_FORM_EXTERNAL_FIRST (external, then
+/*     internal if the forms differ) or MAIL_ADDR_FORM_EXTERNAL
+/*     (quoted form).
 /* .IP in_form
 /* .IP out_form
 /*     Input and output address forms, either MAIL_ADDR_FORM_INTERNAL
 /* mail_addr_map - map a canonical address */
 
 ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
-                                 int in_form, int out_form)
+                                 int in_form, int query_form, int out_form)
 {
     VSTRING *buffer = 0;
     const char *myname = "mail_addr_map";
@@ -111,16 +113,9 @@ ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
     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_opt(). But the consequence is that we have to convert
-     * the internal-form input's localpart to external form when mapping
-     * @domain -> @domain.
+     * input to avoid unnecessary input conversion in mail_addr_find_opt().
      */
     if (in_form == MAIL_ADDR_FORM_EXTERNAL) {
        int_address = vstring_alloc(100);
@@ -134,16 +129,16 @@ ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
     /*
      * 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_opt() variant that does not
-     * convert the lookup result.
      */
     if ((string = mail_addr_find_opt(path, int_addr, &extension,
-                                    in_form, mid_form,
-                                    MAIL_ADDR_FIND_DEFAULT)) != 0) {
+                                    in_form, query_form,
+                                    MAIL_ADDR_FORM_EXTERNAL,
+                                    MAF_STRATEGY_DEFAULT)) != 0) {
 
        /*
         * Prepend the original user to @otherdomain, but do not propagate
-        * the unmatched address extension.
+        * the unmatched address extension. Convert the address to external
+        * form just like the mail_addr_find_opt() output.
         */
        if (*string == '@') {
            buffer = vstring_alloc(100);
@@ -153,9 +148,9 @@ ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
                vstring_strcpy(buffer, int_addr);
            if (extension)
                vstring_truncate(buffer, LEN(buffer) - strlen(extension));
-           ext_address = vstring_alloc(100);
+           vstring_strcat(buffer, string);
+           ext_address = vstring_alloc(2 * LEN(buffer));
            quote_822_local(ext_address, STR(buffer));
-           vstring_strcat(ext_address, string);
            string = STR(ext_address);
        }
 
@@ -164,7 +159,7 @@ ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
         * each address found.
         */
        argv = mail_addr_crunch_opt(string, propagate ? extension : 0,
-                                   mid_form, out_form);
+                                   MAIL_ADDR_FORM_EXTERNAL, out_form);
        if (buffer)
            vstring_free(buffer);
        if (ext_address)
@@ -199,3 +194,334 @@ ARGV   *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
 
     return (argv);
 }
+
+#ifdef TEST
+
+/*
+ * 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'.
+ */
+
+/* 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     query_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_opt() works
+  * as intended. mail_addr_map() has always been used for maps that expect
+  * external-form queries, so there are no tests here for internal-form
+  * queries.
+  */
+static MAIL_ADDR_MAP_TEST pass_tests[] = {
+    {
+       "1 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "aa@example.com",
+       {"bb@example.com"}, 1,
+    },
+    {
+       "2 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "aa+ext@example.com",
+       {"bb+ext@example.com"}, 1,
+    },
+    {
+       "3 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "aa+ext@example.com",
+       {0}, 0,
+    },
+    {
+       "4 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "cc+ext@example.com",
+       {"dd@example.com", "ee@example.com"}, 2,
+    },
+    {
+       "5 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "\"a a\"@example.com",
+       {"\"b b\"@example.com"}, 1,
+    },
+    {
+       "6 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "\"a a+ext\"@example.com",
+       {"\"b b+ext\"@example.com"}, 1,
+    },
+    {
+       "7 internal -external-> 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_EXTERNAL,
+       MAIL_ADDR_FORM_INTERNAL,
+       "a a@example.com",
+       {"b b@example.com"}, 1,
+    },
+    {
+       "8 internal -external-> 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_EXTERNAL,
+       MAIL_ADDR_FORM_INTERNAL,
+       "a a+ext@example.com",
+       {"b b+ext@example.com"}, 1,
+    },
+    {
+       "9 internal -external-> 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_EXTERNAL,
+       MAIL_ADDR_FORM_INTERNAL,
+       "a_a@example.com",
+       {"b b@example.com"}, 1,
+    },
+    {
+       "10 internal -external-> 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_EXTERNAL,
+       MAIL_ADDR_FORM_INTERNAL,
+       "a_a+ext@example.com",
+       {"b b+ext@example.com"}, 1,
+    },
+    {
+       "11 internal -external-> internal, no extension, @domain",
+       "inline:{ {@example.com=@example.net} }",
+       DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
+       MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_EXTERNAL,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "a@a@example.com",
+       {"\"a@a\"@example.net"}, 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 -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "\"a a\"@example.com",
+       {"\"bXb\"@example.com"}, 1,
+    },
+    {
+       "selftest 2 external -external-> 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,
+       MAIL_ADDR_FORM_EXTERNAL,
+       "\"aXa\"@example.com",
+       {"\"b b\"@example.com"}, 1,
+    },
+    {
+       "selftest 3 external -external-> 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,
+       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->query_form) == 0) {
+           msg_warn("test %s: bad query_form field: %d",
+                    test->testname, test->query_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_opt(maps, test->address, test->propagate,
+                          test->in_form, test->query_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("query_form = %s", mail_addr_form_to_string(test->query_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);
+}
+
+#endif
index 945417b86c1b499d795e5e10eaf62651845aac56..46bbebf978b711228c2826404aea523679f4dfa0 100644 (file)
  /*
   * External interface.
   */
-extern ARGV *mail_addr_map_opt(MAPS *, const char *, int, int, int);
+extern ARGV *mail_addr_map_opt(MAPS *, const char *, int, int, int, int);
 
  /* The least-overhead form. */
 #define mail_addr_map_internal(path, address, propagate) \
        mail_addr_map_opt((path), (address), (propagate), \
-                 MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_INTERNAL)
+                 MAIL_ADDR_FORM_INTERNAL, MAIL_ADDR_FORM_EXTERNAL, \
+                 MAIL_ADDR_FORM_INTERNAL)
 
 /* LICENSE
 /* .ad
index c4b3f053578cdc9819297857fb969507cea5eeda..28647b136ece4383f1ebf546aff92b3577016393 100644 (file)
@@ -1,23 +1,26 @@
-unknown: warning: fail test selftest 1 external to external, no extension, quoted: expect[0]='"bXb"@example.com', result[0]='"b b"@example.com'
+unknown: warning: fail test selftest 1 external -external-> 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: query_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: warning: fail test selftest 2 external -external-> 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: query_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: warning: fail test selftest 3 external -external-> 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: query_form = external
 unknown: out_form = external
 unknown: address = "a a"@example.com
diff --git a/postfix/src/global/mail_addr_map_tester.c b/postfix/src/global/mail_addr_map_tester.c
deleted file mode 100644 (file)
index aae1a3b..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-/*++
-/* 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_opt() 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_opt(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);
-}
index 27b9895a99482e9198e870334ae0f468ca7f999e..6b81b85275f99b4d30bfdb5b345035aa753ea067 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20170117"
+#define MAIL_RELEASE_DATE      "20170122"
 #define MAIL_VERSION_NUMBER    "3.2"
 
 #ifdef SNAPSHOT
index 453531acbc464073158b20fcddc78c554f69d393..2cb433365c994c4d8d4b857081f282158f582af4 100644 (file)
@@ -48,7 +48,7 @@
 /*     When the \fIkey\fR specifies email address information, the
 /*     localpart should be enclosed with double quotes if required
 /*     by RFC 5322. For example, an address localpart that contains
-/*     ';' or that ends on '.'.
+/*     ";", or a localpart that starts or ends with ".".
 /*
 /*     By default the lookup key is mapped to lowercase to make
 /*     the lookups case insensitive; as of Postfix 2.3 this case
@@ -64,7 +64,7 @@
 /* .IP \fB-b\fR
 /*     Enable message body query mode. When reading lookup keys
 /*     from standard input with "\fB-q -\fR", process the input
-/*     as if it is an email message in RFC 2822 format.  Each line
+/*     as if it is an email message in RFC 5322 format.  Each line
 /*     of body content becomes one lookup key.
 /* .sp
 /*     By default, the \fB-b\fR option starts generating lookup
 /* .IP \fB-h\fR
 /*     Enable message header query mode. When reading lookup keys
 /*     from standard input with "\fB-q -\fR", process the input
-/*     as if it is an email message in RFC 2822 format.  Each
+/*     as if it is an email message in RFC 5322 format.  Each
 /*     logical header line becomes one lookup key. A multi-line
 /*     header becomes one lookup key with one or more embedded
 /*     newline characters.
index 71739b61e0bc662cd5e1393e708bb3f93aa1d1de..d2088c30bccf5bcfd78ef8e5addbcab7438a7c2e 100644 (file)
@@ -81,7 +81,9 @@ int     smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate)
     const char *result;
 
     if ((new_addr = mail_addr_map_opt(maps, STR(addr), propagate,
-                 MAIL_ADDR_FORM_EXTERNAL, MAIL_ADDR_FORM_EXTERNAL)) != 0) {
+                                     MAIL_ADDR_FORM_EXTERNAL,
+                                     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];
index 20bb96feb636d0205f834a9b194d74647a1d442a..f9811960be3fdfdafdde53e093b071af18376b56 100644 (file)
@@ -114,6 +114,8 @@ smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref
 
 # This requires that the DNS server can query porcupine.org.
 
+ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/'
+
 smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
        $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
        $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_exp.in >smtpd_exp.tmp 2>&1
@@ -122,13 +124,13 @@ smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
 
 smtpd_server_test: smtpd_check smtpd_server.in smtpd_server.ref
        $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_server.in >smtpd_server.tmp 2>&1
-       diff smtpd_server.ref smtpd_server.tmp
+       $(ADDRINFO_FIX) smtpd_server.tmp | diff smtpd_server.ref -
        rm -f smtpd_server.tmp smtpd_check_access.*
 
 smtpd_nullmx_test: smtpd_check smtpd_nullmx.in smtpd_nullmx.ref
        $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
        $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_nullmx.in >smtpd_nullmx.tmp 2>&1
-       diff smtpd_nullmx.ref smtpd_nullmx.tmp
+       $(ADDRINFO_FIX) smtpd_nullmx.tmp | diff smtpd_nullmx.ref -
        rm -f smtpd_nullmx.tmp smtpd_check_access.*
 
 smtpd_dns_filter_test: smtpd_check smtpd_dns_filter.in smtpd_dns_filter.ref \
index c627d4fe10a8a0a7600ac28c113d6bdb053234e5..9924afa7e2227051c52d8cfe89a76c99bb2e5d02 100644 (file)
@@ -653,6 +653,8 @@ static ARGV *smtpd_check_parse(int flags, const char *checks)
     return (argv);
 }
 
+#ifndef TEST
+
 /* has_required - make sure required restriction is present */
 
 static int has_required(ARGV *restrictions, const char **required)
@@ -711,6 +713,8 @@ static void fail_required(const char *name, const char **required)
              name, STR(example));
 }
 
+#endif
+
 /* smtpd_check_init - initialize once during process lifetime */
 
 void    smtpd_check_init(void)
@@ -719,6 +723,7 @@ void    smtpd_check_init(void)
     const char *name;
     const char *value;
     char   *cp;
+#ifndef TEST
     static const char *rcpt_required[] = {
        REJECT_UNAUTH_DEST,
        DEFER_UNAUTH_DEST,
@@ -728,6 +733,7 @@ void    smtpd_check_init(void)
        CHECK_RELAY_DOMAINS,
        0,
     };
+#endif
     static NAME_CODE tempfail_actions[] = {
        DEFER_ALL, DEFER_ALL_ACT,
        DEFER_IF_PERMIT, DEFER_IF_PERMIT_ACT,
@@ -3205,9 +3211,9 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
      * Look up user+foo@domain if the address has an extension, user@domain
      * otherwise.
      */
-#define LOOKUP_STRATEGY (MAIL_ADDR_FIND_FULL | MAIL_ADDR_FIND_NOEXT \
-                        | MAIL_ADDR_FIND_DOMAIN | MAIL_ADDR_FIND_PMS \
-                        | MAIL_ADDR_FIND_LOCALPART_AT)
+#define LOOKUP_STRATEGY (MAF_STRATEGY_FULL | MAF_STRATEGY_NOEXT \
+                        | MAF_STRATEGY_DOMAIN | MAF_STRATEGY_PMS \
+                        | MAF_STRATEGY_LOCALPART_AT)
 
     if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) {
        msg_warn("%s: unexpected dictionary: %s", myname, table);
@@ -5893,7 +5899,6 @@ int     main(int argc, char **argv)
     char   *bp;
     char   *resp;
     char   *addr;
-    INET_PROTO_INFO *proto_info;
 
     /*
      * Initialization. Use dummies for client information.
@@ -5905,7 +5910,7 @@ int     main(int argc, char **argv)
     int_init();
     smtpd_check_init();
     smtpd_expand_init();
-    proto_info = inet_proto_init(argv[0], INET_PROTO_NAME_IPV4);
+    (void) inet_proto_init(argv[0], INET_PROTO_NAME_IPV4);
     smtpd_state_init(&state, VSTREAM_IN, "smtpd");
     state.queue_id = "<queue id>";
 
index d4d5dbbc561b370245a5a70e76e3aeab78118816..4fa9082f1902b69acf80b2936a38048bbc54c512 100644 (file)
@@ -261,9 +261,9 @@ int     transport_lookup(TRANSPORT_INFO *tp, const char *addr,
      * internal form.
      */
 #define LOOKUP_STRATEGY \
-       (MAIL_ADDR_FIND_FULL | MAIL_ADDR_FIND_NOEXT | MAIL_ADDR_FIND_DOMAIN | \
+       (MAF_STRATEGY_FULL | MAF_STRATEGY_NOEXT | MAF_STRATEGY_DOMAIN | \
        (transport_match_parent_style == MATCH_FLAG_PARENT ? \
-               MAIL_ADDR_FIND_PMS : MAIL_ADDR_FIND_PMDS))
+               MAF_STRATEGY_PMS : MAF_STRATEGY_PMDS))
 
     if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
        msg_panic("transport_lookup: bad address: \"%s\"", addr);
@@ -427,7 +427,7 @@ int     main(int argc, char **argv)
     vstring_free(nexthop);
     vstring_free(channel);
     vstring_free(buffer);
-    exit(0);
+    exit(errs != 0);
 }
 
 #endif