Postfix, relay MX
The goal here is to set up a spam and virus filtering relay MX, capable of acting as one of potentially many relays which show as the primary MX record for a domain. Ideally, the true destination MX will not show up in DNS at all - to prevent spammers from trying to target it directly. (Also ideally, that final destination MX will ONLY accept inbound mail from your relays, for hopefully obvious reasons.)
Build /usr/ports/mail/postfix, /usr/ports/mail/spamassassin, and /usr/ports/security/clamav from ports.
Once they're built, you'll need to enable them in /etc/rc.conf and start them:
# echo 'spamd_enable="YES"' >> /etc/rc.conf # echo 'clamav_freshclam_enable="YES"' >> /etc/rc.conf # echo 'clamav_clamd_enable="YES"' >> /etc/rc.conf # echo 'sendmail_enable="NONE"' >> /etc/rc.conf # echo 'postfix_enable="YES"' >> /etc/rc.conf # killall sendmail # /usr/local/etc/rc.d/postfix start # /usr/local/etc/rc.d/clamav-clamd start # /usr/local/etc/rc.d/clamav-freshclam start # /usr/local/etc/rc.d/sa-spamd start # /usr/local/etc/rc.d/postfix start
You'll also need Postprox, which is not in the ports tree. Get it from http://www.ivarch.com/programs/quickref/postprox.shtml - untar it, get into the directory the source code is in, and ./configure and make install without parameters. This will dump the binary in /usr/local/sbin and the manpages into the appropriate places.
You'll need to add this at the top:
permit_auth_destination = yes transport_maps = hash:/usr/local/etc/postfix/transport relay_domains = hash:/usr/local/etc/postfix/transport relay_recipient_maps = hash:/usr/local/etc/postfix/recipients unknown_relay_recipient_reject_code = 550 smtpd_client_restrictions = permit_sasl_authenticated, reject_unauth_destination, reject_unlisted_recipient, reject_rbl_client zen.spamhaus.org smtpd_proxy_filter=127.0.0.1:10025
Note that relay_recipient_maps is unnecessary if you aren't going to do recipient address verification during the SMTP conversation.
Also note that if you haven't actually set up SASL (authenticated SMTP) on your server, you shouldn't permit it in the smtpd_client_restrictions line. (As written, the permit argument is in place so that if you are running SASL, SASL authenticated clients won't be denied if they are listed in an RBL. This is necessary to allow legitimate, authenticated users on residential or other dynamic networks to relay mail.)
Finally, note that reject_rbl_client zen.spamhaus.org is set dead last on the smtpd_client_restrictions line - this means that before checking with a remote RBL, the local server will do address verification against its local tables. This saves traffic to the RBL and should result in cheaper rejections as well.
You'll need to append this at the bottom:
# SMTP Proxy. # 127.0.0.1:10025 inet n n n - 20 spawn user=filter argv=/usr/local/sbin/postprox -v -r -c /usr/local/bin/filter.sh 127.0.0.1:10026 # After-filter SMTP server. Receive mail from the content filter # on localhost port 10026. # 127.0.0.1:10026 inet n - n - - smtpd -o smtpd_authorized_xforward_hosts=127.0.0.0/8 -o smtpd_client_restrictions= -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions= -o smtpd_junk_command_limit=100000 -o smtpd_soft_error_limit=10000 -o smtpd_error_sleep_time=0 -o smtpd_proxy_filter= -o mynetworks=127.0.0.0/8 -o receive_override_options=no_unknown_recipient_checks
Now you'll need a filter script:
#!/bin/sh # If you don't export a HOME, you get 554 "~/.mailrc: No match" errors. HOME=/home/filter ; export HOME /usr/local/bin/clamdscan --no-summary --stdout - < "$EMAIL" VSTATUS=$? [ -z "$VSTATUS" ] && VSTATUS=9999 if [ $VSTATUS -eq 1 ]; then echo 550 Message contains a virus 1>&2 exit 1 elif [ $VSTATUS -ne 0 ]; then echo 450 Unexpected virus scanning error, please retry later 1>&2 echo CLAMDSCAN_ERROR - clamdscan returned unexpected exit code $VSTATUS | /usr/bin/logger -p mail.notice exit 1 fi echo "X-Antivirus-Scanner: ClamAV (PASSED)" > "$OUTFILE" cat "$EMAIL" | /usr/local/bin/spamc -E -x --headers -t 30 >> "$OUTFILE" SSTATUS=$? [ -z "$SSTATUS" ] && SSTATUS=9999 [ $SSTATUS -eq 0 ] && [ $VSTATUS -eq 0 ] && exit 0 if [ $SSTATUS -eq 1 ]; then echo 550 This message appears to be spam, sorry 1>&2 exit 1 elif [ $SSTATUS -eq 74 ]; then echo 451 Spam filtering timeout, please retry later 1>&2 echo SPAMC_TIMEOUT - spamc returned exit code $SSTATUS | /usr/bin/logger -p mail.notice exit 1 elif [ $SSTATUS -ne 0 ]; then echo 452 Unexpected spam filtering error, please retry later 1>&2 echo SPAMC_ERROR - spamc returned unexpected exit code $SSTATUS | /usr/bin/logger -p mail.notice exit 1 fi echo UNEXPECTED ERROR clamdscan exit code $VSTATUS spamd exit code $SSTATUS | /usr/bin/logger -p mail.notice exit 2
It's worth noting that there is some "belt and suspenders" logic in this script - it should be impossible ever to hit the UNEXPECTED error and the exit 2 at the bottom of the script; and it should be unnecessary to check that SSTATUS is not equal to zero in the spam filtering section (since you already have a check to exit 0 if both VSTATUS and SSTATUS are 0 prior to that). This is not accidental! Odds are relatively good that you'll decide to monkey with your filtering at some point, and "unnecessary" checks like this may make it less likely that you'll screw yourself in unexpected ways if and when you do.
Now you'll need to set up the transport file. This is a list of each of your domains, with the destination to relay mail for that domain to, in the following format:
testing.test smtp:[192.168.0.15]:2525 moretests.test smtp:[192.168.0.16]
(Note that you can append a port number, if you don't want to use the standard port 25.)
Once you've got your transport file set up, you need to hash it for fast access by Postfix: postmap transport will do the trick. Do this each time you update the transport file.
This is a list of all valid email addresses at your domains. In this example, the second domain listed accepts any address at its domain.
email@example.com OK @moretests.test OK
As with transport, once you've got this file set up you need to hash it. postmap recipients will do the trick; do it again any time you change the file.
add the filter user
We need an unprivileged user to run the filter script.
pw useradd filter -s /sbin/nologin -d /home/filter mkdir /home/filter chown /home/filter
And since we're using site-wide scanning, we want to make sure that the same database always gets used for Bayes analysis. These lines go in /usr/local/etc/mail/spamassassin/local.cf:
# change to non-NFS-safe file lock on Bayes databases (for speed) lock_method flock bayes_path /home/filter/.spamassassin/bayes use_bayes 1
And restart spamd; /usr/local/etc/rc.d/sa-spamd restart. Note that unless you manually train the Bayes database with ham and spam, it will only learn by the autolearn mechanism! You can issue the command sa-learn --dump magic to check and see how many messages have been learned, how many tokens exist, etc.
postfix reload and you should be good to go.
If you expect to be maintaining a lot of domains or addresses, you'll probably also want to see Postfix, maintaining relay destinations.