Courier authlib pipeprog using libhome

Courier authlib, the authentication framework from courier projects (courier-pop, courier-imap, sqwebmail…), can be set up using authpipe mode, where authentication can be done externaly.

Protocol of exchange is described in courier-mta's website in the section "The authpipe protocol".

Libhome is a nice authentication library that emulates use of getpwnam and can talk to mysql, ldap backends. And, libhome has a perl module (Home available in the libhome sources).

So, here is a perl script which can act as an authProg for courier-authlib. It uses libhome to actually perform authentication.

It implements LOGIN, CRAM-MD5 and CRAM-SHA1 methods.

Source code.

#!/usr/bin/perl -w
# $Id: authProg.pl,v 1.5 2007/08/03 13:57:02 cbellot Exp $
#   
# Module d'authentification externe pour courier-authdaemon
# en mode authpipe
#
# http://www.courier-mta.org/authlib/README_authlib.html
#
 
use Home;
use MIME::Base64;
use Digest::MD5 qw(md5 md5_hex);
use Digest::SHA1 qw(sha1 sha1_hex);
 
my %userinfo;
 
# Retrieve data via libhome
sub getuserinfo($$) {
    my $service = shift;
    my $user = shift;
    # Look up data for this account
    &Home::setpwtag($service);
    my ($name,$passwd,$uid,$gid, $quota,$comment,$gcos,$dir,$shell, $expire) =
        &Home::getpwnam($user);
    %userinfo = (
        name    => $name,
        passwd  => $passwd,
        uid     => $uid,
        gid     => $gid,
        quota   => $quota,
        dir     => $dir );
}   
 
sub printuserinfo() {
    # Return retrieved data
    if (!defined($userinfo{'name'}) || $userinfo{'name'} eq "") {
        print "FAIL\n";
        return;
    }
    #print "NAME=$userinfo{'name'}\n";
    #print "USERNAME=$userinfo{'name'}\n";
    print "ADDRESS=$userinfo{'name'}\n";
    print "PASSWD2=$userinfo{'passwd'}\n";
    print "UID=$userinfo{'uid'}\n";
    print "GID=$userinfo{'gid'}\n";
    print "HOME=$userinfo{'dir'}\n";
    #print "MAILDIR=$userinfo{'dir'}/Maildir\n";
    print "QUOTA=$userinfo{'quota'}S\n";
    print "OPTIONS=\n";
    print ".\n";
}
 
 
# MAIN
 
$|=1;
 
print STDERR "libhome compatible authProg module starting\n";
 
while (<>)
{
    if (/^PRE (\S+) (\S+) (.*)$/) {
        # Request type PRE
        # $1=. $2=service $3=uid
        &getuserinfo($2, $3);
        &printuserinfo();
    }
    elsif (/^AUTH (\d+)$/ && read(STDIN,$buf,$1) == $1) {
        # Request type AUTH
 
        # AUTH method LOGIN
        if ($buf =~ /^(.*)\n(login)\n(.*)\n(.*)$/) {
            # $1=service, $2=login, $3=user, $4=password
            &getuserinfo($1,$3);
            if ($userinfo{'passwd'} ne $4) {
                print "FAIL\n";
                next;
            }
            &printuserinfo();
            next;
        }
        # AUTH method CRAM-MD5
        if ($buf =~ /^(.*)\n(cram-md5)\n(.*)\n(.*)$/) {
            # $1=service, $2=cram-md5, $3=challenge, $4=response
            my $authservice = $1;
            my $challenge = decode_base64($3);
            my $response = decode_base64($4);
            $response =~ /(.*) (.*)/;
            my $username = $1;
            my $digest = $2;
            &getuserinfo($authservice,$username);
            my $digest2 = md5_hex(($userinfo{'passwd'} ^ "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\").md5(($userinfo{'passwd'} ^ "6666666666666666666666666666666666666666666666666666666666666666").$challenge));
            if ($digest ne $digest2) {
                print "FAIL\n";
                next;
            }
            &printuserinfo();
            next;
        }
        # AUTH method CRAM-SHA1
        if ($buf =~ /^(.*)\n(cram-sha1)\n(.*)\n(.*)$/) {
            # $1=service, $2=cram-sha1, $3=challenge, $4=response
            my $authservice = $1;
            my $challenge = decode_base64($3);
            my $response = decode_base64($4);
            $response =~ /(.*) (.*)/;
            my $username = $1;
            my $digest = $2;
            &getuserinfo($authservice,$username);
            my $digest2 = sha1_hex(($userinfo{'passwd'} ^ "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\").sha1(($userinfo{'passwd'} ^ "6666666666666666666666666666666666666666666666666666666666666666").$challenge));
            if ($digest ne $digest2) {
                print "FAIL\n";
                next;
            }
            &printuserinfo();
            next;
        }
        # AUTH method CRAM-SHA256
 
    }
    elsif (/^PASSWD (.*?)\t(.*?)\t(.*?)\t(.*?)$/) {
        # Request type PASSWD
        # Not implemented
        print "FAIL\n";
        next;
    }
    elsif (/^ENUMERATE$/) {
        # Request type ENUMERATE
        # Not implemented
        print ".\n";
        next;
    }
    else {
        print "FAIL\n";
    }
}