--- plugins/auth/auth_checkpassword | 69 +++++++++++++++++++++++++------- t/plugin_tests/auth/auth_checkpassword | 44 ++++++++++++++++++++ 2 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 t/plugin_tests/auth/auth_checkpassword
diff --git a/plugins/auth/auth_checkpassword b/plugins/auth/auth_checkpassword index e6baa3b..520d608 100644 --- a/plugins/auth/auth_checkpassword +++ b/plugins/auth/auth_checkpassword @@ -14,7 +14,12 @@ or with sudo. =head1 CONFIGURATION -Configure the path to your checkpassword binary: +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 /usr/bin/true + +or by editing the config file config/smtpauth-checkpassword: echo "/usr/local/vpopmail/bin/vchkpw /usr/bin/true" > ~qpsmtpd/config/smtpauth-checkpassword @@ -101,50 +106,86 @@ Please see the LICENSE file included with qpsmtpd for details. =cut sub register { - my ($self, $qp) = @_; + my ($self, $qp, %args ) = @_; + + my ($checkpw, $true) = $self->get_checkpw( \%args ); + return DECLINED if ! $checkpw || ! $true; - $self->register_hook("auth-plain", "auth_checkpassword"); - $self->register_hook("auth-login", "auth_checkpassword"); + $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 $command = $self->qp->config("smtpauth-checkpassword") - or return (DECLINED); - my ($binary, $params) = $command =~ /^(\S+)(.*)$/; + my $binary = $self->connection->notes('auth_checkpassword_bin'); + my $true = $self->connection->notes('auth_checkpassword_true'); - return (DECLINED) if (!-x $binary); my $sudo = get_sudo($binary); - open(CPW, "|$sudo $binary $params 3<&0"); + $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 = $?; - return (DECLINED) if ($status != 0); + 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 + 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; + return '' if ! -x $sudo; + $sudo .= ' -C4'; # prevent sudo from clobbering file descriptor 3 - return "$sudo -u vpopmail" if $binary =~ /vchkpw/; - return $sudo; + return $sudo if $binary !~ /vchkpw$/; + return "$sudo -u vpopmail"; } diff --git a/t/plugin_tests/auth/auth_checkpassword b/t/plugin_tests/auth/auth_checkpassword new file mode 100644 index 0000000..c51fa2d --- /dev/null +++ b/t/plugin_tests/auth/auth_checkpassword @@ -0,0 +1,44 @@ +#!perl -w + +warn "loaded auth_checkpassword\n"; + +sub register_tests { + my $self = shift; + + my ($vpopdir) = (getpwnam('vpopmail'))[7]; + + if ( ! $vpopdir ) { + warn "skipping tests, vpopmail not installed\n"; + return; + }; + + if ( ! -d "$vpopdir/domains/example.com" ) { + warn "skipping tests, no example users set up.\n"; + return; + }; + + $self->register_test("test_auth_checkpassword", 3); +} + +my @u_list = qw ( good bad none ); +my %u_data = ( + good => [ 'postmas...@example.com', OK, 'Good Strong Passphrase' ], + bad => [ 'b...@example.com', DENY, 'not_bad_pass' ], + none => [ 'n...@example.com', DECLINED, '' ], + ); + +sub test_auth_checkpassword { + my $self = shift; + my ($tran, $ret, $note, $u, $r, $p, $a ); + $tran = $self->qp->transaction; + for $u ( @u_list ) { + ( $a,$r,$p ) = @{$u_data{$u}}; + ($ret, $note) = $self->auth_checkpassword($tran,'LOGIN',$a,$p); + defined $note or $note='No-Message'; + is ($ret, $r, $note); + + ($ret, $note) = $self->auth_checkpassword($tran,'PLAIN',$a,$p); + defined $note or $note='No-Message'; + is ($ret, $r, $note); + } +} -- 1.7.9.6