From 74ea1f9da809873e9202fcc2147347d63555807d Mon Sep 17 00:00:00 2001 From: Jamie Cameron Date: Thu, 29 Mar 2012 22:37:37 -0700 Subject: [PATCH] Add new password restriction for min days before a change is allowed --- ldap-useradmin/save_user.cgi | 5 ++-- passwd/change-passwd.pl | 2 +- passwd/save_passwd.cgi | 2 +- useradmin/CHANGELOG | 2 ++ useradmin/config.info | 1 + useradmin/lang/en | 1 + useradmin/save_user.cgi | 6 ++-- useradmin/user-lib.pl | 53 ++++++++++++++++++++++++++++-------- 8 files changed, 54 insertions(+), 18 deletions(-) diff --git a/ldap-useradmin/save_user.cgi b/ldap-useradmin/save_user.cgi index d6edb9c72..0d259b04b 100755 --- a/ldap-useradmin/save_user.cgi +++ b/ldap-useradmin/save_user.cgi @@ -246,7 +246,7 @@ else { # Password is blank if (!$mconfig{'empty_mode'}) { local $err = &useradmin::check_password_restrictions( - "", $user); + "", $user, $in{'new'} ? 'none' : \%ouser); &error($err) if ($err); } $pass = ""; @@ -263,7 +263,8 @@ else { elsif ($in{'passmode'} == 3) { # Normal password entered - check restrictions local $err = &useradmin::check_password_restrictions( - $in{'pass'}, $user); + $in{'pass'}, $user, + $in{'new'} ? 'none' : \%ouser); &error($err) if ($err); $pass = $pfx.&encrypt_password($in{'pass'}); $plainpass = $in{'pass'}; diff --git a/passwd/change-passwd.pl b/passwd/change-passwd.pl index 819be0d69..3f9b6864b 100755 --- a/passwd/change-passwd.pl +++ b/passwd/change-passwd.pl @@ -46,7 +46,7 @@ $again =~ s/\r|\n//g; $pass eq $again || &errordie("Passwords don't match"); # Check password sanity -$err = &useradmin::check_password_restrictions($pass, $ARGV[0]); +$err = &useradmin::check_password_restrictions($pass, $ARGV[0], $user); &errordie($err) if ($err); # Do the change! diff --git a/passwd/save_passwd.cgi b/passwd/save_passwd.cgi index 569c222b3..8d0447d4e 100755 --- a/passwd/save_passwd.cgi +++ b/passwd/save_passwd.cgi @@ -68,7 +68,7 @@ else { $in{'new'} eq $in{'repeat'} || &error($text{'passwd_erepeat'}); } $err = &useradmin::check_password_restrictions( - $in{'new'}, $in{'user'}); + $in{'new'}, $in{'user'}, $user); &error($err) if ($err); &can_edit_passwd([ $user->{'user'}, $user->{'pass'}, diff --git a/useradmin/CHANGELOG b/useradmin/CHANGELOG index a6045f719..de4b52d92 100644 --- a/useradmin/CHANGELOG +++ b/useradmin/CHANGELOG @@ -69,3 +69,5 @@ Fixed an XSS vulnerability that can be triggered if an attacker has the ability ---- Changes since 1.550 ---- Updated all links to users and groups to be by name instead of by index, to avoid incorrect links if the passwd or group files are changed manually or by another Webmin session. The faster lastlog command is now used to get the most recent login time on Linux, for display in the user list. +---- Changes since 1.580 ---- +Added a new password restriction for the minimum number of days before a password can be changed. diff --git a/useradmin/config.info b/useradmin/config.info index dd216219b..5df444bc0 100644 --- a/useradmin/config.info +++ b/useradmin/config.info @@ -54,6 +54,7 @@ passwd_re=Perl regexp to check password against,3,None passwd_same=Prevent passwords containing username?,1,1-Yes,0-No passwd_prog=External password-checking program,3,None passwd_progmode=Pass username and password to program,1,1-As input,0-As parameters +passwd_mindays=Number of days before password change is allowed,3,None line0=Before and after commands,11 pre_command=Command to run before making changes,0 diff --git a/useradmin/lang/en b/useradmin/lang/en index dac5b03d4..7a6606420 100644 --- a/useradmin/lang/en +++ b/useradmin/lang/en @@ -176,6 +176,7 @@ usave_epasswd_min=Password must be at least $1 letters long usave_epasswd_re=Password does not match regexp $1 usave_epasswd_dict=Password is a dictionary word usave_epasswd_same=Password contains or is the same as username +usave_epasswd_mindays=Password was changed less than $1 days ago usave_eothers=The user was successfully saved, but an error occured in another module : $1 gedit_title=Edit Group diff --git a/useradmin/save_user.cgi b/useradmin/save_user.cgi index 628766c85..02e536f5a 100755 --- a/useradmin/save_user.cgi +++ b/useradmin/save_user.cgi @@ -282,7 +282,8 @@ if ($access{'ugroups'} ne "*") { if ($in{'passmode'} == 0) { # Password is blank if (!$config{'empty_mode'}) { - local $err = &check_password_restrictions("", $user{'user'}); + local $err = &check_password_restrictions("", $user{'user'}, + $in{'old'} eq '' ? 'none' : \%ouser); &error($err) if ($err); } $user{'pass'} = ""; @@ -297,7 +298,8 @@ elsif ($in{'passmode'} == 2) { } elsif ($in{'passmode'} == 3) { # Normal password entered - check restrictions - local $err = &check_password_restrictions($in{'pass'}, $user{'user'}); + local $err = &check_password_restrictions($in{'pass'}, $user{'user'}, + $in{'old'} eq '' ? 'none' : \%ouser); &error($err) if ($err); $user{'pass'} = &encrypt_password($in{'pass'}); } diff --git a/useradmin/user-lib.pl b/useradmin/user-lib.pl index 80fe2c644..f144288be 100755 --- a/useradmin/user-lib.pl +++ b/useradmin/user-lib.pl @@ -1502,7 +1502,7 @@ foreach my $k (keys %group_properties_map) { } } -=head2 check_password_restrictions(pass, username) +=head2 check_password_restrictions(pass, username, [&user-hash|"none"]) Returns an error message if the given password fails length and other checks, or undef if it is OK. @@ -1510,20 +1510,21 @@ checks, or undef if it is OK. =cut sub check_password_restrictions { +local ($pass, $username, $uinfo) = @_; return &text('usave_epasswd_min', $config{'passwd_min'}) - if (length($_[0]) < $config{'passwd_min'}); + if (length($pass) < $config{'passwd_min'}); local $re = $config{'passwd_re'}; return &text('usave_epasswd_re', $re) - if ($re && !eval { $_[0] =~ /^$re$/ }); + if ($re && !eval { $pass =~ /^$re$/ }); if ($config{'passwd_same'}) { - return &text('usave_epasswd_same') if ($_[0] =~ /\Q$_[1]\E/i); + return &text('usave_epasswd_same') if ($pass =~ /\Q$username\E/i); } -if ($config{'passwd_dict'} && $_[0] =~ /^[A-Za-z\'\-]+$/ && +if ($config{'passwd_dict'} && $pass =~ /^[A-Za-z\'\-]+$/ && (&has_command("ispell") || &has_command("spell"))) { # Call spell or ispell to check for dictionary words local $temp = &transname(); open(TEMP, ">$temp"); - print TEMP $_[0],"\n"; + print TEMP $pass,"\n"; close(TEMP); if (&has_command("ispell")) { open(SPELL, "ispell -a <$temp |"); @@ -1547,8 +1548,8 @@ if ($config{'passwd_prog'}) { local $out; if ($config{'passwd_progmode'} == 0) { # Run external validation program with user and password as args - local $qu = quotemeta($_[1]); - local $qp = quotemeta($_[0]); + local $qu = quotemeta($username); + local $qp = quotemeta($pass); $out = &backquote_command( "$config{'passwd_prog'} $qu $qp 2>&1 $temp", 0, 1); - &print_tempfile(TEMP, $_[1],"\n"); - &print_tempfile(TEMP, $_[0],"\n"); + &print_tempfile(TEMP, $username,"\n"); + &print_tempfile(TEMP, $pass,"\n"); &close_tempfile(TEMP); $out = &backquote_command("$config{'passwd_prog'} <$temp 2>&1"); } @@ -1565,6 +1566,33 @@ if ($config{'passwd_prog'}) { return $out; } } +if ($config{'passwd_mindays'} && $uinfo ne "none") { + # Check if password was changed too recently + if (!$uinfo) { + ($uinfo) = grep { $_->{'user'} eq $username } &list_users(); + } + if ($uinfo) { + local $pft = &passfiles_type(); + local $when; + if ($pft == 1 || $pft == 6) { + # BSD (unix time) + $when = $uinfo->{'change'}; + } + elsif ($pft == 2 || $pft == 5) { + # Linux (number of days) + $when = $uinfo->{'change'} * 24*60*60; + } + elsif ($pft == 4) { + # AIX (unix time) + $when = $uinfo->{'change'}; + } + if ($when && time() - $when < + $config{'passwd_mindays'}*24*60*60) { + return &text('usave_epasswd_mindays', + $config{'passwd_mindays'}); + } + } + } return undef; } @@ -1576,12 +1604,13 @@ it is OK. =cut sub check_username_restrictions { -if ($config{'max_length'} && length($_[0]) > $config{'max_length'}) { +local ($username) = @_; +if ($config{'max_length'} && length($username) > $config{'max_length'}) { return &text('usave_elength', $config{'max_length'}); } local $re = $config{'username_re'}; return &text('usave_ere', $re) - if ($re && !eval { $_[0] =~ /^$re$/ }); + if ($re && !eval { $username =~ /^$re$/ }); return undef; }