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