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:
parent
48059c122c
commit
654179e8c8
@ -487,20 +487,30 @@ sub 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 {
|
||||
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} : "" );
|
||||
}
|
||||
|
||||
sub auth_ticket {
|
||||
my ($self, $ticket) = @_;
|
||||
$self->{_auth_ticket} = $ticket if $ticket;
|
||||
return (defined $self->{_auth_ticket} ? $self->{_auth_ticket} : "" );
|
||||
}
|
||||
|
||||
sub auth_mechanism {
|
||||
my ($self, $mechanism) = @_;
|
||||
$mechanism =~ s/[\r\n].*//s;
|
||||
$self->{_auth_mechanism} = $mechanism if $mechanism;
|
||||
$self->{_auth_mechanism} = lc($mechanism) if $mechanism;
|
||||
return (defined $self->{_auth_mechanism} ? $self->{_auth_mechanism} : "" );
|
||||
}
|
||||
|
||||
|
||||
sub fd {
|
||||
return shift->{fd};
|
||||
}
|
||||
|
@ -214,119 +214,4 @@ Please see the LICENSE file included with qpsmtpd for details.
|
||||
|
||||
=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;
|
||||
|
@ -26,6 +26,7 @@ my %return_codes = (
|
||||
DECLINED => 909,
|
||||
DONE => 910,
|
||||
CONTINUATION => 911,
|
||||
AUTH_PENDING => 912,
|
||||
);
|
||||
|
||||
use vars qw(@ISA @EXPORT);
|
||||
|
@ -15,9 +15,10 @@ use fields qw(
|
||||
hooks
|
||||
start_time
|
||||
cmd_timeout
|
||||
_auth
|
||||
_auth_user
|
||||
_auth_mechanism
|
||||
_auth_state
|
||||
_auth_ticket
|
||||
_auth_user
|
||||
_commands
|
||||
_config_cache
|
||||
_connection
|
||||
@ -158,6 +159,9 @@ sub process_cmd {
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
elsif ( $self->authenticated == AUTH_PENDING ) {
|
||||
return $self->auth_process($line);
|
||||
}
|
||||
else {
|
||||
# No such method - i.e. unrecognized command
|
||||
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $meth, @params);
|
||||
@ -315,7 +319,7 @@ sub end_of_data {
|
||||
}
|
||||
|
||||
# only true if client authenticated
|
||||
if ( defined $self->{_auth} and $self->{_auth} == OK ) {
|
||||
if ( $self->authenticated == OK ) {
|
||||
$header->add("X-Qpsmtpd-Auth","True");
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ use Qpsmtpd::Auth;
|
||||
use Qpsmtpd::Address ();
|
||||
|
||||
use Mail::Header ();
|
||||
use MIME::Base64;
|
||||
#use Data::Dumper;
|
||||
use POSIX qw(strftime);
|
||||
use Net::DNS;
|
||||
@ -48,6 +49,11 @@ sub dispatch {
|
||||
|
||||
$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}) {
|
||||
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd, @_);
|
||||
return $self->unrecognized_command_respond($rc, $msg, @_) unless $rc == CONTINUATION;
|
||||
@ -114,13 +120,13 @@ sub connect_respond {
|
||||
elsif ($rc != DONE) {
|
||||
my $greets = $self->config('smtpgreeting');
|
||||
if ( $greets ) {
|
||||
$greets .= " ESMTP";
|
||||
$greets .= " ESMTP";
|
||||
}
|
||||
else {
|
||||
$greets = $self->config('me')
|
||||
. " ESMTP qpsmtpd "
|
||||
. $self->version
|
||||
. " ready; send us your mail, but not your spam.";
|
||||
$greets = $self->config('me')
|
||||
. " ESMTP qpsmtpd "
|
||||
. $self->version
|
||||
. " ready; send us your mail, but not your spam.";
|
||||
}
|
||||
|
||||
$self->respond(220, $greets);
|
||||
@ -197,8 +203,8 @@ sub ehlo_respond {
|
||||
$self->transaction;
|
||||
|
||||
my @capabilities = $self->transaction->notes('capabilities')
|
||||
? @{ $self->transaction->notes('capabilities') }
|
||||
: ();
|
||||
? @{ $self->transaction->notes('capabilities') }
|
||||
: ();
|
||||
|
||||
# Check for possible AUTH mechanisms
|
||||
my %auth_mechanisms;
|
||||
@ -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 {
|
||||
my ( $self, $arg, @stuff ) = @_;
|
||||
my ( $self, $mechanism, $prekey ) = @_;
|
||||
|
||||
#they AUTH'd once already
|
||||
return $self->respond( 503, "but you already said AUTH ..." )
|
||||
if ( defined $self->{_auth}
|
||||
and $self->{_auth} == OK );
|
||||
if ( $self->authenticated == OK );
|
||||
return $self->respond( 503, "AUTH not defined for 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 {
|
||||
@ -541,8 +678,8 @@ sub data_respond {
|
||||
# FIXME - call plugins to work on just the header here; can
|
||||
# save us buffering the mail content.
|
||||
|
||||
# Save the start of just the body itself
|
||||
$self->transaction->set_body_start();
|
||||
# Save the start of just the body itself
|
||||
$self->transaction->set_body_start();
|
||||
|
||||
}
|
||||
|
||||
@ -564,8 +701,9 @@ sub data_respond {
|
||||
$self->transaction->header($header);
|
||||
|
||||
my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP";
|
||||
my $authheader = (defined $self->{_auth} and $self->{_auth} == OK) ?
|
||||
"(smtp-auth username $self->{_auth_user}, mechanism $self->{_auth_mechanism})\n" : "";
|
||||
my $authheader = ($self->authenticated == OK)
|
||||
? "(smtp-auth username $self->auth_user, mechanism $self->auth_mechanism)\n"
|
||||
: "";
|
||||
|
||||
$header->add("Received", "from ".$self->connection->remote_info
|
||||
." (HELO ".$self->connection->hello_host . ") (".$self->connection->remote_ip
|
||||
|
Loading…
Reference in New Issue
Block a user