squid_dbpg_auth



#!/usr/bin/perl
use strict;
use DBI;
use Getopt::Long;
use Pod::Usage;

use Digest::Perl::MD5;
my $debug=undef ;

$|=1;

=pod

=head1 NAME

db_auth.pl - Database auth helper for Squid

=cut

my $dsn = "DBI:Pg:database=postfix;host=database";
my $db_user = 'squid';
my $db_passwd = 'password';
my $db_table = "proxy_users";
my $db_usercol = "proxy_user">";
my $db_passwdcol = "password";
my $db_cond = undef; 
my $plaintext = 0;
my $persist = 0;

=pod

=head1 SYNOPSIS

db_auth.pl [options]

=head1 DESCRIPTOIN

This program verifies username & password to a database

=over 8

=item   B

Database DSN. Default "DBI:pg:database=squid"

=item   B

Database User

=item   B

Database password

=item   B

Database table. Default "passwd".

=item   B

Username column. Default "user".

=item   B

Password column. Default "password".

=item   B

Condition, defaults to enabled=1. Specify 1 or "" for no condition

=item   B

Database contains plain-text passwords

=item   B

Keep a persistent database connection open between queries. 

=back

=cut

GetOptions(
        'dsn=s' => \$dsn,
        'user=s' => \$db_user,
        'password=s' => \$db_passwd,
        'table=s' => \$db_table,
        'usercol=s' => \$db_usercol,
        'passwdcol=s' => \$db_passwdcol,
        'cond=s' => \$db_cond,
        'plaintext' => \$plaintext,
        'persist' => \$persist,
        );

my ($_dbh, $_sth);

sub close_db()
{
    return if !defined($_dbh);
    $_dbh->disconnect();
    undef $_dbh;
    undef $_sth;
}

sub open_db()
{
    return $_sth if defined $_sth;
    $_dbh = DBI->connect($dsn, $db_user, $db_passwd);
    if (!defined $_dbh) {
        warn ("Could not connect to $dsn\n");
        return undef;
    }
    my $sql = "SELECT $db_passwdcol FROM $db_table WHERE $db_usercol ilike ?" . ($db_cond ne "" ? " AND $db_cond" : "");
#print" $sql\n";
    $_sth = $_dbh->prepare($sql);
    #$_sth = $_dbh->prepare("SELECT $db_passwdcol FROM $db_table WHERE $db_usercol = ?" . ($db_cond ne "" ? " AND $db_cond" : "")) || die;
    return $_sth;
}


sub crampass {

# http://www.scconsult.com/bill/crampass.pl

# Copyright (c) 2008 William K. Cole. All rights reserved.
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice, this permission notice, and the following disclaimer
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

# crampass.pl: a simplistic script that replicates one function of the
#              dovecotpw tool that is included in the Dovecot mailstore 
#              server, turning a password given on the command line into
#              a string that Dovecot can use as a 'CRAM-MD5' auth string. 
#              This string consists of the two 16-byte state arrays that 
#              are sometimes referred to as "contexts" and are generated
#              by feeding 2 differently padded versions of the password 
#              into MD5 and recording the resulting (unfinalized) state
#              arrays. This is an intermediate step in the HMAC-MD5 
#              algorithm, and the resulting contexts can be used to 
#              initialize MD5 hash objects for authentication of any 
#              message. The contexts cannot be programmatically converted
#              back to the original password, and so are marginally safer
#              to store on a server than a plaintext password, which is
#              the only other option for use with the CRAM-MD5 auth 
#              mechanism. This script was written in response to a query 
#              on the Dovecot mailing list by Douglas Willcocks on 
#              2008-04-11 and ensuing discussion. It is intended as a
#              demo, not a production tool. It assumes that the immediate
#              user is neither malicious nor a fool. 
#
# References: RFC1321(MD5) RFC2195(CRAM-MD5) RFC2104(HMAC)
#             ID:draft-ietf-sasl-crammd5-09(CRAM-MD5 as SASL mech)  

        my $pass = shift;
        my $secret = $pass;

        my ($Ki, $Ko);
        my ($Ci, $Co);
        my ($innermd5, $outermd5);
        my ($innerhex, $outerhex);

        if (length $secret > 64) {
           $secret = Digest::Perl::MD5::md5($secret);
        }

        $Ki = $secret ^ (chr(0x36) x 64);
        $Ko = $secret ^ (chr(0x5c) x 64);

        $innermd5 = Digest::Perl::MD5->new;
        $innermd5->add($Ki);
        $Ci = pack 'V4', @{$innermd5->{_state}};

        $outermd5 = Digest::Perl::MD5->new;
        $outermd5->add($Ko);
        $Co = pack 'V4', @{$outermd5->{_state}};

        $innerhex=Digest::Perl::MD5::_encode_hex($Ci);
        $outerhex=Digest::Perl::MD5::_encode_hex($Co);

        return $outerhex . $innerhex;
}



sub check_password
{
    my ($password, $username, $key) = @_;

    return 1 if crypt($password, $key) eq $key;

    return 1 if $plaintext && $password eq $key;
return 1 if crampass($password) eq $key;

    return 0;
}

sub query_db($) {
    my ($user) = @_;
    my ($sth) = open_db() || return undef;
        print "looking for user $user \n" if (defined $debug);
    if (!$sth->execute($user)) {
        close_db();
        open_db() || return undef;
        $sth->execute($user) || return undef;;
    }
    return $sth;
}
my $status;

while (<>) {
    my ($user, $password) = split;
    $status = "ERR";
    $user =~ s/%(..)/pack("H*", $1)/ge;
    $password =~ s/%(..)/pack("H*", $1)/ge;
print "checking user $user password $password\n" if defined($debug);

    $status = "ERR database error";
    my $sth = query_db($user) || next;
    $status = "ERR unknown login";
    my $row = $sth->fetchrow_arrayref() || next;
    $status = "ERR login failure";
    next if (!check_password($password, $user, @$row[0] ));
    $status = "OK";
} continue {
    #close_db() if (!$persist);
    print $status . "\n";
}

=pod

=head1 COPYRIGHT

Copyright (C) 2007 Henrik Nordstrom <henrik@henriknordstrom.net>
This program is free software. You may redistribute copies of it under the
terms of the GNU General Public License version 2, or (at youropinion) any
later version.

=cut

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.