-TBH_TABLE
-TBINATTR
-TBINATTR_INFO
+-Tbind_props
-TBINHASH
-TBINHASH_INFO
-TBIO
-TBYTE_MASK
-TCFG_PARSER
-TCIDR_MATCH
+-Tcipher_probe_t
-TCLEANUP_REGION
--TCLEANUP_STATE
-TCLEANUP_STAT_DETAIL
+-TCLEANUP_STATE
-TCLIENT_LIST
-TCLNT_STREAM
-TCONFIG_BOOL_FN_TABLE
-TCRYPTO_EX_DATA
-TCTABLE
-TCTABLE_ENTRY
+-Td2i_X509_t
+-Tdane_digest
+-Tdane_mtype
-TDB_COMMON_CTX
--TDELIVERED_HDR_INFO
-TDELIVER_ATTR
+-TDELIVERED_HDR_INFO
-TDELIVER_REQUEST
-TDELTA_TIME
-TDICT
-TEVP_PKEY
-TEXPAND_ATTR
-TFILE
+-Tfilter_ctx
-TFORWARD_INFO
+-Tgeneral_name_stack_t
-THBC_ACTION_CALL_BACKS
-THBC_CALL_BACKS
-THBC_CHECKS
-THOST
-THTABLE
-THTABLE_INFO
+-Tiana_digest
-TINET_ADDR_LIST
-TINET_PROTO_INFO
-TINSTANCE
-TINST_SELECTION
-TINT32_TYPE
--TINTV
-TINT_TABLE
+-TINTV
-TJMP_BUF_WRAPPER
-TLDAP
--TLDAPMessage
-TLDAP_CONN
+-TLDAPMessage
-TLIB_DP
-TLIB_FN
-TLMTP_ATTR
-TLMTP_STATE
-TLOCAL_EXP
-TLOCAL_STATE
+-TLOGIN_SENDER_MATCH
-TLOGWRITER
-TLONG_NAME_MASK
-TMAC_EXP_CONTEXT
-TMAC_EXP_OP_INFO
-TMAC_HEAD
-TMAC_PARSE
+-TMAI_HOSTADDR_STR
+-TMAI_HOSTNAME_STR
-TMAIL_ADDR_FORMATTER
-TMAIL_ADDR_MAP_TEST
-TMAIL_PRINT
-TMAIL_SCAN
-TMAIL_STREAM
-TMAIL_VERSION
--TMAI_HOSTADDR_STR
--TMAI_HOSTNAME_STR
-TMAI_SERVNAME_STR
-TMAI_SERVPORT_STR
-TMAPS
-TMDB_val
-TMILTER
-TMILTER8
--TMILTERS
-TMILTER_MACROS
-TMILTER_MSG_CONTEXT
+-TMILTERS
-TMIME_ENCODING
-TMIME_INFO
-TMIME_STACK
-TNAME_CODE
-TNAME_MASK
-TNBBIO
+-Toff_t
-TOPTIONS
-TPCF_DBMS_INFO
-TPCF_EVAL_CTX
-TPCF_SERVICE_PATTERN
-TPCF_STRING_NV
-TPEER_NAME
+-Tpem_load_state_t
-TPGSQL_NAME
-TPICKUP_INFO
-TPIPE_ATTR
-TPIPE_STATE
-TPLMYSQL
-TPLPGSQL
--TPOSTMAP_KEY_STATE
-TPOST_MAIL_FCLOSE_STATE
-TPOST_MAIL_STATE
+-TPOSTMAP_KEY_STATE
-TPRIVATE_STR_TABLE
-TPSC_CALL_BACK_ENTRY
-TPSC_CLIENT_INFO
-TRECIPIENT
-TRECIPIENT_LIST
-TREC_TYPE_NAME
+-Tregex_t
+-Tregmatch_t
+-TRES_CONTEXT
-TRESOLVE_REPLY
-TRESPONSE
-TREST_TABLE
--TRES_CONTEXT
-TRWR_CONTEXT
+-Tsasl_conn_t
+-Tsasl_secret_t
-TSCACHE
-TSCACHE_CLNT
-TSCACHE_MULTI
-TSCAN_DIR
-TSCAN_INFO
-TSCAN_OBJ
+-TSENDER_LOGIN_MATCH
-TSESSION
+-Tsfsistat
-TSHARED_PATH
+-Tsigset_t
-TSINGLE_SERVER
-TSINK_COMMAND
-TSINK_STATE
+-Tsize_t
-TSLMDB
-TSMFICTX
+-TSM_STATE
+-TSMTP_ADDR
+-TSMTP_CLI_ATTR
+-TSMTP_CMD
-TSMTPD_CMD
-TSMTPD_DEFER
-TSMTPD_ENDPT_LOOKUP_INFO
-TSMTPD_STATE
-TSMTPD_TOKEN
-TSMTPD_XFORWARD_ATTR
--TSMTP_ADDR
--TSMTP_CLI_ATTR
--TSMTP_CMD
-TSMTP_ITERATOR
-TSMTP_RESP
-TSMTP_SASL_AUTH_CACHE
-TSMTP_TLS_POLICY
-TSMTP_TLS_SESS
-TSMTP_TLS_SITE_POLICY
--TSM_STATE
+-Tsockaddr
-TSOCKADDR_SIZE
-TSPAWN_ATTR
+-Tssize_t
-TSSL
+-Tssl_cipher_stack_t
+-Tssl_comp_stack_t
-TSSL_CTX
-TSSL_SESSION
-TSTATE
-TSTRING_TABLE
-TSYS_EXITS_DETAIL
-TTEST_CASE
--TTLSMGR_SCACHE
--TTLSP_STATE
+-Ttime_t
+-Ttlsa_filter
-TTLS_APPL_STATE
-TTLS_CERTS
-TTLS_CLIENT_INIT_PROPS
-TTLS_CLIENT_PARAMS
-TTLS_CLIENT_START_PROPS
+-TTLScontext_t
-TTLS_DANE
+-TTLSMGR_SCACHE
-TTLS_PKEYS
-TTLS_PRNG_SEED_INFO
-TTLS_PRNG_SRC
+-TTLSP_STATE
-TTLS_ROLE
-TTLS_SCACHE
-TTLS_SCACHE_ENTRY
-TTLS_TLSA
-TTLS_USAGE
-TTLS_VINFO
--TTLScontext_t
-TTOK822
-TTRANSPORT_INFO
-TTRIGGER_SERVER
+-Tuint16_t
+-Tuint32_t
+-Tuint8_t
-TUSER_ATTR
-TVBUF
-TVSTREAM
-TWATCHDOG
-TWATCH_FD
-TX509
--TX509V3_CTX
-TX509_EXTENSION
-TX509_NAME
+-Tx509_stack_t
-TX509_STORE_CTX
+-TX509V3_CTX
-TXSASL_CLIENT
-TXSASL_CLIENT_CREATE_ARGS
-TXSASL_CLIENT_IMPL
-TXSASL_SERVER_CREATE_ARGS
-TXSASL_SERVER_IMPL
-TXSASL_SERVER_IMPL_INFO
--Tbind_props
--Tcipher_probe_t
--Td2i_X509_t
--Tdane_digest
--Tdane_mtype
--Tfilter_ctx
--Tgeneral_name_stack_t
--Tiana_digest
--Toff_t
--Tpem_load_state_t
--Tregex_t
--Tregmatch_t
--Tsasl_conn_t
--Tsasl_secret_t
--Tsfsistat
--Tsigset_t
--Tsize_t
--Tsockaddr
--Tssize_t
--Tssl_cipher_stack_t
--Tssl_comp_stack_t
--Ttime_t
--Ttlsa_filter
--Tuint16_t
--Tuint32_t
--Tuint8_t
--Tx509_stack_t
Cleanup: some wordsmithing of warnings when DNSSEC validation
is unavailable. File: dns/dns_sec.c.
- Clenaup: add missing warnings for libpostfix version
+ Cleanup: add missing warnings for libpostfix version
mismatches. This will help folks with build processes that
mistakenly run newly-built Postfix installation commands
with previously-installed libpostfix files. Files:
Documentation: hyperlink occurrences of the info_log_address_format
parameter name in daemon manpages.
+
+20201005
+
+ Cleanup: move the submit_users check after the postdrop
+ initializations that strip the environment, set up signal
+ handlers, etc. File: postdrop/postdrop.c.
+
+ Documentation: descriptions of Postfix TLS wrappermode
+ support. File: proto/TLS_README.html, proto/SASL_README.html.
+
+20201011
+
+ Cleanup: save a copy of the postscreen_dnsbl_reply_map
+ lookup result. This has no effect when the recommended
+ texthash: look table is used, but it may avoid stale data
+ with other lookup tables. File: postscreen/postscreen_dnsbl.c.
+
+20201015
+
+ Documentation: simplified the recipient_delimiter
+ description. File: proto/postconf.proto.
+
+20201022
+
+ Bugfix (introduced: Postfix 2.2): after processing an
+ XCCLIENT command, the smtps service was waiting for a TLS
+ handshake. Found by Aki Tuomi. File: smtpd/smtpd.c.
+
+20201025
+
+ Feature: local_login_sender_maps to lock down the envelope
+ sender addresses that the postdrop command will accept. The
+ default is backwards compatible. Files: postdrop/postdrop.c,
+ global/mail_params.h, global/local_sender_login_match.[hc],
+ global/local_sender_login_match.in,
+ global/local_sender_login_match.ref, global/quote_822_local.c,
+ global/quote_822_local.in, global/quote_822_local.ref,
+ mantools/postlink, proto/postconf.proto.
+
+ Bugfix (introduced: Postfix 2.3): static maps did not free
+ their casefolding buffer. File: util/dict_static.c.
standard "AUTH=m\bme\bet\bth\bho\bod\bd.\b...." syntax in response to the EHLO command; this
requires no additional Postfix client configuration.
- * The Postfix SMTP client does not support the obsolete "wrappermode"
- protocol, which uses TCP port 465 on the SMTP server. See TLS_README for a
- solution that uses the stunnel command.
+ * With the setting "smtp_tls_wrappermode = yes", the Postfix SMTP client
+ supports the "wrappermode" protocol, which uses TCP port 465 on the SMTP
+ server (Postfix 3.0 and later).
* With the smtp_sasl_password_maps parameter, we configure the Postfix SMTP
client to send username and password information to the mail gateway
standard "AUTH=m\bme\bet\bth\bho\bod\bd.\b...." syntax in response to the EHLO command; this
requires no additional Postfix client configuration.
- * The Postfix SMTP client does not support the obsolete "wrappermode"
- protocol, which uses TCP port 465 on the SMTP server. See TLS_README for a
- solution that uses the stunnel command.
+ * With the setting "smtp_tls_wrappermode = yes", the Postfix SMTP client
+ supports the "wrappermode" protocol, which uses TCP port 465 on the SMTP
+ server (Postfix 3.0 and later).
* With the smtp_sasl_password_maps parameter, we configure the Postfix SMTP
client to send username and password information to the mail gateway
/etc/postfix/main.cf:
smtpd_tls_security_level = encrypt
-TLS is sometimes used in the non-standard "wrapper" mode where a server always
-uses TLS, instead of announcing STARTTLS support and waiting for remote SMTP
-clients to request TLS service. Some clients, namely Outlook [Express] prefer
-the "wrapper" mode. This is true for OE (Win32 < 5.0 and Win32 >=5.0 when run
-on a port<>25 and OE (5.01 Mac on all ports).
+TLS is also used in the "wrapper" mode where a server always uses TLS, instead
+of announcing STARTTLS support and waiting for remote SMTP clients to request
+TLS service. Some clients, namely Outlook [Express] prefer the "wrapper" mode.
+This is true for OE (Win32 < 5.0 and Win32 >=5.0 when run on a port<>25 and OE
+(5.01 Mac on all ports).
It is strictly discouraged to use this mode from main.cf. If you want to
support this service, enable a special port in master.cf and specify "-
C\bCl\bli\bie\ben\bnt\bt-\b-s\bsi\bid\bde\be S\bSM\bMT\bTP\bPS\bS s\bsu\bup\bpp\bpo\bor\brt\bt
These sections show how to send mail to a server that does not support
-STARTTLS, but that provides the deprecated SMTPS service on TCP port 465.
-Depending on the Postfix version, some additional tooling may be required.
+STARTTLS, but that provides the SMTPS service on TCP port 465. Depending on the
+Postfix version, some additional tooling may be required.
P\bPo\bos\bst\btf\bfi\bix\bx >\b>=\b= 3\b3.\b.0\b0
Does tlsproxy terminate to soon after 'postfix reload'?
+ Eliminate duplicate user_acl check from sendmail, and pass
+ the result through the postdrop-to-sendmail protocol. This
+ requires that postdrop reads all inputs before responding.
+ Then we can also consider to save input to dead.letter (drop
+ setgid privilege, use safe_open() to avoid clobbering files).
+
Understand what happens with DNSSEC related status fields
in posttls-finger when resolv.conf points to a host that
runs no DNS server.
syntax in response to the EHLO command; this requires no additional
Postfix client configuration. </p> </li>
-<li> <p> The Postfix SMTP client does not support the obsolete
-"wrappermode" protocol, which uses TCP port <code>465</code> on the
-SMTP server. See <a href="TLS_README.html">TLS_README</a> for a solution that uses the
-<code>stunnel</code> command. </p> </li>
+<li> <p> With the setting "<a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
<li> <p> With the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> parameter,
we configure the Postfix SMTP client to send username and password
syntax in response to the EHLO command; this requires no additional
Postfix client configuration. </p> </li>
-<li> <p> The Postfix SMTP client does not support the obsolete
-"wrappermode" protocol, which uses TCP port <code>465</code> on the
-SMTP server. See <a href="TLS_README.html">TLS_README</a> for a solution that uses the
-<code>stunnel</code> command. </p> </li>
+<li> <p> With the setting "<a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
<li> <p> With the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> parameter,
we configure the Postfix SMTP client to send username and password
</pre>
</blockquote>
-<p> TLS is sometimes used in the non-standard "wrapper" mode where
+<p> TLS is also used in the "wrapper" mode where
a server always uses TLS, instead of announcing STARTTLS support
and waiting for remote SMTP clients to request TLS service. Some
clients, namely
<h3> <a name="client_smtps">Client-side SMTPS support </a> </h3>
<p> These sections show how to send mail to a server that does not
-support STARTTLS, but that provides the deprecated SMTPS service
+support STARTTLS, but that provides the SMTPS service
on TCP port 465. Depending on the Postfix version, some additional
tooling may be required. </p>
<p> This feature is available in Postfix 2.7 and later. </p>
+</DD>
+
+<DT><b><a name="empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a>
+(default: <>)</b></DT><DD>
+
+<p>
+The lookup key to be used in <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> tables, instead
+of the null sender address.
+</p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
</DD>
<DT><b><a name="empty_address_recipient">empty_address_recipient</a>
</blockquote>
+</DD>
+
+<DT><b><a name="local_login_sender_maps">local_login_sender_maps</a>
+(default: <a href="DATABASE_README.html#types">static</a>:*)</b></DT><DD>
+
+<p> A list of lookup tables that are searched by the UNIX login name,
+and that return a list of allowed envelope sender patterns separated
+by space or comma. These sender patterns are enforced by the Postfix
+<a href="postdrop.1.html">postdrop(1)</a> command. The default is backwards-compatible:
+every user may specify any envelope information. </p>
+
+<p> When no UNIX login name is available, the postdrop command will
+prepend '#' to the numerical UID and use that instead. </p>
+
+<p> Before checking a sender address against <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a>,
+Postfix will strip an address extension based on the current
+<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> value.
+
+<p> The following sender patterns are special; these cannot be used
+as part of a longer pattern. </p>
+
+<dl>
+
+<dt> <b> * </b> <dd> This pattern allows everything. </dd>
+
+<dt> <b> <> </b> </dt> <dd> This pattern allows the null sender
+address. It is configured with the
+<a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a> configuration
+parameter. </dd>
+
+<dt> <b> @<i>domain</i></b> </dt> <dd> This pattern allows a sender
+address when the '@' and <i>domain</i> part match. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use # followed by the numerical UID
+ # when the UID has no entry in the UNIX password file.
+ <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> =
+ <a href="DATABASE_README.html#types">inline</a>:{ { root = *}, { postfix = * } },
+ <a href="pcre_table.5.html">pcre</a>:/etc/postfix/login_senders
+</pre>
+
+<pre>
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com/
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
</DD>
<DT><b><a name="local_recipient_maps">local_recipient_maps</a>
<DT><b><a name="recipient_delimiter">recipient_delimiter</a>
(default: empty)</b></DT><DD>
-<p> The set of characters that can separate a user name from its
-extension (example: user+foo), or a .forward file name from its
-extension (example: .forward+foo). Basically, the software tries
-user+foo and .forward+foo before trying user and .forward. This
-implementation recognizes one delimiter character and one extension
-per email address or .forward file name. </p>
+<p> The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward. </p>
-<p> When the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> set contains multiple characters
-(Postfix 2.11 and later), a user name or .forward file name is
+<p> More formally, an email address localpart or user name is
separated from its extension by the first character that matches
-the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> set. </p>
+the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earler, the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> specifies a single character. </p>
<p> See <a href="canonical.5.html">canonical(5)</a>, <a href="local.8.html">local(8)</a>, <a href="relocated.5.html">relocated(5)</a> and <a href="virtual.5.html">virtual(5)</a> for the
effects of <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> on lookups in aliases, canonical,
<a href="sendmail.1.html"><b>mail</b>(1)</a> command (and with the privileged <a href="postdrop.1.html"><b>postdrop</b>(1)</a> helper com-
mand).
+ Available in Postfix version 3.6 and later:
+
+ <b><a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> (<a href="DATABASE_README.html#types">static</a>:*)</b>
+ A list of lookup tables that are searched by the UNIX login
+ name, and that return a list of allowed envelope sender patterns
+ separated by space or comma.
+
+ <b><a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a> (</b><><b>)</b>
+ The lookup key to be used in <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> tables,
+ instead of the null sender address.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
<b>FILES</b>
/var/spool/postfix/<a href="QSHAPE_README.html#maildrop_queue">maildrop</a>, <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a>
The location of the Postfix top-level queue directory.
<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
- The set of characters that can separate a user name from its
- extension (example: user+foo), or a .forward file name from its
- extension (example: .forward+foo).
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
<b><a href="postconf.5.html#smtpd_banner">smtpd_banner</a> ($<a href="postconf.5.html#myhostname">myhostname</a> ESMTP $<a href="postconf.5.html#mail_name">mail_name</a>)</b>
- The text that follows the 220 status code in the SMTP greeting
+ The text that follows the 220 status code in the SMTP greeting
banner.
<b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
The syslog facility of Postfix logging.
<b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
- A prefix that is prepended to the process name in syslog
+ A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
Available in Postfix version 2.2 and later:
<b><a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a> (CONNECT, GET, POST)</b>
- List of commands that cause the Postfix SMTP server to immedi-
+ List of commands that cause the Postfix SMTP server to immedi-
ately terminate the session with a 221 code.
Available in Postfix version 2.5 and later:
Available in Postfix 3.4 and later:
<b><a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a> (empty)</b>
- Lookup tables, indexed by the complete Postfix SMTP server 4xx
+ Lookup tables, indexed by the complete Postfix SMTP server 4xx
or 5xx response, with reject footer templates.
<b>SEE ALSO</b>
.IP "\fBauthorized_submit_users (static:anyone)\fR"
List of users who are authorized to submit mail with the \fBsendmail\fR(1)
command (and with the privileged \fBpostdrop\fR(1) helper command).
+.PP
+Available in Postfix version 3.6 and later:
+.IP "\fBlocal_login_sender_maps (static:*)\fR"
+A list of lookup tables that are searched by the UNIX login name,
+and that return a list of allowed envelope sender patterns separated
+by space or comma.
+.IP "\fBempty_address_local_login_sender_maps_lookup_key (<>)\fR"
+The lookup key to be used in local_login_sender_maps tables, instead
+of the null sender address.
+.IP "\fBrecipient_delimiter (empty)\fR"
+The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
.SH "FILES"
.na
.nf
will be used instead of the null sender address.
.PP
This feature is available in Postfix 2.7 and later.
+.SH empty_address_local_login_sender_maps_lookup_key (default: <>)
+The lookup key to be used in local_login_sender_maps tables, instead
+of the null sender address.
+.PP
+This feature is available in Postfix 3.6 and later.
.SH empty_address_recipient (default: MAILER\-DAEMON)
The recipient of mail addressed to the null address. Postfix does
not accept such addresses in SMTP commands, but they may still be
.ad
.ft R
.in -4
+.SH local_login_sender_maps (default: static:*)
+A list of lookup tables that are searched by the UNIX login name,
+and that return a list of allowed envelope sender patterns separated
+by space or comma. These sender patterns are enforced by the Postfix
+\fBpostdrop\fR(1) command. The default is backwards\-compatible:
+every user may specify any envelope information.
+.PP
+When no UNIX login name is available, the postdrop command will
+prepend '#' to the numerical UID and use that instead.
+.PP
+Before checking a sender address against local_login_sender_maps,
+Postfix will strip an address extension based on the current
+recipient_delimiter value.
+.PP
+The following sender patterns are special; these cannot be used
+as part of a longer pattern.
+.IP "\fB * \fR
+This pattern allows everything.
+.br
+.IP "\fB <> \fR"
+This pattern allows the null sender
+address. It is configured with the
+empty_address_local_login_sender_maps_lookup_key configuration
+parameter.
+.br
+.IP "\fB @\fIdomain\fR\fR"
+This pattern allows a sender
+address when the '@' and \fIdomain\fR part match.
+.br
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use # followed by the numerical UID
+ # when the UID has no entry in the UNIX password file.
+ local_login_sender_maps =
+ inline:{ { root = *}, { postfix = * } },
+ pcre:/etc/postfix/login_senders
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com/
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.6 and later.
.SH local_recipient_maps (default: proxy:unix:passwd.byname $alias_maps)
Lookup tables with all names or addresses of local recipients:
a recipient address is local when its domain matches $mydestination,
.ad
.ft R
.SH recipient_delimiter (default: empty)
-The set of characters that can separate a user name from its
-extension (example: user+foo), or a .forward file name from its
-extension (example: .forward+foo). Basically, the software tries
-user+foo and .forward+foo before trying user and .forward. This
-implementation recognizes one delimiter character and one extension
-per email address or .forward file name.
+The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "recipient_delimiter = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward.
.PP
-When the recipient_delimiter set contains multiple characters
-(Postfix 2.11 and later), a user name or .forward file name is
+More formally, an email address localpart or user name is
separated from its extension by the first character that matches
-the recipient_delimiter set.
+the recipient_delimiter set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earler, the recipient_delimiter specifies a single character.
.PP
See \fBcanonical\fR(5), \fBlocal\fR(8), \fBrelocated\fR(5) and \fBvirtual\fR(5) for the
effects of recipient_delimiter on lookups in aliases, canonical,
.IP "\fBqueue_directory (see 'postconf -d' output)\fR"
The location of the Postfix top\-level queue directory.
.IP "\fBrecipient_delimiter (empty)\fR"
-The set of characters that can separate a user name from its
-extension (example: user+foo), or a .forward file name from its
-extension (example: .forward+foo).
+The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
.IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
The text that follows the 220 status code in the SMTP greeting
banner.
s;\bmail[-</bB>]*\n*[ <bB>]*log_file_rotate_suffix\b;<a href="postconf.5.html#maillog_file_rotate_suffix">$&</a>;g;
s;\bpostlog_service_name\b;<a href="postconf.5.html#postlog_service_name">$&</a>;g;
s;\bpostlogd_watchdog_timeout\b;<a href="postconf.5.html#postlogd_watchdog_timeout">$&</a>;g;
+ s;\blocal_login_sender_maps\b;<a href="postconf.5.html#local_login_sender_maps">$&</a>;g;
+ s;\bempty_address_local_login_sender_maps_lookup_key\b;<a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">$&</a>;g;
# Service-defined parameters...
syntax in response to the EHLO command; this requires no additional
Postfix client configuration. </p> </li>
-<li> <p> The Postfix SMTP client does not support the obsolete
-"wrappermode" protocol, which uses TCP port <code>465</code> on the
-SMTP server. See TLS_README for a solution that uses the
-<code>stunnel</code> command. </p> </li>
+<li> <p> With the setting "smtp_tls_wrappermode = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
<li> <p> With the <code>smtp_sasl_password_maps</code> parameter,
we configure the Postfix SMTP client to send username and password
</pre>
</blockquote>
-<p> TLS is sometimes used in the non-standard "wrapper" mode where
+<p> TLS is also used in the "wrapper" mode where
a server always uses TLS, instead of announcing STARTTLS support
and waiting for remote SMTP clients to request TLS service. Some
clients, namely
<h3> <a name="client_smtps">Client-side SMTPS support </a> </h3>
<p> These sections show how to send mail to a server that does not
-support STARTTLS, but that provides the deprecated SMTPS service
+support STARTTLS, but that provides the SMTPS service
on TCP port 465. Depending on the Postfix version, some additional
tooling may be required. </p>
%PARAM recipient_delimiter
-<p> The set of characters that can separate a user name from its
-extension (example: user+foo), or a .forward file name from its
-extension (example: .forward+foo). Basically, the software tries
-user+foo and .forward+foo before trying user and .forward. This
-implementation recognizes one delimiter character and one extension
-per email address or .forward file name. </p>
+<p> The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "recipient_delimiter = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward. </p>
-<p> When the recipient_delimiter set contains multiple characters
-(Postfix 2.11 and later), a user name or .forward file name is
+<p> More formally, an email address localpart or user name is
separated from its extension by the first character that matches
-the recipient_delimiter set. </p>
+the recipient_delimiter set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earler, the recipient_delimiter specifies a single character. </p>
<p> See canonical(5), local(8), relocated(5) and virtual(5) for the
effects of recipient_delimiter on lookups in aliases, canonical,
value to disable the feature. </p>
<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM local_login_sender_maps static:*
+
+<p> A list of lookup tables that are searched by the UNIX login name,
+and that return a list of allowed envelope sender patterns separated
+by space or comma. These sender patterns are enforced by the Postfix
+postdrop(1) command. The default is backwards-compatible:
+every user may specify any envelope information. </p>
+
+<p> When no UNIX login name is available, the postdrop command will
+prepend '#' to the numerical UID and use that instead. </p>
+
+<p> Before checking a sender address against local_login_sender_maps,
+Postfix will strip an address extension based on the current
+recipient_delimiter value.
+
+<p> The following sender patterns are special; these cannot be used
+as part of a longer pattern. </p>
+
+<dl>
+
+<dt> <b> * </b> <dd> This pattern allows everything. </dd>
+
+<dt> <b> <> </b> </dt> <dd> This pattern allows the null sender
+address. It is configured with the
+empty_address_local_login_sender_maps_lookup_key configuration
+parameter. </dd>
+
+<dt> <b> @<i>domain</i></b> </dt> <dd> This pattern allows a sender
+address when the '@' and <i>domain</i> part match. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use # followed by the numerical UID
+ # when the UID has no entry in the UNIX password file.
+ local_login_sender_maps =
+ inline:{ { root = *}, { postfix = * } },
+ pcre:/etc/postfix/login_senders
+</pre>
+
+<pre>
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com/
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM empty_address_local_login_sender_maps_lookup_key <>
+
+<p>
+The lookup key to be used in local_login_sender_maps tables, instead
+of the null sender address.
+</p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
smtputf8.c mail_conf_over.c mail_parm_split.c midna_adomain.c \
mail_addr_form.c quote_flags.c maillog_client.c \
normalize_mailhost_addr.c map_search.c reject_deliver_request.c \
- info_log_addr_form.c sasl_mech_filter.c
+ info_log_addr_form.c sasl_mech_filter.c login_sender_match.c
OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \
clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \
smtputf8.o attr_override.o mail_parm_split.o midna_adomain.o \
$(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \
normalize_mailhost_addr.o map_search.o reject_deliver_request.o \
- info_log_addr_form.o sasl_mech_filter.o
+ info_log_addr_form.o sasl_mech_filter.o login_sender_match.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
haproxy_srvr.h dsn_filter.h dynamicmaps.h uxtext.h smtputf8.h \
attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \
maillog_client.h normalize_mailhost_addr.h map_search.h \
- info_log_addr_form.h sasl_mech_filter.h
+ info_log_addr_form.h sasl_mech_filter.h login_sender_match.h
TESTSRC = rec2stream.c stream2rec.c recdump.c
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
data_redirect addr_match_list safe_ultostr verify_sender_addr \
mail_version mail_dict server_acl uxtext mail_parm_split \
fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
- haproxy_srvr map_search delivered_hdr
+ haproxy_srvr map_search delivered_hdr login_sender_match
LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
LIB_DIR = ../../lib
delivered_hdr: delivered_hdr.c $(LIB) $(LIBS)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+login_sender_match: login_sender_match.c $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
xtext_test scache_multi_test ehlo_mask_test \
namadr_list_test mail_conf_time_test header_body_checks_tests \
smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \
mail_addr_find_test mail_addr_map_test quote_822_local_test \
normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
- delivered_hdr_test
+ delivered_hdr_test login_sender_match_test
mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \
mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4
diff delivered_hdr.ref delivered_hdr.tmp
rm -f delivered_hdr.tmp
+login_sender_match_test: update login_sender_match login_sender_match.ref
+ -$(SHLIB_ENV) $(VALGRIND) ./login_sender_match >login_sender_match.tmp 2>&1
+ diff login_sender_match.ref login_sender_match.tmp
+ rm -f login_sender_match.tmp
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
log_adhoc.o: mail_params.h
log_adhoc.o: msg_stats.h
log_adhoc.o: recipient_list.h
+login_sender_match.o: ../../include/argv.h
+login_sender_match.o: ../../include/check_arg.h
+login_sender_match.o: ../../include/dict.h
+login_sender_match.o: ../../include/msg.h
+login_sender_match.o: ../../include/myflock.h
+login_sender_match.o: ../../include/mymalloc.h
+login_sender_match.o: ../../include/stringops.h
+login_sender_match.o: ../../include/sys_defs.h
+login_sender_match.o: ../../include/vbuf.h
+login_sender_match.o: ../../include/vstream.h
+login_sender_match.o: ../../include/vstring.h
+login_sender_match.o: login_sender_match.c
+login_sender_match.o: login_sender_match.h
+login_sender_match.o: mail_params.h
+login_sender_match.o: maps.h
+login_sender_match.o: quote_822_local.h
+login_sender_match.o: quote_flags.h
+login_sender_match.o: strip_addr.h
mail_addr.o: ../../include/check_arg.h
mail_addr.o: ../../include/stringops.h
mail_addr.o: ../../include/sys_defs.h
CLEANUP_STAT_SIZE, 552, "5.3.4", "message file too big",
CLEANUP_STAT_CONT, 550, "5.7.1", "message content rejected",
CLEANUP_STAT_WRITE, 451, "4.3.0", "queue file write error",
+ CLEANUP_STAT_NOPERM, 550, "5.7.1", "service denied",
};
static CLEANUP_STAT_DETAIL cleanup_stat_success = {
#define CLEANUP_STAT_RCPT (1<<6) /* No recipients found */
#define CLEANUP_STAT_PROXY (1<<7) /* Proxy reject */
#define CLEANUP_STAT_DEFER (1<<8) /* Temporary reject */
+#define CLEANUP_STAT_NOPERM (1<<9) /* Denied by non-content policy */
/*
* These are set when we can't bounce even if we were asked to.
--- /dev/null
+/*++
+/* NAME
+/* login_sender_match 3
+/* SUMMARY
+/* match login and sender against (login, sender) patterns
+/* SYNOPSIS
+/* #include <login_sender.h>
+/*
+/* typedef LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH;
+/*
+/* LOGIN_SENDER_MATCH *login_sender_create(
+/* const char *title,
+/* const char *map_names,
+/* const char *ext_delimiters,
+/* const char *null_sender,
+/* const char *wildcard)
+/*
+/* void login_sender_free(
+/* LOGIN_SENDER_MATCH *lsm)
+/*
+/* int login_sender_match(
+/* LOGIN_SENDER_MATCH *lsm,
+/* const char *login_name,
+/* const char *sender_addr)
+/* DESCRIPTION
+/* This module determines if a login name and internal-form
+/* sender address match a (login name, external-form sender
+/* patterns) table entry. A login name matches if it matches
+/* a lookup table key. A sender address matches the corresponding
+/* table entry if it matches a sender pattern. A wildcard
+/* sender pattern matches any sender address. A sender pattern
+/* that starts with '@' matches the '@' and the domain portion
+/* of a sender address. Otherwise, the matcher ignores the
+/* extension part of a sender address, and requires a
+/* case-insensitive match against a sender pattern.
+/*
+/* login_sender_create() creates a (login name, sender patterns)
+/* matcher.
+/*
+/* login_sender_free() destroys the specified (login name,
+/* sender patterns) matcher.
+/*
+/* login_sender_match() looks up an entry for the \fBlogin_name\fR
+/* argument, and determines if the lookup result matches the
+/* \fBsender_adddr\fR argument.
+/*
+/* Arguments:
+/* .IP title
+/* The name of the configuration parameter that specifies the
+/* map_names value, used for error messages.
+/* .IP map_names
+r* The lookup table(s) with (login name, sender patterns) entries.
+/* .IP ext_delimiters
+/* The set of address extension delimiters.
+/* .IP null_sender
+/* If a sender pattern equals the null_sender pattern, then
+/* the empty address is matched.
+/* .IP wildcard
+/* Null pointer, or non-empty string with a wildcard pattern.
+/* If a sender pattern equals the wildcard pattern, then any
+/* sender address is matched.
+/* .IP login_name
+/* The login name (for example, UNIX account, or SASL username)
+/* that will be used as a search key to locate a list of senders.
+/* .IP sender_addr
+/* The sender email address (unquoted form) that will be matched
+/* against a (login name, sender patterns) table entry.
+/* DIAGNOSTICS
+/* login_sender_match() returns LSM_STAT_FOUND if a
+/* match was found, LOGIN_STAT_NOTFOUND if no match was found,
+/* LSM_STAT_RETRY if the table lookup failed, or
+/* LSM_STAT_CONFIG in case of a configuration error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <maps.h>
+#include <quote_822_local.h>
+#include <strip_addr.h>
+#include <login_sender_match.h>
+
+ /*
+ * Private data structure.
+ */
+struct LOGIN_SENDER_MATCH {
+ MAPS *maps;
+ VSTRING *ext_stripped_sender;
+ char *ext_delimiters;
+ char *null_sender;
+ char *wildcard;
+};
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* login_sender_create - create (login name, sender patterns) matcher */
+
+LOGIN_SENDER_MATCH *login_sender_create(const char *title,
+ const char *map_names,
+ const char *ext_delimiters,
+ const char *null_sender,
+ const char *wildcard)
+{
+ LOGIN_SENDER_MATCH *lsm = mymalloc(sizeof *lsm);
+
+ lsm->maps = maps_create(title, map_names, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
+ lsm->ext_stripped_sender = vstring_alloc(100);
+ lsm->ext_delimiters = mystrdup(ext_delimiters);
+ if (null_sender == 0 || *null_sender == 0)
+ msg_panic("login_sender_create: null or empty null_sender");
+ lsm->null_sender = mystrdup(null_sender);
+ lsm->wildcard = (wildcard && *wildcard) ? mystrdup(wildcard) : 0;
+ return (lsm);
+}
+
+/* login_sender_free - destroy (login name, sender patterns) matcher */
+
+void login_sender_free(LOGIN_SENDER_MATCH *lsm)
+{
+ maps_free(lsm->maps);
+ vstring_free(lsm->ext_stripped_sender);
+ myfree(lsm->ext_delimiters);
+ myfree(lsm->null_sender);
+ if (lsm->wildcard)
+ myfree(lsm->wildcard);
+ myfree((void *) lsm);
+}
+
+/* strip_externalize_addr - strip address extension and externalize remainder */
+
+static VSTRING *strip_externalize_addr(VSTRING *ext_addr, const char *int_addr,
+ const char *delims)
+{
+ char *int_stripped_addr;
+
+ if ((int_stripped_addr = strip_addr_internal(int_addr,
+ /* extension= */ (char **) 0,
+ delims)) != 0) {
+ quote_822_local(ext_addr, int_stripped_addr);
+ myfree(int_stripped_addr);
+ return (ext_addr);
+ } else {
+ return quote_822_local(ext_addr, int_addr);
+ }
+}
+
+/* login_sender_match - match login and sender against (login, senders) table */
+
+int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
+ const char *sender_addr)
+{
+ int found_or_error = LSM_STAT_NOTFOUND;
+
+ /* Sender patterns and derived info */
+ const char *sender_patterns;
+ char *saved_sender_patterns;
+ char *cp;
+ char *sender_pattern;
+
+ /* Actual sender and derived info */
+ const char *ext_stripped_sender = 0;
+ const char *at_sender_domain;
+
+ /*
+ * Match the login.
+ */
+ if ((sender_patterns = maps_find(lsm->maps, login_name,
+ /* flags= */ 0)) != 0) {
+
+ /*
+ * Match the sender. TODO: don't break a sender pattern on a
+ * comma/space inside a quoted localpart.
+ */
+ cp = saved_sender_patterns = mystrdup(sender_patterns);
+ while (found_or_error == LSM_STAT_NOTFOUND
+ && (sender_pattern = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ /* Special pattern: @domain. */
+ if (*sender_pattern == '@') {
+ if ((at_sender_domain = strrchr(sender_addr, '@')) != 0
+ && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Special pattern: wildcard. */
+ else if (strcasecmp(sender_pattern, lsm->wildcard) == 0) {
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Special pattern: empty sender. */
+ else if (strcasecmp(sender_pattern, lsm->null_sender) == 0) {
+ if (*sender_addr == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ /* Literal pattern: match the stripped and externalized sender. */
+ if (ext_stripped_sender == 0)
+ ext_stripped_sender =
+ STR(strip_externalize_addr(lsm->ext_stripped_sender,
+ sender_addr,
+ lsm->ext_delimiters));
+ if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
+ myfree(saved_sender_patterns);
+ } else {
+ found_or_error = lsm->maps->error;
+ }
+ return (found_or_error);
+}
+
+#ifdef TEST
+
+int main(int argc, char **argv)
+{
+ struct testcase {
+ const char *title;
+ const char *map_names;
+ const char *ext_delimiters;
+ const char *null_sender;
+ const char *wildcard;
+ const char *login_name;
+ const char *sender_addr;
+ int exp_return;
+ };
+ struct testcase testcases[] = {
+ {"wildcard works",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "root", "anything", LSM_STAT_FOUND
+ },
+ {"unknown user",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "toor", "anything", LSM_STAT_NOTFOUND
+ },
+ {"bare user",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo", LSM_STAT_FOUND
+ },
+ {"user@domain",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo@example.com", LSM_STAT_FOUND
+ },
+ {"user+ext@domain",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "foo+bar@example.com", LSM_STAT_FOUND
+ },
+ {"wrong sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "bar@example.com", LSM_STAT_NOTFOUND
+ },
+ {"@domain",
+ "inline:{root=*, {foo = @example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "anyone@example.com", LSM_STAT_FOUND
+ },
+ {"wrong @domain",
+ "inline:{root=*, {foo = @example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "anyone@example.org", LSM_STAT_NOTFOUND
+ },
+ {"null sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "bar", "", LSM_STAT_FOUND
+ },
+ {"wrong null sender",
+ "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+ "+-", "<>", "*", "baz", "", LSM_STAT_NOTFOUND
+ },
+ {"error",
+ "inline:{root=*}, fail:sorry",
+ "+-", "<>", "*", "baz", "whatever", LSM_STAT_RETRY
+ },
+ {"no error",
+ "inline:{root=*}, fail:sorry",
+ "+-", "<>", "*", "root", "whatever", LSM_STAT_FOUND
+ },
+ };
+ struct testcase *tp;
+ int act_return;
+ int pass;
+ int fail;
+ LOGIN_SENDER_MATCH *lsm;
+
+ /*
+ * Fake variable settings.
+ */
+ var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
+ var_ownreq_special = DEF_OWNREQ_SPECIAL;
+
+#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
+
+ for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+ msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
+#if 0
+ msg_info("title=%s", tp->title);
+ msg_info("map_names=%s", tp->map_names);
+ msg_info("ext_delimiters=%s", tp->ext_delimiters);
+ msg_info("null_sender=%s", tp->null_sender);
+ msg_info("wildcard=%s", tp->wildcard);
+ msg_info("login_name=%s", tp->login_name);
+ msg_info("sender_addr=%s", tp->sender_addr);
+ msg_info("exp_return=%d", tp->exp_return);
+#endif
+ lsm = login_sender_create("test map", tp->map_names,
+ tp->ext_delimiters, tp->null_sender,
+ tp->wildcard);
+ act_return = login_sender_match(lsm, tp->login_name, tp->sender_addr);
+ if (act_return == tp->exp_return) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ } else {
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ }
+ login_sender_free(lsm);
+ }
+ return (fail > 0);
+}
+
+#endif /* TEST */
--- /dev/null
+#ifndef _LOGIN_SENDER_MATCH_H_INCLUDED_
+#define _LOGIN_SENDER_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* login_sender_match 3h
+/* SUMMARY
+/* oracle for per-login allowed sender addresses
+/* SYNOPSIS
+/* #include <login_sender_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+typedef struct LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH;
+
+extern LOGIN_SENDER_MATCH *login_sender_create(const char *title,
+ const char *map_names,
+ const char *ext_delimiters,
+ const char *null_sender,
+ const char *wildcard);
+extern void login_sender_free(LOGIN_SENDER_MATCH *lsm);
+extern int login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
+ const char *sender_addr);
+
+#define LSM_STAT_FOUND (1)
+#define LSM_STAT_NOTFOUND (0)
+#define LSM_STAT_RETRY (DICT_ERR_RETRY)
+#define LSM_STAT_CONFIG (DICT_ERR_CONFIG)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif /* _LOGIN_SENDER_MATCH_H_INCLUDED_ */
--- /dev/null
+unknown: RUN test case 0 wildcard works
+unknown: PASS test 0
+unknown: RUN test case 1 unknown user
+unknown: PASS test 1
+unknown: RUN test case 2 bare user
+unknown: PASS test 2
+unknown: RUN test case 3 user@domain
+unknown: PASS test 3
+unknown: RUN test case 4 user+ext@domain
+unknown: PASS test 4
+unknown: RUN test case 5 wrong sender
+unknown: PASS test 5
+unknown: RUN test case 6 @domain
+unknown: PASS test 6
+unknown: RUN test case 7 wrong @domain
+unknown: PASS test 7
+unknown: RUN test case 8 null sender
+unknown: PASS test 8
+unknown: RUN test case 9 wrong null sender
+unknown: PASS test 9
+unknown: RUN test case 10 error
+unknown: warning: fail:sorry lookup error for "baz"
+unknown: PASS test 10
+unknown: RUN test case 11 no error
+unknown: PASS test 11
#define DEF_SUBMIT_ACL STATIC_ANYONE_ACL
extern char *var_submit_acl;
+ /*
+ * Local submission, envelope sender ownership.
+ */
+#define VAR_LOCAL_LOGIN_SND_MAPS "local_login_sender_maps"
+#define DEF_LOCAL_LOGIN_SND_MAPS "static:*"
+extern char *var_local_login_snd__maps;
+
+#define VAR_NULL_LOCAL_LOGIN_SND_MAPS_KEY "empty_address_local_login_sender_maps_lookup_key"
+#define DEF_NULL_LOCAL_LOGIN_SND_MAPS_KEY "<>"
+extern char *var_null_local_login_snd_maps_key;
+
/*
* What goes on the right-hand side of addresses of mail sent from this
* machine.
void mail_stream_cleanup(MAIL_STREAM *info)
{
- FREE_AND_WIPE(info->close, info->stream);
+ if (info->stream && info->close(info->stream))
+ msg_warn("mail_stream_cleanup: close error");
FREE_AND_WIPE(myfree, info->queue);
FREE_AND_WIPE(myfree, info->id);
FREE_AND_WIPE(myfree, info->class);
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20201003"
+#define MAIL_RELEASE_DATE "20201025"
#define MAIL_VERSION_NUMBER "3.6"
#ifdef SNAPSHOT
/*
* Proof-of-concept test program. Read an unquoted address from stdin, and
- * show the quoted and unquoted results.
+ * show the quoted and unquoted results. Specify <> to test behavior for an
+ * empty unquoted adress.
*/
#include <ctype.h>
#include <string.h>
bp++;
if (*bp == 0) {
msg_warn("missing argument");
- } else if (strcmp(cmd, "quote") == 0) {
+ continue;
+ }
+ if (strcmp(bp, "<>") == 0)
+ bp = "";
+ if (strcmp(cmd, "quote") == 0) {
quote_822_local(out, bp);
vstream_printf("'%s' quoted '%s'\n", bp, STR(out));
} else if (strcmp(cmd, "quote_with_flags") == 0) {
unquote "a@b@c"@d
unquote "a@b@c"
unquote "a@b@c"@d@e
+quote <>
'"a@b@c"@d' unquoted 'a@b@c@d'
'"a@b@c"' unquoted 'a@b@c'
'"a@b@c"@d@e' unquoted 'a@b@c@d@e'
+'' quoted '""'
static int qmgr_deliver_initial_reply(VSTREAM *stream)
{
- int stat;
-
if (peekfd(vstream_fileno(stream)) < 0) {
msg_warn("%s: premature disconnect", VSTREAM_PATH(stream));
return (DELIVER_STAT_CRASH);
postdrop.o: ../../include/check_arg.h
postdrop.o: ../../include/clean_env.h
postdrop.o: ../../include/cleanup_user.h
+postdrop.o: ../../include/dict.h
postdrop.o: ../../include/htable.h
postdrop.o: ../../include/iostuff.h
+postdrop.o: ../../include/login_sender_match.h
postdrop.o: ../../include/mail_conf.h
postdrop.o: ../../include/mail_dict.h
postdrop.o: ../../include/mail_params.h
postdrop.o: ../../include/maillog_client.h
postdrop.o: ../../include/msg.h
postdrop.o: ../../include/msg_vstream.h
+postdrop.o: ../../include/myflock.h
postdrop.o: ../../include/mymalloc.h
+postdrop.o: ../../include/mypwd.h
postdrop.o: ../../include/nvtable.h
postdrop.o: ../../include/rec_attr_map.h
postdrop.o: ../../include/rec_type.h
/* .IP "\fBauthorized_submit_users (static:anyone)\fR"
/* List of users who are authorized to submit mail with the \fBsendmail\fR(1)
/* command (and with the privileged \fBpostdrop\fR(1) helper command).
+/* .PP
+/* Available in Postfix version 3.6 and later:
+/* .IP "\fBlocal_login_sender_maps (static:*)\fR"
+/* A list of lookup tables that are searched by the UNIX login name,
+/* and that return a list of allowed envelope sender patterns separated
+/* by space or comma.
+/* .IP "\fBempty_address_local_login_sender_maps_lookup_key (<>)\fR"
+/* The lookup key to be used in local_login_sender_maps tables, instead
+/* of the null sender address.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
/* FILES
/* /var/spool/postfix/maildrop, maildrop queue
/* SEE ALSO
#include <argv.h>
#include <iostuff.h>
#include <stringops.h>
+#include <mypwd.h>
/* Global library. */
#include <rec_attr_map.h>
#include <mail_parm_split.h>
#include <maillog_client.h>
+#include <login_sender_match.h>
/* Application-specific. */
* Local mail submission access list.
*/
char *var_submit_acl;
+char *var_local_login_snd_maps;
+char *var_null_local_login_snd_maps_key;
static const CONFIG_STR_TABLE str_table[] = {
VAR_SUBMIT_ACL, DEF_SUBMIT_ACL, &var_submit_acl, 0, 0,
+ VAR_LOCAL_LOGIN_SND_MAPS, DEF_LOCAL_LOGIN_SND_MAPS, &var_local_login_snd_maps, 0, 0,
+ VAR_NULL_LOCAL_LOGIN_SND_MAPS_KEY, DEF_NULL_LOCAL_LOGIN_SND_MAPS_KEY, &var_null_local_login_snd_maps_key, 0, 0,
0,
};
postdrop_sig(0);
}
+/* check_login_sender_acl - check if a user is authorized to use this sender */
+
+static int check_login_sender_acl(uid_t uid, VSTRING *sender_buf,
+ VSTRING *reason)
+{
+ const char myname[] = "check_login_sender_acl";
+ struct mypasswd *user_info;
+ char *user_name;
+ VSTRING *user_name_buf = 0;
+ LOGIN_SENDER_MATCH *lsm;
+ int res;
+
+ /*
+ * Sanity checks.
+ */
+ if (vstring_memchr(sender_buf, '\0') != 0) {
+ vstring_sprintf(reason, "NUL in FROM record");
+ return (CLEANUP_STAT_BAD);
+ }
+
+ /*
+ * Optimization.
+ */
+ if (strcmp(var_local_login_snd_maps, DEF_LOCAL_LOGIN_SND_MAPS) == 0)
+ return (CLEANUP_STAT_OK);
+
+ /*
+ * Get the username.
+ */
+ if ((user_info = mypwuid(uid)) != 0) {
+ user_name = user_info->pw_name;
+ } else {
+ user_name_buf = vstring_alloc(10);
+ vstring_sprintf(user_name_buf, "#%ld", (long) uid);
+ user_name = vstring_str(user_name_buf);
+ }
+
+
+ /*
+ * Apply the a login-sender matcher. TODO: add DICT flags.
+ */
+ lsm = login_sender_create(VAR_LOCAL_LOGIN_SND_MAPS,
+ var_local_login_snd_maps,
+ var_rcpt_delim,
+ var_null_local_login_snd_maps_key, "*");
+ res = login_sender_match(lsm, user_name, vstring_str(sender_buf));
+ login_sender_free(lsm);
+ if (user_name_buf)
+ vstring_free(user_name_buf);
+ switch (res) {
+ case LSM_STAT_FOUND:
+ return (CLEANUP_STAT_OK);
+ case LSM_STAT_NOTFOUND:
+ vstring_sprintf(reason, "not authorized to use sender='%s'",
+ vstring_str(sender_buf));
+ return (CLEANUP_STAT_NOPERM);
+ case LSM_STAT_RETRY:
+ case LSM_STAT_CONFIG:
+ vstring_sprintf(reason, "%s table lookup error for '%s'",
+ VAR_LOCAL_LOGIN_SND_MAPS, var_local_login_snd_maps);
+ return (CLEANUP_STAT_WRITE);
+ default:
+ msg_panic("%s: bad login_sender_match() result: %d", myname, res);
+ }
+}
+
MAIL_VERSION_STAMP_DECLARE;
/* main - the main program */
int fd;
int c;
VSTRING *buf;
- int status;
+ int status = CLEANUP_STAT_OK;
+ VSTRING *reason = vstring_alloc(100);
MAIL_STREAM *dst;
int rec_type;
static char *segment_info[] = {
maillog_client_init(mail_task("postdrop"), MAILLOG_CLIENT_FLAG_NONE);
get_mail_conf_str_table(str_table);
- /*
- * Mail submission access control. Should this be in the user-land gate,
- * or in the daemon process?
- */
- mail_dict_init();
- if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl,
- uid)) != 0)
- msg_fatal("User %s(%ld) is not allowed to submit mail",
- errstr, (long) uid);
-
/*
* Stop run-away process accidents by limiting the queue file size. This
* is not a defense against DOS attack.
/* End of initializations. */
+ /*
+ * Mail submission access control. Should this be in the user-land gate,
+ * or in the daemon process?
+ */
+ mail_dict_init();
+ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl,
+ uid)) != 0)
+ msg_fatal("User %s(%ld) is not allowed to submit mail",
+ errstr, (long) uid);
+
/*
* Don't trust the caller's time information.
*/
if (rec_type == REC_TYPE_TIME)
continue;
/* Check these at submission time instead of pickup time. */
- if (rec_type == REC_TYPE_FROM)
+ if (rec_type == REC_TYPE_FROM) {
+ status |= check_login_sender_acl(uid, buf, reason);
from_count++;
+ }
if (rec_type == REC_TYPE_RCPT)
rcpt_count++;
/* Limit the attribute types that users may specify. */
}
continue;
}
- if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) {
+ if (status != CLEANUP_STAT_OK
+ || REC_PUT_BUF(dst->stream, rec_type, buf) < 0) {
/* rec_get() errors must not clobber errno. */
saved_errno = errno;
while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit,
* reporting (report at submission time instead of pickup time). Besides
* the segment terminator records, there aren't any other mandatory
* records in a Postfix submission queue file.
+ *
+ * TODO: return an informative reason for missing sender, too many senders,
+ * or missing recipient.
*/
- if (validate_input && (from_count == 0 || rcpt_count == 0)) {
- status = CLEANUP_STAT_BAD;
+ if (validate_input && (from_count == 0 || rcpt_count == 0))
+ status |= CLEANUP_STAT_BAD;
+ if (status != CLEANUP_STAT_OK) {
mail_stream_cleanup(dst);
}
/*
* Finish the file.
*/
- else if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0) {
+ else if ((status = mail_stream_finish(dst, reason)) != 0) {
msg_warn("uid=%ld: %m", (long) uid);
postdrop_cleanup();
}
*/
attr_print(VSTREAM_OUT, ATTR_FLAG_NONE,
SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
- SEND_ATTR_STR(MAIL_ATTR_WHY, ""),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, status != CLEANUP_STAT_OK
+ && VSTRING_LEN(reason) > 0 ?
+ vstring_str(reason) : ""),
ATTR_TYPE_END);
vstream_fflush(VSTREAM_OUT);
exit(status);
int weight;
HTABLE_INFO *ht;
char *parse_err;
+ const char *safe_dnsbl;
/*
* Parse the required DNSBL domain name, the optional reply filter and
ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head);
/* Translate the DNSBL name into a safe name if available. */
if (psc_dnsbl_reply == 0
- || (head->safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0)
- head->safe_dnsbl = ht->key;
+ || (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0)
+ safe_dnsbl = ht->key;
+ head->safe_dnsbl = mystrdup(safe_dnsbl);
if (psc_dnsbl_reply && psc_dnsbl_reply->error)
msg_fatal("%s:%s lookup error", psc_dnsbl_reply->type,
psc_dnsbl_reply->name);
VSTRING *postdrop_command;
uid_t uid = getuid();
int status;
+ VSTRING *why; /* postdrop status message */
int naddr;
int prev_type;
MIME_STATE *mime_state = 0;
if (vstream_ferror(VSTREAM_IN))
msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m",
saved_sender, (long) uid);
- if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0)
+ why = vstring_alloc(100);
+ if ((status = mail_stream_finish(handle, why)) != CLEANUP_STAT_OK)
msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE :
(status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL :
+ (status & CLEANUP_STAT_NOPERM) ? EX_NOPERM :
EX_UNAVAILABLE, "%s(%ld): %s", saved_sender,
- (long) uid, cleanup_strerror(status));
+ (long) uid, VSTRING_LEN(why) ?
+ STR(why) : cleanup_strerror(status));
+ vstring_free(why);
/*
* Don't leave them in the dark.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/* The location of the Postfix top-level queue directory.
/* .IP "\fBrecipient_delimiter (empty)\fR"
-/* The set of characters that can separate a user name from its
-/* extension (example: user+foo), or a .forward file name from its
-/* extension (example: .forward+foo).
+/* The set of characters that can separate an email address
+/* localpart, user name, or a .forward file name from its extension.
/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
/* The text that follows the 220 status code in the SMTP greeting
/* banner.
* obsolete, so we don't have to provide perfect support.
*/
#ifdef USE_TLS
- if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode) {
+ if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode
+ && state->tls_context == 0) {
#ifdef USE_TLSPROXY
/* We garbage-collect the VSTREAM in smtpd_state_reset() */
state->tlsproxy =
if (dict_static->value)
myfree(dict_static->value);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
dict_free(dict);
}