<!doctype html public "-//IETF//DTD HTML//EN">
<html>
<!-- $Id: pop-before-smtp.html,v 1.2 1999/03/23 02:48:55 johnl Exp $ -->
<head>
<title>POP before SMTP for Sendmail</title>
</head>
<body bgcolor="#FFFFFF" text="#000000">

<center><img src="http://spam.abuse.net/spam/images/spam-banner-1.gif" height=44 width=334 alt="Campaign banner"></center>

<H2>How to restrict relaying through your mailserver to only local 
users, that have authenticated using Post Office Protocol.</H2>
<em>From an idea by John Levine &lt;johnl@abuse.net&gt;,
described by Scott Hazen Mueller
and implemented by Neil Harkins &lt;nharkins@well.com&gt;
and John Levine
<BR>
Updated March 1999</em>
<P>
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.
<P>
Travelling users need only check their mail to ``unlock'' the mail server,
no changes need be made to the client mail software.
<P>
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.
<P>
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.

<H3>Modify the POP3 daemon</H3>

Here are modifications to qpopper version 2.4, the most popular
Post Office Protocol daemon.
It's available in source from at no charge from
<A HREF="http://www.eudora.com/freeware/servers.html">www.eudora.com</A>.
(The current version is now 2.5, but this piece of code hasn't changed.)
<P>
These changes log a message like this each time a user logs in:
<P>
<TT>POP login for "</TT><I>username</I><TT>" at (</TT><I>remote.host.name</I><TT>) </TT><I>000.000.000.000</I><P>
The patches go at the end of the source file pop_pass.c.
<P>
<PRE>
*** 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));
</PRE>
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.
<P>
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 
<I>absolutely</I> necessary. 

<H3>Modify the sendmail.cf configuration file</H3>

Apply the following additions to your sendmail.cf file.
Note that the wide white spaces have to be tabs, not multiple
spaces, or sendmail will get very confused.
<PRE>
# At the top, with the other Class definitions:
F{LocalIP} /etc/mail/localip
F{BackupMX} /etc/mail/backupmx
K popauth hash /etc/mail/pophash

# At the bottom of the rulesets:
Scheck_rcpt
R&lt;$+ @ $=w &gt;                $@ OK  # if it is to domain delivered by us
R&lt;$+ @ $={BackupMX}&gt;        $@ OK  # if it is to domain we will backup MX for
R$+                        $: $(dequote "" $&amp;{client_addr} $) $| $1   # get addr
R0 $| $*                $@ OK                # OK if sendmail run locally 
R$={LocalIP}$* $| $*        $@ OK                # OK if from our address space
R$* $| $*                $: $(popauth $1 $)        # OK if from a POP-authed address
ROK                        $@ OK
R$*                        $#error $@ 5.5.0 $: "550 Relaying Denied: Authenticate with POP first."
</PRE>
<P>
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}.)
<P>
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.)

<H3>Maintaining the database of authorized relay IP addresses</H3>
First, create the directory /var/spool/popauth.
<P>
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.
<P>
Here are two versions of the script.
<P>
<B>Original script</B>
<P>
<PRE>
#!/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,"&gt;&gt;$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,"&gt;$popwatcherpidfile");
print PID "$$\n";
close(PID);
 
open(POPPER,"$TAIL -f $poppersyslog |") || die("Can't $TAIL -f $poppersyslog");
while(&lt;POPPER&gt;) {
   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,"&gt; $popauthspool/$ip");
          close(TEMP);
 
         open (OUT,"&gt;/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 &lt; 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);
}
</PRE>
<P>
<B>Spiffier script</B>
<P>
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.
<P>
<PRE>
#!/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(<PREV>) {
#       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(<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;
        # 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);
}
</PRE>
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:
<P>
<PRE>
# 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 &amp;
</PRE>
<P>
And that should be it!
<HR>
<ADDRESS>Neil Harkins &lt;nharkins@well.com&gt;</ADDRESS><BR>
<ADDRESS>John Levine &lt;johnl@abuse.net&gt;</ADDRESS>
<HR>
<BR>
(Also see an alternative implmentation of a
<A HREF="http://www.cynic.net/~cjs/computer/sendmail/poprelay.html">
POP3 Authenticated Relaying
</A> mechanism.)

</BODY>
</HTML>