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,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} : "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -114,13 +120,13 @@ sub connect_respond {
|
|||||||
elsif ($rc != DONE) {
|
elsif ($rc != DONE) {
|
||||||
my $greets = $self->config('smtpgreeting');
|
my $greets = $self->config('smtpgreeting');
|
||||||
if ( $greets ) {
|
if ( $greets ) {
|
||||||
$greets .= " ESMTP";
|
$greets .= " ESMTP";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$greets = $self->config('me')
|
$greets = $self->config('me')
|
||||||
. " ESMTP qpsmtpd "
|
. " ESMTP qpsmtpd "
|
||||||
. $self->version
|
. $self->version
|
||||||
. " ready; send us your mail, but not your spam.";
|
. " ready; send us your mail, but not your spam.";
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->respond(220, $greets);
|
$self->respond(220, $greets);
|
||||||
@ -197,8 +203,8 @@ sub ehlo_respond {
|
|||||||
$self->transaction;
|
$self->transaction;
|
||||||
|
|
||||||
my @capabilities = $self->transaction->notes('capabilities')
|
my @capabilities = $self->transaction->notes('capabilities')
|
||||||
? @{ $self->transaction->notes('capabilities') }
|
? @{ $self->transaction->notes('capabilities') }
|
||||||
: ();
|
: ();
|
||||||
|
|
||||||
# Check for possible AUTH mechanisms
|
# Check for possible AUTH mechanisms
|
||||||
my %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 {
|
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 {
|
||||||
@ -541,8 +678,8 @@ sub data_respond {
|
|||||||
# FIXME - call plugins to work on just the header here; can
|
# FIXME - call plugins to work on just the header here; can
|
||||||
# save us buffering the mail content.
|
# save us buffering the mail content.
|
||||||
|
|
||||||
# Save the start of just the body itself
|
# Save the start of just the body itself
|
||||||
$self->transaction->set_body_start();
|
$self->transaction->set_body_start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user