Working AUTH support in PollServer mode. All AUTH code moved to

SMTP.pm (the Auth.pm POD will get renamed to README.authentication).


git-svn-id: https://svn.perl.org/qpsmtpd/trunk@605 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
John Peacock 2006-01-26 21:31:05 +00:00
parent 48059c122c
commit 654179e8c8
5 changed files with 176 additions and 138 deletions

View File

@ -487,17 +487,27 @@ sub size_threshold {
return $Size_threshold; return $Size_threshold;
} }
sub authenticated {
my ($self, $state) = @_;
$self->{_auth_state} = $state if $state;
return (defined $self->{_auth_state} ? $self->{_auth_state} : 0);
}
sub auth_user { sub auth_user {
my ($self, $user) = @_; my ($self, $user) = @_;
$user =~ s/[\r\n].*//s;
$self->{_auth_user} = $user if $user; $self->{_auth_user} = $user if $user;
return (defined $self->{_auth_user} ? $self->{_auth_user} : "" ); return (defined $self->{_auth_user} ? $self->{_auth_user} : "" );
} }
sub auth_ticket {
my ($self, $ticket) = @_;
$self->{_auth_ticket} = $ticket if $ticket;
return (defined $self->{_auth_ticket} ? $self->{_auth_ticket} : "" );
}
sub auth_mechanism { sub auth_mechanism {
my ($self, $mechanism) = @_; my ($self, $mechanism) = @_;
$mechanism =~ s/[\r\n].*//s; $self->{_auth_mechanism} = lc($mechanism) if $mechanism;
$self->{_auth_mechanism} = $mechanism if $mechanism;
return (defined $self->{_auth_mechanism} ? $self->{_auth_mechanism} : "" ); return (defined $self->{_auth_mechanism} ? $self->{_auth_mechanism} : "" );
} }

View File

@ -214,119 +214,4 @@ Please see the LICENSE file included with qpsmtpd for details.
=cut =cut
package Qpsmtpd::Auth;
use Qpsmtpd::Constants;
use MIME::Base64;
sub e64
{
my ($arg) = @_;
my $res = encode_base64($arg);
chomp($res);
return($res);
}
sub SASL {
# $DB::single = 1;
my ( $session, $mechanism, $prekey ) = @_;
my ( $user, $passClear, $passHash, $ticket );
$mechanism = lc($mechanism);
if ( $mechanism eq "plain" ) {
if (!$prekey) {
$session->respond( 334, "Please continue" );
$prekey= <>;
}
( $passHash, $user, $passClear ) = split /\x0/,
decode_base64($prekey);
}
elsif ($mechanism eq "login") {
if ( $prekey ) {
($passHash, $user, $passClear) = split /\x0/, decode_base64($prekey);
}
else {
$session->respond(334, e64("Username:"));
$user = decode_base64(<>);
#warn("Debug: User: '$user'");
if ($user eq '*') {
$session->respond(501, "Authentification canceled");
return DECLINED;
}
$session->respond(334, e64("Password:"));
$passClear = <>;
$passClear = decode_base64($passClear);
#warn("Debug: Pass: '$pass'");
if ($passClear eq '*') {
$session->respond(501, "Authentification canceled");
return DECLINED;
}
}
}
elsif ( $mechanism eq "cram-md5" ) {
# rand() is not cryptographic, but we only need to generate a globally
# unique number. The rand() is there in case the user logs in more than
# once in the same second, of if the clock is skewed.
$ticket = sprintf( "<%x.%x\@" . $session->config("me") . ">",
rand(1000000), time() );
# We send the ticket encoded in Base64
$session->respond( 334, encode_base64( $ticket, "" ) );
my $line = <>;
chop($line);
chop($line);
if ( $line eq '*' ) {
$session->respond( 501, "Authentification canceled" );
return DECLINED;
}
( $user, $passHash ) = split( ' ', decode_base64($line) );
}
else {
$session->respond( 500, "Unrecognized authentification mechanism" );
return DECLINED;
}
# try running the specific hooks first
my ( $rc, $msg ) =
$session->run_hooks( "auth-$mechanism", $mechanism, $user, $passClear,
$passHash, $ticket );
# try running the polymorphous hooks next
if ( !$rc || $rc == DECLINED ) {
( $rc, $msg ) =
$session->run_hooks( "auth", $mechanism, $user, $passClear,
$passHash, $ticket );
}
if ( $rc == OK ) {
$msg = "Authentication successful for $user" .
( defined $msg ? " - " . $msg : "" );
$session->respond( 235, $msg );
$session->connection->relay_client(1);
$session->log( LOGINFO, $msg );
$session->auth_user($user);
$session->auth_mechanism($mechanism);
return OK;
}
else {
$msg = "Authentication failed for $user" .
( defined $msg ? " - " . $msg : "" );
$session->respond( 535, $msg );
$session->log( LOGERROR, $msg );
return DENY;
}
}
# tag: qpsmtpd plugin that sets RELAYCLIENT when the user authentifies
1; 1;

View File

@ -26,6 +26,7 @@ my %return_codes = (
DECLINED => 909, DECLINED => 909,
DONE => 910, DONE => 910,
CONTINUATION => 911, CONTINUATION => 911,
AUTH_PENDING => 912,
); );
use vars qw(@ISA @EXPORT); use vars qw(@ISA @EXPORT);

View File

@ -15,9 +15,10 @@ use fields qw(
hooks hooks
start_time start_time
cmd_timeout cmd_timeout
_auth
_auth_user
_auth_mechanism _auth_mechanism
_auth_state
_auth_ticket
_auth_user
_commands _commands
_config_cache _config_cache
_connection _connection
@ -158,6 +159,9 @@ sub process_cmd {
} }
return $resp; return $resp;
} }
elsif ( $self->authenticated == AUTH_PENDING ) {
return $self->auth_process($line);
}
else { else {
# No such method - i.e. unrecognized command # No such method - i.e. unrecognized command
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $meth, @params); my ($rc, $msg) = $self->run_hooks("unrecognized_command", $meth, @params);
@ -315,7 +319,7 @@ sub end_of_data {
} }
# only true if client authenticated # only true if client authenticated
if ( defined $self->{_auth} and $self->{_auth} == OK ) { if ( $self->authenticated == OK ) {
$header->add("X-Qpsmtpd-Auth","True"); $header->add("X-Qpsmtpd-Auth","True");
} }

View File

@ -12,6 +12,7 @@ use Qpsmtpd::Auth;
use Qpsmtpd::Address (); use Qpsmtpd::Address ();
use Mail::Header (); use Mail::Header ();
use MIME::Base64;
#use Data::Dumper; #use Data::Dumper;
use POSIX qw(strftime); use POSIX qw(strftime);
use Net::DNS; use Net::DNS;
@ -48,6 +49,11 @@ sub dispatch {
$self->{_counter}++; $self->{_counter}++;
if ( $self->authenticated == AUTH_PENDING ) {
# must be in the middle of prompting for auth parameters
return $self->auth_process($cmd,@_);
}
if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) { if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) {
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd, @_); my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd, @_);
return $self->unrecognized_command_respond($rc, $msg, @_) unless $rc == CONTINUATION; return $self->unrecognized_command_respond($rc, $msg, @_) unless $rc == CONTINUATION;
@ -229,17 +235,148 @@ HOOK: foreach my $hook ( keys %{$self->{hooks}} ) {
} }
} }
sub e64
{
my ($arg) = @_;
my $res = encode_base64($arg);
chomp($res);
return($res);
}
sub auth { sub auth {
my ( $self, $arg, @stuff ) = @_; my ( $self, $mechanism, $prekey ) = @_;
#they AUTH'd once already #they AUTH'd once already
return $self->respond( 503, "but you already said AUTH ..." ) return $self->respond( 503, "but you already said AUTH ..." )
if ( defined $self->{_auth} if ( $self->authenticated == OK );
and $self->{_auth} == OK );
return $self->respond( 503, "AUTH not defined for HELO" ) return $self->respond( 503, "AUTH not defined for HELO" )
if ( $self->connection->hello eq "helo" ); if ( $self->connection->hello eq "helo" );
return $self->{_auth} = Qpsmtpd::Auth::SASL( $self, $arg, @stuff ); # $DB::single = 1;
$self->auth_mechanism($mechanism);
$self->authenticated(AUTH_PENDING);
if ( $prekey ) { # easy single step
unless ( $mechanism =~ /^(plain|login)$/i ) {
# must be plain or login
$self->respond( 500, "Unrecognized authentification mechanism" );
return DECLINED;
}
my ($passHash, $user, $passClear) = split /\x0/,decode_base64($prekey);
# we have all of the elements ready to go now
if ( $mechanism =~ /login/i ) {
$self->auth_user($user);
return $self->auth_process(e64($passClear));
}
else {
return $self->auth_process($prekey);
}
}
else {
if ( $mechanism =~ /plain/i ) {
$self->respond( 334, "Please continue" );
}
elsif ( $mechanism =~ /login/i ) {
$self->respond( 334, e64("Username:") );
}
elsif ( $mechanism =~ /cram-md5/i ) {
# rand() is not cryptographic, but we only need to generate a globally
# unique number. The rand() is there in case the user logs in more than
# once in the same second, or if the clock is skewed.
my $ticket = sprintf( "<%x.%x\@" . $self->config("me") . ">",
rand(1000000), time() );
# Store this for later
$self->auth_ticket($ticket);
# We send the ticket encoded in Base64
$self->respond( 334, encode_base64( $ticket, "" ) );
}
}
return DECLINED;
}
sub auth_process {
my ($self, $line) = @_;
my ( $user, $passClear, $passHash, $ticket, $mechanism );
# do this once here
$mechanism = $self->auth_mechanism;
$user = $self->auth_user;
$ticket = $self->auth_ticket;
if ( $mechanism eq 'plain' ) {
( $passHash, $user, $passClear ) = split /\x0/,
decode_base64($line);
}
elsif ( $mechanism eq 'login' ) {
if ( $user ) {
# must be getting the password now
$passClear = decode_base64($line);
}
else {
# must be getting the user now
$user = decode_base64($line);
$self->auth_user($user);
$self->respond(334, e64("Password:"));
}
}
elsif ( $mechanism eq "cram-md5" ) {
$line =~ tr/[\r\n]//d; # cannot simply chomp CRLF
( $user, $passHash ) = split( ' ', decode_base64($line) );
}
else {
$self->respond( 500, "Unrecognized authentification mechanism" );
return DECLINED;
}
if ($user eq '*') {
$self->respond(501, "Authentification canceled");
return DECLINED;
}
# check to see if we can proceed with the hooks
if ( $user and ( $passClear or $passHash ) ) {
# try running the specific hooks first
my ( $rc, $msg ) =
$self->run_hooks( "auth-$mechanism",
$mechanism, $user, $passClear,
$passHash, $ticket );
# try running the polymorphous hooks next
if ( !$rc || $rc == DECLINED ) {
( $rc, $msg ) =
$self->run_hooks( "auth", $mechanism, $user, $passClear,
$passHash, $ticket );
}
return $self->auth_respond($rc, $msg, $mechanism, $user)
unless $rc == CONTINUATION;
}
else {
return CONTINUATION;
}
}
sub auth_respond {
my ($self, $rc, $msg, $mechanism, $user) = @_;
if ( $rc == OK ) {
$msg = "Authentication successful for $user" .
( defined $msg ? " - " . $msg : "" );
$self->respond( 235, $msg );
$self->connection->relay_client(1);
$self->log( LOGINFO, $msg );
$self->authenticated(OK);
return OK;
}
else {
$msg = "Authentication failed for $user" .
( defined $msg ? " - " . $msg : "" );
$self->respond( 535, $msg );
$self->log( LOGERROR, $msg );
return DENY;
}
} }
sub mail { sub mail {
@ -564,8 +701,9 @@ sub data_respond {
$self->transaction->header($header); $self->transaction->header($header);
my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP"; my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP";
my $authheader = (defined $self->{_auth} and $self->{_auth} == OK) ? my $authheader = ($self->authenticated == OK)
"(smtp-auth username $self->{_auth_user}, mechanism $self->{_auth_mechanism})\n" : ""; ? "(smtp-auth username $self->auth_user, mechanism $self->auth_mechanism)\n"
: "";
$header->add("Received", "from ".$self->connection->remote_info $header->add("Received", "from ".$self->connection->remote_info
." (HELO ".$self->connection->hello_host . ") (".$self->connection->remote_ip ." (HELO ".$self->connection->hello_host . ") (".$self->connection->remote_ip