]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.2-20170108-nonprod
authorWietse Venema <wietse@porcupine.org>
Sun, 8 Jan 2017 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Mon, 9 Jan 2017 04:38:39 +0000 (23:38 -0500)
61 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/WISHLIST
postfix/html/postmap.1.html
postfix/makedefs
postfix/man/man1/postmap.1
postfix/src/cleanup/Makefile.in
postfix/src/cleanup/cleanup_addr.c
postfix/src/cleanup/cleanup_map11.c
postfix/src/cleanup/cleanup_map1n.c
postfix/src/global/Makefile.in
postfix/src/global/mail_addr_crunch.c
postfix/src/global/mail_addr_crunch.h
postfix/src/global/mail_addr_crunch.in [new file with mode: 0644]
postfix/src/global/mail_addr_crunch.ref [new file with mode: 0644]
postfix/src/global/mail_addr_find.c
postfix/src/global/mail_addr_find.h
postfix/src/global/mail_addr_find.in [new file with mode: 0644]
postfix/src/global/mail_addr_find.ref [new file with mode: 0644]
postfix/src/global/mail_addr_form.c [new file with mode: 0644]
postfix/src/global/mail_addr_form.h [new file with mode: 0644]
postfix/src/global/mail_addr_map.c
postfix/src/global/mail_addr_map.h
postfix/src/global/mail_addr_map.ref [new file with mode: 0644]
postfix/src/global/mail_addr_map_tester.c [new file with mode: 0644]
postfix/src/global/mail_version.h
postfix/src/global/quote_822_local.c
postfix/src/global/quote_822_local.in [new file with mode: 0644]
postfix/src/global/quote_822_local.ref [new file with mode: 0644]
postfix/src/global/split_addr.c
postfix/src/global/split_addr.h
postfix/src/global/strip_addr.c
postfix/src/global/strip_addr.h
postfix/src/global/strip_addr.ref
postfix/src/local/bounce_workaround.c
postfix/src/local/recipient.c
postfix/src/oqmgr/qmgr_message.c
postfix/src/pipe/pipe.c
postfix/src/postmap/Makefile.in
postfix/src/postmap/postmap.c
postfix/src/postmap/quote_test.in [new file with mode: 0644]
postfix/src/postmap/quote_test.ref [new file with mode: 0644]
postfix/src/qmgr/qmgr_message.c
postfix/src/smtp/Makefile.in
postfix/src/smtp/map11_map
postfix/src/smtp/smtp_map11.c
postfix/src/smtp/smtp_map11.ref
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_sasl_glue.c
postfix/src/smtpd/Makefile.in
postfix/src/smtpd/smtpd_check.c
postfix/src/trivial-rewrite/resolve.c
postfix/src/trivial-rewrite/transport.c
postfix/src/util/Makefile.in
postfix/src/util/dict_inline.c
postfix/src/util/dict_thash.c
postfix/src/util/dict_thash.map
postfix/src/util/split_qnameval.c [new file with mode: 0644]
postfix/src/util/stringops.h
postfix/src/util/vstream.c
postfix/src/virtual/mailbox.c

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