3 # Actually update a user's password by directly modifying /etc/shadow
5 $ENV{'MINISERV_INTERNAL'} || die "Can only be called by miniserv.pl";
6 require './web-lib.pl';
10 &get_miniserv_config(\%miniserv);
11 $miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!";
14 $in{'new1'} ne '' || &pass_error($text{'password_enew1'});
15 $in{'new1'} eq $in{'new2'} || &pass_error($text{'password_enew2'});
17 # Is this a Webmin user?
18 if (&foreign_check("acl")) {
19 &foreign_require("acl", "acl-lib.pl");
20 ($wuser) = grep { $_->{'name'} eq $in{'user'} } &acl::list_users();
21 if ($wuser->{'pass'} eq 'x') {
22 # A Webmin user, but using Unix authentication
25 elsif ($wuser->{'pass'} eq '*LK*' ||
26 $wuser->{'pass'} =~ /^\!/) {
27 &pass_error("Webmin users with locked accounts cannot change ".
31 if (!$in{'pam'} && !$wuser) {
32 $miniserv{'passwd_cindex'} ne '' && $miniserv{'passwd_mindex'} ne '' ||
33 die "Missing password file configuration";
37 # Update Webmin user's password
38 $enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
39 $enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
40 $perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
41 $perr && &pass_error(&text('password_enewpass', $perr));
42 $wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
43 $wuser->{'temppass'} = 0;
44 &acl::modify_user($wuser->{'name'}, $wuser);
47 elsif ($gconfig{'passwd_cmd'}) {
48 # Use some configured command
49 $passwd_cmd = &has_command($gconfig{'passwd_cmd'});
50 $passwd_cmd || &pass_error("The password change command <tt>$gconfig{'passwd_cmd'}</tt> was not found");
52 &foreign_require("proc", "proc-lib.pl");
54 $ENV{'REMOTE_USER'} = $in{'user'}; # some programs need this
55 $passwd_cmd .= " ".quotemeta($in{'user'});
56 ($fh, $fpid) = &proc::pty_process_exec($passwd_cmd, 0, 0);
59 local $rv = &wait_for($fh,
61 '(old|current|login).*:',
63 'too\s+many\s+failures',
64 'attributes\s+changed\s+on|successfully\s+changed',
65 'pick your passwords');
66 $out .= $wait_for_input;
69 # Prompt for the new password
70 syswrite($fh, $in{'new1'}."\n", length($in{'new1'})+1);
73 # Prompt for the old password
74 syswrite($fh, $in{'old'}."\n", length($in{'old'})+1);
77 # Request for a menu option (SCO?)
78 syswrite($fh, "1\n", 2);
81 # Failed too many times
89 # Request for a menu option (HP/UX)
90 syswrite($fh, "p\n", 2);
95 last if (++$count > 10);
100 if ($? || $count > 10 ||
101 $out =~ /error|failed/i || $out =~ /bad\s+password/i) {
102 &pass_error("<tt>".&html_escape($out)."</tt>");
106 # Use PAM to make the change..
107 eval "use Authen::PAM;";
109 &pass_error(&text('password_emodpam', $@));
112 # Check if the old password is correct
113 $service = $miniserv{'pam'} ? $miniserv{'pam'} : "webmin";
114 $pamh = new Authen::PAM($service, $in{'user'}, \&pam_check_func);
115 $rv = $pamh->pam_authenticate();
116 $rv == PAM_SUCCESS() ||
117 &pass_error($text{'password_eold'});
120 # Change the password with PAM, in a sub-process. This is needed because
121 # the UID must be changed to properly signal to the PAM libraries that
122 # the password change is not being done by the root user.
123 $temp = &transname();
125 @uinfo = getpwnam($in{'user'});
127 ($>, $<) = (0, $uinfo[2]);
128 $pamh = new Authen::PAM("passwd", $in{'user'}, \&pam_change_func);
129 $rv = $pamh->pam_chauthtok();
130 open(TEMP, ">$temp");
132 print TEMP ($messages || $pamh->pam_strerror($rv)),"\n";
139 chop($messages = <TEMP>);
142 $rv == PAM_SUCCESS || &pass_error(&text('password_epam', $messages));
146 # Directly update password file
148 # Read shadow file and find user
149 &lock_file($miniserv{'passwd_file'});
150 $lref = &read_file_lines($miniserv{'passwd_file'});
151 for($i=0; $i<@$lref; $i++) {
152 @line = split(/:/, $lref->[$i], -1);
153 local $u = $line[$miniserv{'passwd_uindex'}];
154 if ($u eq $in{'user'}) {
159 defined($idx) || &pass_error($text{'password_euser'});
161 # Validate old password
162 &unix_crypt($in{'old'}, $line[$miniserv{'passwd_pindex'}]) eq
163 $line[$miniserv{'passwd_pindex'}] ||
164 &pass_error($text{'password_eold'});
166 # Make sure new password meets restrictions
167 if (&foreign_check("changepass")) {
168 &foreign_require("changepass", "changepass-lib.pl");
169 $err = &changepass::check_password($in{'new1'}, $in{'user'});
170 &pass_error($err) if ($err);
172 elsif (&foreign_check("useradmin")) {
173 &foreign_require("useradmin", "user-lib.pl");
174 $err = &useradmin::check_password_restrictions(
175 $in{'new1'}, $in{'user'});
176 &pass_error($err) if ($err);
179 # Set new password and save file
180 $salt = chr(int(rand(26))+65) . chr(int(rand(26))+65);
181 $line[$miniserv{'passwd_pindex'}] = &unix_crypt($in{'new1'}, $salt);
182 $days = int(time()/(24*60*60));
183 $line[$miniserv{'passwd_cindex'}] = $days;
184 $lref->[$idx] = join(":", @line);
186 &unlock_file($miniserv{'passwd_file'});
189 # Change password in Usermin too
190 if (&get_product_name() eq 'usermin' &&
191 &foreign_check("changepass")) {
193 &foreign_require("changepass", "changepass-lib.pl");
194 &changepass::change_mailbox_passwords(
195 $in{'user'}, $in{'old'}, $in{'new1'});
199 &header(undef, undef, undef, undef, 1, 1);
201 print "<center><h3>",&text('password_done', "/"),"</h3></center>\n";
207 &header(undef, undef, undef, undef, 1, 1);
210 print "<center><h3>",$text{'password_err'}," : ",@_,"</h3></center>\n";
225 $ans = $in{'user'} if ($code == PAM_PROMPT_ECHO_ON());
226 $ans = $in{'old'} if ($code == PAM_PROMPT_ECHO_OFF());
228 push @res, PAM_SUCCESS();
231 push @res, PAM_SUCCESS();
244 if ($code == PAM_PROMPT_ECHO_ON()) {
245 # Assume asking for username
246 push @res, PAM_SUCCESS();
247 push @res, $in{'user'};
249 elsif ($code == PAM_PROMPT_ECHO_OFF()) {
250 # Assume asking for a password (old first, then new)
251 push @res, PAM_SUCCESS();
252 if ($msg =~ /old|current/i) {
253 push @res, $in{'old'};
256 push @res, $in{'new1'};
260 # Some message .. ignore it
261 push @res, PAM_SUCCESS();
265 push @res, PAM_SUCCESS();