Configuring Mail Gateway Using Postfix

by Hoang Q. Tran

This howto will use Solaris 8 install with Core System Support component and the latest Postfix release 1.1.0 release.

Building Postfix

Solaris uses the old dbm UNIX database for lookup table. dbm is old and can run into problems if the lookup table contains lots of information. Berkeley DB is a modern version and standard on all 4.4BSD and Linux systems. Sleepycat distributes free for non-commercial use here. Please read the license before proceeding.

Download Berkeley DB and unpack it into a directory.

Change to the Berkeley DB unpack directory, build and install it:

$ cd build_unix
$ ../dist/configure
$ make
# make install
Download Postfix and unpack it into a directory.

Change to the Postfix unpack directory and build it with Berkeley DB:

$ make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB.3.3/include" \
    AUXLIBS="-L/usr/local/BerkeleyDB.3.3/lib -ldb"
$ make
Once the sources are built and ready to install, a postfix user, group and a postdrop group need to be pre-created.

Add user/group postfix and group maildrop:

# echo "postfix:x:11:11:Postfix pseudo-user:/:" >> /etc/passwd
# echo "postfix:NP:6445::::::" >> /etc/shadow
# echo "postfix::11:" >> /etc/group
# echo "postdrop::13:" >> /etc/group
You also need to add postfix alias to root:
# echo "postfix: root" >> /etc/aliases
On a single user system, use maildrop for submission mechanism. On multi user, use set-gid privileges on a small ``maildrop'' command. We will use set-gid privileges
# make install
Choose default settings except for ``setgid'' question where you need to put ``maildrop'' for set-gid privileges.
install_root: [/]
tempdir: [/tmp/snapshot-20011008]
config_directory: [/etc/postfix]
daemon_directory: [/usr/libexec/postfix]
command_directory: [/usr/sbin]
queue_directory: [/var/spool/postfix]
sendmail_path: [/usr/lib/sendmail]
newaliases_path: [/usr/bin/newaliases]
mailq_path: [/usr/bin/mailq]
mail_owner: [postfix]
setgid_group: [postdrop]
manpages: [/usr/local/man]
sample_directory: [/etc/postfix]
readme_directory: [no]
2. Update /etc/syslog.conf to log mail to /var/log/maillog
# echo "mail.info /var/log/maillog" >> /etc/syslog.conf
# touch /var/log/maillog
# /etc/init.d/syslog stop
# /etc/init.d/syslog start
3. Enable Postfix on startup:
# vi /etc/init.d/postfix

#!/sbin/sh
#
LD_LIBRARY_PATH=/usr/local/BerkeleyDB.3.3/lib; export LD_LIBRARY_PATH

case "$1" in
'start')
        if [ -x /usr/sbin/postfix ]; then
                /usr/sbin/postfix start > /dev/console 2>&1
        fi
        ;;

'stop')
        /usr/sbin/postfix stop
        ;;

*)
        echo "Usage: $0 { start | stop }"
        exit 1
        ;;
esac
exit 0
Turn on executable bit:
# chmod 744 /etc/init.d/postfix
Start Postfix in init level 2 and make sure it does not run on other levels:
# cp /etc/init.d/postfix /etc/rcS.d/K89postfix
# cp /etc/init.d/postfix /etc/rc0.d/K89postfix
# cp /etc/init.d/postfix /etc/rc1.d/K89postfix
# cp /etc/init.d/postfix /etc/rc2.d/S89postfix
Postfix out of the box has pretty much everything configured for a mail system. In order to start Postfix, you only need to change one parameter in the main configuration file /etc/postfix/main.cf.
# vi /etc/postfix/main.cf

myhostname = mailgateway.example.com
Populate the mail aliases:
# /usr/bin/newaliases
Start Postfix:
# postfix start
This is the minimum change to get postfix up and running.

Configuring Postfix

Purpose of the mail gateway:

Some data for this howto:
Mail gateway hostname: mailgateway
Domain name: example.com
Client network: 192.168.1.0/24
example.com backup MX host for: other.com
other.com IP address: 10.0.0.1
Since Postfix is already started, stop it and start the fun.
# postfix stop
Postfix out of the box relay policy: Postfix main configuration file is /etc/postfix/main.cf. For controlling Postfix processes, the correct file to edit is /etc/postfix/master.cf. Don't forget to do postfix reload each time either file is modified.

1. The basic

Identify the mail gateway hostname. This hostname will be used during ehlo/helo smtp session:
myhostname = mailgateway.example.com
Identify the mail gateway domain name. This value is used for many other parameters:
mydomain = example.com
Outgoing emails will appear as @example.com:
myorigin = $mydomain
Accept incoming emails and delivery to local mail boxes for the mailgateway and smtp clients in example.com domain:
mydestination = $myhostname, localhost.$mydomain, $mydomain
Postfix default to relay for clients on the same subnet. In the case of public DSL/cable network, it is not desired to relay for those clients. Therefore, explicitly identify the trusted clients IP address range. SMTP clients whose IP address belonging in these ranges will be permitted to relay emails.
mynetworks = 192.168.1.0/24, 127.0.0.0/8
Add my friend mail server other.com domain as trusted smtp client and allow it to relay emails through this mail server:
relay_domains = $mydestination, other.com
If an email destine to an unknown user, Postfix will default to accept, queue and bounce later once it found out that user is non-existent. Force Postfix to query for valid user and bounce if non-existent user instead:
local_recipient_maps = $alias_maps, unix:passwd.byname
Log and report everything to postmaster:
notify_classes = bounce, delay, policy, protocol, resource, software
Masquerade smtp client hostnames behind the mail gateway for outbound emails. e.g. me@host.example.com will appear as me@example.com:
masquerade_domains = $mydomain
Note:

At this point, the mail server is not an open relay host and will only relay emails for its trusted network clients and its friend other.com domain. Going further is not necessary for a very basic mail gateway.

If you don't use NIS, disable NIS client code in Postfix local delivery agent:

alias_maps = $alias_database
Otherwise, you will find similar entries in /var/log/maillog:
NIS domain name not set - NIS lookups disabled

2. Lock down

Disable VRFY command. This stops some spammers from trying to extract valid email address:
disable_vrfy_command = yes
People have no business in knowing what email software and its version. Under a US proposed federal law, unsolicited commercial email cannot be sent through a server that includes the string NO UCE in the 220 greeting line. More information from DJB's technical information on SMTP Greeting:
smtpd_banner = $myhostname NO UCE ESMTP
Disable notify local users of the arrival of new mail. This feature makes use of the comsat network service, which is turned off on many UNIX systems for performance and/or security reasons.
biff = no
Postfix default to 10M per message size. Reduce it to 2M as a prevention for possible mail abuse:
message_size_limit = 2048000

3. Dealing with unsolicited commercial email (UCE)

Understanding Postfix UCE is quite important on cutting down the amount of spam emails. Before delving into the smtp restrictions, lets review how smtp works during mail transferring process.
Transcript of session follows.

1  Out: 220 mailgateway.example.com ESMTP
2  In:  EHLO imbad.spammer.com
3  Out: 250-mailgateway.example.com
4  Out: 250-PIPELINING
5  Out: 250-SIZE 2048000
6  Out: 250-ETRN
7  Out: 250-XVERP
8  Out: 250 8BITMIME
9  In:  MAIL FROM:<jackass@spammer.com> SIZE=1897
10 Out: 250 Ok
11 In:  RCPT TO:<niceguy@example.com>
12 Out: 450 <imbad.spammer.com>: Helo command rejected: Host not found
13 In:  QUIT
14 Out: 221 Bye

No message was collected successfully.
Detail description of each parameter in action:

0. $smtpd_client_restrictions
1. $smtpd_banner
2. $smtpd_helo_required = yes; $smtpd_helo_restrictions
3. Our mail server introduces itself 4-8. Our mail server advertises its settings
9. $smtpd_sender_restrictions
10. Our mail server responses with Ok
11. $smtpd_recipient_restrictions
12. imbad.spammer.com got reject with ``Host not found'' during $smtpd_helo_restrictions. Otherwise, the smtp client will start sending its DATA and $header_checks and $body_checks the last 2 remaining restriction to defend us from spammer will apply. If these 2 stages are passes, the mail will be accepted and niceguy@example.com will receive the email.
13. Remote mail server disconnects
14. Our mail server disconnects

Why reject an EHLO ``Host not found'' after $smtpd_recipient_restrictions stage? Why not reject at once after $smtpd_helo_restrictions stage where we know the spammer didn't have the reverse PTR record? The answer is the default setting $smtpd_delay_reject = yes. The reason to have this parameter enable is because some windows smtp client will simple ignore and continue until the RCPT TO: stage.

Once smtpd_delay_reject = no, the mail gateway will reject the spammer after $smtp_helo_restrictions as below:

Transcript of session follows.

 Out: 220 redsand.muine.org ESMTP
 In:  EHLO imbad.spammer.com
 Out: 450 <imbad.spammer.com>: Helo command rejected: Host not found
 In:  HELO imbad.spammer.com
 Out: 450 <imbad.spammer.com>: Helo command rejected: Host not found

Session aborted, reason: lost connection
Here is the summary of how Postfix apply its UCE restrictions:
<smtpd_client_restrictions>
<smtpd_banner>
<smtpd_helo_required = yes>
<smtpd_helo_restrictions>
<smtpd_etrn_restrictions>
[smtp server advertises its settings]
MAIL FROM: <smtpd_sender_restrictions>
RCPT TO: <smtpd_recipient_restrictions>
[Got rejected? If yes, skip header and body checks below. Otherwise,
apply them.]
DATA
<header_checks>
<body_checks>
Now, with UCE knowledge, lets put the restrictions to use.

Reject immediately. Do not delay until RCPT TO: to reject the email:

smtpd_delay_reject = no
Require HELO command at the beginning of smtp session. Requiring this will stop bulk mail programs:
smtpd_helo_required = yes
Require strict RFC 821 envelopes. This will stop unwanted emails:
strict_rfc821_envelopes = yes
Identify our friends DNS blacklist hostnames to query when using $reject_maps_rbl restriction:
maps_rbl_domains =
        relays.ordb.org,
        inputs.orbz.org
Client Restrictions

Example of using client restrictions:

smtpd_client_restrictions =
        check_client_access hash:/etc/postfix/client_access,
        reject_unauth_pipelining,
        reject_unknown_client,
        reject_maps_rbl
client_access should contain the client hostname, parent domains, client IP address, or networks obtained by stripping least significant octets.

Sometimes, it is necessary to permit a friend mail server with offending ``unknown client'' to pass through. The solution is to put that IP in the client_access with OK action.

client_access:
10.0.0.1      OK

Then generate the client access lookup table:

# postmap client_access
ehlo/helo Restrictions

Example of using ehlo/helo restrictions:

smtpd_helo_restrictions =
        check_helo_access regexp:/etc/postfix/helo_access,
        reject_invalid_hostname,
        reject_unknown_hostname,
        reject_non_fqdn_hostname
helo_access should contain the HELO hostname or parent domains in the specified table.

Sometimes, it is necessary to permit a friend mail server with offending ``unknown hostname'' to pass through. The solution is to put that hostname in the helo_access with OK action.

helo_access:
/^smtp\.goodfriend\.com$/      OK
Then generate the helo access lookup table:
# postmap helo_access
ETRN Restrictions

Example of using etrn restrictions:

smtpd_etrn_restrictions =
        check_etrn_access hash:/etc/postfix/etrn_access
etrn_access should contain the the domain specified in the ETRN command, or its parent domains.

Permit our friend at other.com to issue ETRN:

etrn_access:
other.com       OK

Then generate the etrn access lookup table:

# postmap etrn_access
Sender Restrictions

Example of using sender restrictions:

smtpd_sender_restrictions =
        check_sender_access hash:/etc/postfix/sender_access,
        reject_unknown_sender_domain,
        reject_non_fqdn_sender
sender_access should contain the sender mail address, parent domain, or localpart@.

Then generate the sender access lookup table:

# postmap sender_access
Recipient Restrictions

Example of using recipient restrictions:

smtpd_recipient_restrictions =
        check_recipient_access hash:/etc/postfix/recipient_access,
        reject_unknown_recipient_domain,
        reject_non_fqdn_recipient,
        check_relay_domains
recipient_access should contain the resolved destination address, parent domain, or localpart@.

Then generate the recipient access lookup table:

# postmap recipient_access
Header Restrictions

Example of using header checks:

header_checks = regexp:/etc/postfix/header_checks:
/^From:(.*)@spammer\.com$/         REJECT
/^From:(.*)@babbabbad\.com$/       IGNORE
/^From:(.*)@sex\.com$/             REJECT Geeks have no time for this
/^From:(.*)@goodfriend\.com$/      OK
/^Subject: hot hot hot$/           WARN

Reload Postfix to pickup new header change:

# postfix reload
Body Restrictions

Example of using body checks:

body_checks = pcre:/etc/postfix/body_checks:
/^(.*)name\=\"(.*)\.(zip|gz|Z|bz)\"$/                      REJECT
/^(.*)name\=\"(.*)\.(hta|vb[esx]|ws[fh]|js[e]|bat|cmd)\"$/ IGNORE
/^(.*)name\=\"(.*)\.(com|exe)\"$/                          REJECT Please no executable
/^(.*)name\=\"(.*)\.(doc)\"$/                              OK
/^hot deal$/                                               WARN

Reload Postfix to pickup new header change:

# postfix reload
Custom UCE Restriction

Postfix SMTP server allows you to define custom UCE restrictions on the right-hand side of lookup tables. The benefit of it is to allow easy-to-remember names to groups of UCE restrictions.

smtpd_helo_restrictions =
        regexp:/etc/postfix/helo_friends,
        reject_invalid_hostname,
        reject_unknown_hostname,
        reject_non_fqdn_hostname
Define a restriction class name friends_only:
smtpd_restriction_classes = friends_only
friends_only = check_helo_access regexp:/etc/postfix/friends
Use friends_only UCE restriction on the right hand side of a lookup table:
helo_friends:
/^smtp\.goodfriend\.com$/   friends_only
Finally, create the lookup table friends:
friends:
/^smtp\.goodfriend\.com$/   OK
Don't forget to reload postfix to pick up new change.

Monitor the mail log

The single most important when running Postfix is to frequently monitor the /var/log/maillog for any reject, warning, panic or fatal.
# egrep '(reject|warning|error|fatal|panic):' /var/log/maillog
Alternatively, you can use a nicer Postfix log summary like pflogsumm to output useful statistics.

Familiarize with Postfix

Find out what Postfix version is installed:
# postconf mail_version
Show Postfix default settings:
# postconf -d
Show Postfix non-default settings:
# postconf -n
Delete one message with the named queue ID from default queues: incoming, active and deferred
# postsuper -d queue-id
Delete all messages from the queues:
# postsuper -d ALL
Flush the mail queue:
# postfix flush

Reference

SMTP Protocol
   http://www.muine.org/rfc/rfc821.txt
New SMTP Protocol
   http://www.muine.org/rfc/rfc2821.txt
The Postfix Home Page
   http://www.postfix.org/
SMTP: Simple Mail Transfer Protocol
   http://cr.yp.to/smtp.html
Postfix Configuration - UCE Controls
   http://www.muine.org/postfix/uce.html
postfix-uce-guide
   http://www.mengwong.com/misc/postfix-uce-guide.txt
Requirements for Internet Hosts -- Application and Support
   http://www.muine.org/rfc/rfc1123.txt


last update: July 27, 2003