[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[tpop3d-discuss] auth-perl example config script (was: new installation)
From: |
Dave Baker |
Subject: |
[tpop3d-discuss] auth-perl example config script (was: new installation) |
Date: |
Wed, 05 Jun 2002 21:59:56 -0400 |
User-agent: |
Mutt/1.3.28i |
On Wed, Jun 05, 2002 at 03:02:24PM +0100, Chris Lightfoot wrote:
> > If anyone is interested in more examples of perl_auth I'd be happy to
> > share what I have.
>
> If you have some which are of general applicability, I'd
> be gratfeul if you would let me include them in the
> distribution.
>
Actually I misspoke, since I only have *one*. I don't know if you'd
consider it generally applicable or not. For that matter, I also don't
know if you'd call it bug free, but I've tried to be careful. I also have
an exim hook in here which I'm using for SMTP auth along with the example
usage.
The code is still fairly fresh, so has some a certain amount of
housecleaning left to do.
-------- 8< cut here 8< --------
#!/usr/bin/perl -w -T
#
# Sample smtp/pop3 authentication code to look up in a qpopper style
# database file. Expects berkeley db file, will only work with the
# version(s) of db that your perl DB_File can talk to.
#
# (c) 2002 Dave Baker <address@hidden>
#
# Can be distributed and modified - I'd appreciate the credit if you do.
# This is still a work in progress. Bugs may exist. Yadda Yadda Yadda.
#
#
# ***********************************************************************
# BE CAREFUL - this code may get executed with root privs
# ***********************************************************************
#
#
# Mail authentication extensions, written in perl. Single file shared
# between exim and tpop3d (both have libperl hooks), although each one
# only uses it's own functions.
#
# If there are syntax errors in this file, tpop3d will only report
# "Undefined subroutine" when it tries to call it's function instead
# of reporting the real problem. I believe exim behaves slightly better
# in this regard.
#
# Even though I no longer use qpopper, I maintain a semblence of
# qpopper compatability with the "xor 0xff" obfuscation. The passwords
# are needed in plaintext for both cram-md5 and apop authentication so
# it makes sense to help avoid accidental exposure within the db file.
#
# This file is 'loaded' with a perl-hook in the config file that reads:
# do '/etc/mail/mail.auth.pl';
#
# 2002-06-01 Dave Baker "exim.pl" created
# 2002-06-03 Dave Baker "tpop3d.pl" created
# 2002-06-04 Dave Baker Merged together into "mail.auth.pl"
#
#
# NOTES:
# - Still need to generalize read/write interface to avoid code
# duplication.
# - Need to write hooks to also replace 'qpopauth' to allow users
# to set their own passwords, and passwords for the virtual domains
# they 'own'
#
# - Need to re-audit the code for security oopsies.
#
# Required modules. If significantly large modules are needed by only
# some of the functions, they can be 'require'd instead of forcing all
# applications to load them into memory.
#
use strict;
use DB_File;
use Digest::MD5;
# constants
use vars qw/ $authdb /;
$authdb = "/etc/mail/mail.auth.db";
# Exim: cram_md5()
#
# Takes username, and looks it up in our configured (constant) auth
# berkeley DB file. If found, password is deobfuscated and returned.
# Otherwise 'undef' is returned.
#
# Example usage:
# # in main configuration settings
# perl_startup = do '/etc/mail/mail.auth.pl'
#
# # in authentication configuration settings
# cram_md5:
# driver = cram_md5
# public_name = CRAM-MD5
# server_secret = ${perl{cram_md5}{$1}}
#
sub cram_md5() {
# Some exim's will pass a 'zeroth' dummy parameter that needs to be ignored
# my ($dummy, $user) = @_;
my ($user) = @_;
my $password = undef;
# undef is plain 'fail' - we don't want to give hints as to filename
#die "DEBUG: file $file not there" unless -e $file;
return undef unless -e $authdb;
my $dbh = tie my %DB, 'DB_File', $authdb, O_RDONLY, 0660, $DB_HASH;
#die "DEBUG: file $authdb not opened $!" unless $dbh;
return undef unless $dbh;
# Install database filters - needed to handle \0 line endings.
# Note that the value field may have multiple \0 on the end
#
$dbh->filter_fetch_key ( sub { s/\0$// } ) ;
$dbh->filter_store_key ( sub { $_ .= "\0" } ) ;
$dbh->filter_fetch_value( sub { s/\0+$// } ) ;
$dbh->filter_store_value( sub { $_ .= "\0" } ) ;
# Need to xor stored password with 0xff, per qpopper's default
# obfuscation - could put this in the fetch_value filter, actually.
$password = join("", map { $_ ^ chr(255) } split (/|/, $DB{$user}));
# close up
undef $dbh;
untie %DB;
# Return password or undef if not found.
return $password ? $password : undef;
}
# tpop3d_apop()
#
# Given APOP login credentials, look up password in our mail.auth.db
# and return accordingly.
#
# Example usage (trivial):
# auth-perl-enable: yes
# auth-perl-start: do '/etc/mail/mail.auth.pl';
# auth-perl-apop: tpop3d_apop
#
sub tpop3d_apop {
my ($packet) = @_;
# logmsg doesn't get sent to user, so it's safe to include
# if the password file is there, a file, and readable bail out now
return { "result" => "NO", logmsg => "File not there or unreadable" }
unless (-e $authdb && -f $authdb && -r $authdb);
# start in known 'safe' position
my $password = undef;
# perform sanity checks on data collected from user
if ($packet->{user} =~ /[^a-zA-Z0-9_-]/o ||
$packet->{domain} =~ /[^a-zA-Z0-9.-]/o ||
$packet->{digest} =~ /[^a-fA-F0-9]/o) {
return { "result" => "NO", logmsg => "Unclean input data" };
}
# todo - also perform sanity checks on tpop3d provided data
my $user = $packet->{user};
my $domain = $packet->{domain};
my $mailbox = "/dev/null";
# given our user and domain information, work out what password
# we need to look up in the password file
# No domain - take 'raw' user.
if (! $packet->{domain}) {
$user = $packet->{user};
$mailbox = "/var/mail/$user";
}
# Have domain - use 'address@hidden'
# todo - lookup domain in /etc/mail/local-domains to make sure
# we should even be considering it, also grab "owner" data so we
# know what uid to run as
else {
$user = $packet->{user} . '@' . $packet->{domain};
$mailbox = "/var/mail/$packet->{domain}/$packet->{user}";
}
my $dbh = tie my %DB, 'DB_File', $authdb, O_RDONLY, 0660, $DB_HASH;
return { "result" => "NO", "logmsg" => "File $authdb failed with $!" }
unless $dbh;
# Install database filters - needed to handle \0 line endings.
# Note that the value field will have multiple \0 on the end
#
$dbh->filter_fetch_key ( sub { s/\0$// } ) ;
$dbh->filter_store_key ( sub { $_ .= "\0" } ) ;
$dbh->filter_fetch_value( sub { s/\0+$// } ) ;
$dbh->filter_store_value( sub { $_ .= "\0" } ) ;
# Need to xor stored password with 0xff, per qpopper's default
# obfuscation
$password = join("", map { $_ ^ chr(255) } split (/|/, $DB{$user}));
# close up
undef $dbh;
untie %DB;
# Fail if we have a blank password, or have no password
return { "result" => "NO", "logmsg" => "Blank or no password" }
unless $password;
# password not match?
if (lc($packet->{digest}) ne
lc(Digest::MD5::md5_hex($packet->{timestamp} . $password))) {
return { "result" => "NO", "logmsg" => "Password does not match" };
}
# we made it?!
# we have a couple of alternatives here for uid/gid. Either the main
# server can be running with root privs, in which case at this point
# we either drop to pop:mail or work out what end-user ID we should be
# running as and drop to that. Alternatively, the main server just runs
# as pop:mail (the mail group ownership is what we need to read/write
# the maildrop files) and setting this uid/gid will have no effect.
#
# As a todo, we should determine what UID/EUID we're running as and
# make a determination at that point as to what uid/gid to return to
# the calling program.
#
my ($uid, $gid) = ("pop", "mail");
my ($domain) = $packet->{domain} || "";
my ($mboxtype) = ("bsd");
return {
result => "YES",
uid => $uid,
gid => $gid,
domain => $domain,
mailbox => $mailbox,
mboxtype => $mboxtype,
};
}
# tpop3d_pass()
#
# Always returns failure since USER/PASS authentication is not supported.
# TODO: We actually want to avoid calling this procedure since that means
# the user *attempted* user/pass and thus sent their password over the
# network. tpop3d should be able to intercept the 'user' line and fail
# immediately.
#
sub tpop3d_pass {
return { "result" => "NO" };
}
# habitual
1;