#!/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