From 7e3253a8e182b7f49f222a760af741dbd27f8367 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Mon, 15 Sep 2008 15:26:52 +0200 Subject: [PATCH] Implement password quality checking in update --- Util.pm | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ update.cgi | 36 +++++++++++++--- 2 files changed, 153 insertions(+), 6 deletions(-) diff --git a/Util.pm b/Util.pm index 2b5b266..e97a9e9 100644 --- a/Util.pm +++ b/Util.pm @@ -1,9 +1,14 @@ # -*- perl -*-x + +# Copyright (c) 1999-2006 Debian Admin Team Members and Developers (taken from debian/copyright in 2008 by weasel) +# Copyright (c) 2002, 2003, 2004, 2008 Peter Palfrader + package Util; use strict; use Date::Manip qw(ParseDate); use Net::LDAP qw(:all); +use English; my $blocksize = 8; # A blowfish block is 8 bytes my $configfile = "/etc/userdir-ldap/userdir-ldap.conf"; @@ -353,4 +358,122 @@ sub UpgradeConnection($) { exit(1); }; }; + +sub readwrite3($$$$) { + my ($in, $inputfd, $stdoutfd, $stderrfd) = @_; + + #Echolot::Log::trace("Entering readwrite_gpg."); + + local $INPUT_RECORD_SEPARATOR = undef; + my $sout = IO::Select->new(); + my $sin = IO::Select->new(); + my $offset = 0; + + #Echolot::Log::trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef')."."); + + $inputfd->blocking(0); + $stdoutfd->blocking(0); + $stderrfd->blocking(0); + $sout->add($stdoutfd); + $sout->add($stderrfd); + $sin->add($inputfd); + + my ($stdout, $stderr) = ("", "", ""); + + my ($readyr, $readyw); + while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { + #Echolot::Log::trace("select waiting for ".($sout->count())." fds."); + ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, 42); + #Echolot::Log::trace("ready: write: ".(defined $readyw ? scalar @$readyw : 'none')."; read: ".(defined $readyr ? scalar @$readyr : 'none')); + for my $wfd (@$readyw) { + #Echolot::Log::trace("writing to $wfd."); + my $written = 0; + if ($offset != length($in)) { + $written = $wfd->syswrite($in, length($in) - $offset, $offset); + } + unless (defined ($written)) { + #Echolot::Log::warn("Error while writing to GnuPG: $!"); + close $wfd; + $sin->remove($wfd); + $sin = undef; + } else { + $offset += $written; + if ($offset == length($in)) { + #Echolot::Log::trace("writing to $wfd done."); + close $wfd; + $sin->remove($wfd); + $sin = undef; + } + } + } + + next unless (defined(@$readyr)); # Wait some more. + + for my $rfd (@$readyr) { + if ($rfd->eof) { + #Echolot::Log::trace("reading from $rfd done."); + $sout->remove($rfd); + close($rfd); + next; + } + #Echolot::Log::trace("reading from $rfd."); + if ($rfd == $stdoutfd) { + $stdout .= <$rfd>; + next; + } + if ($rfd == $stderrfd) { + $stderr .= <$rfd>; + next; + } + } + } + #Echolot::Log::trace("readwrite_gpg done."); + return ($stdout, $stderr); +}; + +sub checkPasswordQuality($$$) { + my ($pw, $oldpw, $ldapelements) = @_; + my ($stdinR, $stdinW) = (IO::Handle->new(), IO::Handle->new()); + my ($stdoutR, $stdoutW) = (IO::Handle->new(), IO::Handle->new()); + my ($stderrR, $stderrW) = (IO::Handle->new(), IO::Handle->new()); + pipe $stdinR, $stdinW; + pipe $stdoutR, $stdoutW; + pipe $stderrR, $stderrW; + + my $pid = fork(); + unless (defined $pid) { + return (2, "Could not fork: $!"); + }; + unless ($pid) { # child + $stdinW->close; + $stdoutR->close; + $stderrR->close; + close STDIN; + close STDOUT; + close STDERR; + open (STDIN, "<&".$stdinR->fileno) or warn ("Cannot dup stdinR (fd ".$stdinR->fileno.") as STDIN: $!"); + open (STDOUT, ">&".$stdoutW->fileno) or warn ("Cannot dup stdoutW (fd ".$stdoutW->fileno.") as STDOUT: $!"); + open (STDERR, ">&".$stderrW->fileno) or warn ("Cannot dup stderrW (fd ".$stderrW->fileno.") as STDERR: $!"); + { exec('/usr/lib/userdir-ldap-cgi/password-qualify-check'); } + $stderrW->print("Could not exec password-qualify-check: $!\n"); + exit(1); + }; + $stdinR->close; + $stdoutW->close; + $stderrW->close; + + $oldpw = '' unless defined $oldpw; + my $out = join("\n", $pw, $oldpw, @$ldapelements)."\n"; + my ($stdout, $stderr) = readwrite3($out, $stdinW, $stdoutR, $stderrR); + waitpid $pid, 0; + + my $exitcode = $? >> 8; + if ($exitcode == 0 && $stdout eq '' && $stderr eq '') { + return (0, "ok"); + } elsif ($exitcode == 1 && $stderr eq '') { + return (1, $stdout); + } else { + return (2, "check exited with exit code $exitcode, said '$stdout' on stdout, and '$stderr' on stderr."); + }; +}; 1; diff --git a/update.cgi b/update.cgi index 1a66b75..716faa9 100755 --- a/update.cgi +++ b/update.cgi @@ -119,8 +119,8 @@ if (!($query->param('doupdate'))) { . '>female'; my $confirmstring = ''; my $sudopassword = ''; - for my $entry (@{$entry->{'sudopassword'}}) { - my ($uuid, $status, $hosts, $crypted) = ($entry =~ /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$/); + for my $e(@{$entry->{'sudopassword'}}) { + my ($uuid, $status, $hosts, $crypted) = ($e =~ /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$/); unless (defined $uuid) { $sudopassword .= "Unparseable line!\n"; next; @@ -165,6 +165,14 @@ if (!($query->param('doupdate'))) { } close F; } else { + my @ldapinfo_for_pwcheck; + for my $a (qw{cn sn md gecos uid}) { + for my $e (@{$entry->{$a}}) { + push @ldapinfo_for_pwcheck, $e; + } + } + + # Actually update stuff... my ($newpassword, $newstaddress); @@ -179,7 +187,13 @@ if (!($query->param('doupdate'))) { if ($query->param('newpass') ne $query->param('newpassvrfy')) { # passwords don't match... &Util::HTMLError("The passwords you specified do not match. Please go back and try again."); - } + } + + my ($r, $msg) = &Util::checkPasswordQuality($query->param('newpass'), undef, [@ldapinfo_for_pwcheck]); + if ($r) { + &Util::HTMLError("Password check failed: $msg. Please go back and try again."); + } + # create a md5 crypted password $newpassword = '{crypt}'.crypt($query->param('newpass'), &Util::CreateCryptSalt(1)); @@ -237,13 +251,23 @@ if (!($query->param('doupdate'))) { my $newsudo; my $newsudo_hosts; if ($query->param('newsudopass') && $query->param('newsudopassvrfy')) { - if ($query->param('newsudopass') ne $query->param('newsudopassvrfy')) { - &Util::HTMLError("The sudo passwords you specified do not match. Please go back and try again."); - } my $host = $query->param('newsudopass-host'); if ($host =~ /[^a-z0-9.-]/ and $host ne '*') { &Util::HTMLError("The sudo host has weird characters '$host'."); } + + if ($query->param('newsudopass') ne $query->param('newsudopassvrfy')) { + &Util::HTMLError("The sudo passwords you specified do not match. Please go back and try again."); + } + + my $ldappass = $password; + $ldappass = $query->param('newpass') if $query->param('newpass'); + push @ldapinfo_for_pwcheck, $host, split(/\./, $host); + my ($r, $msg) = &Util::checkPasswordQuality($query->param('newsudopass'), $ldappass, [@ldapinfo_for_pwcheck]); + if ($r) { + &Util::HTMLError("Password check failed for new sudo pass: $msg. Please go back and try again."); + } + # create a md5 crypted password my $newsudopassword = crypt($query->param('newsudopass'), &Util::CreateCryptSalt(1)); my $ug = new Data::UUID; -- 2.20.1