#! /usr/bin/perl
# QMAIL2SMTP server
#
# This program waits on the defined port and takes user name and md5 hash
# passwords similarly to the RFC-1725 APOP command. If it matches the password
# defined for the user in the apop password file then it starts up MAILDIR2SMTP
# to the calling IP address.
#
# $Log: qsmtpd,v $
# Revision 1.15  1997/03/18 03:49:17  david
# Fixed to use standard qmail control file rather than Linux-specific one.
#
# Revision 1.14  1997/03/18 03:29:24  david
# Fixed the security-by-obscurity bug where anyone that could read the
# encrypted password on the server side could freely download e-mail.
# Extended the password control file to include the Maildir being delivered
# out of.
#
# Revision 1.13  1996/12/16 18:44:45  david
# Fixed to automatically determine Linux vs. Solaris.
# Fixed to eliminate duplicate e-mail by waiting synchronously for
# maildir2smtp to finish.
# Fixed to clean up defunct(zombie) processes.
# Also now reports maildir2smtp messages to client side.
#
# Revision 1.12  1996/12/11 03:05:03  david
# Fixed to wait forever after initial handshake so big files can
# be transmitted.
#
# Revision 1.11  1996/12/11 02:59:04  david
# Fixed to not put maildir2smtp in the background as that can cause
# duplicate e-mail if the script is called again while the first one
# is sending e-mail.
#
# Revision 1.10  1996/12/09 04:32:05  david
# Fixed to work with both Linux and Solaris.
#
# Revision 1.9  1996/12/08 02:56:38  david
# Fixed name of program logged to logfile.
#
# Revision 1.8  1996/12/08 02:55:19  david
# Forgot to debug error line!!!
#
# Revision 1.7  1996/12/08 02:54:01  david
# Forgot to debug two print lines.
#
# Revision 1.6  1996/12/08 02:43:11  david
# Completely rewrote to use APOP RFC-1725 password hashes.
# Also simplified the protocol.
#
# Revision 1.5  1996/11/25 23:50:50  david
# Added logging.
#
# Revision 1.4  1996/11/25 16:36:23  david
# First server code that should work.
#
# Revision 1.3  1996/11/25 16:32:26  david
# First shot at portable PERL code.
#

use Socket;
#use Sys::Hostname;

$debug = 0;
$DEFAULT_PORT = 1234;

$mach = `uname -s`; chop $mach;
if ( $mach eq "Linux" )
   { # Linux
   $MD5SUM_PATH="/usr/bin/md5sum";
   $ECHO="/bin/echo -n"; # Linux
   }
 else
   {
   # Solaris
   $ECHO="/usr/ucb/echo -n";
   $MD5SUM_PATH="/usr/local/bin/md5sum";
   }

( $port, $passfile ) = @ARGV;
$port = $DEFAULT_PORT unless $port;
$passfile = "/var/qmail/control/passwords" unless $passfile;

$myhost = `hostname`;
chop $myhost;
$sockaddr = 'S n a4 x8';

printf "System = $myhost\n" if $debug;

#($name, $aliases, $proto) = getprotobyname( 'tcp' );
#if ($port !~ /^\d_$/)
#   {
#   ($name, $aliases, $port) = getservbyport( $port, 'tcp' );
#   }

printf "Port = $port\n" if $debug;

$this = pack( $sockaddr, AF_INET, $port, "\0\0\0\0" );

select( NS ); $| = 1; select(STDOUT); # Unbuffered I/O

$proto = getprotobyname( 'tcp' );
socket( S, AF_INET, SOCK_STREAM, $proto) || die "socket: $!";
bind( S, $this) || die "bind: $!";
listen( S, 5 ) || die "connect: $!";

select( S ); $| = 1; select( STDOUT ); # Unbuffered I/O

for ( $con = 1; ; $con++ )
   {
   printf( "Listening for connection %d.....\n", $con ) if $debug; 
   ($addr = accept( NS, S ) ) || die $!;

   if ( ($child = fork()) == 0 )
      {
      ($af, $port, $inetaddr) = unpack( $sockaddr, $addr );
      @inetaddr = unpack( 'C4', $inetaddr);
      $inetaddr = join( '.', @inetaddr );
      printf "$con: $inetaddr\n" if $debug;

      $pid = $$;
      $time = time;
      printf "+OK <$pid.$time\@$myhost> QSMTPD (APOP-RFC1725)\n" if $debug;
      printf NS "+OK <$pid.$time\@$myhost> QSMTPD (APOP-RFC1725)\r\n";

      alarm( 180 );

      $_ = <NS>; chop; chop;
      printf "$_\n" if $debug;
      ($loginid, $inpass) = split( ' ' );
      #printf "Login ID = '$loginid' Input Pass='$inpass'\n" if $debug;

      #openlog( 'smtpsend', 'cons', 'user');
      #syslog( 'mail', "Connection from %s@%s", $loginid, $inetaddr);
      #closelog();

      open( LOG, "|logger -p mail.info -t qsmtpd" ) || die "Can't start logger.";
      printf( LOG "Connect from $loginid\@$inetaddr" );
      close LOG;

      if ( $loginid eq "" || $inpass eq "" )
         {
         printf NS "-ERR Bad Login\r\n";
         goto shut;
         }

      open( PASS, "<$passfile" ) || die "Can't open $passfile.";
      while ( ($_ = <PASS> ) && ($luser ne $loginid) )
         {
         chop;
         ($lhost, $luser, $lpass, $dir) = split ":";
         }
      close( PASS );

      if ( $luser ne $loginid )
         {
         printf NS "-ERR Bad Login\r\n";
         goto shut;
         }

      $md5clear = "<$pid.$time\@$myhost>" . $lpass;
      $md5pass  = &md5sum( $md5clear );

      printf "Name = $loginid, Passwd = $lpass, Maildir = $dir\n" if $debug;
      printf "Md5clear = $md5clear\n" if $debug;
      printf "Md5pass = $md5pass\n" if $debug;


      if ( $md5pass ne $inpass )
         {
         printf NS "-ERR Bad Login\r\n";
         printf "-ERR Bad Login\n" if $debug;
         goto shut;
         }


      alarm( 0 ); # Now wait forever....

      printf NS "+OK /var/qmail/bin/maildir2smtp $dir $loginid- $inetaddr $myhost\r\n";
      printf "+OK /var/qmail/bin/maildir2smtp $dir $loginid- $inetaddr $myhost\n" if $debug;
      open( PIPE,  "/var/qmail/bin/maildir2smtp $dir $loginid- $inetaddr $myhost 2>&1 |" );
      while ( <PIPE> )
         {
         chop;
         printf NS "$_\r\n";
         printf "$_\n" if $debug;
         }

      close( PIPE );

      printf NS "+FINISHED\r\n";
      printf "+FINISHED\n" if $debug;

      shut:
         {
         close( NS );
         }
      exit;
      }
    else
      {
      waitpid( -1, WNOHANG );
      }
   }

sub md5sum
{
$_ = `$ECHO "$_[0]" | $MD5SUM_PATH`;
chop;
($_, $junk) = split ' ';
return $_;
}
