Using postfix as a front-end for MS Exchange

This article is as much a “how to” as it is for myself to document how I built a postfix front-end mail server for MS Exchange and integrated spam and virus scanning. The base for this server is a HP DL360, running openSUSE Linux 11.3., but it should work for most other distributions out there. It assumes you already have a running installation of Linux. We’ll be using postfix, amavisd-new, clamav and spamassassin.

Postfix

Since this is going to be a front-end server and not a final destination for incoming mail, we’re going to have to tell postfix it needs to hand off mail to our MS Exchange server after it has received it. First, let’s set up postfix so it will handle mail for our domain(s) and deliver it to the Exchange machine. Then, we will add in spam and virus scanning. Though postfix is arguably easier to set up than for instance sendmail,  it does require a correct and safe configuration of the MTA. Of course, first we’ll have to install postfix through the package manager of the distribution of choice. On openSUSE 11.3 this is not necessary because it is already the default MTA, but in case it isn’t there, you can get it quickly as follows:

# zypper in postfix

This will also pull in all dependencies required for the correct operation of postfix. The main configuration of postfix is done through the file main.cf, which on openSUSE lives in /etc/postfix. On openSUSE it is possible to set up the MTA through Yast, but we’ll do it on the command line. Open the main.cf file and edit the following parameters to match your needs and circumstances:

mydomain = example.com
myhostname = mail.example.com
mydestination = $myhostname, localhost.$mydomain
mynetworks = 10.0.0.0/8
relay_domains = $mydestination, hash:/etc/postfix/relay
smtpd_recipient_restrictions = permit_mynetworks,reject_unauth_destination, 
reject_invalid_hostname,reject_unauth_pipelining,reject_non_fqdn_sender, 
reject_unknown_recipient_domain,reject_unknown_sender_domain
soft_bounce = yes unknown_local_recipient_reject_code = 450

This is enough to tell postfix that it is the MTA for our domain. The last two parameters in red are safety precautions, telling postfix not to refuse messages but instead telling the sender to try again later. In case we make a configuration mistake, the sender will be notified of a temporary failure. Once we get postfix et al up and running and everything seems okay, we will change these back to the default: to bounce mail definitively.
We need one additional but important step because our postfix server is not the final destination for our domain, it is a front-end. It will have to be instructed to hand mail over to our internal MS Exchange server. In order to do this, we need to edit the /etc/postfix/transport file. This file is used to build the /etc/postfix/transport.db file. Open the file with your favourite editor and add the following at the bottom of the file:

example.com  smtp:[192.168.1.1]

This line instructs postfix to hand off all email destined for the domain example.com, via the smtp protocol, to the server at 192.168.1.1. Of course you need to change this to match the IP address of your internal mail server. The brackets around the IP address make sure that no DNS lookups are performed for this address. This saves time. When you have added all the domains and transports that you require, save the file and execute the following command to build the transport.db file:

# postmap /etc/postfix/transport

Now, reload postfix so that it will pick up the new configuration. On openSUSE this is accomplished by the command:

# rcpostfix reload

If all is well, postfix will now be accepting email for your domain and then passing it on to your internal MS Exchange server. Next, we will add spam scanning and virus scanning.

Spamassassin

Spamassassin is available from the openSUSE repositories and comes configured, though you may wish to tweak it a bit. However, because spamassassin is called through amavisd-new, many settings (such as what to do with spam) will have to be configured in amavisd-new. You do not have to start spamassassin as a daemon in this set-up. However, you will probably want to update spamassassin detection rules and keep them up to date. This will help your detect spam, so it is pretty important to do. For updating spamassassin detection rules, you can use the tool sa-update. You can read all you want to know about that tool in spamassassin wiki on Apache.org. I have added the following rule in /etc/crontab to take care of the updates automatically once a day.

* 8 * * *         root   /usr/bin/sa-update -D >> /var/log/sa-update 2>&1

All output is logged in /var/log/sa-update and since I have switched on debug logging for the update command, you will want to incorporate this file in the logrotate scheme of your distribution. Or, if you do not want logging, you can redirect the output to /dev/null instead. I’m paranoid, so I want the logging.

Amavisd-new

Amavisd-new is best compared to glue, which glues your MTA (postfix in this case), your spam scanner (spamassassin) and your virus scanner together. How it works, is probably best explained by a diagram.

postfix-amavis-spamassassin-blog

As you can see, each email that arrives, is handed over to amavisd-new which then disassembles the message into its different components and feeds these to the various virus scanners which it supports and to spamassassin. When neither virus scanners or spamassassin find anything wrong with the message, it is reassembled and handed back to postfix as clean. Postfix then takes care of further delivery. However, if any of the virus scanners or spamassassin find something wrong, the message is marked as not clean and actions are taken which can be configured in amavisd-new. Although an MTA should not lose mail, personally I think it is better to discard messages which appear infected by a virus. Spam can be rerouted to a special mailbox, which can be monitored for false positives. However, as spamassassin learns, you will get less and less false positives.

First, we must start amavisd-new so that it will listen on tcp port 10024 by default, though you may specify another port in the amavisd-new configuration file, which on openSUSE lives in /etc.

# rcamavis start

In order to get postfix to hand off messages to amavisd-new for scanning, we need to edit both the /etc/postfix/master.cf and /etc/postfix/main.cf files. We will define a new transport in master.cf for amavisd-new at tcp port 10024 and a we will tell postfix to also listen on tcp port 10025 so that amavisd-new can hand messages back to postfix for delivery. Before you do this, make sure to create a copy of both files as especially editing master.cf can be tricky. One mistake and you could end up with trouble.
To begin, open up master.cf and add the following lines:

smtp-amavis unix  -     -       y       -       -       smtp
 -o smtp_data_done_timeout=1200
 -o disable_dns_lookups=yes

This creates a transport for postfix, called smtp-amavis. In a moment, we will tell postfix in main.cf that this is the name of its content-filter. First, let’s finish editing master.cf though. We still need to instruct postfix to listen on tcp port 10025 as well. To do this, add the following to master.cf:

127.0.0.1:10025 inet    n      -        n       -      -        smtpd
 -o content_filter=
 -o local_recipient_maps=
 -o smtpd_helo_restrictions=
 -o smtpd_sender_restrictions=
 -o smtpd_recipient_restrictions=permit_mynetworks,reject_unauth_destination
 -o mynetworks=127.0.0.0/8

With the above definition, postfix will listen only on localhost on port 10025 and accept mail only from localhost sent to this listener. To finish up, we should tell postfix about the content filter, which is done by adding the following to main.cf:

content_filter = smtp-amavis:[127.0.0.1]:10024

Notice that the name here must match exactly the entry we created before in master.cf! Again, the block quotes tell postfix not to do DNS lookups for this address. It would only slow things down if it did. We need to restart postfix for the changes to take effect. On openSUSE, this is done as follows:

# rcpostfix restart

The restart should be almost instantaneous. To check if our changes worked we check if postfix and amavisd-new are listening on their respective ports.

# netstat -atpn

tcp    0   0 0.0.0.0:25           0.0.0.0:*       LISTEN      9374/smtpd
tcp    0   0 127.0.0.1:10024      0.0.0.0:*       LISTEN      9353/amavisd
tcp    0   0 127.0.0.1:10025      0.0.0.0:*       LISTEN      9305/smtpd

It seems that we have succeeded. Among the output of the netstat command, we find the lines above, indicating there is an smtp daemon listening on ports 25 and 10025 and also that amavisd is listening on port 10024. We can test this further by trying to telnet to the mentioned ports:

# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.example.com ESMTP Postfix

----------------

# telnet localhost 10025
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.example.com ESMTP Postfix

----------------

# telnet localhost 10024
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 [127.0.0.1] ESMTP amavisd-new service ready

These tests have also succeeded. At this point, we may conclude that amavisd-new is running as expected and that postfix should be able to hand off any messages it receives to amavids-new and to get them back for further delivery.

By default, amavisd-new calls several virus scanners in its configuration file. Most of them you will not have installed on your system, so you may wish to comment them out so amavisd-new doesn’t complain about them in the log.

Clamav

Clamav is a free virus tool-kit which runs on Linux and Unix. It is specifically designed for email scanning on mail gateways, so it suits our purpose perfectly. It can run daemonized but in our case, it will be called by amavisd-new per email message. Clamav is packaged with openSUSE, so not much configuration is required. The amavisd-new configuration file supplied with openSUSE already points to the right location so out of the box, messages can be scanned for viruses.

# ### http://www.clamav.net/
 ['ClamAV-clamd',
 &ask_daemon, ["CONTSCAN {}n", "/var/lib/clamav/clamd-socket"],
 qr/bOK$/m, qr/bFOUND$/m,
 qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],

However, a virus scanner is only as good as its most recent update so it is important to keep our copy of Clamav up to date. Because new viruses and malware come out every day, I have chosen to let Clamav update itself every hour. For this, we can make use of the ‘freshclam’ utility. I call freshclam hourly from cron and for the rest, things just work.

59 * * * *      root    /usr/bin/freshclam >/dev/null 2>&1

The line above calls freshclam every 59 minutes past the hour. Any output from the command is directed to /dev/null, but Clamav logs messages about updates to the general mail log in /var/log/mail through syslog so we can keep track of updates succeeding or failing.

Finishing touches

If you’ve done the above steps, everything should work, but probably not exactly as you might like. You probably want to specify what you want to happen to spam messages, virus messages and when to mark messages as spam. Maybe you want to block certain attachments at the mail gateway. All of this is possible, so let’s see where and how we have to change settings.

Tweaking spamassassin

It’s important to remember that when spamassassin is called through amavisd-new, certain settings in its own configuration file are ignored. Mainly these are the settings for at which score to mark messages as spam and if and how to rewrite the subject header. These settings are handled by amavisd-new in its own configuration file. Other settings are configured in the spamassassin configuration file, which on openSUSE lives in /etc/mail/spamassassin. Here we find the file local.cf which holds the site-wide configuration. In my local.cf are the following settings:

use_bayes 1
bayes_auto_learn 1
bayes_auto_learn_threshold_nonspam 2.0
bayes_auto_learn_threshold_spam 5.0
skip_rbl_checks 0
report_safe 1

From top to bottom, these settings have the following effect:

  • Activate the Bayesian database, which allows spamassassin to learn about certain characteristics of messages that you receive, so that detection of spam is tailored to your situation over time.
  • Activate the Bayesian auto-learning system. This setting is tied in with the two settings below it and tells spamassassin to learn about messages automatically as they arrive. If you do not use auto-learning, you have to train the Bayesian filter by hand.
  • This setting sets the threshold below which spamassassin learns a message as non-spam (a.k.a. “ham”). It is important not to set this too high, otherwise spam messages may be learned as ham. If you set it too low, virtually no messages will get learned as ham, decreasing the effectiveness of your Bayesian filtering. Do not set this arbitrarily, but check how your mail scores on the whole and set your threshold accordingly.
  • The setting above which spamassassin learns  a message as spam. Like the previous setting, this should not be set arbitrarily, but checked against the mail you receive on general. Too low a setting may result in legitimate messages being learned as spam, too high and your filter will not be trained on low-scoring spam.
  • This setting instructs spamassassin not to skip tests which check if senders have been added to real-time blacklists. Be aware that these checks may cause mail delivery to slow down. Also, be aware that sometimes legitimate senders may be added to RBL’s under certain conditions, causing messages from them not being delivered to your users. So be careful enabling these tests.
  • This setting instructs spamassassin to generate a new message for a detected spam message, encapsulating the original message.

Acting on infected messages and spam messages

It’s probably safe to assume that you do not want your users to receive viruses on their client PC’s, hand-held device or what have you. Likewise, you most likely don’t want them bothered with messages peddling Viagra, fake Rolexes and university diplomas. Through amavisd-new we can act on these messages. This behaviour is configured in the amavisd-new configuration file in /etc/amavisd.conf. On openSUSE the default appears to be to quarantine all types of possibly unwanted messages. This is safe because you do not lose messages and should a message be a false positive, it can be retrieved from the quarantine. However, you may not want this for the long term, because it means having to review and delete messages from the quarantine manually or by some automatic process. But if you’re going to clean the quarantine using an automatic process, you may want certain messages not to be quarantined in the first place. Especially for messages which are infected with a virus it may be better to discard them completely. Below are the options to accomplish these things in the amavisd-new configuration file.

$final_virus_destiny      = D_DISCARD;
$final_banned_destiny     = D_BOUNCE;
$final_spam_destiny       = D_BOUNCE;
$final_bad_header_destiny = D_PASS;

The above settings are the default settings, you can alter them as you like. Your options for that are D_DISCARD, D_BOUNCE, D_REJECT and D_PASS.
Virus infected messages: The top setting tells amavisd-new to discard virus infected messages without delivering them. For viruses, this seems like a sane option because your users will never see these messages and thus can not become infected. The messages will be discarded silently.
Banned messages: The setting final_banned_destiny controls what to do with messages that violate certain policies, for instance messages with executable attachments. By default they are bounced, meaning that the recipient does not get the message but the sender receives a notification that his/her message was not delivered.
Spam messages: The default setting for spam messages (final_spam_destiny) is also set to bounce, but this seems a bit optimistic. The sender address of most spam messages is faked so it is not likely that the original sender gets the bounce message. It may be better to quarantine low scoring messages (to prevent false positives from being lost) and to discard high scoring messages altogether. Here’s how to do that:

# $final_spam_destiny       = D_BOUNCE;
$sa_tag_level_deflt  = 2.0; # spam info headers are inserted (no action taken)
$sa_tag2_level_deflt = 5.0; # subject of message is altered
$sa_kill_level_deflt = 5.5; # mail is not delivered
$sa_dsn_cutoff_level = 5.5; # bounce message not sent
$sa_crediblefrom_dsn_cutoff_level = 10; # bounce sent if sender credible

What the above does is to disable the general bouncing of spam, instead looking at the score of each individual message and acting according to the score. A score of 2 or more means that spam headers are inserted into the message, but taking no other action. If the message scores 5 or more points, the subject header is prefaced with  a custom text, usually something like “*** SPAM ***”. This text can be set in the configuration file. At score 5.5 and higher, the message is no longer delivered to the recipient. A dsn (=delivery status notification)message is also not sent at this score, unless (see final option) the sender address looks credible to spamassassin, which may indicate that the message may be a false positive. In this case the sender will be notified but the message not delivered. Notice that these settings are fairly strict and you should adapt them for your situation, not just copy them from me.
Messages with bad headers: This is a tricky category. Bad headers can indicate a lot of things, but they are often caused by poorly designed mail software, mailing lists that do not adhere to standards, etc.. The default is to let them pass and this seems to be the best choice to me as well.

Blocking messages with certain attachments

It’s very easy to block messages with various kinds of attachments using amavisd-new. There are plenty of examples of this in the configuration file. For our site, we have the following settings, which can of course be altered:

### BLOCKED ANYWHERE
# qr'^UNDECIPHERABLE$',  # is or contains any undecipherable components
 qr'^.(exe-ms|dll)$',                   # banned file(1) types, rudimentary

### BLOCK THE FOLLOWING, EXCEPT WITHIN UNIX ARCHIVES:
 [ qr'^.(rpm|cpio|tar)$'       => 0 ],  # allow any in Unix-type archives

 qr'..(pif|scr)$'i,                     # banned extensions - rudimentary

 qr'^application/x-msdownload$'i,        # block these MIME types
 qr'^application/x-msdos-program$'i,
 qr'^application/hta$'i,

 # block certain double extensions in filenames
 qr'.[^./]*[A-Za-z][^./]*.s*(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)[.s]*$'i,

 qr'..(exe|vbs|pif|scr|cpl)$'i,             # banned extension - basic
 qr'..(ani|cur|ico)$'i,                 # banned cursors and icons filename

This prevents people from receiving and sending out the following file types: exe, com, bat, dll, pif, scr, double extensions, animated cursors and most other nasty things. Of course it is also possible to disable receiving of video clips and audio clips and the like. For more information, see the amavisd-new configuration file.

Cleaning amavisd-new quarantine

Over time (if you use quarantine), your amavisd-new quarantine may fill up with old messages, never looked at and probably no longer needed. It is safe to delete these. You can do this manually, but it is better to automate this process so it will not be forgotten. We’ll do that by creating a simple cron job that will remove any file older than 31 days from the cache. If no one has missed the email in question in a month, it will probably never be missed at all. First, we need to find out where amavisd-new keeps its cache. We can find that by looking at the configuration file (/etc/amavisd.conf).

$MYHOME = '/var/spool/amavis';
$TEMPBASE = "$MYHOME/tmp";   # working directory, needs to exist, -T
$ENV{TMPDIR} = $TEMPBASE;    # environment variable TMPDIR, used by SA, etc.
$QUARANTINEDIR = '/var/spool/amavis/virusmails';  # -Q

The above shows us that the quarantine is at /var/spool/amavis/virusmails and that there is a directory for temporary files in /var/spool/amavis/tmp. We will clean both out via cron.

# Cleanup amavis quarantine automatically, throw out everything older > 31 days
* 3 * * *       root    find /var/spool/amavis/virusmails/ -mtime +31 -exec rm {} ;
30 3 * * *      root    find /var/spool/amavis/tmp/ -mtime +31 -exec rm {} ;

That should take care of things and keep our quarantine to a manageable size.

Now, there is only one thing left to do. At the very beginning of this document, we instructed postfix to soft bounce messages. If you are certain your front-end mail server is working the way you want it, you can now alter this setting back to the default, to bounce messages permanently. Go back to /etc/postfix/main.cf and alter the following settings:

soft_bounce = yes
unknown_local_recipient_reject_code = 450

They should read:

soft_bounce = no
unknown_local_recipient_reject_code = 550

Issue an rcpostfix restart at the command line and the new settings should be effective right away. We’re all done now and should have a safe, fast and reliable front-end mail server protecting our Exchange machine from spam, viruses and other internet nasties.

If you have comments, additions or corrections for this document, please leave them via the comment form below. The post shall be updated as necessary.

8 thoughts on “Using postfix as a front-end for MS Exchange”

  1. Thanks Joop for such a comprehensive tutorial.I have read a couple of other articles with regard to this scenario but yours was the best.i was wondering if you would let me translate it to Persian.
    Mohsen

    1. Thank you for the feedback, Mohsen. I’m glad if the above was helpful to you. Feel free to translate it to Persian. I would appreciate a link to this article with the translation if you decide to post your translation online. I’ll link to the Persian translation from here then, to help people find it.

  2. Joop, many thanks for the excellent tutorial, it was just the solution I was looking for as a front end for my exchange 2013. Now just a question, where do you put this centos machine? outside of your Internal network or within your internal network?

    cheers

    sakaio

    1. Hello Sakaio,

      Glad to hear you found this tutorial useful. It’s probably a bit dated by now but I’m pleased it still seems to do the trick.

      Technically you can put the front-end where you like, as long as it can deliver mail to the Exchange backend and the SMTP daemon can be reached from the Internet. I’ve implemented this solution several times and usually put it in a DMZ when available. That seems to be the most sensible place for it.

      Thanks for the feedback!

      Joop

Any thoughts about this? Please leave a comment!