The overall idea is that normally you only permit relay from within your own network. But some users travel and connect from other places, and you want to let those users relay mail through your server. Whenever someone logs in for POP3 mail, this trick notes the IP address from which the connection was made, and permits relay from the IP for a limited time.
Travelling users need only check their mail to ``unlock'' the mail server, no changes need be made to the client mail software.
For sendmail users, this implementation is in two parts. The first part modifies the POP3 daemon to log a message identifying the IP address used for each successful login. The second part reads the log entries and builds a dbm file listing the permissible relay addresses.
Earlier implementations expired the IP addresses after 15 minutes or so. This one expires them after one day, which still seems to be plenty short to deter spammers, but puts a much lower load on the system.
These changes log a message like this each time a user logs in:
POP login for "username" at (remote.host.name) 000.000.000.000
The patches go at the end of the source file pop_pass.c.
*** pop_pass.c.orig Wed Dec 17 23:05:42 1997 --- pop_pass.c Thu Nov 20 01:14:59 1997 *************** *** 630,635 **** --- 630,640 ---- p->last_msg = 0; /* Authorization completed successfully */ + /* begin pop-before-smtp patch */ + pop_log(p,POP_PRIORITY, + "(v%s) POP login for \"%s\" at (%s) %s", + VERSION,p->user,p->client,p->ipaddr); + /* end pop-before-smtp patch */ return (pop_msg (p,POP_SUCCESS, "%s has %d message%s (%d octets).", p->user,p->msg_count, p->msg_count == 1 ? "" : "s", p->drop_size));You may want to also compile the daemon to use a separate log facility, such as LOCAL1. Of course it would also be simple to modify the daemon to update the list of POP authenticated addresses itself, but we wanted to have compatibilty across different machines and hardware platforms.
In some mailserver configurations, there are multiple mailservers, some which only relay SMTP mail, and one which only serves as a POP server. In order to propagate the list of POP authenticated addresses to the relaying mailservers, we decided on using syslog to log all the POP daemon messages to the relaying hosts, where a log watcher can update its own version of the list. This could be solved by a small read-only NFS mount from the POP server, but we tend to use NFS only when absolutely necessary.
The exact changes depend on your version of sendmail. For 8.9 and later, use the POPAUTH hack provided at http://www.sendmail.org/~ca/email/chk-89n.html.
For 8.8, it would be a good idea to update to 8.9 since there are many security holes fixed. If that's not possible, there are rulesets at http://www.sendmail.org/~ca/email/check.html. Look for "Relay control for roaming users".
For versions older than 8.8, you must update to something newer. Older versions are too buggy to support this feature.
Create the file /etc/mail/backupmx and place in it any fully-qualified domains for which you provide backup MX service. (NOTE: the above ruleset is very simple, and will not match and OK subdomains. They must be expicitly listed in /etc/mail/backupmx. However, if this is needed, it is easy to change with the addition of a $* before the $={BackupMX}.)
Create /etc/mail/localip and place in it all of your local network address ranges, each on a line by themselves, omiting any host bits. (i.e. "206.169" to denote 206.169.0.0/16 or "206.80.6" to denote 206.80.6.0/24.)
Create a popper syslog watcher script similar to the following ``popwatch'' script. This script watches for valid POP-authenticated IP addresses, creates a temporary file for each address as the filename, and updates the popauth database each time a new address is seen.
Here are two versions of the script.
Original script
#!/usr/local/bin/perl5 # Change the following for your system: $popauthspool = "/var/spool/popauth"; $poppersyslog = "/var/log/popper"; $watcherlog = "/var/log/popwatch"; $popwatcherpidfile = "/etc/popwatch.pid"; $TAIL = "/usr/local/bin/tail"; $date = `/usr/local/bin/date`; chop($date); # make database of IPs seen so far @ips = `ls $popauthspool`; #print @ips; foreach $ip (@ips) {chop($ip); $ipok{$ip} = "OK"; } # now watch log file and add new IPs as encountered # performance buglet: this will also add IPs in the local range as well # as travellers, but it's probably not worth the effort to filter them # out since each IP will be added a maximum of once per day. open(LOG,">>$watcherlog") || die("Can't open $watcherlog"); print LOG "\n$date Starting log for popauth.watcher at pid $$\n"; select(LOG); $| = 1; select(STDOUT); $| = 1; $SIG{'INT'} = 'handler'; $SIG{'QUIT'} = 'handler'; $SIG{'KILL'} = 'handler'; open(PID,">$popwatcherpidfile"); print PID "$$\n"; close(PID); open(POPPER,"$TAIL -f $poppersyslog |") || die("Can't $TAIL -f $poppersyslog"); while(<POPPER>) { if(/^([A-Za-z]+\s+\d+\s+\d+\:\d+\:\d+).+POP login for \"(.+)\".+\s(\d+\.\d+\.\d+.\d+).*$/) { $time = $1; $user = $2; $ip = $3; if ($ipok{$ip} eq "OK") { # print LOG "$time $user $ip $ipok{$ip} already exists\n"; } else { print LOG "$time $user $ip $ipok{$ip}\n"; $ipok{$ip} = "OK"; open(TEMP,"> $popauthspool/$ip"); close(TEMP); open (OUT,">/etc/mail/pophash.tmp"); foreach $key (keys %ipok) { print OUT "$key OK\n"; } close (OUT); $rc = system ("cd /etc/mail; /etc/makemap hash pophash.junk < pophash.tmp"); $rc = system ("mv /etc/mail/pophash.junk.db /etc/mail/pophash.db"); } } } close(POPPER); close(LOG); exit(1); sub handler { local($sig) = @_; close(POPPER); close(LOG); exit(0); }
Spiffier script
If you already have a check_rcpt ruleset, this simplified perl daemon simply concatenates to the .cR file. The daemon doesn't write local IPs or repeated IPs.
#!/usr/bin/perl5 # Change the following for your system: $popauthspool = "/etc/sendmail.cR"; $poppersyslog = "/var/log/messages"; $watcherlog = "/var/log/popwatch"; $popwatcherpidfile = "/var/run/popwatch.pid"; $TAIL = "/usr/bin/tail"; $date = `/bin/date`; chop($date); # make database of IPs seen so far open(PREV,"$popauthspool")|| die "Can't open $popauthspool"; while(Run this watcher in the background, killing and restarting it from cron whenever the syslog (and its own log) files are rotated. At the time you kill and restart it, run through the directory of IP addresses and delete any that are stale. Here's a snippet of shell script:) { # print $_; $prevIp{chop($_)} = "GOOOOOD"; } close(PREV); # now watch log file and add new IPs as encountered open(LOG,">>$watcherlog") || die("Can't open $watcherlog"); print LOG "\n$date Starting log for popauth.watcher at pid $$\n"; select(LOG); $| = 1; select(STDOUT); $| = 1; $SIG{'INT'} = 'handler'; $SIG{'QUIT'} = 'handler'; $SIG{'KILL'} = 'handler'; open(PID,">$popwatcherpidfile"); print PID "$$\n"; close(PID); open(POPPER,"$TAIL -f $poppersyslog |") || die("Can't $TAIL -f $poppersyslog"); while( ) { if(/^([A-Za-z]+\s+\d+\s+\d+\:\d+\:\d+).+POP login for \"(.+)\".+\s(\d+\.\d+\. \d+.\d+).*$/) { $time = $1; $user = $2; $ip = $3; # in the following line, change the pattern to match your # local known good IP range if ($ip =~ /999\.000\.[0-31]/){ #print LOG "$time native $user \t$ip\n"; } elsif ($prev{$ip} eq "GOOOOOD") { #print LOG "$time $user \t$ip is already logged\n"; } else { #print LOG "$time $user $ip LOGGED TO .cR\n"; $prev{$ip} = "GOOOOOD"; open(SPOOL,">>$popauthspool"); print SPOOL $ip . "\n"; close (SPOOL); } } } close(POPPER); close(LOG); exit(1); sub handler { local($sig) = @_; close(POPPER); close(LOG); exit(0); }
# after logs are rotated kill `cat /etc/popwatch.pid` # delete stale relay IPs ( cd /var/spool/popauth; find . -mtime +0 -print | xargs rm -f ) # restart popwatch popwatch &
And that should be it!