-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
with @ in the recipient localpart no longer bounces with
"user unknown" but instead is rejected with "relay access
denied" or "source-routed relay access denied".
+
+19991227
+
+ Workaround: the BSD/OS "mkdir -p" and "cmp -s" commands
+ misbehave on boundary cases: directory exists or file does
+ not exist. Those who re-invent...
+
+19991229
+
+ Added the no source routing info requirement to addresses
+ accepted by the permit_mx_backup UCE restriction.
+
+19991230
+
+ Added a spawn daemon (not compiled and installed by default)
+ to enable LMTP delivery over UNIX-domain sockets. The goal
+ is to simplify the experimental LMTP delivery agent by
+ ripping out the privileged code that forks the LMTP server.
test -d $QUEUE_DIRECTORY || mkdir -p $QUEUE_DIRECTORY || exit 1
for path in $SENDMAIL_PATH $NEWALIASES_PATH $MAILQ_PATH
do
- dir=`echo $path|sed 's/[^/]*[/]*$//'`
+ dir=`echo $path|sed -e 's/[/][/]*[^/]*$//' -e 's/^$/\//'`
test -d $dir || mkdir -p $dir || exit 1
done
done
for file in man?/*
do
- cmp -s $file $MANPAGES/$file || {
+ (test -f $MANPAGES/$file && cmp -s $file $MANPAGES/$file) || {
rm -f $MANPAGES/$file
cp $file $MANPAGES/$file || exit 1
chmod 644 $MANPAGES/$file || exit 1
DIRS = util global dns master postfix smtpstone sendmail error \
pickup cleanup smtpd local trivial-rewrite qmgr smtp bounce pipe \
showq postalias postcat postconf postdrop postkick postlock postlog \
- postmap postsuper # man html
+ postmap postsuper # spawn man html
default: update
-Incompatible changes with snapshot 19991227
+Incompatible changes with postfix-19991231:
===========================================
- The SMTP server no longer forwards mail from untrusted clients
-with sender-specified routing (stuff[@%!]stuff[@%!]stuff) to
+with sender-specified routing (stuff[@%!]stuff[@%!]stuff) through
destinations that are authorized by the relay_domains parameter.
This closes a loophole that exploits trust relationships between
hosts. Example: a trusted backup MX host forwards junk mail to
a primary MX host which forwards the junk to the Internet. Specify
"allow_untrusted_routing = yes" to restore the old behavior.
+- The SMTP server no longer forwards mail with sender-specified
+routing (stuff[@%!]stuff[@%!]stuff) through destinations that are
+authorized by the permit_mx_backup feature. This change is under
+control by the allow_untrusted_routing parameter discussed above.
+
- In order to support the above, the data structure and protocol
of the trivial-rewrite service was changed. This means you must
re-compile and re-link existing software that uses the Postfix
with @ in the localpart (user@remote@here) no longer bounces with
"user unknown" but instead is rejected with "relay access denied".
-- The experimental permit_recipient_map and local_transports features
-are gone. They were never part of an official release. Both are
-replaced by a "local_recipient_map" parameter that allows the SMTP
-server to reject mail for unknown local users (see below).
+- Incompatible SMTPD access map changes:
+
+ An all-numeric right-hand side now means OK. This is for better
+ cooperation with out-of-band authentication mechanisms such as
+ POP before SMTP etc.
-- In an SMTPD access map, an all-numeric right-hand side now means
-OK. This is for better cooperation with out-of-band authentication
-mechanisms such as POP before SMTP etc.
+ An empty right-hand sides still mean OK, but Postfix will log a
+ warning in order to discourage such usage.
-- You can no longer use an empty right-hand side in SMTPD access
-maps.
+ You can no longer use virtual, canonical or aliases tables as
+ SMTPD access maps. Use the local_recipient_maps feature instead.
- Recipient addresses may no longer begin with `-'. In order to
-reinstate the old behavior, specify "allow_min_user = yes" in
-main.cf.
+get the old behavior, specify "allow_min_user = yes" in main.cf.
+
+- Incompatible transport map changes:
-- You can no longer use virtual, canonical or aliases tables as
-SMTPD access control tables. Use the local_recipient_maps feature
-instead.
+ Transport map entries override mydestination. If you use transport
+ maps, it is better to always have explicit entries for all domain
+ names you have in $mydestination. See the html/faq.html sections
+ for firewalls and intranets.
-- transport_maps entries override mydestination. If you use
-transport maps, it is better to always have explicit entries for
-all domain names you have in $mydestination. See the html/faq.html
-sections for firewalls and intranets.
+ The nexthop information given to a local delivery agent may have
+ changed. This information was never intended to be used as a
+ next-hop destination.
-Major changes with snapshot 19991227
+Major changes with postfix-19991231:
====================================
- It is now much more difficult to configure Postfix as an open
is the default). There were too many accidents with changes to
the UCE restrictions.
-- The SMTP server no longer forwards mail from untrusted clients
-with sender-specified routing (stuff[@%!]stuff[@%!]stuff) to
-destinations that are authorized by the relay_domains parameter.
-This closes a loophole that exploits trust relationships between
-hosts. Example: a trusted backup MX host forwards junk mail to
-a primary MX host which forwards the junk to the Internet. Specify
-"allow_untrusted_routing = yes" to restore the old behavior.
-
- The relay_domains parameter no longer needs to contain $virtual_maps.
- Overhauled FAQ (html/faq.html) with many more examples.
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
# In order to use the "cyrus" message transport below, configure it
# in main.cf as the mailbox_transport.
#
+# SPECIFY ONLY PROGRAMS THAT ARE WRITTEN TO RUN AS POSTFIX DAEMONS.
+# ALL DAEMONS SPECIFIED HERE MUST SPEAK A POSTFIX-INTERNAL PROTOCOL.
+#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (50)
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=F. user=foo argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
-
# .IP "\fI.domain transport\fR:\fInexthop\fR"
# Mail for any subdomain of \fIdomain\fR is delivered through
# \fItransport\fR to \fInexthop\fR.
+# .PP
+# Note: transport map entries take precedence over domains
+# specified in the \fBmydestination\fR parameter. If you use
+# the optional transport map, it may be safer to specify explicit
+# entries for all domains specified in \fBmydestination\fR,
+# for example:
+#
+# .ti +5
+# \fBhostname.my.domain local:\fR
+# .ti +5
+# \fBlocalhost.my.domain local:\fR
#
# The interpretation of the \fInexthop\fR field is transport
# dependent. In the case of SMTP, specify \fIhost\fR:\fIservice\fR for a
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-19991227"
+#define DEF_MAIL_VERSION "Postfix-19991231"
extern char *var_mail_version;
/* LICENSE
<li><a href="#root">Root's mail is delivered to nobody</a>
-<li><a href="#bogus">Postfix accepts mail for non-existing users</a>
+<li><a href="#bogus">Postfix accepts mail for non-existing local users</a>
<li><a href="#duplicate">Postfix sends duplicate mail</a>
<li><a href="#root">Root's mail is delivered to nobody</a>
-<li><a href="#bogus">Postfix accepts mail for non-existing users</a>
+<li><a href="#bogus">Postfix accepts mail for non-existing local users</a>
<li><a href="#some_local">Delivering some users locally while
sending mail as user@domain</a>
The exact location of the <b>sendmail</b> command is system-specific.
With some UNIX versions, use <b>/usr/lib/sendmail</b>.
+<p>
+
+In order to find out if the mail queue is flushed, use something
+like:
+
+<p>
+<pre>
+ #!/bin/sh
+
+ # Start deliveries.
+ /usr/sbin/sendmail -q
+
+ # Allow deliveries to start.
+ sleep 10
+
+ # Loop until all messages have been tried at least once.
+ while mailq | grep '^[^ ]*\*' >/dev/null
+ do
+ sleep 10
+ done
+</pre>
+
+<p>
+
If you have disabled <a href="#spontaneous_smtp">spontaneous SMTP
mail delivery</a>, you also need to run the above command every
now and then while the dialup link is up, so that newly-posted mail
<<< 250 Ok: queued as A958F5A15
</pre>
-
<p>
Don't Panic! Upgrade to a Postfix version of 19991227 or later.
<p>
-Older Postfix versions would either <i>bounce</i> the mail because
-"test@some.other.site" is not a known local username (which is
-good), or they would <i>forward</i> the mail to a primary MX host
-for "some.site" which would then spam it into the Internet (which
-is bad).
+With earlier Postfix versions,
+
+<ol>
+
+<li>Good but confusing: a Postfix primary MX host for <i>some.site</i>
+accepts <i>test@some.other.site@some.site</i> then bounces it because
+<i>test@some.other.site</i> is not a known local username.
+
+<li>Good: a Postfix primary MX host for <i>some.site</i> rejects
+other source-routed addresses such as <i>test%some.other.site@some.site</i>
+or <i>some.other.site!test@some.site</i>.
+
+<li>Loophole: a Postfix backup MX host for <i>some.site</i> forwards
+source-routed addresses such as <i>test@some.other.site@some.site</i>
+or <i>test%some.other.site@some.site</i> to a primary MX host for
+<i>some.site</i>. Depending on the primary MX host's mailer
+configuration, the primary MX host could then spam the mail into
+the Internet.
+
+</ol>
+
+<p>
+
+With newer Postfix versions,
+
+<ol>
+
+<li>A Postfix primary MX host for <i>some.site</i> host rejects
+<i>test@some.other.site@some.site</i> just like it rejects
+<i>test%some.other.site@some.site</i>. This ends the confusion
+mentioned in 1 above.
+
+<li>A Postfix backup MX host for <i>some.site</i> host rejects
+source-routed addresses including <i>test@some.other.site@some.site</i>.
+This closes the loophole mentioned in 3 above.
+
+</ol>
+
+<p>
+
+To be precise, Postfix UCE restrictions refuse to forward source-routed
+addresses under the following conditions:
+
+<p>
+
+<ul>
+
+<li> <a href="uce.html#check_relay_domains">check_relay_domains</a>:
+reject when the destination is not local and when the client hostname
+does not match <a href="uce.html#relay_domains">relay_domains</a>.
+
+<li> <a
+href="uce.html#permit_auth_destination">permit_auth_destination</a>:
+skip when the destination is not local.
+
+<li> <a
+href="uce.html#reject_unauth_destination">reject_unauth_destination</a>:
+reject when the destination is not local.
+
+<li> <a href="uce.html#permit_mx_backup">permit_mx_backup</a>:
+reject when the destination is not local.
+
+<li> Other UCE restrictions (e.g., SMTPD access maps) are not aware
+of sender-provided routing information.
+
+</ul>
+
+<p>
+
+However, a Postfix primary MX host for still forwards source-routed
+addresses <b>if received from a trusted client</b>, just like it
+did before.
+
+<p>
+
+In order to have guaranteed protection against source-routed relaying
+through trusted SMTP clients, specify a regular expression restriction
+ahead of the other SMTPD recipient restrictions:
+
+<p>
+
+<pre>
+ <b>/etc/postfix/main.cf</b>:
+ smtpd_recipient_restrictions =
+ regexp:/etc/postfix/regexp_access
+ <i>...other restrictions...</i>
+
+ <b>/etc/postfix/regexp_access</b>:
+ /[%!@].*[%!@]/ 550 Sender specified routing is not supported here.
+</pre>
+
+<p>
+
+This would be installed on all MX hosts.
<hr>
<hr>
-<a name="bogus"><h3>Postfix accepts mail for non-existing users</h3>
+<a name="bogus"><h3>Postfix accepts mail for non-existing local users</h3>
The information in this section applies to Postfix versions 19991216
-and later.
+and later. See elsewhere for <a href="#unknown_virtual">unknown
+virtual</a> users.
<p>
Of course mail for a non-existent local user will eventually bounce
as undeliverable, but why accept such mail in the first place? You
-can tell the Postfix SMTP server how find out if a user exists by
+can tell the Postfix SMTP server how to find out if a user exists by
listing all tables with local addresses in the <b>local_recipient_maps</b>
parameter:
<ul>
-<li>Specify that mail for, let's say, <i>some.domain</i>, should be
-delivered via UUCP, for example, to a host named <i>uucp-host</i>:
+<li>You need an <b>rmail</b> program that extracts the sender
+address from mail that arrives via UUCP, and that feeds the mail
+into the Postfix <b>sendmail<b> command. Most UNIX systems come
+with an <b>rmail</b> utility. If you're in a pinch, try the one
+bundled with the Postfix source code in the <b>auxiliary</b>
+directory. Some day Postfix may have its own <b>rmail</b> command.
+
+<p>
+
+<li>Specify that mail for, let's say, <i>some.domain</i>, should
+be delivered via UUCP, for example, to a host named <i>uucp-host</i>:
<p>
Mail for any subdomain of <i>domain</i> is delivered
through <i>transport</i> to <i>nexthop</i>.
- The interpretation of the <i>nexthop</i> field is trans-
- port dependent. In the case of SMTP, specify
- <i>host</i>:<i>service</i> for a non-default server port, and use
- [<i>host</i>] or [<i>host</i>:<i>port</i>] in order to disable MX (mail
- exchanger) DNS lookups. The [] form can also be
- used with IP addresses instead of hostnames.
+ Note: transport map entries take precedence over domains
+ specified in the <b>mydestination</b> parameter. If you use the
+ optional transport map, it may be safer to specify
+ explicit entries for all domains specified in <b>mydestina-</b>
+ <b>tion</b>, for example:
+
+ <b>hostname.my.domain</b> <b>local:</b>
+ <b>localhost.my.domain</b> <b>local:</b>
+
+ The interpretation of the <i>nexthop</i> field is transport
+ dependent. In the case of SMTP, specify <i>host</i>:<i>service</i> for a
+ non-default server port, and use [<i>host</i>] or [<i>host</i>:<i>port</i>] in
+ order to disable MX (mail exchanger) DNS lookups. The []
+ form can also be used with IP addresses instead of host-
+ names.
<b>EXAMPLES</b>
In order to send mail for <b>foo.org</b> and its subdomains
<b>foo.org</b> <b>uucp:foo</b>
<b>.foo.org</b> <b>uucp:foo</b>
- When no <i>nexthop</i> host name is specified, the destination domain
- name is used instead. For example, the following directs mail for
- <i>user</i>@<b>foo.org</b> via the <b>slow</b> transport to a mail
- exchanger for <b>foo.org</b>. The <b>slow</b> transport could be
- something that runs at most one delivery process at a time:
-
- <b>foo.org</b> <b>slow:</b>
-
-
1
TRANSPORT(5) TRANSPORT(5)
+ When no <i>nexthop</i> host name is specified, the destination domain
+ name is used instead. For example, the following directs mail for
+ <i>user</i>@<b>foo.org</b> via the <b>slow</b> transport to a mail
+ exchanger for <b>foo.org</b>. The <b>slow</b> transport could be
+ something that runs at most one delivery process at a time:
+
+ <b>foo.org</b> <b>slow:</b>
+
When no <i>transport</i> is specified, the default transport is
used, as specified via the <b>default</b><i>_</i><b>transport</b> configuration
parameter. The following sends all mail for <b>foo.org</b> and its
The default host to send to when no transport table
entry matches.
-<b>SEE</b> <b>ALSO</b>
- <a href="postmap.1.html">postmap(1)</a> create mapping table
- <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> rewrite and resolve addresses
-
-<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
- software.
-
TRANSPORT(5) TRANSPORT(5)
+<b>SEE</b> <b>ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a> create mapping table
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> rewrite and resolve addresses
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this
+ software.
+
<b>AUTHOR(S)</b>
Wietse Venema
IBM T.J. Watson Research
-
-
-
-
-
-
-
-
<li>from untrusted clients to destinations that match <a
href="#relay_domains"> $relay_domains</a> or a subdomain thereof,
except for addresses that contain sender-specified routing
-(<i>user@there@here</i>).
+(<i>user@elsewhere@domain</i>).
</ul>
<li>the resolved destination address matches <a
href="#relay_domains">$relay_domains</a> or a subdomain thereof,
and the address contains no sender-specified routing
-(<i>user@there@here</i>),
+(<i>user@elsewhere@domain</i>),
<li>Postfix is the final destination: any destination that matches
<a href="basic.html#mydestination">$mydestination</a>, <a
<a name="permit_auth_destination">
<dt> <b>permit_auth_destination</b> <dd> Ignore the client hostname.
-Permit the request when:
+Permit the request when one of the following is true:
<ul>
<li>the resolved destination address matches <a
href="#relay_domains">$relay_domains</a> or a subdomain thereof,
and the address contains no sender-specified routing
-(<i>user@there@here</i>),
+(<i>user@elsewhere@domain</i>),
<li>Postfix is the final destination: any destination that matches
<a href="basic.html#mydestination">$mydestination</a>, <a
<a name="reject_unauth_destination">
<dt> <b>reject_unauth_destination</b> <dd> Ignore the client
-hostname. Reject the request unless:
+hostname. Reject the request unless one of the following is true:
<ul>
<li>the resolved destination address matches <a
href="#relay_domains">$relay_domains</a> or a subdomain thereof,
and the address contains no sender-specified routing
-(<i>user@there@here</i>),
+(<i>user@elsewhere@domain</i>),
<li>Postfix is the final destination: any destination that matches
<a href="basic.html#mydestination">$mydestination</a>, <a
<dt> <b>permit_mx_backup</b> <dd> Permit the request when the local
mail system is MX host for the resolved destination. This includes
the case that the local mail system is the final destination.
+However, the SMTP server will not forward mail with addresses that
+have sender-specified routing information (example:
+<i>user@elsewhere@domain</i>),
+
+<p>
+
Relevant configuration parameters: <a href="basic.html#mydestination">
$mydestination</a>, <a href="basic.html#inet_interfaces">
$inet_interfaces</a>.
<li>from untrusted clients to destinations that match <a
href="#relay_domains"> $relay_domains</a> or a subdomain thereof,
except for addresses that contain sender-specified routing
-(<i>user@there@here</i>).
+(<i>user@elsewhere@domain</i>).
</ul>
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
.IP "\fI.domain transport\fR:\fInexthop\fR"
Mail for any subdomain of \fIdomain\fR is delivered through
\fItransport\fR to \fInexthop\fR.
+.PP
+Note: transport map entries take precedence over domains
+specified in the \fBmydestination\fR parameter. If you use
+the optional transport map, it may be safer to specify explicit
+entries for all domains specified in \fBmydestination\fR,
+for example:
+
+.ti +5
+\fBhostname.my.domain local:\fR
+.ti +5
+\fBlocalhost.my.domain local:\fR
The interpretation of the \fInexthop\fR field is transport
dependent. In the case of SMTP, specify \fIhost\fR:\fIservice\fR for a
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
if (msg_verbose)
msg_info("%s: not local: %s", myname, recipient);
+ /*
+ * Skip source-routed mail (uncertain destination).
+ */
+ if (var_allow_untrust_route == 0 && (reply.flags & RESOLVE_FLAG_ROUTED))
+ return (SMTPD_CHECK_DUNNO);
+
/*
* Skip numerical forms that didn't match the local system.
*/
}
/*
- * Recursively evaluate the restrictions given in the right-hand side.
+ * Recursively evaluate the restrictions given in the right-hand side. In
+ * the dark ages, an empty right-hand side meant OK. Make some
+ * discouraging comments.
*/
restrictions = argv_split(value, " \t\r\n,");
- status = generic_checks(state, restrictions, reply_name,
- reply_class, def_acl);
+ if (restrictions->argc == 0) {
+ msg_warn("SMTPD access map %s entry %s has empty value",
+ table, value);
+ status = SMTPD_CHECK_OK;
+ } else {
+ status = generic_checks(state, restrictions, reply_name,
+ reply_class, def_acl);
+ }
argv_free(restrictions);
return (status);
}
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
--- /dev/null
+-TALIAS_TOKEN
+-TARGV
+-TBH_TABLE
+-TBINHASH
+-TBINHASH_INFO
+-TBOUNCE_STAT
+-TCLEANUP_STATE
+-TCLIENT_LIST
+-TCLNT_STREAM
+-TCONFIG_BOOL_FN_TABLE
+-TCONFIG_BOOL_TABLE
+-TCONFIG_INT_FN_TABLE
+-TCONFIG_INT_TABLE
+-TCONFIG_STR_FN_TABLE
+-TCONFIG_STR_TABLE
+-TDELIVER_ATTR
+-TDELIVER_REQUEST
+-TDICT
+-TDICT_DB
+-TDICT_DBM
+-TDICT_ENV
+-TDICT_HT
+-TDICT_LDAP
+-TDICT_MYSQL
+-TDICT_NI
+-TDICT_NIS
+-TDICT_NISPLUS
+-TDICT_NODE
+-TDICT_OPEN_INFO
+-TDICT_PCRE
+-TDICT_REGEXP
+-TDICT_REGEXP_RULE
+-TDICT_UNIX
+-TDNS_FIXED
+-TDNS_REPLY
+-TDNS_RR
+-TDOMAIN_LIST
+-TEXPAND_ATTR
+-TFILE
+-TFORWARD_INFO
+-THEADER_OPTS
+-THOST
+-THTABLE
+-THTABLE_INFO
+-TINET_ADDR_LIST
+-TINT_TABLE
+-TLOCAL_EXP
+-TLOCAL_STATE
+-TMAC_EXP
+-TMAC_HEAD
+-TMAC_PARSE
+-TMAIL_PRINT
+-TMAIL_SCAN
+-TMAPS
+-TMASTER_PROC
+-TMASTER_SERV
+-TMASTER_STATUS
+-TMBLOCK
+-TMKMAP
+-TMKMAP_OPEN_INFO
+-TMULTI_SERVER
+-TMVECT
+-TMYSQL_NAME
+-TNAMADR_LIST
+-TNAME_MASK
+-TPEER_NAME
+-TPICKUP_INFO
+-TPIPE_ATTR
+-TPIPE_PARAMS
+-TPLMYSQL
+-TQMGR_ENTRY
+-TQMGR_MESSAGE
+-TQMGR_QUEUE
+-TQMGR_RCPT_LIST
+-TQMGR_RECIPIENT
+-TQMGR_SCAN
+-TQMGR_TRANSPORT
+-TRECIPIENT
+-TRECIPIENT_LIST
+-TREC_TYPE_NAME
+-TRESOLVE_REPLY
+-TRESPONSE
+-TSCAN_DIR
+-TSCAN_INFO
+-TSCAN_OBJ
+-TSESSION
+-TSINGLE_SERVER
+-TSINK_COMMAND
+-TSINK_STATE
+-TSMTPD_CMD
+-TSMTPD_STATE
+-TSMTPD_TOKEN
+-TSMTP_ADDR
+-TSMTP_CMD
+-TSMTP_RESP
+-TSMTP_SESSION
+-TSMTP_STATE
+-TSOCKADDR_SIZE
+-TSPAWN_ATTR
+-TSTRING_TABLE
+-TSYS_EXITS_TABLE
+-TTOK822
+-TTRIGGER_SERVER
+-TUSER_ATTR
+-TVBUF
+-TVSTREAM
+-TVSTREAM_POPEN_ARGS
+-TVSTRING
+-TWAIT_STATUS_T
+-TWATCHDOG
+-TWATCH_FD
--- /dev/null
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 3 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
--- /dev/null
+SHELL = /bin/sh
+SRCS = spawn.c
+OBJS = spawn.o
+HDRS =
+TESTSRC =
+WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
+ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
+ -Wunused
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = spawn
+INC_DIR = ../include
+LIBS = ../lib/libmaster.a ../lib/libglobal.a ../lib/libutil.a
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+Makefile: Makefile.in
+ (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../makedefs; cat $?) >$@
+
+test: $(TESTPROG)
+
+update: ../libspawn/$(PROG)
+
+../libspawn/$(PROG): $(PROG)
+ cp $(PROG) ../libspawn
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \
+ done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @make -f Makefile.in Makefile
+
+# do not edit below this line - it is generated by 'make depend'
+spawn.o: spawn.c
+spawn.o: ../include/sys_defs.h
+spawn.o: ../include/msg.h
+spawn.o: ../include/argv.h
+spawn.o: ../include/dict.h
+spawn.o: ../include/vstream.h
+spawn.o: ../include/vbuf.h
+spawn.o: ../include/mymalloc.h
+spawn.o: ../include/spawn_command.h
+spawn.o: ../include/split_at.h
+spawn.o: ../include/timed_wait.h
+spawn.o: ../include/set_eugid.h
+spawn.o: ../include/mail_params.h
+spawn.o: ../include/mail_server.h
+spawn.o: ../include/mail_conf.h
--- /dev/null
+/*++
+/* NAME
+/* spawn 8
+/* SUMMARY
+/* Postfix external command spawner
+/* SYNOPSIS
+/* \fBspawn\fR [generic Postfix daemon options] command_attributes...
+/* DESCRIPTION
+/* The \fBspawn\fR daemon provides the Postfix equivalent of \fBinetd\fR.
+/* It listens on a port as specified in the Postfix \fBmaster.cf\fR file
+/* and spawns an external command whenever a connection is established.
+/* The connection can be made over local IPC (such as UNIX-domain
+/* sockets) or over non-local IPC (such as TCP sockets).
+/* The command\'s standard input, output and error streams are connected
+/* directly to the communication endpoint.
+/*
+/* This daemon expects to be run from the \fBmaster\fR(8) process
+/* manager.
+/* COMMAND ATTRIBUTE SYNTAX
+/* .ad
+/* .fi
+/* The external command attributes are given in the \fBmaster.cf\fR
+/* file at the end of a service definition. The syntax is as follows:
+/* .IP "\fBuser\fR=\fIusername\fR (required)"
+/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
+/* The external command is executed with the rights of the
+/* specified \fIusername\fR. The software refuses to execute
+/* commands with root privileges, or with the privileges of the
+/* mail system owner. If \fIgroupname\fR is specified, the
+/* corresponding group ID is used instead of the group ID of
+/* of \fIusername\fR.
+/* .IP "\fBargv\fR=\fIcommand\fR... (required)"
+/* The command to be executed. This must be specified as the
+/* last command attribute.
+/* The command is executed directly, i.e. without interpretation of
+/* shell meta characters by a shell command interpreter.
+/* BUGS
+/* In order to enforce standard Postfix process resource controls,
+/* the \fBspawn\fR daemon runs only one external command at a time.
+/* As such, it presents a noticeable overhead by wasting precious
+/* process resources. The \fBspawn\fR daemon is expected to be
+/* replaced by a more structural solution.
+/* DIAGNOSTICS
+/* The \fBspawn\fR daemon reports abnormal child exits.
+/* Problems are logged to \fBsyslogd\fR(8).
+/* SECURITY
+/* .fi
+/* .ad
+/* This program needs root privilege in order to execute external
+/* commands as the specified user. It is therefore security sensitive.
+/* However the \fBspawn\fR daemon does not talk to the external command
+/* and thus is not vulnerable to data-driven attacks.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program. See the Postfix \fBmain.cf\fR file for syntax details
+/* and for default values. Use the \fBpostfix reload\fR command after
+/* a configuration change.
+/* .SH Miscellaneous
+/* .ad
+/* .fi
+/* .IP \fBmail_owner\fR
+/* The process privileges used while not running an external command.
+/* .SH Resource control
+/* .ad
+/* .fi
+/* .IP \fIservice\fB_command_time_limit\fR
+/* The amount of time the command is allowed to run before it is
+/* killed with force. The \fIservice\fR name is the name of the entry
+/* in the \fBmastr.cf\fR file. The default time limit is given by the
+/* global \fBcommand_time_limit\fR configuration parameter.
+/* SEE ALSO
+/* master(8) process manager
+/* syslogd(8) system logging
+/* 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 <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <spawn_command.h>
+#include <split_at.h>
+#include <timed_wait.h>
+#include <set_eugid.h>
+
+/* Single server skeleton. */
+
+#include <mail_params.h>
+#include <mail_server.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters. Values are taken from the config file, after
+ * prepending the service name to _name, and so on.
+ */
+int var_command_maxtime; /* system-wide */
+
+ /*
+ * For convenience. Instead of passing around lists of parameters, bundle
+ * them up in convenient structures.
+ */
+typedef struct {
+ char **argv; /* argument vector */
+ uid_t uid; /* command privileges */
+ gid_t gid; /* command privileges */
+ int time_limit; /* per-service time limit */
+} SPAWN_ATTR;
+
+/* get_service_attr - get service attributes */
+
+static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
+{
+ char *myname = "get_service_attr";
+ struct passwd *pwd;
+ struct group *grp;
+ char *user; /* user name */
+ char *group; /* group name */
+
+ /*
+ * Initialize.
+ */
+ user = 0;
+ group = 0;
+ attr->argv = 0;
+
+ /*
+ * Figure out the command time limit for this transport.
+ */
+ attr->time_limit =
+ get_mail_conf_int2(service, "_time_limit", var_command_maxtime, 1, 0);
+
+ /*
+ * Iterate over the command-line attribute list.
+ */
+ for ( /* void */ ; *argv != 0; argv++) {
+
+ /*
+ * user=username[:groupname]
+ */
+ if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
+ user = *argv + sizeof("user=") - 1;
+ if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
+ if (*group == 0)
+ group = 0;
+ if ((pwd = getpwnam(user)) == 0)
+ msg_fatal("%s: unknown username: %s", myname, user);
+ attr->uid = pwd->pw_uid;
+ if (group != 0) {
+ if ((grp = getgrnam(group)) == 0)
+ msg_fatal("%s: unknown group: %s", myname, group);
+ attr->gid = grp->gr_gid;
+ } else {
+ attr->gid = pwd->pw_gid;
+ }
+ }
+
+ /*
+ * argv=command...
+ */
+ else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
+ *argv += sizeof("argv=") - 1; /* XXX clobbers argv */
+ attr->argv = argv;
+ break;
+ }
+
+ /*
+ * Bad.
+ */
+ else
+ msg_fatal("unknown attribute name: %s", *argv);
+ }
+
+ /*
+ * Sanity checks. Verify that every member has an acceptable value.
+ */
+ if (user == 0)
+ msg_fatal("missing user= attribute");
+ if (attr->argv == 0)
+ msg_fatal("missing argv= attribute");
+ if (attr->uid == 0)
+ msg_fatal("request to deliver as root");
+ if (attr->uid == var_owner_uid)
+ msg_fatal("request to deliver as mail system owner");
+ if (attr->gid == 0)
+ msg_fatal("request to use privileged group id %d", attr->gid);
+ if (attr->gid == var_owner_gid)
+ msg_fatal("request to use mail system owner group id %d", attr->gid);
+
+ /*
+ * Give the poor tester a clue of what is going on.
+ */
+ if (msg_verbose)
+ msg_info("%s: uid %d, gid %d; time %d",
+ myname, attr->uid, attr->gid, attr->time_limit);
+}
+
+/* spawn_service - perform service for client */
+
+static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
+{
+ char *myname = "spawn_service";
+ static SPAWN_ATTR attr;
+ WAIT_STATUS_T status;
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to running an external command.
+ */
+ if (msg_verbose)
+ msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
+
+ /*
+ * Look up service attributes and config information only once. This is
+ * safe since the information comes from a trusted source.
+ */
+ if (attr.argv == 0) {
+ get_service_attr(&attr, service, argv);
+ }
+
+ /*
+ * Execute the command.
+ */
+ status = spawn_command(SPAWN_CMD_STDIN, vstream_fileno(client_stream),
+ SPAWN_CMD_STDOUT, vstream_fileno(client_stream),
+ SPAWN_CMD_STDERR, vstream_fileno(client_stream),
+ SPAWN_CMD_UID, attr.uid,
+ SPAWN_CMD_GID, attr.gid,
+ SPAWN_CMD_ARGV, attr.argv,
+ SPAWN_CMD_TIME_LIMIT, attr.time_limit,
+ SPAWN_CMD_END);
+
+ /*
+ * Warn about unsuccessful completion.
+ */
+ if (!NORMAL_EXIT_STATUS(status)) {
+ if (WIFEXITED(status))
+ msg_warn("command %s exit status %d",
+ attr.argv[0], WEXITSTATUS(status));
+ if (WIFSIGNALED(status))
+ msg_warn("command %s killed by signal %d",
+ attr.argv[0], WTERMSIG(status));
+ }
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ if (dict_changed()) {
+ msg_info("table has changed -- exiting");
+ exit(0);
+ }
+}
+
+/* drop_privileges - drop privileges most of the time */
+
+static void drop_privileges(char *unused_name, char **unused_argv)
+{
+ set_eugid(var_owner_uid, var_owner_gid);
+}
+
+/* main - pass control to the single-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static CONFIG_INT_TABLE int_table[] = {
+ VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
+ 0,
+ };
+
+ single_server_main(argc, argv, spawn_service,
+ MAIL_SERVER_INT_TABLE, int_table,
+ MAIL_SERVER_POST_INIT, drop_privileges,
+ MAIL_SERVER_PRE_ACCEPT, pre_accept,
+ 0);
+}
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
-TSMTP_SESSION
-TSMTP_STATE
-TSOCKADDR_SIZE
+-TSPAWN_ATTR
-TSTRING_TABLE
-TSYS_EXITS_TABLE
-TTOK822
vstream.c vstream_popen.c vstring.c vstring_vstream.c writable.c \
write_buf.c write_wait.c dict_unix.c dict_pcre.c stream_listen.c \
stream_connect.c stream_trigger.c dict_regexp.c mac_expand.c \
- clean_env.c watchdog.c
+ clean_env.c watchdog.c spawn_command.c
OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \
close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \
dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \
vstream.o vstream_popen.o vstring.o vstring_vstream.o writable.o \
write_buf.o write_wait.o dict_unix.o dict_pcre.o stream_listen.o \
stream_connect.o stream_trigger.o dict_regexp.o mac_expand.o \
- clean_env.o watchdog.o
+ clean_env.o watchdog.o spawn_command.o
HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \
dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \
dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \
timed_connect.h timed_wait.h trigger.h username.h valid_hostname.h \
vbuf.h vbuf_print.h vstream.h vstring.h vstring_vstream.h \
dict_unix.h dict_pcre.h dict_regexp.h mac_expand.h clean_env.h \
- watchdog.h
+ watchdog.h spawn_command.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
skipblanks.o: skipblanks.c
skipblanks.o: sys_defs.h
skipblanks.o: stringops.h
+spawn_command.o: spawn_command.c
+spawn_command.o: sys_defs.h
+spawn_command.o: msg.h
+spawn_command.o: timed_wait.h
+spawn_command.o: set_ugid.h
+spawn_command.o: argv.h
+spawn_command.o: spawn_command.h
+spawn_command.o: exec_command.h
+spawn_command.o: clean_env.h
split_at.o: split_at.c
split_at.o: sys_defs.h
split_at.o: split_at.h
--- /dev/null
+/*++
+/* NAME
+/* spawn_command 3
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/*
+/* WAIT_STATUS_T spawn_command(key, value, ...)
+/* int key;
+/* DESCRIPTION
+/* spawn_command() runs a command in a child process and returns
+/* the command exit status.
+/*
+/* Arguments:
+/* .IP key
+/* Specifies what value will follow. spawn_command() takes a list
+/* of (key, value) arguments, terminated by SPAWN_CMD_END. The
+/* following is a listing of key codes together with the expected
+/* value type.
+/* .RS
+/* .IP "SPAWN_CMD_COMMAND (char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
+/* See also the SPAWN_CMD_SHELL attribute below.
+/* .IP "SPAWN_CMD_ARGV (char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
+/* .IP "SPAWN_CMD_ENV (char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "SPAWN_CMD_STDIN (int)"
+/* .IP "SPAWN_CMD_STDOUT (int)"
+/* .IP "SPAWN_CMD_STDERR (int)"
+/* Each of these specifies I/O redirection of one of the standard file
+/* descriptors for the command.
+/* .IP "SPAWN_CMD_UID (int)"
+/* The user ID to execute the command as.
+/* .IP "SPAWN_CMD_GID (int)"
+/* The group ID to execute the command as.
+/* .IP "SPAWN_CMD_TIME_LIMIT (int)"
+/* The amount of time in seconds the command is allowed to run before
+/* it is terminated with SIGKILL. The default is no time limit.
+/* .IP "SPAWN_CMD_SHELL (char *)"
+/* The shell to use when executing the command specified with
+/* SPAWN_CMD_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: interface violations (for example, a missing command).
+/*
+/* Fatal error: fork() failure, other system call failures.
+/*
+/* spawn_command() returns the exit status as defined by wait(2).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* exec_command(3) execute command
+/* 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 <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <timed_wait.h>
+#include <set_ugid.h>
+#include <argv.h>
+#include <spawn_command.h>
+#include <exec_command.h>
+#include <clean_env.h>
+
+/* Application-specific. */
+
+struct spawn_args {
+ char **argv; /* argument vector */
+ char *command; /* or a plain string */
+ int stdin_fd; /* read stdin here */
+ int stdout_fd; /* write stdout here */
+ int stderr_fd; /* write stderr here */
+ uid_t uid; /* privileges */
+ gid_t gid; /* privileges */
+ char **env; /* extra environment */
+ char *shell; /* command shell */
+ int time_limit; /* command time limit */
+};
+
+/* get_spawn_args - capture the variadic argument list */
+
+static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
+{
+ char *myname = "get_spawn_args";
+ int key;
+
+ /*
+ * First, set the default values.
+ */
+ args->argv = 0;
+ args->command = 0;
+ args->stdin_fd = -1;
+ args->stdout_fd = -1;
+ args->stderr_fd = -1;
+ args->uid = (uid_t) - 1;
+ args->gid = (gid_t) - 1;
+ args->env = 0;
+ args->shell = 0;
+ args->time_limit = 0;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
+ switch (key) {
+ case SPAWN_CMD_ARGV:
+ if (args->command)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_COMMAND:
+ if (args->argv)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case SPAWN_CMD_STDIN:
+ args->stdin_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDOUT:
+ args->stdout_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDERR:
+ args->stderr_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_UID:
+ args->uid = va_arg(ap, int); /* in case uid_t is short */
+ break;
+ case SPAWN_CMD_GID:
+ args->gid = va_arg(ap, int); /* in case gid_t is short */
+ break;
+ case SPAWN_CMD_TIME_LIMIT:
+ args->time_limit = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
+ if (args->command == 0 && args->shell != 0)
+ msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
+ myname);
+}
+
+/* spawn_command - execute command with extreme prejudice */
+
+WAIT_STATUS_T spawn_command(int key,...)
+{
+ char *myname = "spawn_comand";
+ va_list ap;
+ pid_t pid;
+ WAIT_STATUS_T wait_status;
+ struct spawn_args args;
+ char **cpp;
+ ARGV *argv;
+ int err;
+
+ /*
+ * Process the variadic argument list. This also does sanity checks on
+ * what data the caller is passing to us.
+ */
+ va_start(ap, key);
+ get_spawn_args(&args, key, ap);
+ va_end(ap);
+
+ /*
+ * For convenience...
+ */
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ /*
+ * Spawn off a child process and irrevocably change privilege to the
+ * user. This includes revoking all rights on open files (via the close
+ * on exec flag). If we cannot run the command now, try again some time
+ * later.
+ */
+ switch (pid = fork()) {
+
+ /*
+ * Error. Instead of trying again right now, back off, give the
+ * system a chance to recover, and try again later.
+ */
+ case -1:
+ msg_fatal("fork: %m");
+
+ /*
+ * Child. Run the child in a separate process group so that the
+ * parent can kill not just the child but also its offspring.
+ */
+ case 0:
+ if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
+ set_ugid(args.uid, args.gid);
+ setsid();
+
+ /*
+ * Pipe plumbing.
+ */
+ if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
+ || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
+ || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
+ msg_fatal("%s: dup2: %m", myname);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ clean_env();
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ */
+ closelog();
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, " \t\r\n");
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+
+ /*
+ * Parent.
+ */
+ default:
+
+ /*
+ * Be prepared for the situation that the child does not terminate.
+ * Make sure that the child terminates before the parent attempts to
+ * retrieve its exit status, otherwise the parent could become stuck,
+ * and the mail system would eventually run out of exec daemons. Do a
+ * thorough job, and kill not just the child process but also its
+ * offspring.
+ */
+ if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
+ && errno == ETIMEDOUT) {
+ msg_warn("%s: process id %d: command time limit exceeded",
+ args.command, pid);
+ kill(-pid, SIGKILL);
+ err = waitpid(pid, &wait_status, 0);
+ }
+ if (err < 0)
+ msg_fatal("wait: %m");
+ return (wait_status);
+ }
+}
--- /dev/null
+#ifndef _SPAWN_COMMAND_H_INCLUDED_
+#define _SPAWN_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* spawn_command 3h
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Request arguments.
+ */
+#define SPAWN_CMD_END 0 /* terminator */
+#define SPAWN_CMD_ARGV 1 /* command is array */
+#define SPAWN_CMD_COMMAND 2 /* command is string */
+#define SPAWN_CMD_STDIN 3 /* mail_copy() flags */
+#define SPAWN_CMD_STDOUT 4 /* mail_copy() sender */
+#define SPAWN_CMD_STDERR 5 /* mail_copy() recipient */
+#define SPAWN_CMD_UID 6 /* privileges */
+#define SPAWN_CMD_GID 7 /* privileges */
+#define SPAWN_CMD_TIME_LIMIT 8 /* time limit */
+#define SPAWN_CMD_ENV 9 /* extra environment */
+#define SPAWN_CMD_SHELL 10 /* alternative shell */
+
+extern int spawn_command(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