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