-
Notifications
You must be signed in to change notification settings - Fork 5
/
plainAUTH.pm
185 lines (168 loc) · 3.9 KB
/
plainAUTH.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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.1';
# 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|login|plain)\s*(.*)$/
&& (($method,$param)=(uc($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;