-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
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.
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
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:
: cleanup2/ | \smtp----->
: ^ v :
: | v :
- : smtpd smtp :
+ : smtpd scan :
: 10026 | :
.......................|...........
^ |
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
==========================================================
<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).
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>
<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 <>.
- This substitution is done before all other address
+ The destination for undeliverable mail from <>.
+ This substitution is done before all other address
rewriting.
<b>canonical</b><i>_</i><b>maps</b>
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>
<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>
<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>
/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>
<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
<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>
<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.
<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)
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.
.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
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
.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
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
.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)
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
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
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
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
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
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
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
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
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
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
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
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
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
/* 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
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.
#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
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 */
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.
*/
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;
/*
* 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)))
* 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 */
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,
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,
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;
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);
#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;
/* 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;
}
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 */
/* 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);
* 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);
&& (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);
* 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);
&& (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");
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;
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;
}
/*
* 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;
}
/*
*/
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)
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;
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;
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);
+}
/* 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;
/* 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.
/*
/* 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;
/* 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;
#include <been_here.h>
#include <mail_params.h>
+#include <mime_state.h>
/* Application-specific. */
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);
state->rcpt_count = 0;
state->reason = 0;
state->attr = nvtable_create(10);
+ state->mime_state = 0;
+ state->mime_errs = 0;
return (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)
if (state->reason)
myfree(state->reason);
nvtable_free(state->attr);
+ if (state->mime_state)
+ mime_state_free(state->mime_state);
myfree((char *) state);
}
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 \
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 \
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 \
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
$(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
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
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
--- /dev/null
+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
#include <msg.h>
#include <htable.h>
#include <vstring.h>
+#include <stringops.h>
/* Global library. */
"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,
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)));
}
#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.
#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)
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
/* 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);
}
--- /dev/null
+/*++
+/* 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);
+}
/* 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
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"
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[] = {
#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;
#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
* 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
--- /dev/null
+/*++
+/* 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
--- /dev/null
+#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
--- /dev/null
+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
--- /dev/null
+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
/* 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
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);
}
"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)
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;
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 */
}
}
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;
}
} 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 */
}
}
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);
/* 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.
/* .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
/* 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
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);
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;
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
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
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
/* 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)
&& (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();
}
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)
* 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 */
#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. */
"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)
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)
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.
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);
}
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)
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;
} 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 */
}
}
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;
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;
* 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)
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;
}
/* Global library. */
#include <mail_conf.h>
+#include <mime_state.h>
/* Application-specific. */
smtp_sasl_connect(state);
#endif
state->size_limit = 0;
+ state->space_left = 0;
+ state->mime_state = 0;
return (state);
}
#ifdef USE_SASL_AUTH
smtp_sasl_cleanup(state);
#endif
+ if (state->mime_state)
+ mime_state_free(state->mime_state);
myfree((char *) state);
}
/*
* 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
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);
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",
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;
/* 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