diff --git a/acl/edit_pass.cgi b/acl/edit_pass.cgi index d9595e3ae..94142e348 100644 --- a/acl/edit_pass.cgi +++ b/acl/edit_pass.cgi @@ -25,6 +25,11 @@ print &ui_table_row($text{'pass_maxdays'}, &ui_opt_textbox("maxdays", $miniserv{'pass_maxdays'}, 5, $text{'pass_nomaxdays'})." ".$text{'pass_days'}); +# Days before lockout +print &ui_table_row($text{'pass_lockdays'}, + &ui_opt_textbox("lockdays", $miniserv{'pass_lockdays'}, 5, + $text{'pass_nolockdays'})." ".$text{'pass_days'}); + # Disallow use of username print &ui_table_row($text{'pass_nouser'}, &ui_yesno_radio("nouser", $miniserv{'pass_nouser'})); diff --git a/acl/lang/en b/acl/lang/en index b98dd75af..ae99d7c75 100644 --- a/acl/lang/en +++ b/acl/lang/en @@ -338,6 +338,7 @@ pass_minsize=Minimum password length pass_nominsize=No minimum pass_regexps=Regular expressions passwords must match pass_maxdays=Days before password must be changed +pass_lockdays=Days before un-changed password locks account pass_nomaxdays=Change never required pass_nouser=Disallow passwords containing username? pass_nodict=Disallow dictionary word passwords? @@ -348,6 +349,7 @@ pass_pass=passwords pass_err=Failed to save password restrictions pass_eminsize=Missing or non-numeric minimum password length pass_emaxdays=Missing or non-numeric number of days before changing +pass_elockdays=Missing or non-numeric number of days before account is locked pass_eoldblock=Missing or non-numeric number of old passwords to reject cpass_minsize=Must be at least $1 letters long diff --git a/acl/save_pass.cgi b/acl/save_pass.cgi index 05288c8c2..1ff33bd66 100644 --- a/acl/save_pass.cgi +++ b/acl/save_pass.cgi @@ -22,6 +22,13 @@ else { $in{'maxdays'} =~ /^\d+$/ || &error($text{'pass_emaxdays'}); $miniserv{'pass_maxdays'} = $in{'maxdays'}; } +if ($in{'lockdays_def'}) { + delete($miniserv{'pass_lockdays'}); + } +else { + $in{'lockdays'} =~ /^\d+$/ || &error($text{'pass_elockdays'}); + $miniserv{'pass_lockdays'} = $in{'lockdays'}; + } $miniserv{'pass_nouser'} = $in{'nouser'}; $miniserv{'pass_nodict'} = $in{'nodict'}; if ($in{'oldblock_def'}) { diff --git a/miniserv.pl b/miniserv.pl index 4407158fd..c0d05286c 100755 --- a/miniserv.pl +++ b/miniserv.pl @@ -2899,8 +2899,27 @@ elsif ($canmode == 0) { } elsif ($canmode == 1) { # Attempt Webmin authentication - return $users{$webminuser} eq &unix_crypt($pass, $users{$webminuser}) ? - ( $user, 0, 0 ) : ( undef, 0, 0 ); + if ($users{$webminuser} eq &unix_crypt($pass, $users{$webminuser})) { + # Password is valid .. but check for expiry + local $lc = $lastchanges{$user}; + if ($config{'pass_maxdays'} && $lc) { + local $daysold = (time() - $lc)/(24*60*60); + print DEBUG "maxdays=$config{'pass_maxdays'} daysold=$daysold\n"; + if ($config{'pass_lockdays'} && + $daysold > $config{'pass_lockdays'}) { + # So old that the account is locked + return ( undef, 0, 0 ); + } + elsif ($daysold > $config{'pass_maxdays'}) { + # Password has expired + return ( $user, 1, 0 ); + } + } + return ( $user, 0, 0 ); + } + else { + return ( undef, 0, 0 ); + } } elsif ($canmode == 2 || $canmode == 3) { # Attempt PAM or passwd file authentication @@ -3842,6 +3861,7 @@ undef(%allow); undef(%deny); undef(%allowdays); undef(%allowhours); +undef(%lastchanges); if ($config{'userfile'}) { open(USERS, $config{'userfile'}); while() { @@ -3865,6 +3885,7 @@ if ($config{'userfile'}) { if ($user[5] =~ /hours\s+(\d+)\.(\d+)-(\d+).(\d+)/) { $allowhours{$user[0]} = [ $1*60+$2, $3*60+$4 ]; } + $lastchanges{$user[0]} = $user[6]; } close(USERS); } diff --git a/password_change.cgi b/password_change.cgi index 130c83cdb..377c2bee9 100755 --- a/password_change.cgi +++ b/password_change.cgi @@ -8,16 +8,41 @@ require './web-lib.pl'; &ReadParse(); &get_miniserv_config(\%miniserv); $miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!"; -if (!$in{'pam'}) { - $miniserv{'passwd_cindex'} ne '' && $miniserv{'passwd_mindex'} ne '' || - die "Missing password file configuration"; - } # Validate inputs $in{'new1'} ne '' || &pass_error($text{'password_enew1'}); $in{'new1'} eq $in{'new2'} || &pass_error($text{'password_enew2'}); -if ($in{'pam'}) { +# Is this a Webmin user? +if (&foreign_check("acl")) { + &foreign_require("acl", "acl-lib.pl"); + ($wuser) = grep { $_->{'name'} eq $in{'user'} } &acl::list_users(); + if ($wuser->{'pass'} eq 'x') { + # A Webmin user, but using Unix authentication + $wuser = undef; + } + elsif ($wuser->{'pass'} eq '*LK*' || + $wuser->{'pass'} =~ /^\!/) { + &pass_error("Webmin users with locked accounts cannot change ". + "their passwords!"); + } + } +if (!$in{'pam'} && !$wuser) { + $miniserv{'passwd_cindex'} ne '' && $miniserv{'passwd_mindex'} ne '' || + die "Missing password file configuration"; + } + +if ($wuser) { + # Update Webmin user's password + $enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'}); + $enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'}); + $perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'}); + $perr && &pass_error(&text('password_enewpass', $perr)); + $wuser->{'pass'} = &acl::encrypt_password($in{'new1'}); + &acl::modify_user($wuser->{'name'}, $wuser); + &reload_miniserv(); + } +elsif ($in{'pam'}) { # Use PAM to make the change.. eval "use Authen::PAM;"; if ($@) {