1#!/usr/local/bin/perl 2# password_change.cgi 3# Actually update a user's password by directly modifying /etc/shadow 4 5BEGIN { push(@INC, "."); }; 6use WebminCore; 7 8$ENV{'MINISERV_INTERNAL'} || die "Can only be called by miniserv.pl"; 9&init_config(); 10&ReadParse(); 11&get_miniserv_config(\%miniserv); 12$miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!"; 13 14# Validate inputs 15$in{'new1'} ne '' || &pass_error($text{'password_enew1'}); 16$in{'new1'} eq $in{'new2'} || &pass_error($text{'password_enew2'}); 17 18# Is this a Webmin user? 19if (&foreign_check("acl")) { 20 &foreign_require("acl", "acl-lib.pl"); 21 ($wuser) = grep { $_->{'name'} eq $in{'user'} } &acl::list_users(); 22 if ($wuser) { 23 if ($wuser->{'pass'} eq 'x') { 24 # A Webmin user, but using Unix authentication 25 $wuser = undef; 26 } 27 elsif ($wuser->{'pass'} eq '*LK*' || 28 $wuser->{'pass'} =~ /^\!/) { 29 &pass_error("Webmin users with locked accounts cannot change ". 30 "their passwords!"); 31 } 32 } 33 } 34if (!$in{'pam'} && !$wuser) { 35 $miniserv{'passwd_cindex'} ne '' && $miniserv{'passwd_mindex'} ne '' || 36 die "Missing password file configuration"; 37 } 38 39if ($wuser) { 40 # Update Webmin user's password 41 $ok = &acl::validate_password($in{'old'}, $wuser->{'pass'}); 42 $ok || &pass_error($text{'password_eold'}); 43 $perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'}); 44 $perr && &pass_error(&text('password_enewpass', $perr)); 45 $wuser->{'pass'} = &acl::encrypt_password($in{'new1'}); 46 $wuser->{'temppass'} = 0; 47 &acl::modify_user($wuser->{'name'}, $wuser); 48 &reload_miniserv(); 49 } 50elsif ($gconfig{'passwd_cmd'}) { 51 # Use some configured command 52 $passwd_cmd = &has_command($gconfig{'passwd_cmd'}); 53 $passwd_cmd || &pass_error("The password change command <tt>$gconfig{'passwd_cmd'}</tt> was not found"); 54 55 &foreign_require("proc", "proc-lib.pl"); 56 &clean_environment(); 57 $ENV{'REMOTE_USER'} = $in{'user'}; # some programs need this 58 $passwd_cmd .= " ".quotemeta($in{'user'}); 59 ($fh, $fpid) = &proc::pty_process_exec($passwd_cmd, 0, 0); 60 &reset_environment(); 61 while(1) { 62 local $rv = &wait_for($fh, 63 '(new|re-enter).*:', 64 '(old|current|login).*:', 65 'pick a password', 66 'too\s+many\s+failures', 67 'attributes\s+changed\s+on|successfully\s+changed', 68 'pick your passwords'); 69 $out .= $wait_for_input; 70 sleep(1); 71 if ($rv == 0) { 72 # Prompt for the new password 73 syswrite($fh, $in{'new1'}."\n", length($in{'new1'})+1); 74 } 75 elsif ($rv == 1) { 76 # Prompt for the old password 77 syswrite($fh, $in{'old'}."\n", length($in{'old'})+1); 78 } 79 elsif ($rv == 2) { 80 # Request for a menu option (SCO?) 81 syswrite($fh, "1\n", 2); 82 } 83 elsif ($rv == 3) { 84 # Failed too many times 85 last; 86 } 87 elsif ($rv == 4) { 88 # All done 89 last; 90 } 91 elsif ($rv == 5) { 92 # Request for a menu option (HP/UX) 93 syswrite($fh, "p\n", 2); 94 } 95 else { 96 last; 97 } 98 last if (++$count > 10); 99 } 100 $crv = close($fh); 101 sleep(1); 102 waitpid($fpid, 1); 103 if ($? || $count > 10 || 104 $out =~ /error|failed/i || $out =~ /bad\s+password/i) { 105 &pass_error("<tt>".&html_escape($out)."</tt>"); 106 } 107 } 108elsif ($in{'pam'}) { 109 # Use PAM to make the change.. 110 eval "use Authen::PAM;"; 111 if ($@) { 112 &pass_error(&text('password_emodpam', $@)); 113 } 114 115 # Check if the old password is correct 116 $service = $miniserv{'pam'} ? $miniserv{'pam'} : "webmin"; 117 $pamh = new Authen::PAM($service, $in{'user'}, \&pam_check_func); 118 $rv = $pamh->pam_authenticate(); 119 $rv == PAM_SUCCESS() || 120 &pass_error($text{'password_eold'}); 121 $pamh = undef; 122 123 # Change the password with PAM, in a sub-process. This is needed because 124 # the UID must be changed to properly signal to the PAM libraries that 125 # the password change is not being done by the root user. 126 $temp = &transname(); 127 $pid = fork(); 128 @uinfo = getpwnam($in{'user'}); 129 if (!$pid) { 130 ($>, $<) = (0, $uinfo[2]); 131 $pamh = new Authen::PAM("passwd", $in{'user'}, \&pam_change_func); 132 $rv = $pamh->pam_chauthtok(); 133 open(TEMP, ">$temp"); 134 print TEMP "$rv\n"; 135 print TEMP ($messages || $pamh->pam_strerror($rv)),"\n"; 136 close(TEMP); 137 exit(0); 138 } 139 waitpid($pid, 0); 140 open(TEMP, "<$temp"); 141 chop($rv = <TEMP>); 142 chop($messages = <TEMP>); 143 close(TEMP); 144 unlink($temp); 145 $rv == PAM_SUCCESS || &pass_error(&text('password_epam', $messages)); 146 $pamh = undef; 147 } 148else { 149 # Directly update password file 150 151 # Read shadow file and find user 152 &lock_file($miniserv{'passwd_file'}); 153 $lref = &read_file_lines($miniserv{'passwd_file'}); 154 for($i=0; $i<@$lref; $i++) { 155 @line = split(/:/, $lref->[$i], -1); 156 local $u = $line[$miniserv{'passwd_uindex'}]; 157 if ($u eq $in{'user'}) { 158 $idx = $i; 159 last; 160 } 161 } 162 defined($idx) || &pass_error($text{'password_euser'}); 163 164 # Validate old password 165 &unix_crypt($in{'old'}, $line[$miniserv{'passwd_pindex'}]) eq 166 $line[$miniserv{'passwd_pindex'}] || 167 &pass_error($text{'password_eold'}); 168 169 # Make sure new password meets restrictions 170 if (&foreign_check("changepass")) { 171 &foreign_require("changepass", "changepass-lib.pl"); 172 $err = &changepass::check_password($in{'new1'}, $in{'user'}); 173 &pass_error($err) if ($err); 174 } 175 elsif (&foreign_check("useradmin")) { 176 &foreign_require("useradmin", "user-lib.pl"); 177 $err = &useradmin::check_password_restrictions( 178 $in{'new1'}, $in{'user'}); 179 &pass_error($err) if ($err); 180 } 181 182 # Set new password and save file 183 $salt = chr(int(rand(26))+65) . chr(int(rand(26))+65); 184 $line[$miniserv{'passwd_pindex'}] = &unix_crypt($in{'new1'}, $salt); 185 $days = int(time()/(24*60*60)); 186 $line[$miniserv{'passwd_cindex'}] = $days; 187 $lref->[$idx] = join(":", @line); 188 &flush_file_lines(); 189 &unlock_file($miniserv{'passwd_file'}); 190 } 191 192# Change password in Usermin too 193if (&get_product_name() eq 'usermin' && 194 &foreign_check("changepass")) { 195 &foreign_require("changepass", "changepass-lib.pl"); 196 &changepass::change_mailbox_passwords( 197 $in{'user'}, $in{'old'}, $in{'new1'}); 198 &changepass::change_samba_password( 199 $in{'user'}, $in{'old'}, $in{'new1'}); 200 } 201 202# Show ok page 203&header(undef, undef, undef, undef, 1, 1); 204print &ui_alert_box(&text('password_done', "/"), "success"); 205&footer(); 206 207sub pass_error 208{ 209&header(undef, undef, undef, undef, 1, 1, undef, undef); 210print &ui_alert_box("$text{'password_err'}: @_.", "danger"); 211&footer(); 212exit; 213} 214 215sub pam_check_func 216{ 217my @res; 218while ( @_ ) { 219 my $code = shift; 220 my $msg = shift; 221 my $ans = ""; 222 223 $ans = $in{'user'} if ($code == PAM_PROMPT_ECHO_ON()); 224 $ans = $in{'old'} if ($code == PAM_PROMPT_ECHO_OFF()); 225 226 push @res, PAM_SUCCESS(); 227 push @res, $ans; 228 } 229push @res, PAM_SUCCESS(); 230return @res; 231} 232 233sub pam_change_func 234{ 235my @res; 236while ( @_ ) { 237 my $code = shift; 238 my $msg = shift; 239 my $ans = ""; 240 $messages = $msg; 241 242 if ($code == PAM_PROMPT_ECHO_ON()) { 243 # Assume asking for username 244 push @res, PAM_SUCCESS(); 245 push @res, $in{'user'}; 246 } 247 elsif ($code == PAM_PROMPT_ECHO_OFF()) { 248 # Assume asking for a password (old first, then new) 249 push @res, PAM_SUCCESS(); 250 if ($msg =~ /old|current|login/i) { 251 push @res, $in{'old'}; 252 } 253 else { 254 push @res, $in{'new1'}; 255 } 256 } 257 else { 258 # Some message .. ignore it 259 push @res, PAM_SUCCESS(); 260 push @res, undef; 261 } 262 } 263push @res, PAM_SUCCESS(); 264return @res; 265} 266 267