Listing 1.
The Unix Password-Changing Script
Lincoln D. Stein
Safely Empowering Your CGI Scripts
The Perl Journal, Summer 1998
  The Unix Password-Changing Script
0 #!/usr/bin/perl -T
1
2 # preliminaries to satisfy taint checks
3 $ENV{PATH} = '/bin:/usr/bin';
4 $ENV{IFS} = '';
5
6 # Prevent buffering problems
7 $| = 1;
8
9 use CGI qw/:standard :html3/;
10
11 print header,
12   start_html(-title=>'Change Unix Password',
13                                  -bgcolor=>'white'),
14   h1('Change your Unix password');
15
16 import_names('Q');
17
18 TRY: {
19   last TRY unless $Q::user;
20   my ($rv,$msg) = check_consistency();
21   do_error($msg),last TRY unless $rv;
22
23   # Change the password, after temporarily turning off
24   # an annoying (and irrelevant) error message from su
25   open(SAVERR, ">&STDERR");
26   open(STDERR, ">/dev/null");
27   ($rv,$msg) = set_passwd($Q::user,$Q::old,$Q::new1);
28   open(STDERR, ">&SAVERR");
29   do_error($msg),last TRY unless $rv;
30
31   print $msg;
32   $OK++;
33 }
34
35 create_form() unless $OK;
36
37 print
38   p,
39   a({href=>"$Q::referer" || referer() },"[ EXIT SCRIPT ]");
40   hr,
41   a({href=>'/'},'Home page'),
42   end_html;
43
44 sub check_consistency {
45     return (undef,'Please fill in the user name field.') unless $Q::user;
46     return (undef,'Please fill in the old password field.') unless $Q::old;
47     return (undef,'Please fill in the new password fields.') unless $Q::new1 && $Q::new2;
48     return (undef,"New password fields don't match.") unless $Q::new1 eq $Q::new2;
49     return (undef,"Suspicious user name $Q::user.") unless $Q::user=~/^\w{3,8}$/;
50     return (undef,'Suspiciously long old password.') unless length($Q::old) <= 30;
51     return (undef,'Suspiciously long new password.') unless length($Q::new1) <= 30;
52     my $uid = (getpwnam($Q::user))[2];
53     return (undef,"Unknown user name $Q::user.") if $uid eq '';
54     return (undef,"Can't use this script to set root password.") if $uid == 0;
55     return 1;
56 }
57
58 sub set_passwd ($$$) {
59     require "chat2.pl";
60     my $TIMEOUT = 2;
61     my $PASSWD = "/usr/bin/passwd";
62     my $SU = '/bin/su';
63
64     my ($user,$old,$new) = @_;
65
66     my $h = chat::open_proc($SU,'-c',$PASSWD,$user)
67         || return (undef,"Couldn't open $SU -c $PASSWD: $!");
68
69     # wait for su to prompt for password
70     my $rv = chat::expect($h,$TIMEOUT,
71                           'Password:' => "'ok'",
72                           'user \w+ does not exist' => "'unknown user'"
73                         );
74     $rv || return (undef,"Didn't get su password prompt.");
75     $rv eq 'unknown user' && return (undef,"User $user unknown.");
76     chat::print($h, "$old\n");
77
78     # wait for passwd to prompt for old password
79     $rv = chat::expect($h,$TIMEOUT,
80                        'Enter old password:' => "'ok'",
81                        'incorrect password' => "'not ok'");
82     $rv || return (undef, "Didn't get prompt for old password.");
83     $rv eq 'not ok' && return (undef,"Old password is incorrect.");
84
85     # print old password
86     chat::print($h, "$old\n");
87     $rv = chat::expect($h,$TIMEOUT,
88                        'Enter new password: ' => "'ok'",
89                        'Illegal' => "'not ok'");
90     $rv || return (undef,"Timed out without seeing prompt for new password.");
91     $rv eq 'not ok' && return (undef,"Old password is incorrect.");
92
93     # print new password
94     chat::print($h,"$new\n");
95     ($rv,$msg) = chat::expect($h,$TIMEOUT,
96                               'Re-type new password: ' => "'ok'",
97                               '([\s\S]+)Enter new password:' => "('rejected',\$1)"
98                              );
99     $rv || return (undef,"Timed out without seeing 2d prompt for new password.");
100    $rv eq 'rejected' && return (undef,$msg);
101
102     # reconfirm password
103     chat::print($h, "$new\n");
104     $rv = chat::expect($h,$TIMEOUT,
105                        'Password changed' => "'ok'");
106     $rv || return (undef,"Password program failed at very end.");
107     chat::close($h);
108
109      return (1,"Password changed successfully for $user.");
110   }
111
112 sub create_form {
113     print
114         start_form,
115         table(
116           TR({align=>RIGHT},
117            th('User name'),
118            td(textfield(-name=>'user')),
119            th('Old password'),
120            td(password_field(-name=>'old'))),
121           TR({align=>RIGHT},
122            th('New password'),
123            td(password_field(-name=>'new1')),
124            th('Confirm new password'),
125            td(password_field(-name=>'new2'))),
126         ),
127         hidden(-name=>'referer',-value=>referer()),
128         submit('Change Password'),
129         end_form;
130 }
131
132 sub do_error ($) {
133     print font({-color=>'red',-size=>'+1'},
134           b('Error:'),shift," Password not changed.");
135 }