Postfix, maintaining relay destinations
This script will make maintaining lists of relay destinations - particularly "secret" destinations not listed in DNS - much simpler.
For every relay destination your server maintains, create a file with the name of that server's IP address, and inside it list each valid email address that destination services. For example, if you have one mail server at 192.168.0.15 which handles tom, bob, and sue at example.tld and ANY address at example2.tld, you would put the following file in /home/postfixfiles:
mx# cat /home/postfixfiles/192.168.0.15 tom@example.tld bob@example.tld sue@example.tld @example2.tld
Now run the update-postfix.pl script, and it will generate transport and recipients files for Postfix, map them, and put appropriate entries in the mail log.
mx# /usr/local/bin/update-postfix.pl 192.168.0.15 changed since last check Updating Postfix configuration... 5 recipients written to /usr/local/etc/postfix/recipients 2 domains written to /usr/local/etc/postfix/transport
If you need to target a non-standard SMTP port, you can just place it at the end of the filename. For example, if you want mail to example3.tld to go to port 2525 instead of the default port 25, just make the filename 192.168.0.16-2525:
mx# cat /home/postfixfiles/192.168.0.16-2525 harry@example3.tld mx# /usr/local/bin/update-postfix.pl 192.168.0.15-2525 changed since last check Updating Postfix configuration... 6 recipients written to /usr/local/etc/postfix/recipients 3 domains written to /usr/local/etc/postfix/transport
And looking at /usr/local/etc/postfix/transport shows that the port is specified for example3.tld, but not for example.tld or example2.tld:
mx# cat /usr/local/etc/postfix/transport example.tld smtp:[192.168.0.15] example2.tld smtp:[192.168.0.15] example3.tld smtp:[192.168.0.15]:2525
The script uses md5 checksums to see if the files in its config directory have changed, so you can run it frequently from cron to have it automatically pick up changes. You can also run it manually if you like, or run it manually with the --force argument to make it re-read its configuration files and rebuild the transport and recipients tables whether it thinks it needs to or not.
You can see operations the script has performed in the system mail log. Here is a sample from a relay MX using this script:
mx# grep update-postfix /var/log/mail.log Oct 6 12:35:01 mx root: update-postfix: No data changed in /usr/home/postfixfiles/ - update not needed. Oct 6 12:40:01 mx root: update-postfix: 192.168.0.15-2525 changed since last check Oct 6 12:40:01 mx root: update-postfix: Updating Postfix configuration... Oct 6 12:40:01 mx root: update-postfix: 2385 recipients written to /usr/local/etc/postfix/recipients Oct 6 12:40:01 mx root: update-postfix: 265 domains written to /usr/local/etc/postfix/transport Oct 6 12:45:01 mx root: update-postfix: No data changed in /usr/home/postfixfiles/ - update not needed.
Be sure to check locations of important programs and config directories in the config variables section before you use the script. Also be sure to chown the script root and chmod it 755 (or more restrictively) - you don't want anybody else editing it and having something nasty happen when you (or cron) call it after changing files in your config directory.
#!/usr/bin/perl ######################################################## ### ### update-postfix.pl ### ### purpose: automatically maintain a list of "secret" ### destination MTAs for Postfix to relay mail ### to. ### ### usage: run without arguments to check for ### changes and update if necessary, or run ### with --force to rebuild transport and ### recipient tables and rehash them for ### postfix even if no changes have been made. ### ### method: if the md5sum for any relevant file in ### $dir does not match $dir/$file.md5, ### update $dir/$file.md5, process all ### relevant files into new postfix recipient ### and transport tables, and re-hash those ### tables for postfix. ### ### requires: for each destination MTA, place a ### file in $dir named that server's IP ### address, optionally with a port number ### appended to it, in the form ### $dir/192.168.0.10 or $dir/192.168.0.10-587 ### ### Each file must contain a list of all valid ### addresses to be relayed to the MTA at that, ### address, one address per line, in the form ### user@domain. Catchall addresses may be ### used by omitting the username, and ### merely specifying @domain on that line. ### ### Any line which contains an @ will be ### processed as an email address. Any line ### which does not contain an @ will be ### ignored completely. ### ### bugs: requires IP address as relay target, ### so cannot do round-robin delivery and ### will not notice MX record changes. ### ######################################################## ### config variables # $dir = '/usr/home/postfixfiles/'; $recipientsfile = '/usr/local/etc/postfix/recipients'; $transportfile = '/usr/local/etc/postfix/transport'; $postmapprogram = '/usr/local/sbin/postmap'; $md5sumprogram = '/sbin/md5 -q'; $logger = '/usr/bin/logger -p mail.notice'; $identity = 'update-postfix'; ### begin operation # opendir(DIR,$dir); @files = readdir(DIR); closedir(DIR); my $needsupdate = 0; $logger .= " $identity\: "; if ($ARGV[0] eq '--force') { $needsupdate = 1; } foreach $file (@files) { if ($file =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(-\d{1,4})?$/) { my $path = $dir.$file; my $md5path = $path.'.md5'; my $newmd5 = `$md5sumprogram $path`; my $oldmd5; if (-e $md5path) { open(FH,$md5path) or die "Can't open $md5path: $!"; $oldmd5 = <FH>; close FH; } if ($oldmd5 ne $newmd5) { open(FH,">$md5path") or die "Can't open $md5path: $!"; print FH $newmd5; close FH; $message = "$file changed since last check"; print "$message\n"; print `$logger $message`; $needsupdate = 1; } } } if ($needsupdate == 0) { $message = "No data changed in $dir - update not needed."; print "$message\n"; print `$logger $message`; } else { $message = "Updating Postfix configuration...\n"; if ($ARGV[0] eq '--force') { $message = "UPDATE FORCED: " . $message; } print "$message\n"; print `$logger $message`; foreach $file (@files) { if ($file =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(-\d{1,4})?$/) { open FH, "$dir$file" or die "Can't open $file: $!"; foreach $line (<FH>) { if ($line =~ /\@/) { # add ok to end of address and push it into recipients $recipient = $line; chomp $recipient; $recipient .= " OK\n"; push @recipients,$recipient; # create a domain routing line and push it into rawdomains (my $ip,my $port) = ($file =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})-?(\d{1,4})?/); $line = (split("@",$line))[1]; chomp $line; $line .= " smtp:[$ip]"; if ($port ne ) { $line .= ":$port"; } $line .= "\n"; push @rawdomains,$line; } } close FH; } } # sort domains for transport table to eliminate dupes %hashTemp = map { $_ => 1 } @rawdomains; @sorteddomains = sort keys %hashTemp; open FH, ">$recipientsfile" or die "Can't open $recipientsfile: $!"; print FH @recipients; close FH; $message = scalar @recipients; $message = $message . " recipients written to $recipientsfile"; print "$message\n"; print `$logger $message`; open FH, ">$transportfile" or die "Can't open $transportfile: $!"; print FH @sorteddomains; close FH; $message = scalar @sorteddomains; $message = $message . " domains written to $transportfile"; print "$message\n"; print `$logger $message`; print `$postmapprogram $recipientsfile`; print `$postmapprogram $transportfile`; }