193 lines
5.9 KiB
Plaintext
193 lines
5.9 KiB
Plaintext
|
#!perl -w
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
auth_checkpassword - Authenticate against a DJB style checkpassword program
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
This plugin authenticates users against a DJB style checkpassword
|
||
|
program. Unlike previous checkpassword implementations, this plugin
|
||
|
expects qpsmtpd to be running as the qpsmtpd user. Privilege
|
||
|
escalation can be attained by running the checkpassword binary setuid
|
||
|
or with sudo.
|
||
|
|
||
|
=head1 CONFIGURATION
|
||
|
|
||
|
Configure the path to your checkpassword binary. You can configure this in
|
||
|
config/plugins by defining the checkpw and true arguments as follows:
|
||
|
|
||
|
auth/auth_checkpassword checkpw /usr/local/vpopmail/bin/vchkpw true /bin/true
|
||
|
|
||
|
or by editing the config file config/smtpauth-checkpassword:
|
||
|
|
||
|
echo "/usr/local/vpopmail/bin/vchkpw /bin/true" > ~qpsmtpd/config/smtpauth-checkpassword
|
||
|
|
||
|
vchkpw is the checkpassword program provided by vpopmail. Substitute
|
||
|
your own checkpassword app as appropriate.
|
||
|
|
||
|
If you are using vchkpw and this plugin is being executed by a user ID
|
||
|
other than 89 or 0 (as is the default), and the vchkpw binary is not
|
||
|
setuid (as is the default), this plugin will automatically prepend the
|
||
|
vchkpw command with sudo. If that is the case, you must configure sudo
|
||
|
by adding these two lines to your sudoers file:
|
||
|
|
||
|
Defaults:qpsmtpd closefrom_override
|
||
|
qpsmtpd ALL = (ALL) NOPASSWD: /usr/local/vpopmail/bin/vchkpw
|
||
|
|
||
|
The closefrom_override option is necessary because, by default, sudo
|
||
|
appropriates the first 3 file descriptors. Those descriptors are
|
||
|
necessary to communicate with the checkpassword program. If you run
|
||
|
qpsmtpd as some other user, adjust the sudo lines approriately.
|
||
|
|
||
|
Using sudo is preferable to enabling setuid on the vchkpw binary. If
|
||
|
you reinstall vpopmail and the setuid bit is lost, this plugin will be
|
||
|
broken.
|
||
|
|
||
|
=head1 SEE ALSO
|
||
|
|
||
|
If you are using this plugin with vpopmail, please read the VPOPMAIL
|
||
|
section in docs/authentication.pod
|
||
|
|
||
|
=head1 DIAGNOSTICS
|
||
|
|
||
|
Is the path in the config/smtpauth-checkpassword correct?
|
||
|
|
||
|
Is the path to true in config/smtpauth-checkpassword correct?
|
||
|
|
||
|
Is qpsmtpd running as the qpsmtpd user? If not, did you adjust the
|
||
|
sudo configuration appropriately?
|
||
|
|
||
|
If you are not using sudo, did you remember to make the vchkpw binary
|
||
|
setuid (chmod 4711 ~vpopmail/bin/vchkpw)?
|
||
|
|
||
|
While writing this plugin, I first wrote myself a little test script,
|
||
|
which helped me identify the sudo closefrom_override issue. Here is
|
||
|
that script:
|
||
|
|
||
|
#!/usr/bin/perl
|
||
|
use strict;
|
||
|
my $sudo = "/usr/local/bin/sudo";
|
||
|
$sudo .= " -C4 -u vpopmail";
|
||
|
my $vchkpw = "/usr/local/vpopmail/bin/vchkpw";
|
||
|
my $true = "/bin/true";
|
||
|
|
||
|
open(CPW,"|$sudo $vchkpw $true 3<&0");
|
||
|
printf(CPW "%s\0%s\0Y123456\0",'user@example.com','pa55word');
|
||
|
close(CPW);
|
||
|
|
||
|
my $status = $?;
|
||
|
print "FAIL\n" and exit if ( $status != 0 );
|
||
|
print "OK\n";
|
||
|
|
||
|
Save that script to vchkpw.pl and then run it as the same user that
|
||
|
qpsmtpd runs as:
|
||
|
|
||
|
setuidgid qpsmtpd perl vchkpw.pl
|
||
|
|
||
|
If you aren't using sudo, then remove $sudo from the open line.
|
||
|
|
||
|
=head1 ACKNOWLEDGEMENTS
|
||
|
|
||
|
based upon authcheckpassword by Michael Holzt
|
||
|
and adapted by Johan Almqvist 2006-01-18
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Matt Simerson <msimerson@cpan.org>
|
||
|
|
||
|
=head1 COPYRIGHT AND LICENSE
|
||
|
|
||
|
Copyright (c) 2010 Matt Simerson
|
||
|
|
||
|
This plugin is licensed under the same terms as the qpsmtpd package itself.
|
||
|
Please see the LICENSE file included with qpsmtpd for details.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub register {
|
||
|
my ($self, $qp, %args ) = @_;
|
||
|
|
||
|
my ($checkpw, $true) = $self->get_checkpw( \%args );
|
||
|
return DECLINED if ! $checkpw || ! $true;
|
||
|
|
||
|
$self->connection->notes('auth_checkpassword_bin', $checkpw);
|
||
|
$self->connection->notes('auth_checkpassword_true', $true);
|
||
|
|
||
|
$self->register_hook('auth-plain', 'auth_checkpassword');
|
||
|
$self->register_hook('auth-login', 'auth_checkpassword');
|
||
|
}
|
||
|
|
||
|
sub auth_checkpassword {
|
||
|
my ($self, $transaction, $method, $user, $passClear, $passHash, $ticket) =
|
||
|
@_;
|
||
|
|
||
|
my $binary = $self->connection->notes('auth_checkpassword_bin');
|
||
|
my $true = $self->connection->notes('auth_checkpassword_true');
|
||
|
chomp ($binary, $true);
|
||
|
|
||
|
my $sudo = get_sudo($binary);
|
||
|
|
||
|
$self->log(LOGDEBUG, "auth_checkpassword: $sudo $binary $true 3<&0");
|
||
|
open(CPW, "|$sudo $binary $true 3<&0");
|
||
|
printf(CPW "%s\0%s\0Y123456\0", $user, $passClear);
|
||
|
close(CPW);
|
||
|
|
||
|
my $status = $?;
|
||
|
|
||
|
if ($status != 0) {
|
||
|
$self->log(LOGNOTICE, "authentication failed ($status)");
|
||
|
return (DECLINED);
|
||
|
};
|
||
|
|
||
|
$self->connection->notes('authuser', $user);
|
||
|
return (OK, "auth_checkpassword");
|
||
|
}
|
||
|
|
||
|
sub get_checkpw {
|
||
|
my ($self, $args) = @_;
|
||
|
|
||
|
my ($checkpw) = $args->{checkpw} =~ /^(.*)$/ if $args->{checkpw}; # untaint
|
||
|
my ($true) = $args->{true} =~ /^(.*)$/ if $args->{true}; # untaint
|
||
|
|
||
|
return ( $checkpw, $true )
|
||
|
if ( $checkpw && $true && -x $checkpw && -x $true );
|
||
|
|
||
|
my $missing_config = "disabled due to invalid configuration. See 'perldoc plugins/auth/auth_checkpassword' for how to configure.";
|
||
|
|
||
|
if ( ! $self->qp->config('smtpauth-checkpassword') ) {
|
||
|
$self->log(LOGERROR, $missing_config );
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
$self->log(LOGNOTICE, "reading config from smtpauth-checkpassword");
|
||
|
my $config = $self->qp->config("smtpauth-checkpassword");
|
||
|
($checkpw, $true) = $config =~ /^(\S+)\s+(\S+)\s*$/;
|
||
|
|
||
|
if ( ! $checkpw || ! $true || ! -x $checkpw || ! -x $true ) {
|
||
|
$self->log(LOGERROR, $missing_config );
|
||
|
return;
|
||
|
};
|
||
|
return ($checkpw, $true);
|
||
|
};
|
||
|
|
||
|
sub get_sudo {
|
||
|
my $binary = shift;
|
||
|
|
||
|
return '' if $> == 0; # running as root
|
||
|
return '' if $> == 89 && $binary =~ /vchkpw$/; # running as vpopmail
|
||
|
|
||
|
my $mode = (stat($binary))[2];
|
||
|
$mode = sprintf "%lo", $mode & 07777;
|
||
|
return '' if $mode eq '4711'; # $binary is setuid
|
||
|
|
||
|
my $sudo = `which sudo` || '/usr/local/bin/sudo';
|
||
|
return '' if ! -x $sudo;
|
||
|
|
||
|
$sudo .= ' -C4'; # prevent sudo from clobbering file descriptor 3
|
||
|
|
||
|
return $sudo if $binary !~ /vchkpw$/;
|
||
|
return "$sudo -u vpopmail";
|
||
|
}
|
||
|
|