Postfix relaying for Dynamic clients
My in-laws have a Fedora box on residential ADSL in Spain. They experienced trouble with the terra.es mailservers, so I installed Postfix for them and set Thunderbird to use this local Postfix to directly forward their mail to the destination.
This works well except for a correspondent in Australia, who is on bigpond.com. They use a blackholing service which has blackholed the whole residential ADSL netblock for their ISP, on the basis a lot of spam is coming directly from compromised Windows boxes, meaning that the Postfix on their box doesn't get anywhere talking to bigpond (and annoyingly bigpond rejects the mail with a 450 not a 550, delaying notification that there are problems).
I am in a similar position, I run my own MX at home where I work, so mail is sent directly to me, but I am unable to reliably send outgoing mail directly due to some blackhole lists including my whole netblock. My solution is to run a Postfix instance on warmcat.com, which is not used for incoming mail and is firewalled off from everyone except my home IP address. As a belt-and-braces, the Postfix on warmcat.com is configured to only relay from my IP address anyway.
So the obvious solution to the problem with the in-laws would be to also route their outgoing mail through warmcat.com, which pretty much everybody will talk to since it is sat in a server farm. But the fly in the ointment is that they are on residential ADSL, their IP address is changing every boot. I don't want to add an authentication layer because I don't want to disrupt their mail any further while I get it working and the pinhole in the Firewall method is working fine for me too.
The first move was to regularize their dynamic IP using dyndns.org and the perl client from there. This gave me a reliable FQDN that always resolves to their machine. Then the problem was simplified to "how can I get Postfix to accept a list of clients allowed to relay using FQDNs? And to track changes where the DNS mapping is dynamic?". It seems that you can't, it only accepts netblocks.
To solve this problem I created the following script which runs from a cronjob. See inside the script for instructions.
#!/bin/bash # update-valid-postfix-clients # 2006-08-09 - andy@warmcat.com - v1.0 # # Allows FQDNs to specify trusted clients to Postifx, including detection of IP address # change and firewall opening and closing # list the FQDNs you are allowing to see you server here, separated by spaces # the DNS for these can be dynamic # the script will open and close your firewall as the IPs for these change # the script will take care to notify postfix to allow and disallow these IPs as they change TRUSTED_FQDNS="home.warmcat.com some.domain.dyndns.org" # list the networks that are trusted here separated by spaces # notice that netblocks are handled by specifying the active part only # eg, 192.168.0.0/24 --> 192.168.0 in this table # You must open your firewall for these netblocks manually, the script does not do it TRUSTED_NETS="127" # VERBOSE=0 No output except fatal errors # VERBOSE=1 Output only when something changes # VERBOSE=2 Output each time run, even if nothing changed VERBOSE=1 # # Installation instructions # # 1) copy this file to /usr/local/bin/update-valid-postfix-clients # # 2) edit the above vars to configure for your situation # # 3) edit /etc/postfix/main.cf, comment out any existing mynetworks= line and uncomment the following line # mynetworks = hash:/etc/postfix/network_table # # 4) Add this to /etc/crontab # # open firewall and allow good users in postfix # 00,05,10,15,20,25,30,35,40,45,50,55 * * * * root /usr/local/bin/update-valid-postfix-clients #---------------------------------------- # no user serviceable parts below DIRTY=0 function allow { IP=`host "$1" | cut -d' ' -f4` if [ ! -z "$IP" ] ; then if [ -z "`cat /etc/postfix/network_table | grep $IP`" ] ; then # IP was not in force before DIRTY=1 if [ $VERBOSE -gt 0 ] ; then echo "IP change $1 -> $IP" ; fi OLDIP=`cat /etc/postfix/network_table | grep $1 | cut -d' ' -f1` if [ ! -z $OLDIP ] ; then if [ $VERBOSE -gt 0 ] ; then echo "Removing firewall setting for $1 -> $OLDIP" ; fi iptables -D INPUT -p tcp -s "$OLDIP" --dport 25 -j ACCEPT fi fi echo "$IP OK # $1" >>/etc/postfix/network_table-new # note that we open the firewall always even if we are not marked as dirty for postfix # this is so a local reboot will get the firewall fixed up even if the remote DNS is unchanged if [ -z "`iptables -L INPUT -n | grep "dpt:25" | tr -s ' ' | cut -d' ' -f4 | grep "$IP"`" ] ; then if [ $VERBOSE -gt 0 ] ; then echo "Opening port 25 for $1 -> $IP"; fi iptables -I INPUT -p tcp -s "$IP" --dport 25 -j ACCEPT else if [ $VERBOSE -gt 1 ] ; then echo "(Port 25 for $1 -> $IP already open)" ; fi fi fi } # give us an empty network_table file if it doesn't exist to avoid harmless errors if [ ! -e /etc/postfix/network_table ] ; then if [ $VERBOSE -gt 0 ] ; then echo "Creating /etc/postfix/network_table" ; fi touch /etc/postfix/network_table fi # regenerate list for i in $TRUSTED_FQDNS ; do allow $i ; done for i in $TRUSTED_NETS ; do echo "$i OK" >>/etc/postfix/network_table-new ; done if [ $DIRTY = 1 ] ; then if [ $VERBOSE -gt 0 ] ; then echo "reloading postfix due to changes" ; fi rm /etc/postfix/network_table mv /etc/postfix/network_table-new /etc/postfix/network_table postmap /etc/postfix/network_table service postfix reload else if [ $VERBOSE -gt 1 ] ; then echo "(No changes)" ; fi rm /etc/postfix/network_table-new fiThe script runs in a 5-minute cronjob, and takes care to do nothing if the IP address situation has not changed for the allowed FQDNs. If it does find a change, it removes the firewall pinhole to Postfix from that IP address, and creates a new pinhole for the new address. It also regenerates the list of allowed clients that can relay in Postfix, hashes the list and does a Postfix reload. The result is that just adding a FQDN to the script at the top will allow that FQDN access to the server no matter if it has a dynamic IP, but nobody else can even see the mailserver thanks to the firewall.