185 lines
4.0 KiB
Perl
185 lines
4.0 KiB
Perl
package Net::Server::Mail::ESMTP::plainAUTH;
|
|
use strict;
|
|
use base qw(Net::Server::Mail::ESMTP::Extension);
|
|
use MIME::Base64;
|
|
|
|
use vars qw( $VERSION );
|
|
$VERSION = '1.0';
|
|
|
|
# the following are required by nsme::extension
|
|
# but not documented :(
|
|
sub init
|
|
{
|
|
my ($self,$parent)=@_;
|
|
$self->{AUTH}=();
|
|
return $self;
|
|
}
|
|
|
|
# the smtp operations we add
|
|
sub verb
|
|
{
|
|
return ( [ 'AUTH' => \&handle_auth, ],);
|
|
}
|
|
|
|
# what to add to the esmtp capabilities response
|
|
sub keyword
|
|
{
|
|
return 'AUTH LOGIN PLAIN';
|
|
}
|
|
|
|
# what options to allow for mail from: auth
|
|
sub option
|
|
{
|
|
return (['MAIL', 'AUTH' => sub { return; }]);
|
|
}
|
|
|
|
# and the actual auth handler
|
|
sub handle_auth
|
|
{
|
|
my ($self,$args)=@_;
|
|
my ($method,$param);
|
|
$args=~/^(LOGIN|PLAIN)\s*(.*)$/ && (($method,$param)=($1,$2));
|
|
|
|
if ($self->{AUTH}->{active})
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "Authentication phases mixed up.");
|
|
return undef; # if rv given, server shuts conn!
|
|
}
|
|
elsif ($self->{AUTH}->{completed})
|
|
{
|
|
$self->reply(504,"Already authenticated.");
|
|
return undef;
|
|
}
|
|
elsif (!$method)
|
|
{
|
|
$self->reply(501,"Unknown authentication method.");
|
|
return undef;
|
|
}
|
|
|
|
$self->{AUTH}->{active}=$method;
|
|
|
|
if ($param eq '*')
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(501, "Authentication cancelled.");
|
|
return undef;
|
|
}
|
|
|
|
if ($method eq 'PLAIN')
|
|
{
|
|
if ($param) # plain: immediate with args
|
|
{
|
|
my (undef,$user,$pwd)=split(/\0/,decode_base64($param),3);
|
|
if (!$user)
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "5.7.8 Authentication failed.");
|
|
return undef;
|
|
}
|
|
return run_callback($self,$user,$pwd);
|
|
}
|
|
else # plain: or empty challenge and then response
|
|
{
|
|
$self->reply(334," ");
|
|
# undocumented but crucial: direct stuff to this method
|
|
$self->next_input_to(\&process_response);
|
|
return undef;
|
|
}
|
|
}
|
|
elsif ($method eq 'LOGIN')
|
|
{
|
|
# login is always two challenges
|
|
$self->reply(334, "VXNlcm5hbWU6"); # username
|
|
$self->next_input_to(\&process_response);
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
# runs user-supplied callback on username and password
|
|
# responds success if callback succeeds
|
|
# sets complete if ok, clears active either way
|
|
sub run_callback
|
|
{
|
|
my ($self,$user,$pass)=@_;
|
|
my $ok;
|
|
|
|
my $ref=$self->{callback}->{AUTH};
|
|
if (ref $ref eq 'ARRAY' && ref $ref->[0] eq 'CODE')
|
|
{
|
|
my $c=$ref->[0];
|
|
$ok=&$c($self,$user,$pass);
|
|
}
|
|
if ($ok)
|
|
{
|
|
$self->reply(235, "Authentication successful");
|
|
$self->{AUTH}->{completed}=1;
|
|
}
|
|
else
|
|
{
|
|
$self->reply(535,"Authentication failed.");
|
|
}
|
|
delete $self->{AUTH}->{active};
|
|
return undef;
|
|
}
|
|
|
|
# deals with any response, based on active method
|
|
sub process_response
|
|
{
|
|
my ($self,$args)=@_;
|
|
|
|
if (!$self->{AUTH}->{active} || $self->{AUTH}->{completed})
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "Authentication phases mixed up.");
|
|
return undef;
|
|
}
|
|
if (!$args)
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "5.7.8 Authentication failed.");
|
|
return undef;
|
|
}
|
|
|
|
if ($self->{AUTH}->{active} eq "PLAIN")
|
|
{
|
|
# plain is easy: only one response containing everything
|
|
my (undef,$user,$pwd)=split(/\0/,decode_base64($args),3);
|
|
if (!$user)
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "5.7.8 Authentication failed.");
|
|
return undef;
|
|
}
|
|
return run_callback($self,$user,$pwd);
|
|
}
|
|
elsif ($self->{AUTH}->{active} eq "LOGIN")
|
|
{
|
|
# uglier: two challenges for username+password
|
|
my ($input)=split(/\0/,decode_base64($args));
|
|
|
|
# is this the second time round?
|
|
if ($self->{AUTH}->{user})
|
|
{
|
|
return run_callback($self,$self->{AUTH}->{user},$input);
|
|
}
|
|
else
|
|
{
|
|
# nope, first time: save username and challenge
|
|
# for password
|
|
$self->{AUTH}->{user}=$input;
|
|
$self->reply(334, "UGFzc3dvcmQ6"); # password
|
|
$self->next_input_to(\&process_response);
|
|
return undef;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $self->{AUTH}->{active};
|
|
$self->reply(535, "Authentication mixed up.");
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
1;
|