(August 2019)
These notes cover Postfix, and using it as a mail forwarding and filtering smarthost.
Postfix is an MTA designed to replace Sendmail. Sendmail uses a monolithic architecture. Postfix breaks its functionality into modular, semi-resident, mutually-cooperative processes that each do specific tasks.
The official Postfix documentation is generally good.
main.cf
)Postfix has two primary configuration files:
/etc/postfix/main.cf
stores site-specific Postfix configuration/etc/postfix/master.cf
defines how the master daemon runs the above-mentioned semi-resident sub-daemonsmain.cf
sets options for the various Postfix components using key = value
directives, like:
mydomain = example.com
myorigin = $mydomain
mydestination = $myhostname localhost.$mydomain localhost
mynetworks = 127.0.0.0/8 192.0.2.0/24
relay_domains = $mydomain
relayhost =
proxy_interfaces = 203.0.113.22
Each line in master.cf
defines how a client program connects to a service, and what program the Postfix master daemon runs when the service is requested.
Most of the programs controlled by the master daemon are short-lived, and exit after serving max_use
clients or after max_idle
periods of inactivity.
Daemons specified in master.cf
must speak the Postfix-internal protocol.
Non-Postfix software that does not must use the local(8)
, pipe(8)
or spawn(8)
services.
Run postfix reload
after changing master.cf
.
Postfix uses database files (SQL, LDAP, Berkeley DB) for access control, address rewriting and other purposes.
Postfix also calls these “lookup tables”.
postconf -m
shows what types of databases the installed Postfix understands. http://www.postfix.org/DATABASE_README.html
(The above diagram comes from Wikipedia.)
http://www.postfix.org/OVERVIEW.html#receiving
trivial-
Network → smtpd rewrite
↘ ↓ ↑
Network → qmqpd → cleanup → incoming
↗
pickup ← maildrop
↑
Local → sendmail → postdrop
(The trivial-rewrite server rewrites addresses to the standard “user@fully.qualified.domain” form. See http://www.postfix.org/ADDRESS_REWRITING_README.html#receiving.)
http://www.postfix.org/OVERVIEW.html#delivering
+---→ local --→ file, command
trivial- |
rewrite |---→ lmtp ---→ network
↑ ↓ |
incoming → active → qmgr ---------→ smtp ---→ network
↑ ↓ |
deferred |---→ virtual → file
|
+---→ pipe ---→ command
The incoming and deferred queues might hold a large number of messages. The active queue holds only the small number of messages that qmgr has opened for immediate handling.
The follow sections describe some of the key Postfix programs, with partial excerpts from their manual pages.
network → (smtpd) → (cleanup) → [incoming] → [active] → (qmgr) → …
cleanup(8postfix)
🔗
The cleanup(8) daemon processes inbound mail, inserts it into the incoming mail queue, and informs the queue manager of its arrival.
The cleanup(8) daemon always performs the following transformations:
- Insert missing message headers: (Resent-) From:, To:, Message-Id:, and Date:.
- Transform envelope and header addresses to the standard user@fully-qualified-domain form that is expected by other Postfix programs. This task is delegated to the trivial-rewrite(8) daemon.
- Eliminate duplicate envelope recipient addresses.
- Remove message headers: Bcc, Content-Length, Resent-Bcc, Return-Path.
The following address transformations are optional:
- Optionally, rewrite all envelope and header addresses according to the mappings specified in the canonical(5) lookup tables.
- Optionally, masquerade envelope sender addresses and message header addresses (i.e. strip host or domain information below all domains listed in the masquerade_domains parameter, except for user names listed in masquerade_exceptions).
- Optionally, expand envelope recipients according to information found in the virtual(5) lookup tables.
The cleanup(8) daemon performs sanity checks on the content of each message. When it finds a problem, by default it returns a diagnostic status to the client, and leaves it up to the client to deal with the problem. Alternatively, the client can request the cleanup(8) daemon to bounce the message back to the sender in case of trouble.
BUILT-IN CONTENT FILTERING CONTROLS
Postfix built-in content filtering is meant to stop a flood of worms or viruses. It is not a general content filter.
- body_checks (empty) Optional lookup tables for content inspection as specified in the body_checks(5) manual page.
- header_checks (empty) Optional lookup tables for content inspection of primary non-MIME message headers, as specified in the header_checks(5) manual page.
- body_checks_size_limit (51200) How much text in a message body segment (or attachment, if you prefer to use that term) is subjected to body_checks inspection.
master(8)
🔗
The master(8) daemon is the resident process that runs Postfix daemons on demand.
The behavior of the master(8) daemon is controlled by the master.cf configuration file, as described in master(5).
postconf(1)
🔗
By default, the postconf(1) command displays the values of main.cf configuration parameters, and warns about possible mis-typed parameter names (Postfix 2.9 and later). The command can also change main.cf configuration parameter values, or display other configuration information about the Postfix mail system.
🐚 ~ # postconf -c /tmp/posfix-test-config/
🐚 ~ # postconf 1>/dev/null
Also see postconf(5)
🔗.
postfix(1)
🔗
The postfix(1) command controls the operation of the Postfix mail system: start or stop the master(8) daemon, do a health check, and other maintenance.
🐚 ~ # postfix stop
🐚 ~ # postfix start
🐚 ~ # postfix status
🐚 ~ # postfix reload
🐚 ~ # postfix flush
🐚 ~ # postfix check
🐚 ~ # postfix logrotate
postscreen(8)
🔗
The Postfix postscreen(8) server provides additional protection against mail server overload. One postscreen(8) process handles multiple inbound SMTP connections, and decides which clients may talk to a Postfix SMTP server process. By keeping spambots away, postscreen(8) leaves more SMTP server processes available for legitimate clients, and delays the onset of server overload conditions.
This program should not be used on SMTP ports that receive mail from end-user clients (MUAs). In a typical deployment, postscreen(8) handles the MX service on TCP port 25, and smtpd(8) receives mail from MUAs on the submission service (TCP port 587) which requires client authentication. Alternatively, a site could set up a dedicated, non-postscreen, “port 25” server that provides submission service and client authentication, but no MX service.
postscreen(8) maintains a temporary whitelist for clients that have passed a number of tests. When an SMTP client IP address is whitelisted, postscreen(8) hands off the connection immediately to a Postfix SMTP server process. This minimizes the overhead for legitimate mail.
By default, postscreen(8) logs statistics and hands off each connection to a Postfix SMTP server process, while excluding clients in mynetworks from all tests (primarily, to avoid problems with non-standard SMTP implementations in network appliances). This default mode blocks no clients, and is useful for non-destructive testing.
In a typical production setting, postscreen(8) is configured to reject mail from clients that fail one or more tests. postscreen(8) logs rejected mail with the client address, helo, sender and recipient information.
Changes to main.cf are not picked up automatically, as postscreen(8) processes may run for several hours. Use the command “postfix reload” after a configuration change.
… → [active] → (qmgr) → (smtp) → {network}
qmgr(8)
🔗
The qmgr(8) daemon awaits the arrival of incoming mail and arranges for its delivery via Postfix delivery processes. The actual mail routing strategy is delegated to the trivial-rewrite(8) daemon. This program expects to be run from the master(8) process manager.
The qmgr(8) daemon maintains the following queues:
- incoming: Inbound mail from the network, or mail picked up by the local pickup(8) daemon from the maildrop directory.
- active: Messages that the queue manager has opened for delivery. Only a limited number of messages is allowed to enter the active queue (leaky bucket strategy, for a fixed delivery rate).
- deferred: Mail that could not be delivered upon the first attempt. The queue manager implements exponential backoff by doubling the time between delivery attempts.
- corrupt: Unreadable or damaged queue files are moved here for inspection.
- hold: Messages that are kept “on hold” are kept here until someone sets them free.
Changes to main.cf are not picked up automatically as qmgr(8) is a persistent process. Use the “postfix reload” command after a configuration change.
{local} → (sendmail) → [maildrop] → (pickup) → (cleanup) → …
sendmail(1)
🔗
The Postfix sendmail(1) command implements the Postfix to Sendmail compatibility interface.
By default, Postfix sendmail(1) reads a message from standard input until EOF or until it reads a line with only a . character, and arranges for delivery. Postfix sendmail(1) relies on the postdrop(1) command to create a queue file in the maildrop directory.
Specific command aliases are provided for other common modes of operation:
mailq List the mail queue. Each entry shows the queue file ID, message size, arrival time, sender, and the recipients that still need to be delivered. If mail could not be delivered upon the last attempt, the reason for failure is shown.
newaliases Initialize the alias database.
For reading and sending mail from the command line, mail(1)
(from either the mailutils
or bsd-mailx
package) is handier.
… → (qmgr) → (smtp) → {network}
smtp(8)
🔗
The Postfix SMTP+LMTP client implements the SMTP and LMTP mail delivery protocols. It processes message delivery requests from the queue manager. Each request specifies a queue file, a sender address, a domain or host to deliver to, and recipient information. This program expects to be run from the master(8) process manager.
The SMTP+LMTP client updates the queue file and marks recipients as finished, or it informs the queue manager that delivery should be tried again at a later time.
The SMTP+LMTP client looks up a list of mail exchanger addresses for the destination host, sorts the list by preference, and connects to each listed address until it finds a server that responds.
Most smtp_xxx configuration parameters have an lmtp_xxx “mirror” parameter for the equivalent LMTP feature. This [man page] describes only those LMTP-related parameters that aren’t simply “mirror” parameters. See postconf(5) for more details including examples.
Changes to main.cf are picked up automatically, as smtp(8) processes run for only a limited amount of time. Use the command “postfix reload” to speed up a change.
{network} → (smtpd) → (cleanup) → incoming → …
smtpd(8)
🔗
The SMTP server accepts network connection requests and performs zero or more SMTP transactions per connection. Each received message is piped through the cleanup(8) daemon, and is placed into the incoming queue as one single queue file. For this mode of operation, the program expects to be run from the master(8) process manager.
The SMTP server implements a variety of policies for connection requests, and for parameters given to HELO, ETRN, MAIL FROM, VRFY and RCPT TO commands. They are detailed below and in the main.cf configuration file.
Changes to main.cf are picked up automatically, as smtpd(8) processes run for only a limited amount of time. Use the command “postfix reload” to speed up a change.
smtpd
has a large number of configuration options described in the manual and detailed in postconf(5)
.
(smtpd) ⭤ (tlsmgr)
(smtp) ⭤ (tlsmgr)
tlsmgr(8)
🔗
The tlsproxy(8) server implements a two-way TLS proxy. It is used by the postscreen(8) server to talk SMTP-over-TLS with remote SMTP clients that are not whitelisted (including clients whose whitelist status has expired), and by the smtp(8) client to support TLS connection reuse, but it should also work for non-SMTP protocols.
After receiving a valid remote SMTP client STARTTLS command, the postscreen(8) server sends the remote SMTP client endpoint string, the requested role (server), and the requested timeout to tlsproxy(8). postscreen(8) then receives a “TLS available” indication from tlsproxy(8). If the TLS service is available, postscreen(8) sends the remote SMTP client file descriptor to tlsproxy(8), and sends the plain-text 220 greeting to the remote SMTP client. This triggers TLS negotiations between the remote SMTP client and tlsproxy(8). Upon completion of the TLS-level handshake, tlsproxy(8) translates between plaintext from/to postscreen(8) and ciphertext to/from the remote SMTP client.
network → smtpd ⭤ tlsmgr ⭤ smtp → network
|
-------+-------
/ | \
smtpd session PRNG smtp session
key cache state file key cache
http://www.postfix.org/TLS_README.html
/etc/postfix/main.cf
:
smtpd_tls_cert_file = /etc/postfix/server.pem
smtpd_tls_key_file = $smtpd_tls_cert_file
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1
Enable tlsmgr
in /etc/postfix/master.cf
.
It’s enabled by default in the Debian package.
tlsmgr unix - - y 1000? 1 tlsmg
(cleanup) ↔ (trivial-rewrite)
(qmgr) ↔ (trivial-rewrite)
trivial-rewrite(8)
🔗
Postfix address rewriting and resolving daemon
The trivial-rewrite(8) daemon processes three types of client service requests:
- rewrite context address: Rewrite an address to standard form, according to the address rewriting context:
- local: Append the domain names specified with $myorigin or $mydomain to incomplete addresses; do swap_bangpath and allow_percent_hack processing as described below, and strip source routed addresses (@site,@site:user@domain) to user@domain form.
- remote: Append the domain name specified with $remote_header_rewrite_domain to incomplete addresses. Otherwise the result is identical to that of the local address rewriting context. This prevents Postfix from appending the local domain to spam from poorly written remote clients.
- resolve sender address: Resolve the address to a (transport, nexthop, recipient, flags) quadruple. The meaning of the results is as follows:
- transport: The delivery agent to use. This is the first field of an entry in the master.cf file.
- nexthop: The host to send to and optional delivery method information.
- recipient: The envelope recipient address that is passed on to nexthop.
- flags: The address class, whether the address requires relaying, whether the address has problems, and whether the request failed.
- verify sender address: Resolve the address for address verification purposes.
The trivial-rewrite(8) servers run under control by the Postfix master server.
On busy mail systems a long time may pass before a main.cf change affecting trivial-rewrite(8) is picked up. Use the command “postfix reload” to speed up a change.
The follow sections describe some of the key Postfix queues, with partial excerpts from the qmgr(8)
manual page.
For queue management, see:
postqueue(1)
the Postfix queue management tool for listing the contents of queuespostsuper(1)
the Postfix super-user tool to delete or change messages in queuesThe -p
flag of postqueue
implements the traditional mailq
function of printing the queue contents.
🐚 ~ $ /usr/sbin/postqueue -p
Mail queue is empty
… → (cleanup) → [incoming] → [active] → (qmgr) → …
qmgr(8)
🔗
The “active” queue holds mail the queue manager has open for delivery.
… → [active] ↔ [deferred]
qmgr(8)
🔗
The “deferred” queue holds mail that couldn’t be delivered on the latest try. The queue manager implements exponential backoff, doubling the time between delivery attempts.
… → [active] → [hold]
Messages relegated to the “hold” queue stay there until manually freed.
… (smtpd) → (cleanup) → [incoming] → [active] → …
qmgr(8)
🔗
The “incoming” queue hosts mail inbound from the network (or picked up ty the local pickup(8)
daemon).
The follow sections describe some of the key Postfix databases, with partial excerpts from their manual pages.
[access] → (smtpd) → (cleanup) → …
access(5)
🔗
This document describes access control on remote SMTP client information: host names, network addresses, and envelope sender or recipient addresses; it is implemented by the Postfix SMTP server. See header_checks(5) or body_checks(5) for access control on the content of email messages.
Normally, the access(5) table is specified as a text file that serves as input to the postmap(1) command. The result, an indexed file in dbm or db format, is used for fast searching by the mail system. Execute the command “postmap /etc/postfix/access” to rebuild an indexed file after changing the corresponding text file.
TABLE FORMAT
The input format for the postmap(1) command is as follows:
pattern action: When pattern matches a mail address, domain or host address, perform the corresponding action.
EXAMPLE
/etc/postfix/access:
1.2.3 REJECT
1.2.3.4 OKExecute the command “postmap /etc/postfix/access” after editing the file.
[canonical] → (cleanup) → [incoming] → …
canonical(5)
🔗
The optional canonical(5) table specifies an address mapping for local and non-local addresses. The mapping is used by the cleanup(8) daemon, before mail is stored into the queue. The address mapping is recursive.
Normally, the canonical(5) table is specified as a text file that serves as input to the postmap(1) command. The result, an indexed file in dbm or db format, is used for fast searching by the mail system. Execute the command “postmap /etc/postfix/canonical” to rebuild an indexed file after changing the corresponding text file.
By default the canonical(5) mapping affects both message header addresses (i.e. addresses that appear inside messages) and message envelope addresses (for example, the addresses that are used in SMTP protocol commands). This is controlled with the canonical_classes parameter.
[relocated] → (qmgr) → …
relocted(5)
🔗
The optional relocated(5) table provides the information that is used in “user has moved to new_location” bounce messages.
Normally, the relocated(5) table is specified as a text file that serves as input to the postmap(1) command. The result, an indexed file in dbm or db format, is used for fast searching by the mail system. Execute the command “postmap /etc/postfix/relocated” to rebuild an indexed file after changing the corresponding relocated table.
An entry has one of the following form:
pattern new_location
Where new_location specifies contact information such as an email address, or perhaps a street address or telephone number.
[virtual] → (cleanup) → [incoming] → …
virtual(5)
🔗
The optional virtual(5) alias table rewrites recipient addresses for all local, all virtual, and all remote mail destinations. This is unlike the aliases(5) table which is used only for local(8) delivery. Virtual aliasing is recursive, and is implemented by the Postfix cleanup(8) daemon be‐ fore mail is queued.
The main applications of virtual aliasing are:
- To redirect mail for one address to one or more addresses.
- To implement virtual alias domains where all addresses are aliased to addresses in other domains.
Virtual alias domains are not to be confused with the virtual mailbox domains that are implemented with the Postfix virtual(8) mail delivery agent. With virtual mailbox domains, each recipient address can have its own mailbox.
Virtual aliasing is applied only to recipient envelope addresses, and does not affect message headers. Use canonical(5) mapping to rewrite header and envelope addresses in general.
Normally, the virtual(5) alias table is specified as a text file that serves as input to the postmap(1) command. The result, an indexed file in dbm or db format, is used for fast searching by the mail system. Execute the command “postmap /etc/postfix/virtual” to rebuild an indexed file after changing the corresponding text file.
See maintaining Postfix lookup table files.
In short, for regexp, pcre, cidr or texthash files, there’s little need to do postfix reload
for files used by short-running processes like smtpd, cleanup, or local.
For files used by longer-running processes, like trivial-rewrite, do postfix reload
.
For a local file-based database, like DBM or Berkeley DB, there’s no need to do “postfix reload”. The Postfix daemon will notice changed files, and terminate before handling the next client request, so as to start with the new database.
Because of the aggressive caching used by Berkely DB files, going something like postmap access
might possibly break Postfix.
Instead, move the updated database file into place, like postmap access.in && mv access.in.db access.db
.
See updating Berkeley DB files safely.
Produce two types of mail delivery reports: “what if” and “what happened”.
What if? Report what would happen, but don’t actually deliver mail:
% /usr/sbin/sendmail -bv address...
Mail Delivery Status Report will be mailed to <your login name>.
What happened? Deliver mail and report successes or failures, including replies from remote SMTP servers:
% /usr/sbin/sendmail -v address...
Mail Delivery Status Report will be mailed to <your login name>.
To turn on verbose logging for a particular remote peer:
🐚 ~ $ sudo postconf -e 'debug_peer_list = 10.0.0.93'
🐚 ~ $ sudo postfix reload
http://www.postfix.org/DEBUG_README.html
http://www.postfix.org/POSTSCREEN_README.html
In /etc/postfix/master.cf
, comment the old smtp … smptd
line, and uncomment the smtp … postscreen
and smtpd pass …
lines:
#smtp inet n - y - - smtpd
smtp inet n - y - 1 postscreen
smtpd pass - - y - - smtpd
tlsproxy unix - - y - 0 tlsproxy
In /etc/postfix/main.cf
, add:
postscreen_access_list = permit_mynetworks,
cidr:/etc/postfix/postscreen_access.cidr
postscreen_dnsbl_action = enforce
Run:
🐚 ~ $ sudo touch /etc/postfix/postscreen_access.cidr
🐚 ~ $ sudo postfix reload
Enable RBL support:
postscreen_dnsbl_threshold = 2
postscreen_dnsbl_sites = zen.spamhaus.org*2
bl.spamcop.net*1
b.barracudacentral.org*1
(Note that barracudacentral.org requires registration.)
In /etc/postfix/master.cf
, uncomment this line:
dnsblog unix - - y - 0 dnsblog
🐚 ~ $ sudo vi /etc/postfix/header_checks
/^Subject:/ WARN
🐚 ~ $ sudo vi /etc/postfix/main.cf
header_checks = regexp:/etc/postfix/header_checks
🐚 ~ $ sudo postfix reload
(According to header_checks(5)
, by default, Postfix treats regexp:
and pcre:
patterns as case insensitive.)
http://www.postfix.org/header_checks.5.html
🐚 ~ $ sudo vi /etc/postfix/main.cf
header_checks = pcre:/etc/postfix/header_checks.pcre
🐚 ~ $ sudo vi /etc/postfix/header_checks.pcre
/^Content-(Disposition|Type).*name\s*=\s*"?([^;]*(\.|=2E)(
ade|adp|asp|bas|bat|chm|cmd|com|cpl|crt|dll|exe|
hlp|ht[at]|
inf|ins|isp|jse?|lnk|md[betw]|ms[cipt]|nws|
\{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\}|
ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf|
vb[esx]?|vxd|ws[cfh]))(\?=)?"?\s*(;|$)/x
REJECT ".$4" attachments, like "$2" are blocked.
🐚 ~ $ sudo postfix reload
Local Mail Transfer Protocol is a restricted, SMTP-like protocol, designed to be used to talk to daemons that don’t implement mail queues.
Basically: let the MTA queue manager handle the queues, and don’t require mail delivery agents to also manage queues.
Postfix acts as an LMTP client. Postfix can speak to amavisd, for example, via LMTP.
https://tools.ietf.org/html/rfc2033
The design of the SMTP protocol effectively requires the server to manage a mail delivery queue. This is because a single mail transaction may specify multiple recipients and the final “.” of the DATA command may return only one reply code, to indicate the status of the entire transaction. If, for example, a server is given a transaction for two recipients, delivery to the first succeeds, and delivery to the second encounters a temporary failure condition, there is no mechanism to inform the client of the situation. The server must queue the message and later attempt to deliver it to the second recipient.
This queuing requirement is beneficial in the situation for which SMTP was originally designed: store-and-forward relay of mail between networked hosts. In some limited situations, it is desirable to have a server which does not manage a queue, instead relying on the client to perform queue management.
[…]
It would be desirable to use SMTP over a local inter-process communication channel to transfer messages from the queue manager to the delivery agents. It would, however, significantly increase the complexity of the delivery agents to require them to manage their own mail queues.
The common practice of invoking a delivery agent with the envelope address(es) as command-line arguments, then having the delivery agent communicate status with an exit code has three serious problems: the agent can only return one exit code to be applied to all recipients, it is difficult to extend the interface to deal with ESMTP extensions such as DSN [DSN] and ENHANCEDSTATUSCODES [ENHANCEDSTATUSCODES], and exits performed by system libraries due to temporary conditions frequently get interpreted as permanent errors.
The LMTP protocol causes the server to return, after the final “.” of the DATA command, one reply for each recipient. Therefore, if the queue manager is configured to use LMTP instead of SMTP when transferring messages to the delivery agents, then the delivery agents may attempt delivery to each recipient after the final “.” and individually report the status for each recipient. Connections which should use the LMTP protocol are drawn in the diagram above using asterisks.
Note that it is not beneficial to use the LMTP protocol when transferring messages to the queue manager, either from the network or from a delivery agent. The queue manager does implement a mail queue, so it may store the message and take responsibility for later delivering it.
[…]
The LMTP protocol SHOULD NOT be used over wide area networks.
Milter is a portmanteau of “mail filter”. “Milter” is an API developed by the Sendmail MTA that lets external filters, such as anti-spam or anti-virus programs, evaluate mail at various stages of mail processing. “A milter” is such a filter program.
Postfix can apply milters before-queue.
Specify milters with the smtpd_milters
config parameter.
SMTP-only non-SMTP
filters filters
^ | ^ |
| v | v
Network -> smtpd -> cleanup -> incoming
A receiving mail server checks a DNS SPF record to verify that the sending mail server’s IP address is allowed to send mail for that domain. This only applies to the Sender listed in the message envelope, not the From domain in the body headers.
Create TXT DNS records, like:
@ IN TXT "v=spf1 mx -all"
mail.example.com. IN TXT "v=spf1 a -all"
mail2.example.com. IN TXT "v=spf1 a -all"
Enable SPF checking for Postfix:
🐚 ~ $ sudo apt install postfix-policyd-spf-python
🐚 ~ $ sudo vi /etc/postfix/main.cf
policy-spf_time_limit = 3600s
smtpd_recipient_restrictions = permit_mynetworks
reject_unauth_destination
check_policy_service unix:private/policy-spf
🐚 ~ $ sudo vim /etc/postfix/master.cf
policy-spf unix - n n - - spawn
user=nobody argv=/usr/bin/policyd-spf
🐚 ~ $ sudo postfix reload
N.B. — put the policy services for smtpd_recipient_restrictions
after reject_unauth_destination
to prevent unexpected responses from the policy service from making the system an open relay.
Also, put policy services after permit_mynetworks
to avoid SPF checks on outbound mail from local users.
DKIM uses a public/private key pair to cryptographically sign some or all of a message body, typically including the From header. Receiving mail servers can check the signature to verify From headers originated at that domain. (This is not absolutely fool-proof; e.g., some spammers have been known to resend signed message headers, but append bad contents to the body.)
Use Amavisd for outbound message signing and inbound verification.
Generate a key pair (or install your existing one):
🐚 ~ $ sudo mkdir -p /etc/amavis/dkim
🐚 ~ $ sudo chmod 700 /etc/amavis/dkim
🐚 ~ $ sudo openssl genrsa -out /etc/amavis/dkim/example.com.selector201909.key 2048
🐚 ~ $ sudo openssl rsa -in /etc/ssl/amavis/dkim/dkim.key -pubout -out /etc/amavis/dkim/example.com.selector201909.pub
🐚 ~ $ sudo chown -R amavis:amavis /etc/amavis/dkim
As with SPF, we publish a TXT DNS record containing the public half of our DKIM key.
opendkim-genkey
outputs two files:
myselector.private
contains the private keymyselector.txt
contains a DNS record with the public key, like:selector201909._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArTgyBL9cTXnJjnpDq9ZrjAixiq7YQoR/XE83X29jbD/QxJjcxX8nYqmKmxua3TbtamKYhhaVGPiUt+Hfj6tR5hV7FXYlt89IBSDJMTuFSxMIFiPFG9V+rCv6qjcLUWDo8T8I8P6KFHYTGoZFJ6FPgeqMJVEJGut3mnweJGG2Fhc609tupuNeEbRF0UUafpRBksweFh6bNNWlGZ"
"gx6VZe/pqUo7QTivRaqvLKGdPVrsAa4A+XHbGqamcWPwsd/HfL5MCaqtVFc75oIsbazqbDlj2TN8Cy9Cjlf47Q5+rMt4ZfaH747pWCMmk/ng9KLA365Z5fxWn2bjCDPAh88WLvUwIDAQAB" ) ;
Note that TXT records are limited to 255 characters in a single string, so long keys must be split for some DNS server. Route 53 requires a split value, while Windows DNS requires a whole, unsplit value.
After adding a record for the public key, check it like:
🐚 ~ $ dig +short selector201909._domainkey.example.com TXT
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqCxxZduA47anRLt+SLDiNFK6Vh4OQhyfp/OvHqVQioE27T1cO723Jjth0sevmk6ufIFQ2wYBk5uzfr1FmvI5UUVWcDd/yT47xOKW9Y852BWFpkIIovY4dvb3Px7VKSZhijyjifEC1ewQb" "SM12QDeL854scXyd48EW803Njsn98vvPi1uOKMMOPbfIrAzD7a9vN/4QNdF/X9g5AUaytjsXx+p/V1jTgcySJbJ72SVwhTaL2bKISUZ8Uj7oayfel/i1PkNhsFHoSczujt+pyxbi80mjRWj6yX6jzPTKzG8KjcvSaofNoUFeHyl6JplyvG48Z+gIQm6STSJsokr9UA+fQIDAQ8G"
Add to /etc/amavis/conf.d/50-user
:
@mynetworks = qw( 127.0.0.0/8 [::1]/128 10.0.0.0/8 172.16.0.0/12 [FEC0::]/10 );
[…]
$enable_dkim_verification = 1; # enable DKIM signatures verification
$enable_dkim_signing = 1; # load DKIM signing code, keys defined by dkim_key
dkim_key(
'example.com',
'selector201909',
'/etc/amavis/dkim/example.com.selector201909.key'
);
@dkim_signature_options_bysender_maps = (
{
"example.com" => {
d => 'example.com',
a => 'rsa-sha256',
ttl => 10*24*3600
}
}
);
$policy_bank{'MYNETS'} = { # mail originating from @mynetworks
originating => 1, # is true in MYNETS by default, but let's make it explicit
};
The policy bank MYNETS
is magic.
This makes Amavis mark all mail from @mynetworks
as originating.
Note that @mynetworks
should match Postfix’s mynetworks
.
Also, to enable Amavis to actually see the originating network, make sure to add -o lmtp_send_xforward_command=yes
for the amavisfeed
service in master.cf
.
Add to /etc/postfix/main.cf
:
alias_maps = hash:/etc/aliases
https://unix.stackexchange.com/questions/244199/postfix-mail-logs-keep-showing-nis-domain-not-set
If our normal domain is example.com, we can add a test.example.com subdomain to test a new mail server.
relay_domains = test.example.com
canonical_classes = envelope_recipient, header_recipient
canonical_maps = regexp:/etc/postfix/canonical
The map file /etc/postfix/canonical
looks like:
/^(.*@)test.example.com$/ ${1}example.com
Mailgraph is an RRDtool-based CGI that produces periodic graphs of mail activity.
/usr/share/doc/mailgraph/README.Debian
🐚 ~ $ sudo cp /usr/share/doc/mailgraph/examples/mailgraph.conf /etc/apache2/conf-available/
🐚 ~ $ sudo a2enmod cgi
🐚 ~ $ sudo a2enconf mailgraph
🐚 ~ $ sudo systemctl reload apache2
http://localhost/mailgraph/mailgraph.cgi
http://www.postfix.org/SASL_README.html
Usually, SMTP servers accept mail to remote destinations when the client’s IP address is in the “same network” as the server’s IP address.
SMTP clients outside the SMTP server’s network need a different way to get “same network” privileges. To address this need, Postfix supports SASL authentication (RFC 4954, formerly RFC 2554).
Postfix lacks its own full SASL implementation. Dovecot SASL may be used.
Dovecot is a POP/IMAP server that has its own configuration to authenticate POP/IMAP clients. When the Postfix SMTP server uses Dovecot SASL, it reuses parts of this configuration. Consult the Dovecot documentation for how to configure and operate the Dovecot authentication server.
https://amavis.org/README.postfix.html
🐚 ~ $ cat /etc/amavis/conf.d/50-user
use strict;
#
# Place your configuration directives here. They will override those in
# earlier files.
#
# See /usr/share/doc/amavisd-new/ for documentation and examples of
# the directives you can use in this file
#
@bypass_virus_checks_maps = (
\%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
@bypass_spam_checks_maps = (
\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
#@mynetworks = qw( [FEC0::]/10 [fd80:c96e:7a06::]/64 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 );
$log_level = 1; # verbosity 0..5, -d
$log_recip_templ = $log_verbose_templ; # disable by-recipient level-0 log entries
$nanny_details_level = 2; # nanny verbosity: 1: traditional, 2: detailed
$enable_dkim_verification = 1; # enable DKIM signatures verification
$enable_dkim_signing = 1; # load DKIM signing code, keys defined by dkim_key
$sa_tag_level_deflt = -999.0; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 3.0; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 6.9; # triggers spam evasive actions (e.g. blocks mail)
$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
$sa_crediblefrom_dsn_cutoff_level = 18; # likewise, but for a likely valid From
$penpals_bonus_score = 8; # (no effect without a @storage_sql_dsn database)
$penpals_threshold_high = $sa_kill_level_deflt; # don't waste time on hi spam
$bounce_killer_score = 100; # spam score points to add for joe-jobbed bounces
$sa_mail_body_size_limit = 400*1024; # don't waste time on SA if mail is larger
$sa_spam_subject_tag = '';
$defang_virus = 1; # MIME-wrap passed infected mail
$defang_banned = 1; # MIME-wrap passed mail containing banned name
$defang_by_ccat{CC_BADH.",3"} = 1; # NUL or CR character in header
$defang_by_ccat{CC_BADH.",5"} = 1; # header line longer than 998 characters
$defang_by_ccat{CC_BADH.",6"} = 1; # header field syntax error
push @{$score_sender_maps[0]{'.'}}, read_hash("/etc/amavis/sender_scores_sitewide");
#------------ Do not modify anything below this line -------------
1; # ensure a defined return
Add two services to /etc/postfix/master.cf
— one for Postfix to send messages to Amavis, the other to reinject Amavis-scanned messages into Postfix:
amavisfeed unix - - n - 2 lmtp
-o lmtp_data_done_timeout=1200
-o lmtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - n - - smtpd
-o content_filter=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o smtpd_restriction_classes=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
-o local_header_rewrite_clients=
Add to /etc/postfix/main.cf
:
content_filter=amavisfeed:[127.0.0.1]:10024
Run (at least on Debian Buster):
🐚 ~ $ sudo usermod -a -G amavis clamav
🐚 ~ $ sudo systemctl restart clamav-daemon.service
🐚 ~ $ sudo systemctl restart amavis.service
We want to send mail from a workstation to the LAN’s smarthost. Here’s a minimal config:
🐚 ~ $ sudo newaliases
🐚 ~ $ cat /etc/postfix/main.cf
compatibility_level = 2
mydomain = example.com
myorigin = $myhostname
mydestination = $myhostname localhost.$mydomain localhost
mynetworks = 127.0.0.0/8 [::1]/128
relay_domains =
relayhost = smarthost.$mydomain
🐚 ~ $ sudo postconf 1>/dev/null
🐚 ~ $ sudo postfix reload
postfix/postfix-script: refreshing the Postfix mail system
To receive mail, of course, an MX record must exist for myhostname.example.com.
We want a smarthost to do graylisting, spam scoring, and AV scanning, before forwarding mail to our internal mail server. Things we want:
🐚 ~ $ sudo apt install amavisd-new clamav clamav-daemon postfix postfix-doc \
postfix-pcre postfix-policyd-spf-python postfix-sqlite spamassassin \
unrar arj lhasa lzop lrzip lz4 mailgraph opendkim opendkim-tools \
p7zip-full p7zip-rar cabextract rpm2cpio
🐚 ~ $ sudo newaliases
We run rsyslog for logging, though Postfix offers its own logging service (see postlog(1)
and postlogd(8postfix)
).
Our /etc/rsyslog.conf
contains:
mail.* -/var/log/mail.log
mail.info -/var/log/mail.info
mail.warn -/var/log/mail.warn
mail.err /var/log/mail.err
(The minus sign (“-”) disables syncing the log after each write. See rsynclog.conf
.)
Remember to configure /etc/logrotate.d/rsyslog
as desired.
compatibility_level = 2
mydomain = example.com
myorigin = $myhostname
mydestination = $myhostname localhost.$mydomain localhost
mynetworks = 127.0.0.0/8 172.16.1.0/24 [::1]/128 [fe80::]/10
relay_domains = $mydestination, $mydomain
#relayhost = smarthost.$mydomain
debug_peer_list =
smtpd_tls_cert_file = /etc/ssl/private/STAR_example.com.pem
smtpd_tls_key_file = $smtpd_tls_cert_file
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1
alias_maps = hash:/etc/aliases
header_checks = regexp:/etc/postfix/header_checks
pcre:/etc/postfix/header_checks.pcre
postscreen_access_list = permit_mynetworks,
cidr:/etc/postfix/postscreen_access.cidr
postscreen_dnsbl_action = enforce
postscreen_dnsbl_threshold = 2
postscreen_dnsbl_sites = zen.spamhaus.org*2
bl.spamcop.net*1
b.barracudacentral.org*1
policy-spf_time_limit = 3600s
#smtpd_helo_restrictions = reject_unknown_helo_hostname
smtpd_sender_restrictions = reject_unknown_sender_domain
smtpd_data_restrictions = reject_unauth_pipelining
smtpd_recipient_restrictions = permit_mynetworks
reject_unauth_destination
reject_unknown_recipient_domain
reject_unverified_recipient
check_policy_service unix:private/policy-spf
content_filter=amavisfeed:[127.0.0.1]:10024
main.cf
)