#!/usr/bin/perl -w
#
# /bin/pop3switch - switches the authorization/popup of POP3 users between two
#		    different qmail-pop3d servers running in the same machine
#
# SYNOPSIS
#    /bin/pop3switch --server1-exec "COMMANDLINE1" --server1-user "USERNAME1" \
#		     --server1-domainslistfile "PATH_TO_FILE" \
#		     --server2-exec "COMMANDLINE2" --server2-user "USERNAME2"
#
# DESCRIPTION
#    This program is designed to help in the situation when a given host
#    has two different Qmail servers running, we gonna call them "server1" 
#    and "server2". For example, server1 could be a qmail-ldap
#    server, and server2 could be an installation of Qmail + Vpopmail.
#    pop3switch provides a means for authenticating and getting messages
#    of the POP3 users from both servers.
#    Also, it will execute COMMANDLINEX under the UserID and GroupID of
#    the user specified by the --serverX-user option.
#
# HOW DOES IT DISTINGUISH WHICH SERVER OWNS EACH ACCOUNT
#    When the mail server receives a pop3 client connecting on port 110, tcpserver 
#    will be listening on this port, then it will invoke qmail-popup which, in turn, 
#    will interact with the client and catch the user/password information.
#    qmail-popup will call pop3switch and pass this information to it. 
#    Finally, to discover if this POP3 user pertains to server1 or server2, 
#    pop3switch will check if the user's domain is listed in the file set 
#    with the --server1-domainslistfile option.
#    If the user's domain is not listed in this file, pop2switch assume this 
#    user pertains to server2.
#    When it decides which server to use, pop3switch gonna exec the program specified
#    by --serverX-exec option.
#   
# HOW TO USE IT 
# Call this program from the /service/qmail-pop3d/run file, in substitution of 
# the authorization program you're using there. Bellow, see an example of
# "tcpserver" calling pop3switch to switch between a Qmail-ldap pop3d and
# a Qmail + Vpopmail pop3d:
#
#   tcpserver -u 0 -g 0 0 110 \
#   /var/qmail/bin/qmail-popup $DOMAINNAME \
#   /bin/pop3switch \
#   --server1-exec "/var/qmldap/bin/auth_pop /var/qmldap/bin/qmail-pop3d Maildir" \
#   --server1-user "qmldapv" \
#   --server1-domainslistfile "/var/qmldap/control/ldapdomains" \
#   --server2-exec "/var/vpopmail/bin/vchkpw /var/qmail/bin/qmail-pop3d Maildir" \
#   --server2-user "vpopmail"
#	
# ERROR MESSAGES
# This program prints its error messages to a file called /tmp/pop3switch_error. It can't 
# just print them to standard out since it will be connected to the network when this program
# is executed.
#
# KNOWN LIMITATIONS
# - pop3switch expects to receive an email in the format: "username@somedomain". It won't
# work with e-mails in different formats.
# - To increase performance, pop3switch won't check the validity of the arguments
# it receives. Check them yourself carefully.
#
# SECURITY LIMITATIONS
# - pop3switch must be run as root. 
# - When it passes the username/password to the pop3 server executable, this information 
# can be seen with the "ps -ef" command.
#
# DEPENDENCIES
# DJ Bernstein's Daemontools package is required for the use of 'setuidgid' executable
#
# AUTHOR 
#    Bruno Negrão - vpopmail@engepel.com.br
#
# LAST MODIFIED
#    10/05/2004
##########################################################################################
use strict;

# subroutine prototypes
sub my_die($); # prints the error message in /tmp/pop3switch_error file and dies.

# setuidgid's path
my $SETUIDGID = "/usr/local/bin/setuidgid";

# check for its existence
my_die "$0: ERROR: cannot find $SETUIDGID. If you don't have daemontools installed in your" .
    " system, install it. If it's installed in a path different than '/usr/local/bin'," .
    " then edit the pop3switch's '\$SETUIDGID' variable to reflect the right path.\n"
    if (! -x $SETUIDGID);

# getting the arguments
my %ARG = @ARGV;

# Option Variables
my $SERVER1_EXEC = $ARG{"--server1-exec"};
my $SERVER2_EXEC = $ARG{"--server2-exec"};
my $SERVER1_USER = $ARG{"--server1-user"};
my $SERVER2_USER = $ARG{"--server2-user"};
my $SERVER1_DOMAINSLISTFILE = $ARG{"--server1-domainslistfile"};

# check for the passed arguments validity
if ( (my $error = checkAllArguments()) ) {
    my_die "$error";
}

# 1 - We read the user/password input from qmail-popup in the
# descriptor 3 and store it in a variable
my $qmail_popup_input;	# input read from qmail-popup
open(FH, "<&=3") or my_die "$0: ERROR: I couldn't open descriptor 3 for reading.\n";
binmode (FH);
$qmail_popup_input = <FH>;
close FH;

# 2 - Now we parse this input, breaking it in user, password and
# timestamp (these fields are separated by NUL characters)
my ($user, $pwd, $time) = ( split(/\x00/, $qmail_popup_input) );

# 3 - Now we extract the domain name from the username
my $domain = (split(/\@/, $user))[1];

# 4 - Check if this domain is in server1's database. If it is,
# sets the variable $IS_DOMAIN_IN_SERVER1 to 1. For doing this, we gonna
# search for this domain in the $SERVER1_DOMAINSLISTFILE
my $IS_DOMAIN_IN_SERVER1 = 0; # says if $domain is in the LDAP database
open (LDF, "<$SERVER1_DOMAINSLISTFILE") or my_die "$0: ERROR: I couldn't open the file ".
    "$SERVER1_DOMAINSLISTFILE for reading.\n";
while (<LDF>) {
    chomp;                  # no newline
    s/#.*//;                # no comments
    s/^\s+//;               # no leading white
    s/\s+$//;               # no trailing white
    next unless length;
    if ( /^$domain$/ ) {
	$IS_DOMAIN_IN_SERVER1 = 1;
	last;
    }
}

# 5 - Escaping the <, >, and \0 caracters:
$time =~ s/([<>])/\\$1/g;   # escaping $time
# escaping qmail-popup input to feed shell:
my $qmail_popup_input_escaped = $user . '\\\\000' . $pwd . '\\\\000' . $time . '\\\\000';

# 6 - Choose the apropriate command line to execute
my $CMD_LINE = "$SETUIDGID ";
$CMD_LINE .= ($IS_DOMAIN_IN_SERVER1 ? 
    "$SERVER1_USER $SERVER1_EXEC"   : 
    "$SERVER2_USER $SERVER2_EXEC");

# 7 - exec the AUTH program, taking care to maintain the descriptors 0 and 1
# connected to the network and to read the user/pwd info from descriptor 3
exec (qq(exec 4<&0; echo -ne $qmail_popup_input_escaped | $CMD_LINE 3<&0 0<&4 4<&-));

sub my_die($) {
    my $msg = shift;
    my $error_file = "/tmp/pop3switch_error";
    open (ERROR, "> $error_file");
    print ERROR $msg;
    die "\n";
}

sub checkAllArguments {
    sub error {
	my $opt = shift;
	return "$0: ERROR: The option $opt is not correctly set up.\n";
    }	
    unless ( $SERVER1_EXEC ) {
	return error("--server1-exec");
    }
    unless ( $SERVER2_EXEC ) {
	return error("--server2-exec");
    }
    unless ( $SERVER1_USER ) {
	return error("--server1-user");
    }
    unless ( $SERVER2_USER ) {
	return error("--server2-user");
    }
    unless ( $SERVER1_DOMAINSLISTFILE ) {
	return error("--server1-domainslistfile");
    }
    return "";
}
