ui_hr
[webmin.git] / password_change.cgi
1 #!/usr/local/bin/perl
2 # password_change.cgi
3 # Actually update a user's password by directly modifying /etc/shadow
4
5 $ENV{'MINISERV_INTERNAL'} || die "Can only be called by miniserv.pl";
6 require './web-lib.pl';
7 &init_config();
8 &ReadParse();
9 &get_miniserv_config(\%miniserv);
10 $miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!";
11
12 # Validate inputs
13 $in{'new1'} ne '' || &pass_error($text{'password_enew1'});
14 $in{'new1'} eq $in{'new2'} || &pass_error($text{'password_enew2'});
15
16 # Is this a Webmin user?
17 if (&foreign_check("acl")) {
18         &foreign_require("acl", "acl-lib.pl");
19         ($wuser) = grep { $_->{'name'} eq $in{'user'} } &acl::list_users();
20         if ($wuser->{'pass'} eq 'x') {
21                 # A Webmin user, but using Unix authentication
22                 $wuser = undef;
23                 }
24         elsif ($wuser->{'pass'} eq '*LK*' ||
25                $wuser->{'pass'} =~ /^\!/) {
26                 &pass_error("Webmin users with locked accounts cannot change ".
27                             "their passwords!");
28                 }
29         }
30 if (!$in{'pam'} && !$wuser) {
31         $miniserv{'passwd_cindex'} ne '' && $miniserv{'passwd_mindex'} ne '' || 
32                 die "Missing password file configuration";
33         }
34
35 if ($wuser) {
36         # Update Webmin user's password
37         $enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
38         $enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
39         $perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
40         $perr && &pass_error(&text('password_enewpass', $perr));
41         $wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
42         $wuser->{'temppass'} = 0;
43         &acl::modify_user($wuser->{'name'}, $wuser);
44         &reload_miniserv();
45         }
46 elsif ($in{'pam'}) {
47         # Use PAM to make the change..
48         eval "use Authen::PAM;";
49         if ($@) {
50                 &pass_error(&text('password_emodpam', $@));
51                 }
52
53         # Check if the old password is correct
54         $service = $miniserv{'pam'} ? $miniserv{'pam'} : "webmin";
55         $pamh = new Authen::PAM($service, $in{'user'}, \&pam_check_func);
56         $rv = $pamh->pam_authenticate();
57         $rv == PAM_SUCCESS() ||
58                 &pass_error($text{'password_eold'});
59         $pamh = undef;
60
61         # Change the password with PAM, in a sub-process. This is needed because
62         # the UID must be changed to properly signal to the PAM libraries that
63         # the password change is not being done by the root user.
64         $temp = &transname();
65         $pid = fork();
66         @uinfo = getpwnam($in{'user'});
67         if (!$pid) {
68                 ($>, $<) = (0, $uinfo[2]);
69                 $pamh = new Authen::PAM("passwd", $in{'user'}, \&pam_change_func);
70                 $rv = $pamh->pam_chauthtok();
71                 open(TEMP, ">$temp");
72                 print TEMP "$rv\n";
73                 print TEMP ($messages || $pamh->pam_strerror($rv)),"\n";
74                 close(TEMP);
75                 exit(0);
76                 }
77         waitpid($pid, 0);
78         open(TEMP, $temp);
79         chop($rv = <TEMP>);
80         chop($messages = <TEMP>);
81         close(TEMP);
82         unlink($temp);
83         $rv == PAM_SUCCESS || &pass_error(&text('password_epam', $messages));
84         $pamh = undef;
85         }
86 else {
87         # Directly update password file
88
89         # Read shadow file and find user
90         &lock_file($miniserv{'passwd_file'});
91         $lref = &read_file_lines($miniserv{'passwd_file'});
92         for($i=0; $i<@$lref; $i++) {
93                 @line = split(/:/, $lref->[$i], -1);
94                 local $u = $line[$miniserv{'passwd_uindex'}];
95                 if ($u eq $in{'user'}) {
96                         $idx = $i;
97                         last;
98                         }
99                 }
100         defined($idx) || &pass_error($text{'password_euser'});
101
102         # Validate old password
103         &unix_crypt($in{'old'}, $line[$miniserv{'passwd_pindex'}]) eq
104                 $line[$miniserv{'passwd_pindex'}] ||
105                         &pass_error($text{'password_eold'});
106
107         # Make sure new password meets restrictions
108         if (&foreign_check("changepass")) {
109                 &foreign_require("changepass", "changepass-lib.pl");
110                 $err = &changepass::check_password($in{'new1'}, $in{'user'});
111                 &pass_error($err) if ($err);
112                 }
113         elsif (&foreign_check("useradmin")) {
114                 &foreign_require("useradmin", "user-lib.pl");
115                 $err = &useradmin::check_password_restrictions(
116                                 $in{'new1'}, $in{'user'});
117                 &pass_error($err) if ($err);
118                 }
119
120         # Set new password and save file
121         $salt = chr(int(rand(26))+65) . chr(int(rand(26))+65);
122         $line[$miniserv{'passwd_pindex'}] = &unix_crypt($in{'new1'}, $salt);
123         $days = int(time()/(24*60*60));
124         $line[$miniserv{'passwd_cindex'}] = $days;
125         $lref->[$idx] = join(":", @line);
126         &flush_file_lines();
127         &unlock_file($miniserv{'passwd_file'});
128         }
129
130 # Show ok page
131 &header(undef, undef, undef, undef, 1, 1);
132
133 print "<center><h3>",&text('password_done', "/"),"</h3></center>\n";
134
135 &footer();
136
137 sub pass_error
138 {
139 &header(undef, undef, undef, undef, 1, 1);
140 print &ui_hr();
141
142 print "<center><h3>",$text{'password_err'}," : ",@_,"</h3></center>\n";
143
144 print &ui_hr();
145 &footer();
146 exit;
147 }
148
149 sub pam_check_func
150 {
151 my @res;
152 while ( @_ ) {
153         my $code = shift;
154         my $msg = shift;
155         my $ans = "";
156
157         $ans = $in{'user'} if ($code == PAM_PROMPT_ECHO_ON());
158         $ans = $in{'old'} if ($code == PAM_PROMPT_ECHO_OFF());
159
160         push @res, PAM_SUCCESS();
161         push @res, $ans;
162         }
163 push @res, PAM_SUCCESS();
164 return @res;
165 }
166
167 sub pam_change_func
168 {
169 my @res;
170 while ( @_ ) {
171         my $code = shift;
172         my $msg = shift;
173         my $ans = "";
174         $messages = $msg;
175
176         if ($code == PAM_PROMPT_ECHO_ON()) {
177                 # Assume asking for username
178                 push @res, PAM_SUCCESS();
179                 push @res, $in{'user'};
180                 }
181         elsif ($code == PAM_PROMPT_ECHO_OFF()) {
182                 # Assume asking for a password (old first, then new)
183                 push @res, PAM_SUCCESS();
184                 if ($msg =~ /old|current/i) {
185                         push @res, $in{'old'};
186                         }
187                 else {
188                         push @res, $in{'new1'};
189                         }
190                 }
191         else {
192                 # Some message .. ignore it
193                 push @res, PAM_SUCCESS();
194                 push @res, undef;
195                 }
196         }
197 push @res, PAM_SUCCESS();
198 return @res;
199 }
200