]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-1.1.10-20020524
authorWietse Venema <wietse@porcupine.org>
Fri, 24 May 2002 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:27:58 +0000 (06:27 +0000)
52 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/README_FILES/FILTER_README
postfix/RELEASE_NOTES
postfix/html/cleanup.8.html
postfix/html/postalias.1.html
postfix/html/postconf.1.html
postfix/html/postmap.1.html
postfix/html/smtp.8.html
postfix/man/man1/postalias.1
postfix/man/man1/postconf.1
postfix/man/man1/postmap.1
postfix/man/man8/cleanup.8
postfix/man/man8/smtp.8
postfix/src/cleanup/Makefile.in
postfix/src/cleanup/cleanup.c
postfix/src/cleanup/cleanup.h
postfix/src/cleanup/cleanup_init.c
postfix/src/cleanup/cleanup_message.c
postfix/src/cleanup/cleanup_out.c
postfix/src/cleanup/cleanup_state.c
postfix/src/global/Makefile.in
postfix/src/global/foobar [new file with mode: 0644]
postfix/src/global/header_opts.c
postfix/src/global/header_opts.h
postfix/src/global/header_token.c [new file with mode: 0644]
postfix/src/global/header_token.h [new file with mode: 0644]
postfix/src/global/is_header.c
postfix/src/global/is_header.c+ [new file with mode: 0644]
postfix/src/global/mail_params.c
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/global/mime_state.c [new file with mode: 0644]
postfix/src/global/mime_state.h [new file with mode: 0644]
postfix/src/global/mime_test.in [new file with mode: 0644]
postfix/src/global/mime_test.ref [new file with mode: 0644]
postfix/src/global/rec_streamlf.c
postfix/src/lmtp/lmtp_proto.c
postfix/src/nqmgr/qmgr_message.c
postfix/src/postalias/postalias.c
postfix/src/postconf/postconf.c
postfix/src/postmap/postmap.c
postfix/src/qmgr/qmgr_message.c
postfix/src/qmqpd/qmqpd.c
postfix/src/smtp/Makefile.in
postfix/src/smtp/smtp.c
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_chat.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_state.c
postfix/src/smtpd/smtpd.c
postfix/src/util/mymalloc.c

index 64dbb3f5e4a7794cdf1d97677bd82a3792f746e2..0f9bb2644d89302559676a45e7eb6573cf7eecb6 100644 (file)
@@ -51,6 +51,7 @@
 -TFILE
 -TFORWARD_INFO
 -THEADER_OPTS
+-THEADER_TOKEN
 -THOST
 -THTABLE
 -THTABLE_INFO
 -TMASTER_STATUS
 -TMBLOCK
 -TMBOX
+-TMIME_ENCODING
+-TMIME_INFO
+-TMIME_STACK
+-TMIME_STATE
+-TMIME_TOKEN
 -TMKMAP
 -TMKMAP_OPEN_INFO
 -TMULTI_SERVER
index 61f6744c4cc01fd1df1d4cac76f0eb98fc4dc9df..038af233cf9f1f83db63f38cbf1e97bd71e64241 100644 (file)
@@ -6447,8 +6447,59 @@ Apologies for any names omitted.
        with "resolve_dequoted_address = no"). Victor Duchovny,
        Morgan Stanley. File: smtpd/smtpd_check.c.
 
+20020515
+
+       Workaround: flush the SMTP client output buffer when no
+       output has happened for 10+ seconds. This prevents the
+       socket from timing out, in case DNS CNAME expansion is
+       slow.  Problem experienced by Alex Erdelyi, peregrine.com.
+       File:  smtp/smtp_chat.c. We did the same thing for the SMTP
+       server years ago, and one wonders why the coin didn't drop
+       at the time that the SMTP client could suffer from a similar
+       problem.
+
+20020517
+
+       Cleanup: Mailbox-Line: message header labels should be
+       X-Mailbox-Line:  labels. Files: smtpd/smtpd.c, qmqpd/qmqpd.c.
+
+20020515-21
+
+       Feature: new MIME parser, written from scratch, that recognizes
+       the structure of MIME encapsulated mail. MIME header scanning
+       now happens in header_checks, and are is faster than
+       body_checks could ever be. Thus also eliminates the problem
+       with multi-line MIME headers being matched one line at a
+       time. Files: global/mime_state.[hc], cleanup/cleanup_message.c.
+
+20020521-22
+
+       Feature: 8-bit to quoted-printable conversion. First use in
+       the Postfix SMTP client. File: smtp/smtp_proto.c.
+
+       Logging: the Postfix SMTP and LMTP clients now report the
+       stage of the protocol when it reports a server reply.
+       File:  smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+       Bugfix: the SMTP server complained about ignored client
+       attributes in mail submitted with "sendmail -bs".
+       File: smtpd/smtpd.c.
+
+20020525
+
+       Cleanup: broke out the header value parser from the MIME
+       processor so it can be used elsewhere. File:
+       global/header_token.c.
+
+       Cleanup needs an option to revert to old non-MIME
+       aware behavior - this must override the MIME header
+       match override.
+
 Open problems:
 
+       Medium: old maildrop files are no longer readable by the
+       pickup service. Log a message that suggests a fix.
+
        Low: all table lookups should consistently use internalized
        (unquoted) or externalized (quoted) forms as lookup keys.
        smtpd, qmgr, local, etc. use internalized forms as keys.
index 71db5573bc4682b23caa6a13921541184aa2d24c..20c71cb30829b90c6def1120a2b6959f3c718fb8 100644 (file)
@@ -128,10 +128,11 @@ The second example is considerably more complex, but can give much
 better performance, and is less likely to bounce mail when the
 machine runs into a resource problem.  This approach uses content
 filtering software that can receive and deliver mail via SMTP.
+
 You can expect to lose about a factor of two in Postfix performance
-for transit mail that arrives and leaves via SMTP, provided that
-you create no temporary files. Each temporary file adds another
-factor to the performance loss.
+for transit mail (not delivered on the same machine) that arrives
+and leaves via SMTP; and each temporary file created by the content
+filter adds another factor to the performance loss.
 
 We will set up a content filtering program that receives SMTP mail
 via localhost port 10025, and that submits SMTP mail back into
@@ -158,18 +159,33 @@ To enable content filtering in this manner, specify in main.cf a
 new parameter:
 
     /etc/postfix/main.cf:
-       content_filter = smtp:localhost:10025
+       content_filter = scan:localhost:10025
 
 This causes Postfix to add one extra content filtering record to
-each incoming mail message, with content smtp:localhost:10025.
-You can use the same syntax as in the right-hand side of a Postfix
-transport table.  The content filtering records are added by the
-smtpd and pickup servers.
+each incoming mail message, with content scan:localhost:10025.
+The content filtering records are added by the smtpd and pickup
+servers.
 
 When a queue file has content filtering information, the queue
 manager will deliver the mail to the specified content filter
 regardless of its final destination.
 
+In this example, "scan" is an instance of the Postfix SMTP client
+with slightly different configuration parameters. This is how
+one would set up the service in the Postfix master.cf file:
+
+    /etc/postfix/master.cf:
+        scan      unix  -       -       n       -       10       smtp
+           -o disable_dns_lookups=yes
+
+Turning off DNS lookups at this point can make a great difference
+in content filtering performance. It also isolates the content
+filtering process from temporary outages in DNS service.
+
+Instead of 10, use whatever limit is feasible for your machine.
+Content inspection software can gobble up a lot of system resources,
+so you don't want to have too much of it running at the same time.
+
 The content filter can be set up with the Postfix spawn service,
 which is the Postfix equivalent of inetd. For example, to instantiate
 up to 10 content filtering processes on demand:
@@ -263,7 +279,7 @@ into Postfix via localhost port 10026.
       :          cleanup2/   |   \smtp----->
       :             ^        v          :
       :             |        v          :
-      :           smtpd     smtp        :
+      :           smtpd     scan        :
       :           10026      |          :
       .......................|...........
                     ^        |
index edcdcd2b1a728aa1e070f3c76ad91551f0c0e620..afeb1fc9a099be60525acb42080e0a17811387a4 100644 (file)
@@ -12,6 +12,13 @@ snapshot release).  Patches change the patchlevel and the release
 date. Snapshots change only the release date, unless they include
 the same bugfixes as a patch release.
 
+Incompatible changes with Postfix snapshot 1.1.10-200205XX
+==========================================================
+
+Message headers in MIME attachments etc. are now matched by
+header_checks instead of body_checks. Multi-line headers in MIME
+attachments are now properly recognized.
+
 Incompatible changes with Postfix snapshot 1.1.10-20020514
 ==========================================================
 
index d4680563845832cc9979f16077eb61bb04945856..4cd6c253ba0a99b4840206c0a1521721c68dfdde 100644 (file)
@@ -55,6 +55,8 @@ CLEANUP(8)                                             CLEANUP(8)
 
 <b>STANDARDS</b>
        <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a> (ARPA Internet Text Messages)
+       <a href="http://www.faqs.org/rfcs/rfc2045.html">RFC 2045</a> (MIME: Format of Internet Message Bodies)
+       <a href="http://www.faqs.org/rfcs/rfc2046.html">RFC 2046</a> (MIME: Media Types)
 
 <b>DIAGNOSTICS</b>
        Problems and transactions are logged to <b>syslogd</b>(8).
@@ -76,14 +78,21 @@ CLEANUP(8)                                             CLEANUP(8)
               time, in chunks of at most line_length_limit bytes.
 
        <b>header</b><i>_</i><b>checks</b>
+
+       <b>mime</b><i>_</i><b>header</b><i>_</i><b>checks</b> (default: <b>$header</b><i>_</i><b>checks</b>)
+
+       <b>nested</b><i>_</i><b>header</b><i>_</i><b>checks</b> (default: <b>$header</b><i>_</i><b>checks</b>)
               Lookup tables  with  content  filters  for  message
-              header  lines.   These  filters see logical headers
-              one at a time, including headers that span multiple
-              lines.
+              header  lines:  respectively,  these are applied to
+              the primary message  headers  (not  including  MIME
+              headers),  to the MIME headers anywhere in the mes-
+              sage, and to the initial headers of  attached  mes-
+              sages.   These filters see logical headers one at a
+              time, including headers that span multiple lines.
 
 <b>Miscellaneous</b>
        <b>always</b><i>_</i><b>bcc</b>
-              Address  to send a copy of each message that enters
+              Address to send a copy of each message that  enters
               the system.
 
        <b>hopcount</b><i>_</i><b>limit</b>
@@ -96,8 +105,8 @@ CLEANUP(8)                                             CLEANUP(8)
 
 <b>Address</b> <b>transformations</b>
        <b>empty</b><i>_</i><b>address</b><i>_</i><b>recipient</b>
-              The destination for  undeliverable  mail  from  &lt;&gt;.
-              This  substitution is done before all other address
+              The  destination  for  undeliverable  mail from &lt;&gt;.
+              This substitution is done before all other  address
               rewriting.
 
        <b>canonical</b><i>_</i><b>maps</b>
@@ -113,16 +122,16 @@ CLEANUP(8)                                             CLEANUP(8)
               header sender addresses.
 
        <b>masquerade</b><i>_</i><b>classes</b>
-              List  of  address  classes subject to masquerading:
-              zero or more of  <b>envelope</b><i>_</i><b>sender</b>,  <b>envelope</b><i>_</i><b>recipi-</b>
+              List of address classes  subject  to  masquerading:
+              zero  or  more of <b>envelope</b><i>_</i><b>sender</b>, <b>envelope</b><i>_</i><b>recipi-</b>
               <b>ent</b>, <b>header</b><i>_</i><b>sender</b>, <b>header</b><i>_</i><b>recipient</b>.
 
        <b>masquerade</b><i>_</i><b>domains</b>
-              List  of  domains  that hide their subdomain struc-
+              List of domains that hide  their  subdomain  struc-
               ture.
 
        <b>masquerade</b><i>_</i><b>exceptions</b>
-              List of user names that are not subject to  address
+              List  of user names that are not subject to address
               masquerading.
 
        <b>virtual</b><i>_</i><b>maps</b>
@@ -131,7 +140,7 @@ CLEANUP(8)                                             CLEANUP(8)
 
 <b>Resource</b> <b>controls</b>
        <b>duplicate</b><i>_</i><b>filter</b><i>_</i><b>limit</b>
-              Limit the number of envelope  recipients  that  are
+              Limit  the  number  of envelope recipients that are
               remembered.
 
        <b>header</b><i>_</i><b>size</b><i>_</i><b>limit</b>
@@ -140,11 +149,11 @@ CLEANUP(8)                                             CLEANUP(8)
 
        <b>in</b><i>_</i><b>flow</b><i>_</i><b>delay</b>
               Amount of time to pause before accepting a message,
-              when  the  message arrival rate exceeds the message
+              when the message arrival rate exceeds  the  message
               delivery rate.
 
        <b>extract</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
-              Limit the amount of recipients extracted from  mes-
+              Limit  the amount of recipients extracted from mes-
               sage headers.
 
 <b>SEE</b> <b>ALSO</b>
@@ -159,7 +168,7 @@ CLEANUP(8)                                             CLEANUP(8)
        /etc/postfix/virtual*, virtual mapping table
 
 <b>LICENSE</b>
-       The  Secure  Mailer  license must be distributed with this
+       The Secure Mailer license must be  distributed  with  this
        software.
 
 <b>AUTHOR(S)</b>
index 52d2c78c4a684fc583bec4cc42bccd1b109fb189..34cd1f5b081ceb84f2944bbfd0e6f980c7a71f0f 100644 (file)
@@ -102,6 +102,9 @@ POSTALIAS(1)                                         POSTALIAS(1)
                      <i>file_name</i><b>.db</b>.  This  is  available  only  on
                      systems with support for <b>db</b> databases.
 
+              Use  the command <b>postconf</b> <b>-m</b> to find out what types
+              of database your Postfix installation can  support.
+
               When  no  <i>file_type</i> is specified, the software uses
               the database type specified via  the  <b>database</b><i>_</i><b>type</b>
               configuration  parameter.   The  default  value for
index 2fa962840cd0f75d6f2ed8d3d1683819a1f32e88..ef968e45ca9532ffafb0ad21ffc220df87a8296d 100644 (file)
@@ -55,17 +55,69 @@ POSTCONF(1)                                           POSTCONF(1)
 
        <b>-m</b>     List the names of all supported lookup table types.
 
+              <b>btree</b>  A sorted, balanced tree structure.  This  is
+                     available  only  on systems with support for
+                     Berkeley DB databases.
+
+              <b>dbm</b>    An indexed file type based on hashing.  This
+                     is  available  only  on systems with support
+                     for DBM databases.
+
+              <b>environ</b>
+                     The  UNIX  process  environment  array.  The
+                     lookup  key is the variable name. Originally
+                     implemented for testing,  someone  may  find
+                     this useful someday.
+
+              <b>hash</b>   An indexed file type based on hashing.  This
+                     is available only on  systems  with  support
+                     for Berkeley DB databases.
+
+              <b>ldap</b>   Perform  lookups  using  the  LDAP protocol.
+                     This is described in an LDAP_README file.
+
+              <b>pcre</b>   A lookup table based on Perl Compatible Reg-
+                     ular   Expressions.   The   file  format  is
+                     described in <a href="pcre_table.5.html"><b>pcre</b><i>_</i><b>table</b>(5)</a>.
+
+              <b>regexp</b> A lookup table based on regular expressions.
+                     The   file   format  is  described  in  <b>reg-</b>
+                     <b>exp</b><i>_</i><b>table</b>(5).
+
+              <b>static</b> A table that always returns the same result.
+                     For  example,  <b>static:foobar</b>  always returns
+                     the string <b>foobar</b>.
+
+              <b>unix</b>   A limited way to query the UNIX  authentica-
+                     tion  database.  The  following  tables  are
+                     implemented:
+
+                     <b>unix:passwd.byname</b>
+                             The  table  is  the  UNIX   password
+                             database.  The  key is a login name.
+                             The result is a password file  entry
+                             in passwd(5) format.
+
+                     <b>unix:group.byname</b>
+                             The   table   is   the   UNIX  group
+                             database. The key is a  group  name.
+                             The  result is a group file entry in
+                             group(5) format.
+
+       Other table types may exist depending on how  Postfix  was
+       built.
+
        <b>-n</b>     Print non-default parameter settings only.
 
        <b>-v</b>     Enable verbose logging for debugging purposes. Mul-
-              tiple  <b>-v</b>  options  make  the software increasingly
+              tiple <b>-v</b> options  make  the  software  increasingly
               verbose.
 
 <b>DIAGNOSTICS</b>
        Problems are reported to the standard error stream.
 
 <b>LICENSE</b>
-       The Secure Mailer license must be  distributed  with  this
+       The  Secure  Mailer  license must be distributed with this
        software.
 
 <b>AUTHOR(S)</b>
index b67ff1880632c9d6339fc926ac8d0a22bcf0b4fd..b72767b00bca3a79fd5f79ec4dbc9b889fa5f01d 100644 (file)
@@ -123,6 +123,9 @@ POSTMAP(1)                                             POSTMAP(1)
                      <i>file_name</i><b>.db</b>.   This  is  available  only on
                      systems with support for <b>db</b> databases.
 
+              Use the command <b>postconf</b> <b>-m</b> to find out what  types
+              of  database your Postfix installation can support.
+
               When no <i>file_type</i> is specified, the  software  uses
               the  database  type specified via the <b>database</b><i>_</i><b>type</b>
               configuration parameter.
index ba92da53609562ebd4304a78c5d10e610fbf89ca..f0be0c1da34f77f61576fea495dec9df17bb8d49 100644 (file)
@@ -42,9 +42,12 @@ SMTP(8)                                                   SMTP(8)
 
 <b>STANDARDS</b>
        <a href="http://www.faqs.org/rfcs/rfc821.html">RFC 821</a> (SMTP protocol)
+       <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a> (ARPA Internet Text Messages)
        <a href="http://www.faqs.org/rfcs/rfc1651.html">RFC 1651</a> (SMTP service extensions)
        <a href="http://www.faqs.org/rfcs/rfc1652.html">RFC 1652</a> (8bit-MIME transport)
        <a href="http://www.faqs.org/rfcs/rfc1870.html">RFC 1870</a> (Message Size Declaration)
+       <a href="http://www.faqs.org/rfcs/rfc2045.html">RFC 2045</a> (MIME: Format of Internet Message Bodies)
+       <a href="http://www.faqs.org/rfcs/rfc2046.html">RFC 2046</a> (MIME: Media Types)
        <a href="http://www.faqs.org/rfcs/rfc2197.html">RFC 2197</a> (Pipelining)
        <a href="http://www.faqs.org/rfcs/rfc2554.html">RFC 2554</a> (AUTH command)
        <a href="http://www.faqs.org/rfcs/rfc2821.html">RFC 2821</a> (SMTP protocol)
index eb1cf4533f3cccc5a73594e83ce3252383f867dd..f1d90c1d24256e3524881f1f6cf7de724cfa8f7a 100644 (file)
@@ -92,6 +92,9 @@ This is available only on systems with support for \fBdbm\fR databases.
 The output is a hashed file, named \fIfile_name\fB.db\fR.
 This is available only on systems with support for \fBdb\fR databases.
 .PP
+Use the command \fBpostconf -m\fR to find out what types of database
+your Postfix installation can support.
+
 When no \fIfile_type\fR is specified, the software uses the database
 type specified via the \fBdatabase_type\fR configuration parameter.
 The default value for this parameter depends on the host environment.
index 20689d4f3c5d3180434f08bd5c9941b2e5f06b98..02143214ced2ee21c02ce4617ab436356749fd90 100644 (file)
@@ -54,6 +54,48 @@ stale lock files that were left behind after abnormal termination.
 .RE
 .IP \fB-m\fR
 List the names of all supported lookup table types.
+.RS
+.IP \fBbtree\fR
+A sorted, balanced tree structure.
+This is available only on systems with support for Berkeley DB
+databases.
+.IP \fBdbm\fR
+An indexed file type based on hashing.
+This is available only on systems with support for DBM databases.
+.IP \fBenviron\fR
+The UNIX process environment array. The lookup key is the variable
+name. Originally implemented for testing, someone may find this
+useful someday.
+.IP \fBhash\fR
+An indexed file type based on hashing.
+This is available only on systems with support for Berkeley DB
+databases.
+.IP \fBldap\fR
+Perform lookups using the LDAP protocol. This is described
+in an LDAP_README file.
+.IP \fBpcre\fR
+A lookup table based on Perl Compatible Regular Expressions. The
+file format is described in \fBpcre_table\fR(5).
+.IP \fBregexp\fR
+A lookup table based on regular expressions. The file format is
+described in \fBregexp_table\fR(5).
+.IP \fBstatic\fR
+A table that always returns the same result. For example,
+\fBstatic:foobar\fR always returns the string \fBfoobar\fR.
+.IP \fBunix\fR
+A limited way to query the UNIX authentication database. The
+following tables are implemented:
+.RS
+. IP \fBunix:passwd.byname\fR
+The table is the UNIX password database. The key is a login name.
+The result is a password file entry in passwd(5) format.
+.IP \fBunix:group.byname\fR
+The table is the UNIX group database. The key is a group name.
+The result is a group file entry in group(5) format.
+.RE
+.RE
+.sp
+Other table types may exist depending on how Postfix was built.
 .IP \fB-n\fR
 Print non-default parameter settings only.
 .IP \fB-v\fR
index a87aa1160731c1694c75c44a8fd27af2982cb4e8..e80dc00be9c84bf0918d8dcd24d41b1515f3b50c 100644 (file)
@@ -113,6 +113,9 @@ This is available only on systems with support for \fBdbm\fR databases.
 The output file is a hashed file, named \fIfile_name\fB.db\fR.
 This is available only on systems with support for \fBdb\fR databases.
 .PP
+Use the command \fBpostconf -m\fR to find out what types of database
+your Postfix installation can support.
+
 When no \fIfile_type\fR is specified, the software uses the database
 type specified via the \fBdatabase_type\fR configuration parameter.
 .RE
index ed01ee8b612183fa20088dd33c55cfc653213472..826372d94ef90776bbc6fea0468ab4cb7f111bfb 100644 (file)
@@ -56,6 +56,8 @@ in case of trouble.
 .na
 .nf
 RFC 822 (ARPA Internet Text Messages)
+RFC 2045 (MIME: Format of Internet Message Bodies)
+RFC 2046 (MIME: Media Types)
 .SH DIAGNOSTICS
 .ad
 .fi
@@ -80,7 +82,12 @@ Lookup tables with content filters for message body lines.
 These filters see physical lines one at a time, in chunks of
 at most line_length_limit bytes.
 .IP \fBheader_checks\fR
-Lookup tables with content filters for message header lines.
+.IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+.IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+Lookup tables with content filters for message header lines:
+respectively, these are applied to the primary message headers
+(not including MIME headers), to the MIME headers anywhere in
+the message, and to the initial headers of attached messages.
 These filters see logical headers one at a time, including headers
 that span multiple lines.
 .SH Miscellaneous
index 196c16c746f89e9b0fc4caacafcb3a7caaacba2f..2e5f87ef8be6a4f1505d319927c04e4391d6582d 100644 (file)
@@ -47,9 +47,12 @@ run chrooted at fixed low privilege.
 .na
 .nf
 RFC 821 (SMTP protocol)
+RFC 822 (ARPA Internet Text Messages)
 RFC 1651 (SMTP service extensions)
 RFC 1652 (8bit-MIME transport)
 RFC 1870 (Message Size Declaration)
+RFC 2045 (MIME: Format of Internet Message Bodies)
+RFC 2046 (MIME: Media Types)
 RFC 2197 (Pipelining)
 RFC 2554 (AUTH command)
 RFC 2821 (SMTP protocol)
index 63da2cacefde7860129d8ead92d57fc7710c905c..493a18a496fe6472b655277383e6de14198fcecd 100644 (file)
@@ -86,6 +86,8 @@ cleanup.o: ../../include/tok822.h
 cleanup.o: ../../include/resolve_clnt.h
 cleanup.o: ../../include/been_here.h
 cleanup.o: ../../include/mail_stream.h
+cleanup.o: ../../include/mime_state.h
+cleanup.o: ../../include/header_opts.h
 cleanup_api.o: cleanup_api.c
 cleanup_api.o: ../../include/sys_defs.h
 cleanup_api.o: ../../include/msg.h
@@ -111,6 +113,8 @@ cleanup_api.o: ../../include/tok822.h
 cleanup_api.o: ../../include/resolve_clnt.h
 cleanup_api.o: ../../include/been_here.h
 cleanup_api.o: ../../include/mail_conf.h
+cleanup_api.o: ../../include/mime_state.h
+cleanup_api.o: ../../include/header_opts.h
 cleanup_envelope.o: cleanup_envelope.c
 cleanup_envelope.o: ../../include/sys_defs.h
 cleanup_envelope.o: ../../include/msg.h
@@ -138,6 +142,8 @@ cleanup_envelope.o: ../../include/dict.h
 cleanup_envelope.o: ../../include/been_here.h
 cleanup_envelope.o: ../../include/mail_stream.h
 cleanup_envelope.o: ../../include/mail_conf.h
+cleanup_envelope.o: ../../include/mime_state.h
+cleanup_envelope.o: ../../include/header_opts.h
 cleanup_extracted.o: cleanup_extracted.c
 cleanup_extracted.o: ../../include/sys_defs.h
 cleanup_extracted.o: ../../include/msg.h
@@ -164,6 +170,8 @@ cleanup_extracted.o: ../../include/resolve_clnt.h
 cleanup_extracted.o: ../../include/been_here.h
 cleanup_extracted.o: ../../include/mail_stream.h
 cleanup_extracted.o: ../../include/mail_conf.h
+cleanup_extracted.o: ../../include/mime_state.h
+cleanup_extracted.o: ../../include/header_opts.h
 cleanup_init.o: cleanup_init.c
 cleanup_init.o: ../../include/sys_defs.h
 cleanup_init.o: ../../include/msg.h
@@ -187,6 +195,8 @@ cleanup_init.o: ../../include/resolve_clnt.h
 cleanup_init.o: ../../include/been_here.h
 cleanup_init.o: ../../include/mail_stream.h
 cleanup_init.o: ../../include/mail_conf.h
+cleanup_init.o: ../../include/mime_state.h
+cleanup_init.o: ../../include/header_opts.h
 cleanup_map11.o: cleanup_map11.c
 cleanup_map11.o: ../../include/sys_defs.h
 cleanup_map11.o: ../../include/msg.h
@@ -209,6 +219,8 @@ cleanup_map11.o: ../../include/resolve_clnt.h
 cleanup_map11.o: ../../include/been_here.h
 cleanup_map11.o: ../../include/mail_stream.h
 cleanup_map11.o: ../../include/mail_conf.h
+cleanup_map11.o: ../../include/mime_state.h
+cleanup_map11.o: ../../include/header_opts.h
 cleanup_map1n.o: cleanup_map1n.c
 cleanup_map1n.o: ../../include/sys_defs.h
 cleanup_map1n.o: ../../include/mymalloc.h
@@ -231,6 +243,8 @@ cleanup_map1n.o: ../../include/tok822.h
 cleanup_map1n.o: ../../include/resolve_clnt.h
 cleanup_map1n.o: ../../include/mail_stream.h
 cleanup_map1n.o: ../../include/mail_conf.h
+cleanup_map1n.o: ../../include/mime_state.h
+cleanup_map1n.o: ../../include/header_opts.h
 cleanup_masquerade.o: cleanup_masquerade.c
 cleanup_masquerade.o: ../../include/sys_defs.h
 cleanup_masquerade.o: ../../include/msg.h
@@ -253,6 +267,8 @@ cleanup_masquerade.o: ../../include/dict.h
 cleanup_masquerade.o: ../../include/been_here.h
 cleanup_masquerade.o: ../../include/mail_stream.h
 cleanup_masquerade.o: ../../include/mail_conf.h
+cleanup_masquerade.o: ../../include/mime_state.h
+cleanup_masquerade.o: ../../include/header_opts.h
 cleanup_message.o: cleanup_message.c
 cleanup_message.o: ../../include/sys_defs.h
 cleanup_message.o: ../../include/msg.h
@@ -281,6 +297,7 @@ cleanup_message.o: ../../include/ext_prop.h
 cleanup_message.o: ../../include/mail_proto.h
 cleanup_message.o: ../../include/iostuff.h
 cleanup_message.o: ../../include/attr.h
+cleanup_message.o: ../../include/mime_state.h
 cleanup_message.o: cleanup.h
 cleanup_message.o: ../../include/maps.h
 cleanup_message.o: ../../include/dict.h
@@ -309,6 +326,8 @@ cleanup_out.o: ../../include/resolve_clnt.h
 cleanup_out.o: ../../include/been_here.h
 cleanup_out.o: ../../include/mail_stream.h
 cleanup_out.o: ../../include/mail_conf.h
+cleanup_out.o: ../../include/mime_state.h
+cleanup_out.o: ../../include/header_opts.h
 cleanup_out_recipient.o: cleanup_out_recipient.c
 cleanup_out_recipient.o: ../../include/sys_defs.h
 cleanup_out_recipient.o: ../../include/argv.h
@@ -329,6 +348,8 @@ cleanup_out_recipient.o: ../../include/tok822.h
 cleanup_out_recipient.o: ../../include/resolve_clnt.h
 cleanup_out_recipient.o: ../../include/mail_stream.h
 cleanup_out_recipient.o: ../../include/mail_conf.h
+cleanup_out_recipient.o: ../../include/mime_state.h
+cleanup_out_recipient.o: ../../include/header_opts.h
 cleanup_rewrite.o: cleanup_rewrite.c
 cleanup_rewrite.o: ../../include/sys_defs.h
 cleanup_rewrite.o: ../../include/msg.h
@@ -350,6 +371,8 @@ cleanup_rewrite.o: ../../include/dict.h
 cleanup_rewrite.o: ../../include/been_here.h
 cleanup_rewrite.o: ../../include/mail_stream.h
 cleanup_rewrite.o: ../../include/mail_conf.h
+cleanup_rewrite.o: ../../include/mime_state.h
+cleanup_rewrite.o: ../../include/header_opts.h
 cleanup_state.o: cleanup_state.c
 cleanup_state.o: ../../include/sys_defs.h
 cleanup_state.o: ../../include/mymalloc.h
@@ -359,6 +382,8 @@ cleanup_state.o: ../../include/argv.h
 cleanup_state.o: ../../include/htable.h
 cleanup_state.o: ../../include/been_here.h
 cleanup_state.o: ../../include/mail_params.h
+cleanup_state.o: ../../include/mime_state.h
+cleanup_state.o: ../../include/header_opts.h
 cleanup_state.o: cleanup.h
 cleanup_state.o: ../../include/vstream.h
 cleanup_state.o: ../../include/nvtable.h
index b276cf2ebca9a06b6f9374defcc1355df60fcf2a..b82782cd6c53db575af4681d01db60f2db7de44e 100644 (file)
@@ -48,6 +48,8 @@
 /*     in case of trouble.
 /* STANDARDS
 /*     RFC 822 (ARPA Internet Text Messages)
+/*     RFC 2045 (MIME: Format of Internet Message Bodies)
+/*     RFC 2046 (MIME: Media Types)
 /* DIAGNOSTICS
 /*     Problems and transactions are logged to \fBsyslogd\fR(8).
 /* BUGS
 /*     These filters see physical lines one at a time, in chunks of
 /*     at most line_length_limit bytes.
 /* .IP \fBheader_checks\fR
-/*     Lookup tables with content filters for message header lines.
+/* .IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+/* .IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+/*     Lookup tables with content filters for message header lines:
+/*     respectively, these are applied to the primary message headers 
+/*     (not including MIME headers), to the MIME headers anywhere in 
+/*     the message, and to the initial headers of attached messages.
 /*     These filters see logical headers one at a time, including headers
 /*     that span multiple lines.
 /* .SH Miscellaneous
@@ -219,6 +226,16 @@ static void cleanup_service(VSTREAM *src, char *unused_service, char **argv)
            break;
     }
 
+    /*
+     * Can't do header checks if the MIME structure is nested too deeply.
+     */
+    if ((state->mime_errs & MIME_ERR_NESTING)
+       && (*var_header_checks || *var_mimehdr_checks || *var_nesthdr_checks)
+       && state->reason == 0) {
+       state->errs |= CLEANUP_STAT_CONT;
+       state->reason = mystrdup("too much MIME nesting");
+    }
+
     /*
      * Keep reading in case of problems, until the sender is ready to receive
      * our status report.
index 082bd4e74e78b212e1a6dcece8fd9fa8c5569711..a0e3b1df6ecd0b4814d2163428e4981e418d4370 100644 (file)
@@ -24,6 +24,7 @@
 #include <been_here.h>
 #include <mail_stream.h>
 #include <mail_conf.h>
+#include <mime_state.h>
 
  /*
   * These state variables are accessed by many functions, and there is only
@@ -46,9 +47,7 @@ typedef struct CLEANUP_STATE {
     int     flags;                     /* processing options */
     int     errs;                      /* any badness experienced */
     int     err_mask;                  /* allowed badness */
-    VSTRING *header_buf;               /* multi-record header */
     int     headers_seen;              /* which headers were seen */
-    int     prev_header_type;          /* multi-record physical header line */
     int     hop_count;                 /* count of received: headers */
     ARGV   *recipients;                        /* recipients from regular headers */
     ARGV   *resent_recip;              /* recipients from resent headers */
@@ -63,8 +62,12 @@ typedef struct CLEANUP_STATE {
     int     rcpt_count;                        /* recipient count */
     char   *reason;                    /* failure reason */
     NVTABLE *attr;                     /* queue file attribute list */
+    MIME_STATE *mime_state;            /* MIME state engine */
+    int     mime_errs;                 /* MIME error flags */
 } CLEANUP_STATE;
 
+#define CLEANUP_CURR_HEADERS   (1<<0)  /* in main headers section */
+
  /*
   * Mappings.
   */
@@ -72,6 +75,8 @@ extern MAPS *cleanup_comm_canon_maps;
 extern MAPS *cleanup_send_canon_maps;
 extern MAPS *cleanup_rcpt_canon_maps;
 extern MAPS *cleanup_header_checks;
+extern MAPS *cleanup_mimehdr_checks;
+extern MAPS *cleanup_nesthdr_checks;
 extern MAPS *cleanup_body_checks;
 extern MAPS *cleanup_virtual_maps;
 extern ARGV *cleanup_masq_domains;
@@ -121,9 +126,9 @@ extern CONFIG_TIME_TABLE cleanup_time_table[];
  /*
   * cleanup_out.c
   */
-extern void cleanup_out(CLEANUP_STATE *, int, char *, int);
-extern void cleanup_out_string(CLEANUP_STATE *, int, char *);
-extern void PRINTFLIKE(3, 4) cleanup_out_format(CLEANUP_STATE *, int, char *,...);
+extern void cleanup_out(CLEANUP_STATE *, int, const char *, int);
+extern void cleanup_out_string(CLEANUP_STATE *, int, const char *);
+extern void PRINTFLIKE(3, 4) cleanup_out_format(CLEANUP_STATE *, int, const char *,...);
 
 #define CLEANUP_OUT_BUF(s, t, b) \
        cleanup_out((s), (t), vstring_str((b)), VSTRING_LEN((b)))
index 866527936a98c56c84b01f5ee1d4c47aec8e16f3..d1699aebd2188f3230d942ae88520d923d36e5dd 100644 (file)
@@ -91,14 +91,15 @@ char   *cleanup_path;                       /* queue file name */
   * Tunable parameters.
   */
 int     var_hopcount_limit;            /* max mailer hop count */
-int     var_header_limit;              /* max header length */
 char   *var_canonical_maps;            /* common canonical maps */
 char   *var_send_canon_maps;           /* sender canonical maps */
 char   *var_rcpt_canon_maps;           /* recipient canonical maps */
 char   *var_virtual_maps;              /* virtual maps */
 char   *var_masq_domains;              /* masquerade domains */
 char   *var_masq_exceptions;           /* users not masqueraded */
-char   *var_header_checks;             /* any header checks */
+char   *var_header_checks;             /* primary header checks */
+char   *var_mimehdr_checks;            /* mime header checks */
+char   *var_nesthdr_checks;            /* nested header checks */
 char   *var_body_checks;               /* any body checks */
 int     var_dup_filter_limit;          /* recipient dup filter */
 char   *var_empty_addr;                        /* destination of bounced bounces */
@@ -112,7 +113,6 @@ int     var_qattr_count_limit;              /* named attribute limit */
 
 CONFIG_INT_TABLE cleanup_int_table[] = {
     VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0,
-    VAR_HEADER_LIMIT, DEF_HEADER_LIMIT, &var_header_limit, 1, 0,
     VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
     VAR_EXTRA_RCPT_LIMIT, DEF_EXTRA_RCPT_LIMIT, &var_extra_rcpt_limit, 0, 0,
     VAR_QATTR_COUNT_LIMIT, DEF_QATTR_COUNT_LIMIT, &var_qattr_count_limit, 1, 0,
@@ -133,6 +133,8 @@ CONFIG_STR_TABLE cleanup_str_table[] = {
     VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
     VAR_MASQ_EXCEPTIONS, DEF_MASQ_EXCEPTIONS, &var_masq_exceptions, 0, 0,
     VAR_HEADER_CHECKS, DEF_HEADER_CHECKS, &var_header_checks, 0, 0,
+    VAR_MIMEHDR_CHECKS, DEF_MIMEHDR_CHECKS, &var_mimehdr_checks, 0, 0,
+    VAR_NESTHDR_CHECKS, DEF_NESTHDR_CHECKS, &var_nesthdr_checks, 0, 0,
     VAR_BODY_CHECKS, DEF_BODY_CHECKS, &var_body_checks, 0, 0,
     VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
     VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0,
@@ -148,6 +150,8 @@ MAPS   *cleanup_comm_canon_maps;
 MAPS   *cleanup_send_canon_maps;
 MAPS   *cleanup_rcpt_canon_maps;
 MAPS   *cleanup_header_checks;
+MAPS   *cleanup_mimehdr_checks;
+MAPS   *cleanup_nesthdr_checks;
 MAPS   *cleanup_body_checks;
 MAPS   *cleanup_virtual_maps;
 ARGV   *cleanup_masq_domains;
@@ -197,6 +201,12 @@ void    cleanup_pre_jail(char *unused_name, char **unused_argv)
     if (*var_header_checks)
        cleanup_header_checks =
            maps_create(VAR_HEADER_CHECKS, var_header_checks, DICT_FLAG_LOCK);
+    if (*var_mimehdr_checks)
+       cleanup_mimehdr_checks =
+           maps_create(VAR_MIMEHDR_CHECKS, var_mimehdr_checks, DICT_FLAG_LOCK);
+    if (*var_nesthdr_checks)
+       cleanup_nesthdr_checks =
+           maps_create(VAR_NESTHDR_CHECKS, var_nesthdr_checks, DICT_FLAG_LOCK);
     if (*var_body_checks)
        cleanup_body_checks =
            maps_create(VAR_BODY_CHECKS, var_body_checks, DICT_FLAG_LOCK);
index 86785783e413b481a12c9e4256accb8d14258ebf..c2d1eeed63f4f2405131812a61853b61eea7060c 100644 (file)
 #include <is_header.h>
 #include <ext_prop.h>
 #include <mail_proto.h>
+#include <mime_state.h>
 
 /* Application-specific. */
 
 #include "cleanup.h"
 
-static void cleanup_message_header(CLEANUP_STATE *, int, char *, int);
-static void cleanup_message_body(CLEANUP_STATE *, int, char *, int);
-
 /* cleanup_out_header - output one header as a bunch of records */
 
-static void cleanup_out_header(CLEANUP_STATE *state)
+static void cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf)
 {
-    char   *start = vstring_str(state->header_buf);
+    char   *start = vstring_str(header_buf);
     char   *line;
     char   *next_line;
 
@@ -113,9 +111,9 @@ static void cleanup_out_header(CLEANUP_STATE *state)
 
 /* cleanup_fold_header - wrap address list header */
 
-static void cleanup_fold_header(CLEANUP_STATE *state)
+static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf)
 {
-    char   *start_line = vstring_str(state->header_buf);
+    char   *start_line = vstring_str(header_buf);
     char   *end_line;
     char   *next_line;
     char   *line;
@@ -138,7 +136,7 @@ static void cleanup_fold_header(CLEANUP_STATE *state)
        }
        next_line = *end_line ? end_line + 1 : 0;
     }
-    cleanup_out_header(state);
+    cleanup_out_header(state, header_buf);
 }
 
 /* cleanup_extract_internal - save unquoted copy of extracted address */
@@ -156,11 +154,13 @@ static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr)
 
 /* cleanup_rewrite_sender - sender address rewriting */
 
-static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
+static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts,
+                                          VSTRING *header_buf)
 {
     TOK822 *tree;
     TOK822 **addr_list;
     TOK822 **tpp;
+    char   *addr;
 
     if (msg_verbose)
        msg_info("rewrite_sender: %s", hdr_opts->name);
@@ -170,8 +170,11 @@ static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
      * sender addresses, and regenerate the header line. Finally, pipe the
      * result through the header line folding routine.
      */
-    tree = tok822_parse(vstring_str(state->header_buf)
-                       + strlen(hdr_opts->name) + 1);
+#define SKIP_HEADER_THRASH(cp) { while (ISSPACE(*cp)) cp++; cp++; }
+
+    addr = vstring_str(header_buf) + strlen(hdr_opts->name);
+    SKIP_HEADER_THRASH(addr);
+    tree = tok822_parse(addr);
     addr_list = tok822_grep(tree, TOK822_ADDR);
     for (tpp = addr_list; *tpp; tpp++) {
        cleanup_rewrite_tree(*tpp);
@@ -185,33 +188,35 @@ static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
            && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM))
            cleanup_masquerade_tree(*tpp, cleanup_masq_domains);
        if (hdr_opts->type == HDR_FROM && state->from == 0)
-           state->from = cleanup_extract_internal(state->header_buf, *tpp);
+           state->from = cleanup_extract_internal(header_buf, *tpp);
        if (hdr_opts->type == HDR_RESENT_FROM && state->resent_from == 0)
            state->resent_from =
-               cleanup_extract_internal(state->header_buf, *tpp);
+               cleanup_extract_internal(header_buf, *tpp);
        if (hdr_opts->type == HDR_RETURN_RECEIPT_TO && !state->return_receipt)
            state->return_receipt =
-               cleanup_extract_internal(state->header_buf, *tpp);
+               cleanup_extract_internal(header_buf, *tpp);
        if (hdr_opts->type == HDR_ERRORS_TO && !state->errors_to)
            state->errors_to =
-               cleanup_extract_internal(state->header_buf, *tpp);
+               cleanup_extract_internal(header_buf, *tpp);
     }
-    vstring_sprintf(state->header_buf, "%s: ", hdr_opts->name);
-    tok822_externalize(state->header_buf, tree, TOK822_STR_HEAD);
+    vstring_sprintf(header_buf, "%s: ", hdr_opts->name);
+    tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
     myfree((char *) addr_list);
     tok822_free_tree(tree);
     if ((hdr_opts->flags & HDR_OPT_DROP) == 0)
-       cleanup_fold_header(state);
+       cleanup_fold_header(state, header_buf);
 }
 
 /* cleanup_rewrite_recip - recipient address rewriting */
 
-static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
+static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts,
+                                         VSTRING *header_buf)
 {
     TOK822 *tree;
     TOK822 **addr_list;
     TOK822 **tpp;
     ARGV   *rcpt;
+    char   *addr;
 
     if (msg_verbose)
        msg_info("rewrite_recip: %s", hdr_opts->name);
@@ -221,8 +226,9 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
      * recipient addresses, and regenerate the header line. Finally, pipe the
      * result through the header line folding routine.
      */
-    tree = tok822_parse(vstring_str(state->header_buf)
-                       + strlen(hdr_opts->name) + 1);
+    addr = vstring_str(header_buf) + strlen(hdr_opts->name);
+    SKIP_HEADER_THRASH(addr);
+    tree = tok822_parse(addr);
     addr_list = tok822_grep(tree, TOK822_ADDR);
     for (tpp = addr_list; *tpp; tpp++) {
        cleanup_rewrite_tree(*tpp);
@@ -249,17 +255,17 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
            && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT))
            cleanup_masquerade_tree(*tpp, cleanup_masq_domains);
     }
-    vstring_sprintf(state->header_buf, "%s: ", hdr_opts->name);
-    tok822_externalize(state->header_buf, tree, TOK822_STR_HEAD);
+    vstring_sprintf(header_buf, "%s: ", hdr_opts->name);
+    tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
     myfree((char *) addr_list);
     tok822_free_tree(tree);
     if ((hdr_opts->flags & HDR_OPT_DROP) == 0)
-       cleanup_fold_header(state);
+       cleanup_fold_header(state, header_buf);
 }
 
 /* cleanup_act - act upon a header/body match */
 
-static int cleanup_act(CLEANUP_STATE *state, char *context, char *buf,
+static int cleanup_act(CLEANUP_STATE *state, char *context, const char *buf,
                               const char *value, const char *map_class)
 {
     const char *optional_text = value + strcspn(value, " \t");
@@ -308,12 +314,13 @@ static int cleanup_act(CLEANUP_STATE *state, char *context, char *buf,
     return (CLEANUP_ACT_KEEP);
 }
 
-/* cleanup_header - process one complete header line */
+/* cleanup_header_callback - process one complete header line */
 
-static void cleanup_header(CLEANUP_STATE *state)
+static void cleanup_header_callback(void *context, int header_class,
+                                HEADER_OPTS *hdr_opts, VSTRING *header_buf)
 {
-    char   *myname = "cleanup_header";
-    HEADER_OPTS *hdr_opts;
+    CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+    const char *myname = "cleanup_header_callback";
     char   *hdrval;
     struct code_map {
        const char *name;
@@ -328,16 +335,31 @@ static void cleanup_header(CLEANUP_STATE *state)
        0,
     };
     struct code_map *cmp;
+    MAPS   *checks;
+    const char *map_class;
 
     if (msg_verbose)
-       msg_info("%s: '%s'", myname, vstring_str(state->header_buf));
+       msg_info("%s: '%s'", myname, vstring_str(header_buf));
+
+    /*
+     * Crude header filtering. This stops malware that isn't sophisticated
+     * enough to use fancy header encodings.
+     */
+#define CHECK(class, maps, var_name) \
+       (header_class == class && (map_class = var_name, checks = maps) != 0)
+
+    if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
+       header_class = MIME_HDR_MULTIPART;
 
-    if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_header_checks) {
-       char   *header = vstring_str(state->header_buf);
+    if ((state->flags & CLEANUP_FLAG_FILTER)
+     && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS)
+    || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS)
+    || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) {
+       char   *header = vstring_str(header_buf);
        const char *value;
 
-       if ((value = maps_find(cleanup_header_checks, header, 0)) != 0) {
-           if (cleanup_act(state, "header", header, value, VAR_HEADER_CHECKS)
+       if ((value = maps_find(checks, header, 0)) != 0) {
+           if (cleanup_act(state, "header", header, value, map_class)
                == CLEANUP_ACT_DROP)
                return;
        }
@@ -346,10 +368,38 @@ static void cleanup_header(CLEANUP_STATE *state)
     /*
      * If this is an "unknown" header, just copy it to the output without
      * even bothering to fold long lines. cleanup_out() will split long
-     * headers that do not fit in a REC_TYPE_NORM record.
+     * headers that do not fit a REC_TYPE_NORM record.
      */
-    if ((hdr_opts = header_opts_find(vstring_str(state->header_buf))) == 0) {
-       cleanup_out_header(state);
+    if (hdr_opts == 0) {
+       cleanup_out_header(state, header_buf);
+       return;
+    }
+
+    /*
+     * Allow 8-bit type info to override 7-bit type info. XXX Should reuse
+     * the effort that went into MIME header parsing.
+     */
+    hdrval = vstring_str(header_buf) + strlen(hdr_opts->name);
+    SKIP_HEADER_THRASH(hdrval);
+    /* trimblanks(hdrval, 0)[0] = 0; */
+    if (hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
+       for (cmp = code_map; cmp->name != 0; cmp++) {
+           if (strcasecmp(hdrval, cmp->name) == 0) {
+               if (nvtable_find(state->attr, MAIL_ATTR_ENCODING) == 0
+                   || strcmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0)
+                   nvtable_update(state->attr, MAIL_ATTR_ENCODING,
+                                  cmp->encoding);
+               break;
+           }
+       }
+    }
+
+    /*
+     * Copy attachment etc. header blocks without further inspection.
+     */
+    if (header_class != MIME_HDR_PRIMARY) {
+       cleanup_out_header(state, header_buf);
+       return;
     }
 
     /*
@@ -383,10 +433,6 @@ static void cleanup_header(CLEANUP_STATE *state)
      */
     else {
        state->headers_seen |= (1 << hdr_opts->type);
-       hdrval = vstring_str(state->header_buf) + strlen(hdr_opts->name) + 2;
-       while (ISSPACE(*hdrval))
-           hdrval++;
-       trimblanks(hdrval, 0);
        if (hdr_opts->type == HDR_MESSAGE_ID)
            msg_info("%s: message-id=%s", state->queue_id, hdrval);
        if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
@@ -394,35 +440,25 @@ static void cleanup_header(CLEANUP_STATE *state)
        if (hdr_opts->type == HDR_RECEIVED)
            if (++state->hop_count >= var_hopcount_limit)
                state->errs |= CLEANUP_STAT_HOPS;
-       if (hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
-           if (nvtable_find(state->attr, MAIL_ATTR_ENCODING) == 0) {
-               for (cmp = code_map; cmp->name != 0; cmp++) {
-                   if (strcasecmp(hdrval, cmp->name) == 0) {
-                       nvtable_update(state->attr, MAIL_ATTR_ENCODING,
-                                      cmp->encoding);
-                       break;
-                   }
-               }
-           }
-       }
        if (CLEANUP_OUT_OK(state)) {
            if (hdr_opts->flags & HDR_OPT_RR)
                state->resent = "Resent-";
            if (hdr_opts->flags & HDR_OPT_SENDER) {
-               cleanup_rewrite_sender(state, hdr_opts);
+               cleanup_rewrite_sender(state, hdr_opts, header_buf);
            } else if (hdr_opts->flags & HDR_OPT_RECIP) {
-               cleanup_rewrite_recip(state, hdr_opts);
+               cleanup_rewrite_recip(state, hdr_opts, header_buf);
            } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
-               cleanup_out_header(state);
+               cleanup_out_header(state, header_buf);
            }
        }
     }
 }
 
-/* cleanup_missing_headers - insert missing message headers */
+/* cleanup_header_done_callback - insert missing message headers */
 
-static void cleanup_missing_headers(CLEANUP_STATE *state)
+static void cleanup_header_done_callback(void *context)
 {
+    CLEANUP_STATE *state = (CLEANUP_STATE *) context;
     char    time_stamp[1024];          /* XXX locale dependent? */
     struct tm *tp;
     TOK822 *token;
@@ -507,148 +543,62 @@ static void cleanup_missing_headers(CLEANUP_STATE *state)
 
     if ((state->headers_seen & VISIBLE_RCPT) == 0)
        cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld);
-}
-
-/* cleanup_message - initialize message content segment */
-
-void    cleanup_message(CLEANUP_STATE *state, int type, char *buf, int len)
-{
-    char   *myname = "cleanup_message";
 
     /*
-     * Write a dummy start-of-content segment marker. We'll update it with
-     * real file offset information after reaching the end of the message
-     * content.
+     * Header buffer overflow is an unrecoverable error only if we extract
+     * recipients from the main message headers.
      */
-    if ((state->mesg_offset = vstream_ftell(state->dst)) < 0)
-       msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
-    cleanup_out_format(state, REC_TYPE_MESG, REC_TYPE_MESG_FORMAT, 0L);
-    if ((state->data_offset = vstream_ftell(state->dst)) < 0)
-       msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
-
-    /*
-     * Pass control to the header processing routine.
-     */
-    state->action = cleanup_message_header;
-    cleanup_message_header(state, type, buf, len);
+    if (state->mime_errs & MIME_ERR_TRUNC_HEADER)
+       state->errs |= CLEANUP_STAT_HOVFL;
 }
 
-/* cleanup_message_header - process message content, header */
+/* cleanup_body_callback - output one body record */
 
-static void cleanup_message_header(CLEANUP_STATE *state, int type, char *buf, int len)
+static void cleanup_body_callback(void *context, int type, const char *buf, int len)
 {
-    char   *myname = "cleanup_message_header";
+    CLEANUP_STATE *state = (CLEANUP_STATE *) context;
 
     /*
-     * Sanity check.
+     * Crude message body content filter for emergencies. This code has
+     * several problems: it sees one line at a time; it looks at long lines
+     * only in chunks of line_length_limit (2048) characters; it is easily
+     * bypassed with encodings and other tricks.
      */
-    if (strchr(REC_TYPE_CONTENT, type) == 0) {
-       msg_warn("%s: %s: unexpected record type %d",
-                state->queue_id, myname, type);
-       state->errs |= CLEANUP_STAT_BAD;
-       return;
-    }
+    if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_body_checks) {
+       const char *value;
 
-    /*
-     * First, deal with header information that we have accumulated from
-     * previous input records.
-     * 
-     * If a physical header line exceeds the capacity of a Postfix queue file
-     * record, reconstruct the long line from multiple records (up to the
-     * header size limit), and break the long line up into multiple Postfix
-     * records upon output to the queue file. Discard text that does not fit
-     * in a header buffer, so as to avoid breaking MIME formatting.
-     * 
-     * It is left up to delivery agents to glue long lines back together and to
-     * enforce an appropriate output line length limit.
-     */
-    if (VSTRING_LEN(state->header_buf) > 0) {
-       if (type != REC_TYPE_XTRA) {
-           if (state->prev_header_type == REC_TYPE_CONT) {
-               if (VSTRING_LEN(state->header_buf) < var_header_limit)
-                   vstring_strcat(state->header_buf, buf);
-               else
-                   state->errs |= CLEANUP_STAT_HOVFL;
-               state->prev_header_type = type;
-               return;
-           }
-           if (ISSPACE(*buf)) {
-               if (VSTRING_LEN(state->header_buf) < var_header_limit) {
-                   VSTRING_ADDCH(state->header_buf, '\n');
-                   vstring_strcat(state->header_buf, buf);
-               } else
-                   state->errs |= CLEANUP_STAT_HOVFL;
-               state->prev_header_type = type;
+       if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
+           if (cleanup_act(state, "body", buf, value, VAR_BODY_CHECKS)
+               == CLEANUP_ACT_DROP)
                return;
-           }
        }
-
-       /*
-        * No more input to append to this saved header. Do output processing
-        * and reset the saved header buffer.
-        */
-       VSTRING_TERMINATE(state->header_buf);
-       cleanup_header(state);
-       VSTRING_RESET(state->header_buf);
-    }
-
-    /*
-     * Switch to body processing if this is not a header. Generate missing
-     * headers. Add one blank line when the message headers are immediately
-     * followed by a non-empty message body.
-     */
-    if (type == REC_TYPE_XTRA || !is_header(buf)) {
-       cleanup_missing_headers(state);
-       if (type != REC_TYPE_XTRA && *buf)      /* output blank line */
-           cleanup_out_string(state, REC_TYPE_NORM, "");
-       state->action = cleanup_message_body;
-       cleanup_message_body(state, type, buf, len);
-    }
-
-    /*
-     * Save this header record until we know that the header is complete.
-     */
-    else {
-       vstring_strcpy(state->header_buf, buf);
-       state->prev_header_type = type;
     }
+    cleanup_out(state, type, buf, len);
 }
 
-/* cleanup_message_body - process message segment, body */
+/* cleanup_message_headerbody - process message content, header and body */
 
-static void cleanup_message_body(CLEANUP_STATE *state, int type, char *buf, int len)
+static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
+                                              char *buf, int len)
 {
-    char   *myname = "cleanup_message_body";
+    char   *myname = "cleanup_message_headerbody";
 
     /*
-     * Copy body record to the output.
+     * Copy text record to the output.
      */
     if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) {
-
-       /*
-        * Crude message body content filter for emergencies. This code has
-        * several problems: it sees one line at a time, and therefore does
-        * not recognize multi-line MIME headers in the body; it looks at
-        * long lines only in chunks of line_length_limit (2048) characters;
-        * it is easily bypassed with encodings and with multi-line tricks.
-        */
-       if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_body_checks) {
-           const char *value;
-
-           if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
-               if (cleanup_act(state, "body", buf, value, VAR_BODY_CHECKS)
-                   == CLEANUP_ACT_DROP)
-                   return;
-           }
-       }
-       cleanup_out(state, type, buf, len);
+       state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
     }
 
     /*
+     * To avoid complications elsewhere, text must not end in REC_TYPE_CONT.
+     * 
      * If we have reached the end of the message content segment, record the
      * current file position so we can compute the message size lateron.
      */
     else if (type == REC_TYPE_XTRA) {
+       state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
+       state->mime_state = mime_state_free(state->mime_state);
        if ((state->xtra_offset = vstream_ftell(state->dst)) < 0)
            msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
        state->action = cleanup_extracted;
@@ -662,3 +612,33 @@ static void cleanup_message_body(CLEANUP_STATE *state, int type, char *buf, int
        state->errs |= CLEANUP_STAT_BAD;
     }
 }
+
+/* cleanup_message - initialize message content segment */
+
+void    cleanup_message(CLEANUP_STATE *state, int type, char *buf, int len)
+{
+    char   *myname = "cleanup_message";
+
+    /*
+     * Write a dummy start-of-content segment marker. We'll update it with
+     * real file offset information after reaching the end of the message
+     * content.
+     */
+    if ((state->mesg_offset = vstream_ftell(state->dst)) < 0)
+       msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+    cleanup_out_format(state, REC_TYPE_MESG, REC_TYPE_MESG_FORMAT, 0L);
+    if ((state->data_offset = vstream_ftell(state->dst)) < 0)
+       msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+
+    /*
+     * Pass control to the header processing routine.
+     */
+    state->mime_state = mime_state_alloc(MIME_OPT_REPORT_TRUNC_HEADER,
+                                        cleanup_header_callback,
+                                        cleanup_header_done_callback,
+                                        cleanup_body_callback,
+                                        (MIME_STATE_ANY_END) 0,
+                                        (void *) state);
+    state->action = cleanup_message_headerbody;
+    cleanup_message_headerbody(state, type, buf, len);
+}
index a68d274152b1677029c1c94aac04fd4e57eacd92..959038386d7beba7ee6d7587159bb5c28aa8b2fe 100644 (file)
 /*     void    cleanup_out(state, type, data, len)
 /*     CLEANUP_STATE *state;
 /*     int     type;
-/*     char    *data;
+/*     const char *data;
 /*     int     len;
 /*
 /*     void    cleanup_out_string(state, type, str)
 /*     CLEANUP_STATE *state;
 /*     int     type;
-/*     char    *str;
+/*     const char *str;
 /*
 /*     void    CLEANUP_OUT_BUF(state, type, buf)
 /*     CLEANUP_STATE *state;
@@ -28,7 +28,7 @@
 /*     void    cleanup_out_format(state, type, format, ...)
 /*     CLEANUP_STATE *state;
 /*     int     type;
-/*     char    *format;
+/*     const char *format;
 /* DESCRIPTION
 /*     This module writes records to the output stream.
 /*
@@ -85,7 +85,7 @@
 
 /* cleanup_out - output one single record */
 
-void    cleanup_out(CLEANUP_STATE *state, int type, char *string, int len)
+void    cleanup_out(CLEANUP_STATE *state, int type, const char *string, int len)
 {
     int     err = 0;
 
@@ -129,14 +129,14 @@ void    cleanup_out(CLEANUP_STATE *state, int type, char *string, int len)
 
 /* cleanup_out_string - output string to one single record */
 
-void    cleanup_out_string(CLEANUP_STATE *state, int type, char *string)
+void    cleanup_out_string(CLEANUP_STATE *state, int type, const char *string)
 {
     cleanup_out(state, type, string, strlen(string));
 }
 
 /* cleanup_out_format - output one formatted record */
 
-void    cleanup_out_format(CLEANUP_STATE *state, int type, char *fmt,...)
+void    cleanup_out_format(CLEANUP_STATE *state, int type, const char *fmt,...)
 {
     static VSTRING *vp;
     va_list ap;
index 6df5fb894e1856c7c83f8ecb03df21ff0c4bf024..f4a6a3c3f2e3cb1bd46515e5f0b8a809a1edc1e5 100644 (file)
@@ -44,6 +44,7 @@
 
 #include <been_here.h>
 #include <mail_params.h>
+#include <mime_state.h>
 
 /* Application-specific. */
 
@@ -71,9 +72,7 @@ CLEANUP_STATE *cleanup_state_alloc(void)
     state->flags = 0;
     state->errs = 0;
     state->err_mask = 0;
-    state->header_buf = vstring_alloc(100);
     state->headers_seen = 0;
-    state->prev_header_type = 0;
     state->hop_count = 0;
     state->recipients = argv_alloc(2);
     state->resent_recip = argv_alloc(2);
@@ -88,6 +87,8 @@ CLEANUP_STATE *cleanup_state_alloc(void)
     state->rcpt_count = 0;
     state->reason = 0;
     state->attr = nvtable_create(10);
+    state->mime_state = 0;
+    state->mime_errs = 0;
     return (state);
 }
 
@@ -111,7 +112,6 @@ void    cleanup_state_free(CLEANUP_STATE *state)
        myfree(state->return_receipt);
     if (state->errors_to)
        myfree(state->errors_to);
-    vstring_free(state->header_buf);
     argv_free(state->recipients);
     argv_free(state->resent_recip);
     if (state->queue_id)
@@ -120,5 +120,7 @@ void    cleanup_state_free(CLEANUP_STATE *state)
     if (state->reason)
        myfree(state->reason);
     nvtable_free(state->attr);
+    if (state->mime_state)
+       mime_state_free(state->mime_state);
     myfree((char *) state);
 }
index a267086ded1b390ff58601020e9ab238b9323af7..28d82fd4380fba128139160d8686f9f06c777543 100644 (file)
@@ -19,7 +19,7 @@ SRCS  = been_here.c bounce.c canon_addr.c cleanup_strerror.c clnt_stream.c \
        timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \
        tok822_resolve.c tok822_rewrite.c tok822_tree.c xtext.c bounce_log.c \
        flush_clnt.c mail_conf_time.c mbox_conf.c mbox_open.c abounce.c \
-       verp_sender.c match_parent_style.c
+       verp_sender.c match_parent_style.c mime_state.c header_token.c
 OBJS   = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \
        debug_peer.o debug_process.o defer.o deliver_completed.o \
        deliver_flock.o deliver_pass.o deliver_request.o domain_list.o \
@@ -40,7 +40,7 @@ OBJS  = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \
        timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \
        tok822_resolve.o tok822_rewrite.o tok822_tree.o xtext.o bounce_log.o \
        flush_clnt.o mail_conf_time.o mbox_conf.o mbox_open.o abounce.o \
-       verp_sender.o match_parent_style.o
+       verp_sender.o match_parent_style.o mime_state.o header_token.o
 HDRS   = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \
        config.h debug_peer.h debug_process.h defer.h deliver_completed.h \
        deliver_flock.h deliver_pass.h deliver_request.h domain_list.h \
@@ -57,7 +57,7 @@ HDRS  = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \
        rewrite_clnt.h sent.h smtp_stream.h split_addr.h string_list.h \
        sys_exits.h timed_ipc.h tok822.h xtext.h bounce_log.h flush_clnt.h \
        mbox_conf.h mbox_open.h abounce.h qmqp_proto.h verp_sender.h \
-       match_parent_style.h quote_flags.h
+       match_parent_style.h quote_flags.h mime_state.h header_token.h
 TESTSRC        = rec2stream.c stream2rec.c recdump.c
 WARN   = -W -Wformat -Wimplicit -Wmissing-prototypes \
        -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
@@ -70,7 +70,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
        mail_addr_map mail_date maps mynetworks mypwd namadr_list \
        off_cvt quote_822_local rec2stream recdump resolve_clnt \
        resolve_local rewrite_clnt stream2rec string_list tok822_parse \
-       quote_821_local mail_conf_time
+       quote_821_local mail_conf_time mime_state
 
 LIBS   = ../../lib/libutil.a
 LIB_DIR        = ../../lib
@@ -213,13 +213,23 @@ mail_conf_time: $(LIB) $(LIBS)
        $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
        mv junk $@.o
 
-tests: tok822_test
+mime_state: $(LIB) $(LIBS)
+       mv $@.o junk
+       $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+       mv junk $@.o
+
+tests: tok822_test mime_test
 
 tok822_test: tok822_parse tok822_parse.in tok822_parse.ref
        ./tok822_parse <tok822_parse.in >tok822_parse.tmp
        diff tok822_parse.ref tok822_parse.tmp
        rm -f tok822_parse.tmp
 
+mime_test: mime_state mime_test.in mime_test.ref
+       ./mime_state <mime_test.in >mime_test.tmp
+       diff  mime_test.ref mime_test.tmp
+       rm -f mime_test.tmp
+
 printfck: $(OBJS) $(PROG)
        rm -rf printfck
        mkdir printfck
@@ -441,7 +451,14 @@ header_opts.o: ../../include/msg.h
 header_opts.o: ../../include/htable.h
 header_opts.o: ../../include/vstring.h
 header_opts.o: ../../include/vbuf.h
+header_opts.o: ../../include/stringops.h
 header_opts.o: header_opts.h
+header_token.o: header_token.c
+header_token.o: ../../include/sys_defs.h
+header_token.o: ../../include/msg.h
+header_token.o: ../../include/vstring.h
+header_token.o: ../../include/vbuf.h
+header_token.o: header_token.h
 is_header.o: is_header.c
 is_header.o: ../../include/sys_defs.h
 is_header.o: is_header.h
@@ -766,6 +783,18 @@ mbox_open.o: ../../include/myflock.h
 mbox_open.o: mbox_conf.h
 mbox_open.o: ../../include/argv.h
 mbox_open.o: mbox_open.h
+mime_state.o: mime_state.c
+mime_state.o: ../../include/sys_defs.h
+mime_state.o: ../../include/mymalloc.h
+mime_state.o: ../../include/msg.h
+mime_state.o: ../../include/vstring.h
+mime_state.o: ../../include/vbuf.h
+mime_state.o: rec_type.h
+mime_state.o: is_header.h
+mime_state.o: header_opts.h
+mime_state.o: mail_params.h
+mime_state.o: header_token.h
+mime_state.o: mime_state.h
 mkmap_db.o: mkmap_db.c
 mkmap_db.o: ../../include/sys_defs.h
 mkmap_db.o: ../../include/msg.h
diff --git a/postfix/src/global/foobar b/postfix/src/global/foobar
new file mode 100644 (file)
index 0000000..7fd8486
--- /dev/null
@@ -0,0 +1,32 @@
+Delivered-To: wietse@porcupine.watson.ibm.com
+Received: from mailhub.watson.ibm.com (mailhub.watson.ibm.com [9.2.250.97])
+       by porcupine.watson.ibm.com (Postfix) with ESMTP for <wietse@porcupine.watson.ibm.com>
+       id 2F0FC188CE; Tue, 12 Jan 1999 15:08:46 -0500 (EST)
+Received: from wzv.watson.ibm.com (wzv.watson.ibm.com [9.2.84.53]) by mailhub.watson.ibm.com (8.8.7/Feb-20-98) with ESMTP id PAA07748 for <wietse@porcupine.watson.ibm.com>; Tue, 12 Jan 1999 15:08:45 -0500
+Received: by wzv.watson.ibm.com (Postfix, from userid 309)
+       id 9AC44827; Tue, 12 Jan 1999 15:08:45 -0500 (EST)
+Delivered-To: wietse@[9.2.84.53]
+From: wietse
+To: wietse
+Subject: testje
+MIME-Version: 1.0
+Content-Type: "multipart" ; boundary = "abcdef" foobar
+Status: RO
+
+prolog
+
+--abcdef
+
+part01
+
+--abcdef
+
+part02
+
+--abcdef
+
+part03
+
+--abcdef--
+
+epilog
index a31f651c6d01ce1a1e02360124624a5969f2d0bb..f826dd57e75179bde68556fdcf4f8d61e7d82f99 100644 (file)
@@ -38,6 +38,7 @@
 #include <msg.h>
 #include <htable.h>
 #include <vstring.h>
+#include <stringops.h>
 
 /* Global library. */
 
@@ -51,14 +52,19 @@ static HEADER_OPTS header_opts[] = {
     "Apparently-To", HDR_APPARENTLY_TO, HDR_OPT_RECIP,
     "Bcc", HDR_BCC, HDR_OPT_DROP | HDR_OPT_XRECIP,
     "Cc", HDR_CC, HDR_OPT_XRECIP,
-    "Content-Transfer-Encoding", HDR_CONTENT_TRANSFER_ENCODING, 0,
+    "Content-Description", HDR_CONTENT_DESCRIPTION, HDR_OPT_MIME,
+    "Content-Disposition", HDR_CONTENT_DISPOSITION, HDR_OPT_MIME,
+    "Content-ID", HDR_CONTENT_ID, HDR_OPT_MIME,
     "Content-Length", HDR_CONTENT_LENGTH, HDR_OPT_DROP,
+    "Content-Transfer-Encoding", HDR_CONTENT_TRANSFER_ENCODING, HDR_OPT_MIME,
+    "Content-Type", HDR_CONTENT_TYPE, HDR_OPT_MIME,
     "Delivered-To", HDR_DELIVERED_TO, 0,
     "Date", HDR_DATE, 0,
     "Errors-To", HDR_ERRORS_TO, HDR_OPT_SENDER,
     "From", HDR_FROM, HDR_OPT_SENDER,
     "Mail-Followup-To", HDR_MAIL_FOLLOWUP_TO, HDR_OPT_SENDER,
     "Message-Id", HDR_MESSAGE_ID, 0,
+    "MIME-Version", HDR_MIME_VERSION, HDR_OPT_MIME,
     "Received", HDR_RECEIVED, 0,
     "Reply-To", HDR_REPLY_TO, HDR_OPT_SENDER,
     "Resent-Bcc", HDR_RESENT_BCC, HDR_OPT_DROP | HDR_OPT_XRECIP | HDR_OPT_RR,
@@ -120,6 +126,9 @@ HEADER_OPTS *header_opts_find(const char *string)
            msg_panic("header_opts_find: no colon in header: %.30s", string);
        VSTRING_ADDCH(header_key, TOLOWER(*cp));
     }
+    vstring_truncate(header_key,
+                    trimblanks(vstring_str(header_key), cp - string)
+                    - vstring_str(header_key));
     VSTRING_TERMINATE(header_key);
     return ((HEADER_OPTS *) htable_find(header_hash, vstring_str(header_key)));
 }
index b4e0b83ddaa7da0b98318a09dc68890e3e5a278f..c5b8388c0716790ce6ad50bb6394d5c29b0edc39 100644 (file)
@@ -49,6 +49,10 @@ typedef struct {
 #define HDR_SENDER                     24
 #define HDR_TO                         25
 #define HDR_MAIL_FOLLOWUP_TO           26
+#define HDR_CONTENT_DESCRIPTION                27
+#define HDR_CONTENT_DISPOSITION                28
+#define HDR_CONTENT_ID                 29
+#define HDR_MIME_VERSION               30
 
  /*
   * Header flags.
@@ -58,6 +62,7 @@ typedef struct {
 #define HDR_OPT_RECIP  (1<<2)          /* recipient address */
 #define HDR_OPT_RR     (1<<3)          /* Resent- header */
 #define HDR_OPT_EXTRACT        (1<<4)          /* extract flag */
+#define HDR_OPT_MIME   (1<<5)          /* MIME header */
 
 #define HDR_OPT_XRECIP (HDR_OPT_RECIP | HDR_OPT_EXTRACT)
 
diff --git a/postfix/src/global/header_token.c b/postfix/src/global/header_token.c
new file mode 100644 (file)
index 0000000..4757458
--- /dev/null
@@ -0,0 +1,263 @@
+/*++
+/* NAME
+/*     header_token 3
+/* SUMMARY
+/*     mail header parser
+/* SYNOPSIS
+/*     #include <header_token.h>
+/*
+/*     typedef struct {
+/* .in +4
+/*         int     type;
+/*         const char *u.value;
+/*         /* ... */
+/* .in
+/*     } HEADER_TOKEN;
+/*
+/*     int     header_token(token, token_len, token_buffer, ptr,
+/*                             specials, terminator)
+/*     HEADER_TOKEN *token;
+/*     int     token_len;
+/*     VSTRING *token_buffer;
+/*     const char **ptr;
+/*     const char *specials;
+/*     int     terminator;
+/* DESCRIPTION
+/*     This module parses a mail header value (text after field-name:)
+/*     into tokens. The parser understands RFC 822 linear white space,
+/*     quoted-string, comment, control characters, and a set of
+/*     user-specified special characters.
+/*
+/*     A token type is one of the following:
+/* .IP HEADER_TOK_QSTRING
+/*     Quoted string as per RFC 822.
+/* .IP HEADER_TOK_TOKEN
+/*     Token as per RFC 822, and the special characters supplied by the
+/*     caller.
+/* .IP other
+/*     The value of a control character or special character.
+/* .PP
+/*     header_token() tokenizes the input and stops after a user-specified
+/*     terminator (ignoring all tokens that exceed the capacity of
+/*     the result storage), or when it runs out of space for the result.
+/*     The terminator is not stored. The result value is the number of
+/*     tokens stored, or -1 when the input was exhausted before any tokens
+/*     were found.
+/*
+/*     Arguments:
+/* .IP token
+/*     Result array of HEADER_TOKEN structures.
+/* .IP token_len
+/*     Length of the array of HEADER_TOKEN structures.
+/* .IP token_buffer
+/*     Storage for result token values.
+/* .IP ptr
+/*     Input/output read position. The input is a null-terminated string.
+/* .IP specials
+/*     Special characters according to the relevant RFC, or a
+/*     null pointer (default to the RFC 822 special characters).
+/*     This must include the optional terminator if one is specified.
+/* .IP terminator
+/*     The special character to stop after, or zero.
+/* BUGS
+/*     Eight-bit characters are not given special treatment.
+/* SEE ALSO
+/*     RFC 822 (ARPA Internet Text Messages)
+/* DIAGNOSTICS
+/*     Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <header_token.h>
+
+/* Application-specific. */
+
+ /*
+  * Special characters and linear white space, as per RFC 822.
+  */
+#define RFC822_SPECIALS        "()<>@,;:\\\".[]"
+#define RFC822_LWSP(ch) (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
+
+ /*
+  * Silly little macros.
+  */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
+
+/* header_token - parse out the next item in a message header */
+
+int     header_token(HEADER_TOKEN *token, int token_len,
+                            VSTRING *token_buffer, const char **ptr,
+                            const char *user_specials, int user_terminator)
+{
+    int     comment_level;
+    const unsigned char *cp;
+    int     len;
+    int     ch;
+    int     tok_count;
+    int     n;
+
+    /*
+     * Initialize.
+     */
+    VSTRING_RESET(token_buffer);
+    cp = CU_CHAR_PTR(*ptr);
+    tok_count = 0;
+    if (user_specials == 0)
+       user_specials = RFC822_SPECIALS;
+
+    /*
+     * Main parsing loop.
+     */
+    while ((ch = *cp) != 0 && (user_terminator != 0 || tok_count < token_len)) {
+       cp++;
+
+       /*
+        * Skip RFC 822 linear white space.
+        */
+       if (RFC822_LWSP(ch))
+           continue;
+
+       /*
+        * Terminator.
+        */
+       if (ch == user_terminator)
+           break;
+
+       /*
+        * Skip RFC 822 comment.
+        */
+       if (ch == '(') {
+           comment_level = 1;
+           while ((ch = *cp) != 0) {
+               cp++;
+               if (ch == '(') {                /* comments can nest! */
+                   comment_level++;
+               } else if (ch == ')') {
+                   if (--comment_level == 0)
+                       break;
+               } else if (ch == '\\') {
+                   if ((ch = *cp) == 0)
+                       break;
+                   cp++;
+               }
+           }
+           continue;
+       }
+
+       /*
+        * Copy quoted text according to RFC 822.
+        */
+       if (ch == '"') {
+           if (tok_count < token_len) {
+               token[tok_count].u.offset = LEN(token_buffer);
+               token[tok_count].type = HEADER_TOK_QSTRING;
+           }
+           while ((ch = *cp) != 0) {
+               cp++;
+               if (ch == '"')
+                   break;
+               if (ch == '\n') {               /* unfold */
+                   len = LEN(token_buffer);
+                   while (len > 0 && RFC822_LWSP(STR(token_buffer)[len - 1]))
+                       len--;
+                   if (len < LEN(token_buffer))
+                       vstring_truncate(token_buffer, len);
+                   continue;
+               }
+               if (ch == '\\') {
+                   if ((ch = *cp) == 0)
+                       break;
+                   cp++;
+               }
+               if (tok_count < token_len)
+                   VSTRING_ADDCH(token_buffer, ch);
+           }
+           if (tok_count < token_len) {
+               VSTRING_ADDCH(token_buffer, 0);
+               tok_count++;
+           }
+           continue;
+       }
+
+       /*
+        * Control, or special.
+        */
+       if (strchr(user_specials, ch) || ISCNTRL(ch)) {
+           if (tok_count < token_len) {
+               token[tok_count].u.offset = LEN(token_buffer);
+               token[tok_count].type = ch;
+               VSTRING_ADDCH(token_buffer, ch);
+               VSTRING_ADDCH(token_buffer, 0);
+               tok_count++;
+           }
+           continue;
+       }
+
+       /*
+        * Token.
+        */
+       else {
+           if (tok_count < token_len) {
+               token[tok_count].u.offset = LEN(token_buffer);
+               token[tok_count].type = HEADER_TOK_TOKEN;
+               VSTRING_ADDCH(token_buffer, ch);
+           }
+           while ((ch = *cp) != 0 && !RFC822_LWSP(ch)
+                  && !ISCNTRL(ch) && !strchr(user_specials, ch)) {
+               cp++;
+               if (tok_count < token_len)
+                   VSTRING_ADDCH(token_buffer, ch);
+           }
+           if (tok_count < token_len) {
+               VSTRING_ADDCH(token_buffer, 0);
+               tok_count++;
+           }
+           continue;
+       }
+    }
+
+    /*
+     * Ignore a zero-length item after the last terminator.
+     */
+    if (tok_count == 0 && ch == 0)
+       return (-1);
+
+    /*
+     * Finalize. Fill in the string pointer array, now that the token buffer
+     * is no longer dynamically reallocated as it grows.
+     */
+    *ptr = (const char *) cp;
+    for (n = 0; n < tok_count; n++)
+       token[n].u.value = STR(token_buffer) + token[n].u.offset;
+
+    if (msg_verbose)
+       msg_info("header_token: %s %s %s",
+                tok_count > 0 ? token[0].u.value : "",
+                tok_count > 1 ? token[1].u.value : "",
+                tok_count > 2 ? token[2].u.value : "");
+
+    return (tok_count);
+}
diff --git a/postfix/src/global/header_token.h b/postfix/src/global/header_token.h
new file mode 100644 (file)
index 0000000..222d3d7
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef _HEADER_TOKEN_H_INCLUDED_
+#define _HEADER_TOKEN_H_INCLUDED_
+
+/*++
+/* NAME
+/*     header_token 3h
+/* SUMMARY
+/*     mail header parser
+/* SYNOPSIS
+/*     #include "header_token.h"
+ DESCRIPTION
+ .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * HEADER header parser tokens. Specials and controls are represented by
+  * themselves. Character pointers point to substrings in a token buffer.
+  */
+typedef struct HEADER_TOKEN {
+    int     type;                      /* see below */
+    union {
+       const char *value;              /* just a pointer, not a copy */
+       int     offset;                 /* index into token buffer */
+    }       u;                         /* indent beats any alternative */
+} HEADER_TOKEN;
+
+#define HEADER_TOK_TOKEN       256
+#define HEADER_TOK_QSTRING     257
+
+extern int header_token(HEADER_TOKEN *, int, VSTRING *, const char **, const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
index 5ed88fadd6eedb63aa927d6cbf65b5859632632b..bc21e4b2013404142de5eb012d1fd2b63619d2b5 100644 (file)
@@ -10,8 +10,8 @@
 /*     const char *string;
 /* DESCRIPTION
 /*     is_header() examines the given string and returns non-zero (true)
-/*     when it begins with a mail header name + colon. This routine
-/*     permits 8-bit data in header labels.
+/*     when it begins with a mail header name + optional space + colon.
+/*     The result is the length of the mail header name.
 /* STANDARDS
 /*     RFC 822 (ARPA Internet Text Messages)
 /* LICENSE
 
 int     is_header(const char *str)
 {
-    const char *cp;
+    const unsigned char *cp;
+    int     state;
     int     c;
+    int     len;
+
+#define INIT           0
+#define IN_CHAR                1
+#define IN_CHAR_SPACE  2
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
 
     /*
-     * XXX RFC 2822 Section 4.5.2, Obsolete header fields: whitespace may
+     * XXX RFC 2822 Section 4.5, Obsolete header fields: whitespace may
      * appear between header label and ":" (see: RFC 822, Section 3.4.2.).
-     * 
-     * The code below allows no such whitespace. This has never been a problem,
-     * and therefore we're not inclined to add code for it.
      */
-    for (cp = str; (c = *(unsigned char *) cp) != 0; cp++) {
-       if (c == ':')
-           return (cp > str);
-       if ( /* !ISASCII(c) || */ ISSPACE(c) || ISCNTRL(c))
-           break;
+    for (len = 0, state = INIT, cp = CU_CHAR_PTR(str); (c = *cp) != 0; cp++) {
+       switch (c) {
+       default:
+           if (!ISASCII(c) || ISCNTRL(c))
+               return (0);
+           if (state == INIT)
+               state = IN_CHAR;
+           if (state == IN_CHAR) {
+               len++;
+               continue;
+           }
+           return (0);
+       case ' ':
+       case '\t':
+           if (state == IN_CHAR)
+               state = IN_CHAR_SPACE;
+           if (state == IN_CHAR_SPACE)
+               continue;
+           return (0);
+       case ':':
+           return ((state == IN_CHAR || state == IN_CHAR_SPACE) ? len : 0);
+       }
     }
     return (0);
 }
diff --git a/postfix/src/global/is_header.c+ b/postfix/src/global/is_header.c+
new file mode 100644 (file)
index 0000000..9cd6144
--- /dev/null
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/*     is_header 3
+/* SUMMARY
+/*     message header classification
+/* SYNOPSIS
+/*     #include <is_header.h>
+/*
+/*     int     is_header(string)
+/*     const char *string;
+/* DESCRIPTION
+/*     is_header() examines the given string and returns non-zero (true)
+/*     when it begins with a mail header name + colon. This routine
+/*     permits 8-bit data in header labels.
+/* STANDARDS
+/*     RFC 822 (ARPA Internet Text Messages)
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Global library. */
+
+#include "is_header.h"
+
+/* is_header - determine if this can be a header line */
+
+int     is_header(const char *str)
+{
+    const char *cp;
+    int     state;
+    int     c;
+
+#define INITIAL                0
+#define IN_CHAR                1
+#define IN_CHAR_SPACE  2
+
+    /*
+     * XXX RFC 2822 Section 4.5, Obsolete header fields: whitespace may
+     * appear between header label and ":" (see: RFC 822, Section 3.4.2.).
+     * 
+     * The code below allows no such whitespace. This has never been a problem,
+     * and therefore we're not inclined to add code for it.
+     * 
+     * XXX It may, however, present another ambiguity with respect to finding
+     * attachment message headers. Sendmail rewrites obsolete forms.
+     */
+    for (state = INITIAL, cp = str; (c = *(unsigned char *) cp) != 0; cp++) {
+       if (c == ':')
+           return (state == IN_CHAR || state == IN_CHAR_SPACE);
+       if (c == ' ') {
+           if (state == IN_CHAR)
+               state = IN_CHAR_SPACE;
+           if (state != IN_CHAR_SPACE)
+               break;
+       }
+       if (!ISASCII(c) || ISCNTRL(c))
+           break;
+       if (state == INITIAL) state = IN_CHAR
+    }
+    return (0);
+}
index b17e099bb1d99d109458b8487d21e4e9debedc7a..e67c98a6e1613f57198a7c47ea17731bbd1d090c 100644 (file)
@@ -87,6 +87,9 @@
 /*     char   *var_flush_service;
 /*     int     var_db_create_buf;
 /*     int     var_db_read_buf;
+/*     int     var_mime_maxdepth;
+/*     int     var_mime_bound_len;
+/*     int     var_header_limit;
 /*
 /*     void    mail_params_init()
 /* DESCRIPTION
@@ -230,6 +233,9 @@ char   *var_error_service;
 char   *var_flush_service;
 int     var_db_create_buf;
 int     var_db_read_buf;
+int     var_mime_maxdepth;
+int     var_mime_bound_len;
+int     var_header_limit;
 
 #define MAIN_CONF_FILE "main.cf"
 
@@ -465,6 +471,9 @@ void    mail_params_init()
        VAR_FAULT_INJ_CODE, DEF_FAULT_INJ_CODE, &var_fault_inj_code, 0, 0,
        VAR_DB_CREATE_BUF, DEF_DB_CREATE_BUF, &var_db_create_buf, 1, 0,
        VAR_DB_READ_BUF, DEF_DB_READ_BUF, &var_db_read_buf, 1, 0,
+       VAR_HEADER_LIMIT, DEF_HEADER_LIMIT, &var_header_limit, 1, 0,
+       VAR_MIME_MAXDEPTH, DEF_MIME_MAXDEPTH, &var_mime_maxdepth, 1, 0,
+       VAR_MIME_BOUND_LEN, DEF_MIME_BOUND_LEN, &var_mime_bound_len, 1, 0,
        0,
     };
     static CONFIG_TIME_TABLE time_defaults[] = {
index 0385b336f9a279324967f0610d7be11901fb0cd7..be7c352b5aaa118b7286643be9c047e8fc1756cf 100644 (file)
@@ -984,6 +984,14 @@ extern int var_queue_minfree;
 #define DEF_HEADER_CHECKS      ""
 extern char *var_header_checks;
 
+#define VAR_MIMEHDR_CHECKS     "mime_header_checks"
+#define DEF_MIMEHDR_CHECKS     "$header_checks"
+extern char *var_mimehdr_checks;
+
+#define VAR_NESTHDR_CHECKS     "nested_header_checks"
+#define DEF_NESTHDR_CHECKS     "$header_checks"
+extern char *var_nesthdr_checks;
+
 #define VAR_BODY_CHECKS                "body_checks"
 #define DEF_BODY_CHECKS                ""
 extern char *var_body_checks;
@@ -1504,6 +1512,17 @@ extern int var_db_read_buf;
 #define DEF_QATTR_COUNT_LIMIT          100
 extern int var_qattr_count_limit;
 
+ /*
+  * MIME support.
+  */
+#define VAR_MIME_MAXDEPTH              "mime_nesting_limit"
+#define DEF_MIME_MAXDEPTH              100
+extern int var_mime_maxdepth;
+
+#define VAR_MIME_BOUND_LEN             "mime_boundary_length_limit"
+#define DEF_MIME_BOUND_LEN             100
+extern int var_mime_bound_len;
+
 /* LICENSE
 /* .ad
 /* .fi
index f948387a9e7d6243cd4cb8598b67a86f182b36e8..38286b9e3c69e51d188a724d9735f9db0ccb5b7a 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change the patchlevel and the release date. Snapshots change the
   * release date only, unless they include the same bugfix as a patch release.
   */
-#define MAIL_RELEASE_DATE      "20020514"
+#define MAIL_RELEASE_DATE      "20020524"
 
 #define VAR_MAIL_VERSION       "mail_version"
 #define DEF_MAIL_VERSION       "1.1.10-" MAIL_RELEASE_DATE
diff --git a/postfix/src/global/mime_state.c b/postfix/src/global/mime_state.c
new file mode 100644 (file)
index 0000000..e0b223b
--- /dev/null
@@ -0,0 +1,1061 @@
+/*++
+/* NAME
+/*     mime_state 3
+/* SUMMARY
+/*     MIME parser state machine
+/* SYNOPSIS
+/*     #include <mime_state.h>
+/*
+/*     MIME_STATE *mime_state_alloc(flags, head_out, body_out, context)
+/*     int     flags;
+/*     void    (*head_out)(void *ptr, int header_class,
+/*                             HEADER_OPTS *header_info, VSTRING *buf);
+/*     void    (*head_end)(void *ptr);
+/*     void    (*body_out)(void *ptr, int rec_type,
+/*                             const char *buf, int len);
+/*     void    (*body_end)(void *ptr);
+/*     void    *context;
+/*
+/*     int     mime_state_update(state, rec_type, buf, len)
+/*     MIME_STATE *state;
+/*     int     rec_type;
+/*     const char *buf;
+/*     int     len;
+/*
+/*     MIME_STATE *mime_state_free(state)
+/*     MIME_STATE *state;
+/*
+/*     const char *mime_state_error(error_code)
+/*     int     error_code;
+/* DESCRIPTION
+/*     This module implements a one-pass MIME processor with optional
+/*     8-bit to quoted-printable conversion.
+/*
+/*     In order to fend off denial of service attacks, message headers
+/*     are truncated at or above var_header_limit bytes, message boundary
+/*     strings are truncated at var_boundary_len bytes, and the message
+/*     nesting level is limited to var_mime_maxdepth levels.
+/*
+/*     mime_state_alloc() creates a MIME state machine. The machine
+/*     is delivered in its initial state, expecting content type
+/*     text/plain, 7-bit data.
+/*
+/*     mime_state_update() updates the MIME state machine according
+/*     to the input record type and the record content.
+/*     The result value is the bit-wise OR of zero or more of the following:
+/* .IP MIME_ERR_TRUNC_HEADER
+/*     A message header was longer than var_header_limit bytes.
+/* .IP MIME_ERR_NESTING
+/*     The MIME structure was nested more than var_mime_maxdepth levels.
+/* .IP MIME_ERR_8BIT_IN_HEADER
+/*     A message header contains 8-bit data. This is always illegal.
+/* .IP MIME_ERR_8BIT_IN_7BIT_BODY
+/*     A MIME header specifies (or defaults to) 7-bit content, but the
+/*     correspnding message body or body parts contain 8-bit content.
+/* .IP MIME_ERR_DOMAIN_ENCODING
+/*     An entity of type "message" or "multipart" specifies the wrong
+/*     content transfer encoding domain, or specifies a transformation
+/*     (quoted-printable, base64) instead of a domain (7bit, 8bit,
+/*     or binary).
+/* .PP
+/*     mime_state_free() releases storage for a MIME state machine,
+/*     and conveniently returns a null pointer.
+/*
+/*     mime_state_error() returns a string representation for the
+/*     specified error code. When multiple errors are specified it
+/*     reports what it deems the most serious one.
+/*
+/*     Arguments:
+/* .IP body_out
+/*     The output routine for body lines. It receives unmodified input
+/*     records, or the result of 8-bit -> 7-bit conversion.
+/* .IP body_end
+/*     A null pointer, or a pointer to a routine that is called after
+/*     the last input record is processed.
+/* .IP buf
+/*     Buffer with the content of a logical or physical message record.
+/* .IP context
+/*     Caller context that is passed on to the head_out and body_out
+/*     routines.
+/* .IP enc_type
+/*     The content encoding: MIME_ENC_7BIT or MIME_ENC_8BIT.
+/* .IP flags
+/*     Processing options. Specify the bit-wise OR of zero or more
+/*     of the following:
+/* .RS
+/* .IP MIME_OPT_DISABLE_MIME
+/*     Pay no attention to Content-* message headers, and switch to
+/*     message body state at the end of the primary message headers.
+/* .IP MIME_OPT_REPORT_TRUNC_HEADER
+/*     Report errors that set the MIME_ERR_TRUNC_HEADER error flag
+/*     (see above).
+/* .IP MIME_OPT_REPORT_8BIT_IN_HEADER
+/*     Report errors that set the MIME_ERR_8BIT_IN_7BIT_BODY error
+/*     flag (see above). This rarely stops legitimate mail.
+/* .IP MIME_OPT_REPORT_8BIT_IN_7BIT_BODY
+/*     Report errors that set the MIME_ERR_8BIT_IN_7BIT_BODY error
+/*     flag (see above). This currently breaks Majordomo mail that is
+/*     forwarded for approval, because Majordomo does not propagate
+/*     MIME type information from the enclosed message to the message
+/*     headers of the request for approval.
+/* .IP MIME_OPT_REPORT_DOMAIN_ENCODING
+/*     Report errors that set the MIME_ERR_DOMAIN_ENCODING error
+/*     flag (see above).
+/* .IP MIME_OPT_RECURSE_ALL_MESSAGE
+/*     Recurse into message/anything types other than message/rfc822.
+/*     This feature can detect "bad" information in headers of
+/*     message/partial and message/external-body types. It must
+/*     not be used with 8-bit -> 7-bit MIME transformations.
+/* .IP MIME_OPT_DOWNGRADE
+/*     Transform content that claims to be 8-bit into quoted-printable.
+/*     Where appropriate, update Content-Transfer-Encoding: message
+/*     headers.
+/* .RE
+/* .sp
+/*     For convenience, MIME_OPT_NONE requests no special processing.
+/* .IP header_class
+/*     Specifies where a message header is located.
+/* .RS
+/* .IP MIME_HDR_PRIMARY
+/*     In the primary message header section.
+/* .IP MIME_HDR_MULTIPART
+/*     In the header section after a multipart boundary string.
+/* .IP MIME_HDR_NESTED
+/*     At the start of a nested (e.g., message/rfc822) message.
+/* .RE
+/* .sp
+/*     To find out if something is a MIME header at the beginning
+/*     of an RFC 822 message, look at the header_info argument.
+/* .IP header_info
+/*     Null pointer or information about the message header, see
+/*     header_opts(3).
+/* .IP head_out
+/*     The output routine that is invoked for outputting a message header.
+/*     A multi-line header is passed as one chunk of text with embedded
+/*     newlines.
+/*     It is the responsibility of the output routine to break the text
+/*     at embedded newlines, and to break up long text between newlines
+/*     into multiple output records.
+/*     Note: an output routine is explicitly allowed to modify the text.
+/* .IP head_end
+/*     A null pointer, or a pointer to a routine that is called after
+/*     the last message header in the first header block is processed.
+/* .IP len
+/*     Length of non-VSTRING input buffer.
+/* .IP rec_type
+/*     The input record type as defined in rec_type(3h). State is
+/*     updated for text records (REC_TYPE_NORM or REC_TYPE_CONT).
+/*     Some input records are stored internally in order to reconstruct
+/*     multi-line input.  Upon receipt of any non-text record type, all
+/*     stored input is flushed and the state is set to "body".
+/* .IP state
+/*     MIME parser state created with mime_state_alloc().
+/* BUGS
+/*     Different mail user agents treat malformed message boundary
+/*     strings in different ways. The Postfix MIME processor cannot
+/*     be bug-compatible with everything.
+/*
+/*     This module will not glue together multipart boundary strings that
+/*     span multiple input records.
+/*
+/*     This module will not glue together RFC 2231 formatted (boundary)
+/*     parameter values. RFC 2231 says claims compatibility with existing
+/*     MIME processors.
+/*
+/*     The "8-bit data inside 7-bit body" test is myopic. It is not aware
+/*     of the enclosing message or multipart encoding information.
+/*
+/*     If the input ends in data other than a hard line break, this module
+/*     will add a hard line break. No line break is added to empty input.
+/* SEE ALSO
+/*     msg(3) diagnostics interface
+/*     header_opts(3) header information lookup
+/*     RFC 822 (ARPA Internet Text Messages)
+/*     RFC 2045 (MIME: Format of internet message bodies)
+/*     RFC 2046 (MIME: Media types)
+/* DIAGNOSTICS
+/*     Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/*     This code was implemented from scratch after reading the RFC
+/*     documents. This was a relatively straightforward effort with
+/*     few if any surprises. Victor Duchovny of Morgan Stanley shared
+/*     his experiences with ambiguities in real-life MIME implementations.
+/*     Liviu Daia of the Romanian Academy shared his insights in some
+/*     of the darker corners.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <rec_type.h>
+#include <is_header.h>
+#include <header_opts.h>
+#include <mail_params.h>
+#include <header_token.h>
+#include <mime_state.h>
+
+/* Application-specific. */
+
+ /*
+  * Mime parser stack element for multipart content.
+  */
+typedef struct MIME_STACK {
+    int     def_ctype;                 /* default content type */
+    int     def_stype;                 /* default content subtype */
+    char   *boundary;                  /* boundary string */
+    int     bound_len;                 /* boundary length */
+    struct MIME_STACK *next;           /* linkage */
+} MIME_STACK;
+
+ /*
+  * Mime parser state.
+  */
+#define MIME_MAX_TOKEN         3       /* tokens per attribute */
+
+struct MIME_STATE {
+
+    /*
+     * Volatile members.
+     */
+    int     curr_state;                        /* header/body state */
+    int     curr_ctype;                        /* last or default content type */
+    int     curr_stype;                        /* last or default content subtype */
+    int     curr_encoding;             /* last or default content encoding */
+    int     curr_domain;               /* last or default encoding unit */
+    VSTRING *output_buffer;            /* headers, quoted-printable body */
+    int     prev_rec_type;             /* previous input record type */
+    int     nesting_level;             /* safety */
+    MIME_STACK *stack;                 /* for composite types */
+    HEADER_TOKEN token[MIME_MAX_TOKEN];        /* header token array */
+    VSTRING *token_buffer;             /* header parser scratch buffer */
+    int     err_flags;                 /* processing errors */
+
+    /*
+     * Static members.
+     */
+    int     static_flags;              /* static processing options */
+    MIME_STATE_HEAD_OUT head_out;      /* header output routine */
+    MIME_STATE_ANY_END head_end;       /* end of prinary header routine */
+    MIME_STATE_BODY_OUT body_out;      /* body output routine */
+    MIME_STATE_ANY_END body_end;       /* end of body output routine */
+    void   *app_context;               /* application context */
+};
+
+ /*
+  * Content types and subtypes that we care about, either because we have to,
+  * or because we want to filter out broken MIME messages.
+  */
+#define MIME_CTYPE_OTHER       0
+#define MIME_CTYPE_TEXT                1
+#define MIME_CTYPE_MESSAGE     2
+#define MIME_CTYPE_MULTIPART   3
+
+#define MIME_STYPE_OTHER       0
+#define MIME_STYPE_PLAIN       1
+#define MIME_STYPE_RFC822      2
+#define MIME_STYPE_PARTIAL     3
+#define MIME_STYPE_EXTERN_BODY 4
+
+ /*
+  * MIME parser states. We steal from the public interface.
+  */
+#define MIME_STATE_PRIMARY     MIME_HDR_PRIMARY        /* primary headers */
+#define MIME_STATE_MULTIPART   MIME_HDR_MULTIPART      /* after --boundary */
+#define MIME_STATE_NESTED      MIME_HDR_NESTED /* message/rfc822 */
+#define MIME_STATE_BODY                (MIME_HDR_NESTED + 1)
+
+#define SET_MIME_STATE(ptr, state, ctype, stype, encoding, domain) do { \
+       (ptr)->curr_state = (state); \
+       (ptr)->curr_ctype = (ctype); \
+       (ptr)->curr_stype = (stype); \
+       (ptr)->curr_encoding = (encoding); \
+       (ptr)->curr_domain = (domain); \
+    } while (0)
+
+ /*
+  * MIME encodings and domains. We intentionally use the same codes for
+  * encodings and domains, so that we can easily find out whether a content
+  * transfer encoding header specifies a domain or whether it specifies
+  * domain+encoding, which is illegal for multipart/any and message/any.
+  */
+typedef struct MIME_ENCODING {
+    const char *name;                  /* external representation */
+    int     encoding;                  /* internal representation */
+    int     domain;                    /* subset of encoding */
+} MIME_ENCODING;
+
+#define MIME_ENC_QP            1       /* encoding + domain */
+#define MIME_ENC_BASE64                2       /* encoding + domain */
+ /* These are defined in mime_state.h as part of the external interface. */
+#ifndef MIME_ENC_7BIT
+#define MIME_ENC_7BIT          7       /* domain only */
+#define MIME_ENC_8BIT          8       /* domain only */
+#define MIME_ENC_BINARY                9       /* domain only */
+#endif
+
+ /*
+  * Silly Little Macros.
+  */
+#define STR(x)         vstring_str(x)
+#define LEN(x)         VSTRING_LEN(x)
+#define END(x)         vstring_end(x)
+#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
+
+/* mime_state_push - push boundary onto stack */
+
+static void mime_state_push(MIME_STATE *state, int def_ctype, int def_stype,
+                                   const char *boundary)
+{
+    MIME_STACK *stack;
+
+    /*
+     * RFC 2046 mandates that a boundary string be up to 70 characters long.
+     * Some MTAs, including Postfix, include the fully-qualified MTA name
+     * which can be longer, so we are willing to handle boundary strings that
+     * exceed the RFC specification. We allow for message headers of up to
+     * var_header_limit characters. In order to avoid denial of service, we
+     * have to impose a configurable limit on the amount of text that we are
+     * willing to store as a boundary string. Despite this truncation way we
+     * will still correctly detect all intermediate boundaries and all the
+     * message headers that follow those boundaries.
+     */
+    if (state->nesting_level > var_mime_maxdepth) {
+       state->err_flags |= MIME_ERR_NESTING;
+    } else {
+       state->nesting_level += 1;
+       stack = (MIME_STACK *) mymalloc(sizeof(*stack));
+       stack->def_ctype = def_ctype;
+       stack->def_stype = def_stype;
+       if ((stack->bound_len = strlen(boundary)) > var_mime_bound_len)
+           stack->bound_len = var_mime_bound_len;
+       stack->boundary = mystrndup(boundary, stack->bound_len);
+       stack->next = state->stack;
+       state->stack = stack;
+       if (msg_verbose)
+           msg_info("PUSH boundary %s", stack->boundary);
+    }
+}
+
+/* mime_state_pop - pop boundary from stack */
+
+static void mime_state_pop(MIME_STATE *state)
+{
+    MIME_STACK *stack;
+
+    if ((stack = state->stack) == 0)
+       msg_panic("mime_state_pop: there is no stack");
+    if (msg_verbose)
+       msg_info("POP boundary %s", stack->boundary);
+    state->nesting_level -= 1;
+    state->stack = stack->next;
+    myfree(stack->boundary);
+    myfree((char *) stack);
+}
+
+/* mime_state_alloc - create MIME state machine */
+
+MIME_STATE *mime_state_alloc(int flags,
+                                    MIME_STATE_HEAD_OUT head_out,
+                                    MIME_STATE_ANY_END head_end,
+                                    MIME_STATE_BODY_OUT body_out,
+                                    MIME_STATE_ANY_END body_end,
+                                    void *context)
+{
+    MIME_STATE *state;
+
+    state = (MIME_STATE *) mymalloc(sizeof(*state));
+
+    /* Volatile members. */
+    state->err_flags = 0;
+    SET_MIME_STATE(state, MIME_STATE_PRIMARY,
+                  MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
+                  MIME_ENC_7BIT, MIME_ENC_7BIT);
+    state->output_buffer = vstring_alloc(100);
+    state->prev_rec_type = 0;
+    state->stack = 0;
+    state->token_buffer = vstring_alloc(1);
+
+    /* Static members. */
+    state->static_flags = flags;
+    state->head_out = head_out;
+    state->head_end = head_end;
+    state->body_out = body_out;
+    state->body_end = body_end;
+    state->app_context = context;
+    return (state);
+}
+
+/* mime_state_free - destroy MIME state machine */
+
+MIME_STATE *mime_state_free(MIME_STATE *state)
+{
+    vstring_free(state->output_buffer);
+    while (state->stack)
+       mime_state_pop(state);
+    if (state->token_buffer)
+       vstring_free(state->token_buffer);
+    myfree((char *) state);
+    return (0);
+}
+
+/* mime_state_content_type - process content-type header */
+
+static void mime_state_content_type(MIME_STATE *state,
+                                           HEADER_OPTS *header_info)
+{
+    const char *cp;
+    int     tok_count;
+    int     def_ctype;
+    int     def_stype;
+
+#define TOKEN_MATCH(tok, text) \
+    ((tok).type == HEADER_TOK_TOKEN && strcasecmp((tok).u.value, (text)) == 0)
+
+#define SKIP_HEADER_THRASH(cp) { while (ISSPACE(*cp)) cp++; cp++; }
+
+#define RFC2045_TSPECIALS      "()<>@,;:\\\"/[]?="
+
+#define PARSE_CONTENT_TYPE_HEADER(state, ptr) \
+    header_token(state->token, MIME_MAX_TOKEN, \
+       state->token_buffer, ptr, RFC2045_TSPECIALS, ';')
+
+    cp = STR(state->output_buffer) + strlen(header_info->name);
+    SKIP_HEADER_THRASH(cp);
+    if ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) > 0) {
+
+       /*
+        * text/whatever. Right now we don't really care if it is plain or
+        * not, but we may want to recognize subtypes later, and then this
+        * code can serve as an example.
+        */
+       if (TOKEN_MATCH(state->token[0], "text")) {
+           state->curr_ctype = MIME_CTYPE_TEXT;
+           if (tok_count >= 3
+               && state->token[1].type == '/'
+               && TOKEN_MATCH(state->token[2], "plain"))
+               state->curr_stype = MIME_STYPE_PLAIN;
+           else
+               state->curr_stype = MIME_STYPE_OTHER;
+           return;
+       }
+
+       /*
+        * message/whatever body parts start with another block of message
+        * headers that we may want to look at. The partial and external-body
+        * subtypes cannot be subjected to 8-bit -> 7-bit conversion, so we
+        * must properly recognize them.
+        */
+       if (TOKEN_MATCH(state->token[0], "message")) {
+           state->curr_ctype = MIME_CTYPE_MESSAGE;
+           state->curr_stype = MIME_STYPE_OTHER;
+           if (tok_count >= 3
+               && state->token[1].type == '/') {
+               if (TOKEN_MATCH(state->token[2], "rfc822"))
+                   state->curr_stype = MIME_STYPE_RFC822;
+               else if (TOKEN_MATCH(state->token[2], "partial"))
+                   state->curr_stype = MIME_STYPE_PARTIAL;
+               else if (TOKEN_MATCH(state->token[2], "external-body"))
+                   state->curr_stype = MIME_STYPE_EXTERN_BODY;
+           }
+           return;
+       }
+
+       /*
+        * multipart/digest has default content type message/rfc822,
+        * multipart/whatever has default content type text/plain.
+        */
+       if (TOKEN_MATCH(state->token[0], "multipart")) {
+           state->curr_ctype = MIME_CTYPE_MULTIPART;
+           if (tok_count >= 3
+               && state->token[1].type == '/'
+               && TOKEN_MATCH(state->token[2], "digest")) {
+               def_ctype = MIME_CTYPE_MESSAGE;
+               def_stype = MIME_STYPE_RFC822;
+           } else {
+               def_ctype = MIME_CTYPE_TEXT;
+               def_stype = MIME_STYPE_PLAIN;
+           }
+
+           /*
+            * Yes, this is supposed to capture multiple boundary strings,
+            * which are illegal and which could be used to hide content in
+            * an implementation dependent manner. The code below allows us
+            * to find embedded message headers as long as the sender uses
+            * only one of these same-level boundary strings.
+            * 
+            * Yes, this is supposed to ignore the boundary value type.
+            */
+           while ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) >= 0) {
+               if (tok_count >= 3
+                   && TOKEN_MATCH(state->token[0], "boundary")
+                   && state->token[1].type == '=')
+                   mime_state_push(state, def_ctype, def_stype,
+                                   state->token[2].u.value);
+           }
+       }
+       return;
+    }
+
+    /*
+     * other/whatever.
+     */
+    else {
+       state->curr_ctype = MIME_CTYPE_OTHER;
+       return;
+    }
+}
+
+/* mime_state_content_encoding - process content-transfer-encoding header */
+
+static void mime_state_content_encoding(MIME_STATE *state,
+                                               HEADER_OPTS *header_info)
+{
+    const char *cp;
+    static MIME_ENCODING code_map[] = {        /* RFC 2045 */
+       "7bit", MIME_ENC_7BIT, MIME_ENC_7BIT,   /* domain */
+       "8bit", MIME_ENC_8BIT, MIME_ENC_8BIT,   /* domain */
+       "binary", MIME_ENC_BINARY, MIME_ENC_BINARY,     /* domain */
+       "base64", MIME_ENC_BASE64, MIME_ENC_7BIT,       /* encoding */
+       "quoted-printable", MIME_ENC_QP, MIME_ENC_7BIT, /* encoding */
+       0,
+    };
+    MIME_ENCODING *cmp;
+
+#define PARSE_CONTENT_ENCODING_HEADER(state, ptr) \
+    header_token(state->token, 1, state->token_buffer, ptr, (char *) 0, 0)
+
+    /*
+     * Do content-transfer-encoding header. Never set the encoding domain to
+     * something other than 7bit, 8bit or binary, even if we don't recognize
+     * the input.
+     */
+    cp = STR(state->output_buffer) + strlen(header_info->name);
+    SKIP_HEADER_THRASH(cp);
+    if (PARSE_CONTENT_ENCODING_HEADER(state, &cp) > 0
+       && state->token[0].type == HEADER_TOK_TOKEN) {
+       for (cmp = code_map; cmp->name != 0; cmp++) {
+           if (strcasecmp(state->token[0].u.value, cmp->name) == 0) {
+               state->curr_encoding = cmp->encoding;
+               state->curr_domain = cmp->domain;
+               break;
+           }
+       }
+    }
+}
+
+/* mime_state_downgrade - convert 8-bit data to quoted-printable */
+
+static void mime_state_downgrade(MIME_STATE *state, int rec_type,
+                                        const char *text, int len)
+{
+    static char hexchars[] = "0123456789ABCDEF";
+    const unsigned char *cp;
+    int     ch = 0;
+
+#define QP_ENCODE(state, ch) { \
+       VSTRING_ADDCH(state->output_buffer, '='); \
+       VSTRING_ADDCH(state->output_buffer, hexchars[(ch >> 4) & 0xff]); \
+       VSTRING_ADDCH(state->output_buffer, hexchars[ch & 0xf]); \
+    }
+
+    /*
+     * Insert a soft line break when the output reaches a critical length
+     * before we reach the end of the input line.
+     */
+    for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) {
+       /* Critical length before the end of the input line. */
+       if (LEN(state->output_buffer) > 72) {
+           VSTRING_ADDCH(state->output_buffer, '=');
+           state->body_out(state->app_context, REC_TYPE_NORM,
+                           STR(state->output_buffer),
+                           LEN(state->output_buffer));
+           VSTRING_RESET(state->output_buffer);
+       }
+       /* Append the next character. */
+       ch = *cp;
+       if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) {
+           QP_ENCODE(state, ch);
+       } else {
+           VSTRING_ADDCH(state->output_buffer, ch);
+       }
+    }
+
+    /*
+     * Flush output after a hard line break (i.e. the end of a REC_TYPE_NORM
+     * record). Fix trailing whitespace as per the RFC: in the worst case,
+     * the output length will grow from 73 characters to 75 characters.
+     */
+    if (rec_type == REC_TYPE_NORM) {
+       if (ch == ' ' || ch == '\t') {
+           vstring_truncate(state->output_buffer,
+                            LEN(state->output_buffer) - 1);
+           QP_ENCODE(state, ch);
+       }
+       state->body_out(state->app_context, REC_TYPE_NORM,
+                       STR(state->output_buffer),
+                       LEN(state->output_buffer));
+       VSTRING_RESET(state->output_buffer);
+    }
+}
+
+/* mime_state_update - update MIME state machine */
+
+int     mime_state_update(MIME_STATE *state, int rec_type,
+                                 const char *text, int len)
+{
+    int     input_is_text = (rec_type == REC_TYPE_NORM
+                            || rec_type == REC_TYPE_CONT);
+    MIME_STACK *sp;
+    HEADER_OPTS *header_info;
+    const unsigned char *cp;
+
+#define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) { \
+       state->prev_rec_type = rec_type; \
+       return (state->err_flags); \
+    }
+
+    /*
+     * Be sure to flush any partial output line that might still be buffered
+     * up before taking any other "end of input" actions.
+     */
+    if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT)
+       mime_state_update(state, REC_TYPE_NORM, "", 0);
+
+    /*
+     * This message state machine is kept simple for the sake of robustness.
+     * Standards evolve over time, and we want to be able to correctly
+     * processes messages that are not yet defined. This state machine knows
+     * about headers and bodies, understands that multipart/whatever has
+     * multiple body parts with a header and body, and that message/whatever
+     * has message headers at the start of a body part.
+     */
+    switch (state->curr_state) {
+
+       /*
+        * First, deal with header information that we have accumulated from
+        * previous input records. Discard text that does not fit in a header
+        * buffer. Our limit is quite generous; Sendmail will refuse mail
+        * with only 32kbyte in all the message headers combined.
+        */
+    case MIME_STATE_PRIMARY:
+    case MIME_STATE_MULTIPART:
+    case MIME_STATE_NESTED:
+       if (LEN(state->output_buffer) > 0) {
+           if (input_is_text) {
+               if (state->prev_rec_type == REC_TYPE_CONT) {
+                   if (LEN(state->output_buffer) < var_header_limit) {
+                       vstring_strcat(state->output_buffer, text);
+                   } else {
+                       if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
+                           state->err_flags |= MIME_ERR_TRUNC_HEADER;
+                   }
+                   SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+               }
+               if (ISSPACE(*text)) {
+                   if (LEN(state->output_buffer) < var_header_limit) {
+                       vstring_strcat(state->output_buffer, "\n");
+                       vstring_strcat(state->output_buffer, text);
+                   } else {
+                       if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
+                           state->err_flags |= MIME_ERR_TRUNC_HEADER;
+                   }
+                   SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+               }
+           }
+
+           /*
+            * The input is (the beginning of) another message header, or is
+            * not a message header, or is not even a text record. With no
+            * more input to append to this saved header, do output
+            * processing and reset the saved header buffer. Hold on to the
+            * content transfer encoding header if we have to do a 8->7
+            * transformation, because the proper information depends on the
+            * content type header: message and multipart require a domain,
+            * leaf entities have either a transformation or a domain.
+            */
+           if (LEN(state->output_buffer) > 0) {
+               header_info = header_opts_find(STR(state->output_buffer));
+               if (!(state->static_flags & MIME_OPT_DISABLE_MIME)
+                   && header_info != 0) {
+                   if (header_info->type == HDR_CONTENT_TYPE)
+                       mime_state_content_type(state, header_info);
+                   if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING)
+                       mime_state_content_encoding(state, header_info);
+               }
+               if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0
+                   && (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) {
+                   for (cp = CU_CHAR_PTR(STR(state->output_buffer));
+                        cp < CU_CHAR_PTR(END(state->output_buffer)); cp++)
+                       if (*cp & 0200) {
+                           state->err_flags |= MIME_ERR_8BIT_IN_HEADER;
+                           break;
+                       }
+               }
+               /* Output routine is explicitly allowed to change the data. */
+               if (header_info == 0
+                   || header_info->type != HDR_CONTENT_TRANSFER_ENCODING
+                   || (state->static_flags & MIME_OPT_DOWNGRADE) == 0
+                   || state->curr_domain == MIME_ENC_7BIT)
+                   state->head_out(state->app_context, state->curr_state,
+                                   header_info, state->output_buffer);
+               state->prev_rec_type = 0;
+               VSTRING_RESET(state->output_buffer);
+           }
+       }
+
+       /*
+        * With past header information moved out of the way, proceed with a
+        * clean slate.
+        */
+       if (input_is_text) {
+
+           /*
+            * See if this input is (the beginning of) a message header.
+            * Normalize obsolete "name space colon" syntax to "name colon".
+            * Things would be too confusing otherwise.
+            */
+           if ((len = is_header(text)) > 0) {
+               vstring_strncpy(state->output_buffer, text, len);
+               for (text += len; ISSPACE(*text); text++)
+                    /* void */ ;
+               vstring_strcat(state->output_buffer, text);
+               SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+           }
+       }
+
+       /*
+        * This input terminates a block of message headers. When converting
+        * 8-bit to 7-bit mail, this is the right place to emit the correct
+        * content-transfer-encoding header. With message or multipart we
+        * specify 7bit, with leaf entities we specify quoted-printable.
+        * 
+        * We're not going to convert non-text data into base 64. If they send
+        * arbitrary binary data as 8-bit text, then the data is already
+        * broken beyond recovery, because the Postfix SMTP server sanitizes
+        * record boundaries, treating broken record boundaries as CRLF.
+        * 
+        * Clear the output buffer, we will need it for storage of the
+        * conversion result.
+        */
+       if ((state->static_flags & MIME_OPT_DOWNGRADE)
+           && state->curr_domain != MIME_ENC_7BIT) {
+           if (state->curr_ctype == MIME_CTYPE_MESSAGE
+               || state->curr_ctype == MIME_CTYPE_MULTIPART)
+               cp = CU_CHAR_PTR("7bit");
+           else
+               cp = CU_CHAR_PTR("quoted-printable");
+           vstring_sprintf(state->output_buffer,
+                           "Content-Transfer-Encoding: %s", cp);
+           state->head_out(state->app_context, state->curr_state,
+                           0, state->output_buffer);
+           VSTRING_RESET(state->output_buffer);
+       }
+
+       /*
+        * This input terminates a block of message headers. Call the
+        * optional header end routine at the end of the first header block.
+        */
+       if (state->curr_state == MIME_STATE_PRIMARY && state->head_end)
+           state->head_end(state->app_context);
+
+       /*
+        * This is the right place to check if the sender specified an
+        * appropriate identity encoding (7bit, 8bit, binary) for multipart
+        * and for message.
+        */
+       if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) {
+           if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
+               if (state->curr_stype == MIME_STYPE_PARTIAL
+                   || state->curr_stype == MIME_STYPE_EXTERN_BODY) {
+                   if (state->curr_domain != MIME_ENC_7BIT)
+                       state->err_flags |= MIME_ERR_ENCODING_DOMAIN;
+               } else {
+                   if (state->curr_encoding != state->curr_domain)
+                       state->err_flags |= MIME_ERR_ENCODING_DOMAIN;
+               }
+           } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
+               if (state->curr_encoding != state->curr_domain)
+                   state->err_flags |= MIME_ERR_ENCODING_DOMAIN;
+           }
+       }
+
+       /*
+        * Find out if the next body starts with its own message headers. In
+        * agressive mode, examine headers of partial and external-body
+        * messages. Otherwise, treat such headers as part of the "body". Set
+        * the proper encoding information for the multipart prolog.
+        */
+       if (input_is_text) {
+           if (*text == 0) {
+               if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
+                   if (state->curr_stype == MIME_STYPE_RFC822
+                   || (state->static_flags & MIME_OPT_RECURSE_ALL_MESSAGE))
+                       SET_MIME_STATE(state, MIME_STATE_NESTED,
+                                      MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
+                                      MIME_ENC_7BIT, MIME_ENC_7BIT);
+                   else
+                       state->curr_state = MIME_STATE_BODY;
+               } else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
+                   SET_MIME_STATE(state, MIME_STATE_BODY,
+                                  MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
+                                  MIME_ENC_7BIT, MIME_ENC_7BIT);
+               } else {
+                   state->curr_state = MIME_STATE_BODY;
+               }
+           }
+
+           /*
+            * Invalid input. Force output of one blank line and jump to the
+            * body state, leaving all other state alone.
+            */
+           else {
+               state->body_out(state->app_context, REC_TYPE_NORM, "", 0);
+               state->curr_state = MIME_STATE_BODY;
+           }
+       }
+
+       /*
+        * This input is not text. Go to body state, unconditionally.
+        */
+       else {
+           state->curr_state = MIME_STATE_BODY;
+       }
+       /* FALLTHROUGH */
+
+       /*
+        * Body text. Look for message boundaries, and recover from missing
+        * boundary strings. Missing boundaries can happen in agressive mode
+        * with text/rfc822-headers or with message/partial. Ignore non-space
+        * cruft after --boundary or --boundary--, because some MUAs do, and
+        * because only perverse software would take advantage of this to
+        * escape detection. We have to ignore trailing cruft anyway, because
+        * our saved copy of the boundary string may have been truncated for
+        * safety reasons.
+        * 
+        * Optionally look for 8-bit data in content that was announced as, or
+        * that defaults to, 7-bit. Unfortunately, we cannot turn this on by
+        * default. Majordomo sends requests for approval that do not
+        * propagate the MIME information from the enclosed message to the
+        * message headers of the approval request.
+        * 
+        * Set the proper state information after processing a message boundary
+        * string.
+        * 
+        * Don't look for boundary strings at the start of a continued record.
+        */
+    case MIME_STATE_BODY:
+       if (input_is_text) {
+           if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0
+               && state->curr_encoding == MIME_ENC_7BIT
+               && (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) {
+               for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++)
+                   if (*cp & 0200) {
+                       state->err_flags |= MIME_ERR_8BIT_IN_7BIT_BODY;
+                       break;
+                   }
+           }
+           if (state->stack && state->prev_rec_type != REC_TYPE_CONT
+               && text[0] == '-' && text[1] == '-') {
+               for (sp = state->stack; sp != 0; sp = sp->next) {
+                   if (strncmp(text + 2, sp->boundary, sp->bound_len) == 0) {
+                       while (sp != state->stack)
+                           mime_state_pop(state);
+                       if (strncmp(text + 2 + sp->bound_len, "--", 2) == 0) {
+                           mime_state_pop(state);
+                           SET_MIME_STATE(state, MIME_STATE_BODY,
+                                        MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
+                                          MIME_ENC_7BIT, MIME_ENC_7BIT);
+                       } else {
+                           SET_MIME_STATE(state, MIME_STATE_MULTIPART,
+                                          sp->def_ctype, sp->def_stype,
+                                          MIME_ENC_7BIT, MIME_ENC_7BIT);
+                       }
+                       break;
+                   }
+               }
+           }
+           /* Put last for consistency with header output routine. */
+           if ((state->static_flags & MIME_OPT_DOWNGRADE)
+               && state->curr_domain != MIME_ENC_7BIT)
+               mime_state_downgrade(state, rec_type, text, len);
+           else
+               state->body_out(state->app_context, rec_type, text, len);
+       }
+
+       /*
+        * The input is not a text record. Inform the application that this
+        * is the last opportunity to send any pending output.
+        */
+       else {
+           if (state->body_end)
+               state->body_end(state->app_context);
+       }
+       SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
+
+       /*
+        * Oops. This can't happen.
+        */
+    default:
+       msg_panic("mime_state_update: unknown state: %d", state->curr_state);
+    }
+}
+
+/* mime_state_error - error code to string */
+
+const char *mime_state_error(int error_code)
+{
+    if (error_code == 0)
+       msg_panic("mime_state_error: there is no error");
+    if (error_code & MIME_ERR_NESTING)
+       return ("MIME nesting exceeds safety limit");
+    if (error_code & MIME_ERR_TRUNC_HEADER)
+       return ("message header was truncated");
+    if (error_code & MIME_ERR_8BIT_IN_HEADER)
+       return ("improper use of 8-bit data in message header");
+    if (error_code & MIME_ERR_8BIT_IN_7BIT_BODY)
+       return ("improper use of 8-bit data in message body");
+    if (error_code & MIME_ERR_ENCODING_DOMAIN)
+       return ("invalid message/* or multipart/* encoding domain");
+    msg_panic("mime_state_error: unknown error code %d", error_code);
+}
+
+#ifdef TEST
+
+#include <stringops.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <rec_streamlf.h>
+
+ /*
+  * Stress test the REC_TYPE_CONT/NORM handling, but don't break header
+  * labels.
+  */
+/*#define REC_LEN      40*/
+
+#define REC_LEN        1024
+
+static void head_out(void *context, int class, HEADER_OPTS *unused_info,
+                            VSTRING *buf)
+{
+    VSTREAM *stream = (VSTREAM *) context;
+
+    vstream_fprintf(stream, "%s\t%s\n",
+                   class == MIME_HDR_PRIMARY ? "MAIN" :
+                   class == MIME_HDR_MULTIPART ? "MULT" :
+                   class == MIME_HDR_NESTED ? "NEST" :
+                   "ERROR", STR(buf));
+}
+
+static void head_end(void *context)
+{
+    VSTREAM *stream = (VSTREAM *) context;
+
+    vstream_fprintf(stream, "HEADER END\n");
+}
+
+static void body_out(void *context, int rec_type, const char *buf, int len)
+{
+    VSTREAM *stream = (VSTREAM *) context;
+
+    vstream_fprintf(stream, "BODY\t");
+    vstream_fwrite(stream, buf, len);
+    if (rec_type == REC_TYPE_NORM)
+       VSTREAM_PUTC('\n', stream);
+}
+
+static void body_end(void *context)
+{
+    VSTREAM *stream = (VSTREAM *) context;
+
+    vstream_fprintf(stream, "BODY END\n");
+}
+
+int     var_header_limit = DEF_HEADER_LIMIT;
+int     var_mime_maxdepth = DEF_MIME_MAXDEPTH;
+int     var_mime_bound_len = DEF_MIME_BOUND_LEN;
+
+int     main(int unused_argc, char **argv)
+{
+    int     rec_type;
+    int     last = 0;
+    VSTRING *buf;
+    MIME_STATE *state;
+    int     err;
+
+    /*
+     * Initialize.
+     */
+#define MIME_OPTIONS \
+           (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
+           | MIME_OPT_REPORT_8BIT_IN_HEADER \
+           | MIME_OPT_REPORT_ENCODING_DOMAIN \
+           | MIME_OPT_DOWNGRADE)
+
+    msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
+    msg_verbose = 1;
+    buf = vstring_alloc(10);
+    state = mime_state_alloc(MIME_OPTIONS,
+                            head_out, head_end,
+                            body_out, body_end,
+                            (void *) VSTREAM_OUT);
+
+    /*
+     * Main loop.
+     */
+    do {
+       rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
+       VSTRING_TERMINATE(buf);
+       err = mime_state_update(state, last = rec_type, STR(buf), LEN(buf));
+       vstream_fflush(VSTREAM_OUT);
+    } while (rec_type > 0);
+
+    /*
+     * Error reporting.
+     */
+    if (err & MIME_ERR_TRUNC_HEADER)
+       msg_warn("message header was truncated");
+    if (err & MIME_ERR_NESTING)
+       msg_warn("MIME nesting exceeds safety limit");
+    if (err & MIME_ERR_8BIT_IN_HEADER)
+       msg_warn("improper use of 8-bit data in message header");
+    if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
+       msg_warn("improper use of 8-bit data in message body");
+    if (err & MIME_ERR_ENCODING_DOMAIN)
+       msg_warn("improper message/* or multipart/* encoding domain");
+
+    /*
+     * Cleanup.
+     */
+    mime_state_free(state);
+    vstring_free(buf);
+    exit(0);
+}
+
+#endif
diff --git a/postfix/src/global/mime_state.h b/postfix/src/global/mime_state.h
new file mode 100644 (file)
index 0000000..28fd7a3
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef _MIME_STATE_H_INCLUDED_
+#define _MIME_STATE_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mime_state 3h
+/* SUMMARY
+/*     MIME parser state engine
+/* SYNOPSIS
+/*     #include "mime_state.h"
+ DESCRIPTION
+ .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <header_opts.h>
+
+ /*
+  * External interface. All MIME_STATE structure members are private.
+  */
+typedef struct MIME_STATE MIME_STATE;
+typedef void (*MIME_STATE_HEAD_OUT) (void *, int, HEADER_OPTS *, VSTRING *);
+typedef void (*MIME_STATE_BODY_OUT) (void *, int, const char *, int);
+typedef void (*MIME_STATE_ANY_END) (void *);
+
+extern MIME_STATE *mime_state_alloc(int, MIME_STATE_HEAD_OUT, MIME_STATE_ANY_END, MIME_STATE_BODY_OUT, MIME_STATE_ANY_END, void *);
+extern int mime_state_update(MIME_STATE *, int, const char *, int);
+extern MIME_STATE *mime_state_free(MIME_STATE *);
+extern const char *mime_state_error(int);
+
+ /*
+  * Processing options.
+  */
+#define MIME_OPT_NONE                          (0)
+#define MIME_OPT_DOWNGRADE                     (1<<0)
+#define MIME_OPT_REPORT_8BIT_IN_7BIT_BODY      (1<<1)
+#define MIME_OPT_REPORT_8BIT_IN_HEADER         (1<<2)
+#define MIME_OPT_REPORT_ENCODING_DOMAIN                (1<<3)
+#define MIME_OPT_RECURSE_ALL_MESSAGE           (1<<4)
+#define MIME_OPT_REPORT_TRUNC_HEADER           (1<<5)
+#define MIME_OPT_DISABLE_MIME                  (1<<6)
+
+ /*
+  * Body encoding domains.
+  */
+#define MIME_ENC_7BIT  (7)
+#define MIME_ENC_8BIT  (8)
+#define MIME_ENC_BINARY        (9)
+
+ /*
+  * Processing errors, not necessarily lethal.
+  */
+#define MIME_ERR_NESTING               (1<<0)
+#define MIME_ERR_TRUNC_HEADER          (1<<1)
+#define MIME_ERR_8BIT_IN_HEADER                (1<<2)
+#define MIME_ERR_8BIT_IN_7BIT_BODY     (1<<3)
+#define MIME_ERR_ENCODING_DOMAIN       (1<<4)
+
+ /*
+  * Header classes. Look at the header_opts argument to find out if something
+  * is a MIME header in a primary or nested section.
+  */
+#define MIME_HDR_PRIMARY       (1)     /* initial headers */
+#define MIME_HDR_MULTIPART     (2)     /* headers after multipart boundary */
+#define MIME_HDR_NESTED                (3)     /* attached message initial headers */
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/global/mime_test.in b/postfix/src/global/mime_test.in
new file mode 100644 (file)
index 0000000..54bcd8d
--- /dev/null
@@ -0,0 +1,38 @@
+subject: primary subject
+content-type : multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd 
+ ef" mumble
+
+abcdef prolog
+
+--abcd ef
+content-type: message/rfc822; mumble
+
+subject: nested subject
+content-type: multipart/mumble; boundary(comment)="pqrs"
+content-transfer-encoding: base64
+
+pqrs prolog
+
+--pqrs
+header: pqrs part 01
+
+body pqrs part 01
+
+--pqrs
+header: pqrs part 02
+
+body pqrs part 02
+
+--bogus-boundary
+header: wietse
+
+body asdasads
+
+--abcd ef
+header: abcdef part 02
+
+body abcdef part 02
+
+--abcd ef--
+
+epilog
diff --git a/postfix/src/global/mime_test.ref b/postfix/src/global/mime_test.ref
new file mode 100644 (file)
index 0000000..7cc26c9
--- /dev/null
@@ -0,0 +1,51 @@
+MAIN   subject: primary subject
+mime_state: header_token: multipart / mumble
+mime_state: header_token: boundary = abcd ef
+mime_state: PUSH boundary abcd ef
+MAIN   content-type: multipart/(co\m\)ment)mumble mumble; boundary = "ab\cd 
+ ef" mumble
+HEADER END
+BODY   
+BODY   abcdef prolog
+BODY   
+BODY   --abcd ef
+mime_state: header_token: message / rfc822
+MULT   content-type: message/rfc822; mumble
+BODY   
+NEST   subject: nested subject
+mime_state: header_token: multipart / mumble
+mime_state: header_token: boundary = pqrs
+mime_state: PUSH boundary pqrs
+NEST   content-type: multipart/mumble; boundary(comment)="pqrs"
+mime_state: header_token: base64  
+NEST   content-transfer-encoding: base64
+BODY   
+BODY   pqrs prolog
+BODY   
+BODY   --pqrs
+MULT   header: pqrs part 01
+BODY   
+BODY   body pqrs part 01
+BODY   
+BODY   --pqrs
+MULT   header: pqrs part 02
+BODY   
+BODY   body pqrs part 02
+BODY   
+BODY   --bogus-boundary
+BODY   header: wietse
+BODY   
+BODY   body asdasads
+BODY   
+mime_state: POP boundary pqrs
+BODY   --abcd ef
+MULT   header: abcdef part 02
+BODY   
+BODY   body abcdef part 02
+BODY   
+mime_state: POP boundary abcd ef
+BODY   --abcd ef--
+BODY   
+BODY   epilog
+BODY END
+mime_state: warning: improper message/* or multipart/* encoding domain
index 69f4b7e3e8d8e04a95cd866dcd6c744b7552d7c9..76bf95ab01a3ade574f4984e3997cbe7d66e126d 100644 (file)
@@ -25,7 +25,7 @@
 /*     This module implements record I/O on top of stream-lf files.
 /*
 /*     rec_streamlf_get() reads one record from the specified stream.
-/*     The result is not null-terminated and may contain embedded null
+/*     The result is null-terminated and may contain embedded null
 /*     characters.
 /*     The \fImaxlen\fR argument specifies an upper bound to the amount
 /*     of data read. The result is REC_TYPE_NORM when the record was
@@ -87,10 +87,13 @@ int     rec_streamlf_get(VSTREAM *stream, VSTRING *buf, int maxlen)
     while (n-- > 0) {
        if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF)
            return (VSTRING_LEN(buf) > 0 ? REC_TYPE_CONT : REC_TYPE_EOF);
-       if (ch == '\n')
+       if (ch == '\n') {
+           VSTRING_TERMINATE(buf);
            return (REC_TYPE_NORM);
+       }
        VSTRING_ADDCH(buf, ch);
     }
+    VSTRING_TERMINATE(buf);
     return (REC_TYPE_CONT);
 }
 
index 2fb41f1cf9c89282d0f0525c75aff715cd5eb712..b7b3ea62c06afd99608a9b6fb7ef55c75bbf7145 100644 (file)
@@ -173,6 +173,15 @@ char   *xfer_states[LMTP_STATE_LAST] = {
     "sending QUIT",
 };
 
+char   *xfer_request[LMTP_STATE_LAST] = {
+    "MAIL FROM command",
+    "RCPT TO command",
+    "DATA command",
+    "end of DATA command",
+    "final RSET command",
+    "QUIT command",
+};
+
 /* lmtp_lhlo - perform initial handshake with LMTP server */
 
 int     lmtp_lhlo(LMTP_STATE *state)
@@ -488,8 +497,10 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state)
                case LMTP_STATE_MAIL:
                    if (resp->code / 100 != 2) {
                        lmtp_mesg_fail(state, resp->code,
-                                      "host %s said: %s", session->namaddr,
-                                      translit(resp->str, "\n", " "));
+                                      "host %s said: %s (in reply to %s)",
+                                      session->namaddr,
+                                      translit(resp->str, "\n", " "),
+                                      xfer_request[LMTP_STATE_MAIL]);
                        mail_from_rejected = 1;
                    }
                    recv_state = LMTP_STATE_RCPT;
@@ -522,8 +533,10 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state)
                            survivors[nrcpt++] = recv_rcpt;
                        } else {
                            lmtp_rcpt_fail(state, resp->code, rcpt,
-                                      "host %s said: %s", session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                       "host %s said: %s (in reply to %s)",
+                                          session->namaddr,
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[LMTP_STATE_RCPT]);
                            rcpt->offset = 0;   /* in case deferred */
                        }
                    }
@@ -540,8 +553,10 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state)
                    if (resp->code / 100 != 3) {
                        if (nrcpt > 0)
                            lmtp_mesg_fail(state, resp->code,
-                                      "host %s said: %s", session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                       "host %s said: %s (in reply to %s)",
+                                          session->namaddr,
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[LMTP_STATE_DATA]);
                        nrcpt = -1;
                    }
                    recv_state = LMTP_STATE_DOT;
@@ -570,8 +585,10 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state)
                            }
                        } else {
                            lmtp_rcpt_fail(state, resp->code, rcpt,
-                                      "host %s said: %s", session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                      "host %s said: %s (in reply to %s)",
+                                          session->namaddr,
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[LMTP_STATE_DOT]);
                            rcpt->offset = 0;   /* in case deferred */
                        }
                    }
index fac420478627eaeb94a4c7cc5685a701f1738381..64642e4fb8cf54c92fb8b939722fe1cccbfbaf1d 100644 (file)
@@ -415,9 +415,12 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
                         message->queue_id, error_text, start);
                break;
            }
-           if (strcmp(name, MAIL_ATTR_ENCODING) == 0)
-               if (message->encoding == 0)
-                   message->encoding = mystrdup(value);
+           /* Allow extra segment to override envelope segment info. */
+           if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
+               if (message->encoding != 0)
+                   myfree(message->encoding);
+               message->encoding = mystrdup(value);
+           }
        } else if (rec_type == REC_TYPE_ERTO) {
            if (message->errors_to == 0) {
                message->errors_to = mystrdup(start);
index 29e013c26c50cb27305c56634deb6f4421cba1f0..fdde2aafbe5b3d04a85d6cf68e84df267c627343 100644 (file)
@@ -86,6 +86,9 @@
 /*     The output is a hashed file, named \fIfile_name\fB.db\fR.
 /*     This is available only on systems with support for \fBdb\fR databases.
 /* .PP
+/*     Use the command \fBpostconf -m\fR to find out what types of database
+/*     your Postfix installation can support.
+/*
 /*     When no \fIfile_type\fR is specified, the software uses the database
 /*     type specified via the \fBdatabase_type\fR configuration parameter.
 /*     The default value for this parameter depends on the host environment.
index 3eae38b0dc041f6907e7ea57fd681dedbbfe5cc3..2f9eeb06f2143bd290ea66a9c9e844d9f393a235 100644 (file)
 /* .RE
 /* .IP \fB-m\fR
 /*     List the names of all supported lookup table types.
+/* .RS
+/* .IP \fBbtree\fR
+/*     A sorted, balanced tree structure.
+/*     This is available only on systems with support for Berkeley DB
+/*     databases.
+/* .IP \fBdbm\fR
+/*     An indexed file type based on hashing.
+/*     This is available only on systems with support for DBM databases.
+/* .IP \fBenviron\fR
+/*     The UNIX process environment array. The lookup key is the variable
+/*     name. Originally implemented for testing, someone may find this
+/*     useful someday.
+/* .IP \fBhash\fR
+/*     An indexed file type based on hashing.
+/*     This is available only on systems with support for Berkeley DB
+/*     databases.
+/* .IP \fBldap\fR
+/*     Perform lookups using the LDAP protocol. This is described
+/*     in an LDAP_README file.
+/* .IP \fBpcre\fR
+/*     A lookup table based on Perl Compatible Regular Expressions. The
+/*     file format is described in \fBpcre_table\fR(5).
+/* .IP \fBregexp\fR
+/*     A lookup table based on regular expressions. The file format is
+/*     described in \fBregexp_table\fR(5).
+/* .IP \fBstatic\fR
+/*     A table that always returns the same result. For example,
+/*     \fBstatic:foobar\fR always returns the string \fBfoobar\fR.
+/* .IP \fBunix\fR
+/*     A limited way to query the UNIX authentication database. The
+/*     following tables are implemented:
+/* .RS
+/*. IP \fBunix:passwd.byname\fR
+/*     The table is the UNIX password database. The key is a login name.
+/*     The result is a password file entry in passwd(5) format.
+/* .IP \fBunix:group.byname\fR
+/*     The table is the UNIX group database. The key is a group name.
+/*     The result is a group file entry in group(5) format.
+/* .RE
+/* .RE
+/* .sp
+/*     Other table types may exist depending on how Postfix was built.
 /* .IP \fB-n\fR
 /*     Print non-default parameter settings only.
 /* .IP \fB-v\fR
index d9495e39e7d82be3411e77cdb105602af2812bd9..42df67e3d0a8b6b568e70f289d7480373fdad5e0 100644 (file)
 /*     The output file is a hashed file, named \fIfile_name\fB.db\fR.
 /*     This is available only on systems with support for \fBdb\fR databases.
 /* .PP
+/*     Use the command \fBpostconf -m\fR to find out what types of database
+/*     your Postfix installation can support.
+/*
 /*     When no \fIfile_type\fR is specified, the software uses the database
 /*     type specified via the \fBdatabase_type\fR configuration parameter.
 /* .RE
index 9433f87732ed818f337de7fd72998af884cd68c6..fe79bb5790e250e408ec6d820a137ef6075c8890 100644 (file)
@@ -305,9 +305,12 @@ static int qmgr_message_read(QMGR_MESSAGE *message)
                         message->queue_id, error_text, start);
                break;
            }
-           if (strcmp(name, MAIL_ATTR_ENCODING) == 0)
-               if (message->encoding == 0)
-                   message->encoding = mystrdup(value);
+           /* Allow extra segment to override envelope segment info. */
+           if (strcmp(name, MAIL_ATTR_ENCODING) == 0) {
+               if (message->encoding != 0)
+                   myfree(message->encoding);
+               message->encoding = mystrdup(value);
+           }
        } else if (rec_type == REC_TYPE_ERTO) {
            if (message->errors_to == 0)
                message->errors_to = mystrdup(start);
index 162610bfc78ed9ea3e6b7d72fc2a30c946ea89a7..e0cf6834f0229c7b2866d3842af169aff50f846f 100644 (file)
@@ -376,7 +376,7 @@ static void qmqpd_write_content(QMQPD_STATE *state)
        if (first) {
            if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
                rec_fprintf(state->cleanup, rec_type,
-                           "Mailbox-Line: %*s", len, start);
+                           "X-Mailbox-Line: %*s", len, start);
                continue;
            }
            first = 0;
index d1cbbcfd82ddcfa310d978259d36fbf3d4bcc626..40011cf33ab8bba7c3d3163074ab2c2e52b00f46 100644 (file)
@@ -152,6 +152,7 @@ smtp_proto.o: ../../include/vstring_vstream.h
 smtp_proto.o: ../../include/stringops.h
 smtp_proto.o: ../../include/mymalloc.h
 smtp_proto.o: ../../include/iostuff.h
+smtp_proto.o: ../../include/split_at.h
 smtp_proto.o: ../../include/mail_params.h
 smtp_proto.o: ../../include/smtp_stream.h
 smtp_proto.o: ../../include/mail_queue.h
@@ -169,6 +170,8 @@ smtp_proto.o: ../../include/quote_821_local.h
 smtp_proto.o: ../../include/quote_flags.h
 smtp_proto.o: ../../include/mail_proto.h
 smtp_proto.o: ../../include/attr.h
+smtp_proto.o: ../../include/mime_state.h
+smtp_proto.o: ../../include/header_opts.h
 smtp_proto.o: smtp.h
 smtp_proto.o: ../../include/argv.h
 smtp_proto.o: smtp_sasl.h
@@ -224,6 +227,8 @@ smtp_state.o: ../../include/vstring.h
 smtp_state.o: ../../include/vbuf.h
 smtp_state.o: ../../include/vstream.h
 smtp_state.o: ../../include/mail_conf.h
+smtp_state.o: ../../include/mime_state.h
+smtp_state.o: ../../include/header_opts.h
 smtp_state.o: smtp.h
 smtp_state.o: ../../include/argv.h
 smtp_state.o: ../../include/deliver_request.h
index a25d7eb4da7b77d415b9a1a03eb79f4efd50b0c4..d0d184dfba8596d0ed60604340e1f601a02c4322 100644 (file)
 /*     run chrooted at fixed low privilege.
 /* STANDARDS
 /*     RFC 821 (SMTP protocol)
+/*     RFC 822 (ARPA Internet Text Messages)
 /*     RFC 1651 (SMTP service extensions)
 /*     RFC 1652 (8bit-MIME transport)
 /*     RFC 1870 (Message Size Declaration)
+/*     RFC 2045 (MIME: Format of Internet Message Bodies)
+/*     RFC 2046 (MIME: Media Types)
 /*     RFC 2197 (Pipelining)
 /*     RFC 2554 (AUTH command)
 /*     RFC 2821 (SMTP protocol)
@@ -328,6 +331,7 @@ static int deliver_message(DELIVER_REQUEST *request)
            && (state->error_mask & name_mask(VAR_NOTIFY_CLASSES,
                                     mail_error_masks, var_notify_classes)))
            smtp_chat_notify(state);
+       /* XXX smtp_xfer() may abort in the middle of DATA. */
        smtp_session_free(state->session);
        debug_peer_restore();
     }
index 81990e8a2f5fec6454c8deae88d9ca06435801bd..a3baeba6ccc1c626a4eab89a3350336a51215275 100644 (file)
@@ -53,6 +53,8 @@ typedef struct SMTP_STATE {
     sasl_callback_t *sasl_callbacks;   /* stateful callbacks */
 #endif
     off_t   size_limit;                        /* server limit or unknown */
+    int     space_left;                        /* output length control */
+    struct MIME_STATE *mime_state;     /* mime state machine */
 } SMTP_STATE;
 
 #define SMTP_FEATURE_ESMTP     (1<<0)
index d33a90e4e0454931d72d45fe401dd557d9788f23..cbd81a2656289da1272a5d7c7c3dcd7d217c8e2f 100644 (file)
@@ -149,6 +149,14 @@ void    smtp_chat_cmd(SMTP_STATE *state, char *fmt,...)
      * Send the command to the SMTP server.
      */
     smtp_fputs(STR(state->buffer), LEN(state->buffer), session->stream);
+
+    /*
+     * Flush unsent output if no I/O happened for a while. This avoids
+     * timeouts with pipelined SMTP sessions that have lots of delays
+     * (typically, DNS lookups for sender/recipient unaliasing).
+     */
+    if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
+       vstream_fflush(session->stream);
 }
 
 /* smtp_chat_resp - read and process SMTP server response */
index e52e3c37d9a624f207f306a26b9d697a5918448f..df089ee179772769caf9570ea2ac7fe0d63e81bd 100644 (file)
@@ -83,6 +83,7 @@
 #include <stringops.h>
 #include <mymalloc.h>
 #include <iostuff.h>
+#include <split_at.h>
 
 /* Global library. */
 
 #include <mark_corrupt.h>
 #include <quote_821_local.h>
 #include <mail_proto.h>
+#include <mime_state.h>
 
 /* Application-specific. */
 
@@ -147,6 +149,15 @@ char   *xfer_states[SMTP_STATE_LAST] = {
     "sending QUIT",
 };
 
+char   *xfer_request[SMTP_STATE_LAST] = {
+    "MAIL FROM command",
+    "RCPT TO command",
+    "DATA command",
+    "end of DATA command",
+    "final RSET command",
+    "QUIT command",
+};
+
 /* smtp_helo - perform initial handshake with SMTP server */
 
 int     smtp_helo(SMTP_STATE *state)
@@ -273,6 +284,64 @@ int     smtp_helo(SMTP_STATE *state)
     return (0);
 }
 
+/* smtp_text_out - output one header/body record */
+
+static void smtp_text_out(void *context, int rec_type,
+                                 const char *text, int len)
+{
+    SMTP_STATE *state = (SMTP_STATE *) context;
+    SMTP_SESSION *session = state->session;
+    int     data_left;
+    const char *data_start;
+
+    /*
+     * Deal with an impedance mismatch between Postfix queue files (record
+     * length <= $message_line_length_limit) and SMTP (DATA record length <=
+     * $smtp_line_length_limit). The code below does a little too much work
+     * when the SMTP line length limit is disabled, but it avoids code
+     * duplication, and thus, it avoids testing and maintenance problems.
+     */
+    data_left = len;
+    data_start = text;
+    do {
+       if (var_smtp_line_limit > 0 && data_left >= state->space_left) {
+           smtp_fputs(data_start, state->space_left, session->stream);
+           data_start += state->space_left;
+           data_left -= state->space_left;
+           state->space_left = var_smtp_line_limit;
+           if (data_left > 0 || rec_type == REC_TYPE_CONT) {
+               smtp_fputc(' ', session->stream);
+               state->space_left -= 1;
+           }
+       } else {
+           if (rec_type == REC_TYPE_CONT) {
+               smtp_fwrite(data_start, data_left, session->stream);
+               state->space_left -= data_left;
+           } else {
+               smtp_fputs(data_start, data_left, session->stream);
+               state->space_left = var_smtp_line_limit;
+           }
+           break;
+       }
+    } while (data_left > 0);
+}
+
+/* smtp_header_out - output one message header */
+
+static void smtp_header_out(void *context, int unused_header_class,
+                                   HEADER_OPTS *unused_info, VSTRING *buf)
+{
+    char   *start = vstring_str(buf);
+    char   *line;
+    char   *next_line;
+
+    for (line = start; line; line = next_line) {
+       next_line = split_at(line, '\n');
+       smtp_text_out(context, REC_TYPE_NORM, line, next_line ?
+                     next_line - line - 1 : strlen(line));
+    }
+}
+
 /* smtp_xfer - send a batch of envelope information and the message data */
 
 int     smtp_xfer(SMTP_STATE *state)
@@ -297,9 +366,8 @@ int     smtp_xfer(SMTP_STATE *state)
     int     sndbuffree;
     SOCKOPT_SIZE optlen = sizeof(sndbufsize);
     int     mail_from_rejected;
-    int     space_left = var_smtp_line_limit;
-    int     data_left;
-    char   *data_start;
+    int     downgrading;
+    int     mime_errs;
 
     /*
      * Macros for readability.
@@ -362,6 +430,8 @@ int     smtp_xfer(SMTP_STATE *state)
        if (getsockopt(vstream_fileno(state->session->stream), SOL_SOCKET,
                       SO_SNDBUF, (char *) &sndbufsize, &optlen) < 0)
            msg_fatal("%s: getsockopt: %m", myname);
+       if (sndbufsize > VSTREAM_BUFSIZE)
+           sndbufsize = VSTREAM_BUFSIZE;
        if (msg_verbose)
            msg_info("Using ESMTP PIPELINING, TCP send buffer size is %d",
                     sndbufsize);
@@ -416,10 +486,10 @@ int     smtp_xfer(SMTP_STATE *state)
            }
            vstring_sprintf(next_command, "MAIL FROM:<%s>",
                            vstring_str(state->scratch));
-           if (state->features & SMTP_FEATURE_SIZE)    /* RFC 1652 */
+           if (state->features & SMTP_FEATURE_SIZE)    /* RFC 1870 */
                vstring_sprintf_append(next_command, " SIZE=%lu",
                                       request->data_size);
-           if (state->features & SMTP_FEATURE_8BITMIME) {
+           if (state->features & SMTP_FEATURE_8BITMIME) {      /* RFC 1652 */
                if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0)
                    vstring_strcat(next_command, " BODY=8BITMIME");
                else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0)
@@ -537,8 +607,10 @@ int     smtp_xfer(SMTP_STATE *state)
                case SMTP_STATE_MAIL:
                    if (resp->code / 100 != 2) {
                        smtp_mesg_fail(state, resp->code,
-                                      "host %s said: %s", session->namaddr,
-                                      translit(resp->str, "\n", " "));
+                                      "host %s said: %s (in reply to %s)",
+                                      session->namaddr,
+                                      translit(resp->str, "\n", " "),
+                                      xfer_request[SMTP_STATE_MAIL]);
                        mail_from_rejected = 1;
                    }
                    recv_state = SMTP_STATE_RCPT;
@@ -567,8 +639,10 @@ int     smtp_xfer(SMTP_STATE *state)
                        } else {
                            rcpt = request->rcpt_list.info + recv_rcpt;
                            smtp_rcpt_fail(state, resp->code, rcpt,
-                                      "host %s said: %s", session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                       "host %s said: %s (in reply to %s)",
+                                          session->namaddr,
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[SMTP_STATE_RCPT]);
                            rcpt->offset = 0;   /* in case deferred */
                        }
                    }
@@ -585,8 +659,10 @@ int     smtp_xfer(SMTP_STATE *state)
                    if (resp->code / 100 != 3) {
                        if (nrcpt > 0)
                            smtp_mesg_fail(state, resp->code,
-                                      "host %s said: %s", session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                       "host %s said: %s (in reply to %s)",
+                                          session->namaddr,
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[SMTP_STATE_DATA]);
                        nrcpt = -1;
                    }
                    recv_state = SMTP_STATE_DOT;
@@ -605,9 +681,10 @@ int     smtp_xfer(SMTP_STATE *state)
                    if (nrcpt > 0) {
                        if (resp->code / 100 != 2) {
                            smtp_mesg_fail(state, resp->code,
-                                          "host %s said: %s",
+                                       "host %s said: %s (in reply to %s)",
                                           session->namaddr,
-                                          translit(resp->str, "\n", " "));
+                                          translit(resp->str, "\n", " "),
+                                          xfer_request[SMTP_STATE_DOT]);
                        } else {
                            for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
                                rcpt = request->rcpt_list.info + nrcpt;
@@ -678,8 +755,24 @@ int     smtp_xfer(SMTP_STATE *state)
         * Special case if the server accepted the DATA command. If the
         * server accepted at least one recipient send the entire message.
         * Otherwise, just send "." as per RFC 2197.
+        * 
+        * XXX If there is a hard MIME error while downgrading to 7-bit mail,
+        * disconnect ungracefully, because there is no other way to cancel a
+        * transaction in progress.
         */
        if (send_state == SMTP_STATE_DOT && nrcpt > 0) {
+           downgrading = ((state->features & SMTP_FEATURE_8BITMIME) == 0
+                    && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0);
+           if (downgrading)
+               state->mime_state = mime_state_alloc(MIME_OPT_DOWNGRADE
+                                        | MIME_OPT_REPORT_8BIT_IN_7BIT_BODY
+                                          | MIME_OPT_REPORT_8BIT_IN_HEADER,
+                                                    smtp_header_out,
+                                                    (MIME_STATE_ANY_END) 0,
+                                                    smtp_text_out,
+                                                    (MIME_STATE_ANY_END) 0,
+                                                    (void *) state);
+           state->space_left = var_smtp_line_limit;
            smtp_timeout_setup(state->session->stream,
                               var_smtp_data1_tmout);
            if ((except = vstream_setjmp(state->session->stream)) != 0)
@@ -695,38 +788,21 @@ int     smtp_xfer(SMTP_STATE *state)
                if (prev_type != REC_TYPE_CONT)
                    if (vstring_str(state->scratch)[0] == '.')
                        smtp_fputc('.', session->stream);
-
-               /*
-                * Deal with an impedance mismatch between Postfix queue
-                * files (record length <= $message_line_length_limit) and
-                * SMTP (DATA record length <= $smtp_line_length_limit). The
-                * code below does a little too much work when the SMTP line
-                * length limit is disabled, but it avoids code duplication,
-                * and thus, it avoids testing and maintenance problems.
-                */
-               data_left = VSTRING_LEN(state->scratch);
-               data_start = vstring_str(state->scratch);
-               do {
-                   if (var_smtp_line_limit > 0 && data_left >= space_left) {
-                       smtp_fputs(data_start, space_left, session->stream);
-                       data_start += space_left;
-                       data_left -= space_left;
-                       space_left = var_smtp_line_limit;
-                       if (data_left > 0 || rec_type == REC_TYPE_CONT) {
-                           smtp_fputc(' ', session->stream);
-                           space_left -= 1;
-                       }
-                   } else {
-                       if (rec_type == REC_TYPE_CONT) {
-                           smtp_fwrite(data_start, data_left, session->stream);
-                           space_left -= data_left;
-                       } else {
-                           smtp_fputs(data_start, data_left, session->stream);
-                           space_left = var_smtp_line_limit;
-                       }
-                       break;
+               if (downgrading == 0) {
+                   smtp_text_out((void *) state, rec_type,
+                                 vstring_str(state->scratch),
+                                 VSTRING_LEN(state->scratch));
+               } else {
+                   mime_errs =
+                       mime_state_update(state->mime_state, rec_type,
+                                         vstring_str(state->scratch),
+                                         VSTRING_LEN(state->scratch));
+                   if (mime_errs) {
+                       smtp_mesg_fail(state, 554, "MIME 7-bit conversion failed: %s",
+                                      mime_state_error(mime_errs));
+                       RETURN(0);
                    }
-               } while (data_left > 0);
+               }
                prev_type = rec_type;
            }
 
index 2c4cad35c4927f1edba38a266c215986bbf16769..e6c97637166d0fff2cd79669006a8d8ff9b0e726 100644 (file)
@@ -43,6 +43,7 @@
 /* Global library. */
 
 #include <mail_conf.h>
+#include <mime_state.h>
 
 /* Application-specific. */
 
@@ -69,6 +70,8 @@ SMTP_STATE *smtp_state_alloc(void)
     smtp_sasl_connect(state);
 #endif
     state->size_limit = 0;
+    state->space_left = 0;
+    state->mime_state = 0;
     return (state);
 }
 
@@ -82,5 +85,7 @@ void    smtp_state_free(SMTP_STATE *state)
 #ifdef USE_SASL_AUTH
     smtp_sasl_cleanup(state);
 #endif
+    if (state->mime_state)
+       mime_state_free(state->mime_state);
     myfree((char *) state);
 }
index bf7d7748a20f2c11a034a7e231072da30ea7c6af..8677f237973632d23522206f864167ec6f92586b 100644 (file)
@@ -617,6 +617,8 @@ static char *extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
     /*
      * Report trouble. Log a warning only if we are going to sleep+reject so
      * that attackers can't flood our logfiles.
+     * 
+     * XXX !allow_empty_addr should also reject <"">.
      */
     if ((naddr < 1 && !allow_empty_addr)
        || naddr > 1
@@ -776,15 +778,17 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
     if (encoding != 0)
        rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
                    MAIL_ATTR_ENCODING, encoding);
-    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-               MAIL_ATTR_CLIENT_NAME, state->name);
-    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-               MAIL_ATTR_CLIENT_ADDR, state->addr);
-    rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-               MAIL_ATTR_ORIGIN, state->namaddr);
-    if (state->helo_name != 0)
+    if (SMTPD_STAND_ALONE(state) == 0) {
        rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
-                   MAIL_ATTR_HELO_NAME, state->helo_name);
+                   MAIL_ATTR_CLIENT_NAME, state->name);
+       rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+                   MAIL_ATTR_CLIENT_ADDR, state->addr);
+       rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+                   MAIL_ATTR_ORIGIN, state->namaddr);
+       if (state->helo_name != 0)
+           rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+                       MAIL_ATTR_HELO_NAME, state->helo_name);
+    }
     if (verp_delims)
        rec_fputs(state->cleanup, REC_TYPE_VERP, verp_delims);
     state->sender = mystrdup(argv[2].strval);
@@ -956,7 +960,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
                    state->protocol, state->queue_id);
        quote_822_local(state->buffer, state->recipient);
        rec_fprintf(state->cleanup, REC_TYPE_NORM,
-               "\tfor <%s>; %s", STR(state->buffer), mail_date(state->time));
+             "\tfor <%s>; %s", STR(state->buffer), mail_date(state->time));
     } else {
        rec_fprintf(state->cleanup, REC_TYPE_NORM,
                    "\tby %s (%s) with %s",
@@ -996,7 +1000,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
        if (first) {
            if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
                rec_fprintf(state->cleanup, curr_rec_type,
-                           "Mailbox-Line: %s", start);
+                           "X-Mailbox-Line: %s", start);
                continue;
            }
            first = 0;
index 61014a800e038e4888ce3eb81f3aab33b8fd8978..174215d7936e8439189948f37f29d9bb0ebd86d5 100644 (file)
@@ -49,7 +49,7 @@
 /*     argument. The result is null-terminated. This routine uses mymalloc().
 /*
 /*     mymemdup() makes a copy of the memory pointed to by \fIptr\fR
-/*     with length \fIlen\fR. The result is null-terminated.
+/*     with length \fIlen\fR. The result is NOT null-terminated.
 /*     This routine uses mymalloc().
 /* SEE ALSO
 /*     msg(3) diagnostics interface