Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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