/usr/share/perl5/Qpsmtpd/Auth.pm is in qpsmtpd 0.94-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | package Qpsmtpd::Auth;
# See the documentation in 'perldoc docs/authentication.pod'
use strict;
use warnings;
use Qpsmtpd::Constants;
use Digest::HMAC_MD5 qw(hmac_md5_hex);
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, $loginas);
if ($mechanism eq 'plain') {
($loginas, $user, $passClear) =
get_auth_details_plain($session, $prekey);
return DECLINED if !$user || !$passClear;
}
elsif ($mechanism eq 'login') {
($user, $passClear) = get_auth_details_login($session, $prekey);
return DECLINED if !$user || !$passClear;
}
elsif ($mechanism eq 'cram-md5') {
($ticket, $user, $passHash) = get_auth_details_cram_md5($session);
return DECLINED if !$user || !$passHash;
}
else {
#this error is now caught in SMTP.pm's sub auth
$session->respond(500, "Internal server error");
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 =
uc($mechanism)
. " authentication successful for $user"
. ($msg ? " - $msg" : '');
$session->respond(235, $msg);
$session->connection->relay_client(1);
if ($session->connection->notes('naughty')) {
$session->log(LOGINFO, "auth success cleared naughty");
$session->connection->notes('naughty', 0);
}
$session->log(LOGDEBUG, $msg); # already logged by $session->respond
$session->{_auth_user} = $user;
$session->{_auth_mechanism} = $mechanism;
s/[\r\n].*//s for ($session->{_auth_user}, $session->{_auth_mechanism});
return OK;
}
else {
$msg =
uc($mechanism)
. " authentication failed for $user"
. ($msg ? " - $msg" : '');
$session->respond(535, $msg);
$session->log(LOGDEBUG, $msg); # already logged by $session->respond
return DENY;
}
}
sub get_auth_details_plain {
my ($session, $prekey) = @_;
if (!$prekey) {
$session->respond(334, ' ');
$prekey = <STDIN>;
}
my ($loginas, $user, $passClear) = split /\x0/, decode_base64($prekey);
if (!$user) {
if ($loginas) {
$session->respond(535, "Authentication invalid ($loginas)");
}
else {
$session->respond(535, "Authentication invalid");
}
return;
}
# Authorization ID must not be different from Authentication ID
if ($loginas ne '' && $loginas ne $user) {
$session->respond(535, "Authentication invalid for $user");
return;
}
return ($loginas, $user, $passClear);
}
sub get_auth_details_login {
my ($session, $prekey) = @_;
my $user;
if ($prekey) {
$user = decode_base64($prekey);
}
else {
$user = get_base64_response($session, 'Username:') or return;
}
my $passClear = get_base64_response($session, 'Password:') or return;
return ($user, $passClear);
}
sub get_auth_details_cram_md5 {
my ($session, $ticket) = @_;
if (!$ticket) { # ticket is only passed in during testing
# 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.
$ticket =
sprintf('<%x.%x@%s>', rand(1000000), time(), $session->config('me'));
}
# send the base64 encoded ticket
$session->respond(334, encode_base64($ticket, ''));
my $line = <STDIN>;
if ($line eq '*') {
$session->respond(501, "Authentication canceled");
return;
}
my ($user, $passHash) = split(/ /, decode_base64($line));
unless ($user && $passHash) {
$session->respond(504, "Invalid authentication string");
return;
}
$session->{auth}{ticket} = $ticket;
return ($ticket, $user, $passHash);
}
sub get_base64_response {
my ($session, $question) = @_;
$session->respond(334, e64($question));
my $answer = decode_base64(<STDIN>);
if ($answer eq '*') {
$session->respond(501, "Authentication canceled");
return;
}
return $answer;
}
sub validate_password {
my ($self, %a) = @_;
my ($pkg, $file, $line) = caller();
$file = (split /\//, $file)[-1]; # strip off the path
my $src_clear = $a{src_clear};
my $src_crypt = $a{src_crypt};
my $attempt_clear = $a{attempt_clear};
my $attempt_hash = $a{attempt_hash};
my $method = $a{method} or die "missing method";
my $ticket = $a{ticket} || $self->{auth}{ticket};
my $deny = $a{deny} || DENY;
if (!$src_crypt && !$src_clear) {
$self->log(LOGINFO, "fail: missing password");
return ($deny, "$file - no such user");
}
if (!$src_clear && $method =~ /CRAM-MD5/i) {
$self->log(LOGINFO, "skip: cram-md5 not supported w/o clear pass");
return (DECLINED, $file);
}
if (defined $attempt_clear) {
if ($src_clear && $src_clear eq $attempt_clear) {
$self->log(LOGINFO, "pass: clear match");
return (OK, $file);
}
if ($src_crypt && $src_crypt eq crypt($attempt_clear, $src_crypt)) {
$self->log(LOGINFO, "pass: crypt match");
return (OK, $file);
}
}
if (defined $attempt_hash && $src_clear) {
if (!$ticket) {
$self->log(LOGERROR, "skip: missing ticket");
return (DECLINED, $file);
}
if ($attempt_hash eq hmac_md5_hex($ticket, $src_clear)) {
$self->log(LOGINFO, "pass: hash match");
return (OK, $file);
}
}
$self->log(LOGINFO, "fail: wrong password");
return ($deny, "$file - wrong password");
}
# tag: qpsmtpd plugin that sets RELAYCLIENT when the user authenticates
1;
|