1: #!/usr/bin/perl
2: #
3: # lcpasswd
4: #
5: # Scott Harrison
6: # SH: October 27, 2000
7: # SH: October 28, 2000
8: # SH: October 29, 2000
9:
10: use strict;
11:
12: # This script is a setuid script that should
13: # be run by user 'www'. This script allows
14: # for synchronous entry of passwords into
15: # both the /etc/passwd and the /etc/smbpasswd
16: # files.
17:
18: # This script works under the same process control mechanism
19: # as lcuseradd and lcpasswd, to make sure that only one of these
20: # processes is running at any one time on the system.
21:
22: # Standard input usage
23: # First line is USERNAME
24: # Second line is CURRENT PASSWORD
25: # Third line is NEW PASSWORD
26:
27: # Valid passwords must consist of the
28: # ascii characters within the inclusive
29: # range of 0x20 (32) to 0x7E (126).
30: # These characters are:
31: # SPACE and
32: # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO
33: # PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
34:
35: # Valid user names must consist of ascii
36: # characters that are alphabetical characters
37: # (A-Z,a-z), numeric (0-9), or the underscore
38: # mark (_). (Essentially, the perl regex \w).
39:
40: # Command-line arguments
41: # Yes, but be very careful here (don't pass shell commands)
42: # and this is only supported to allow perl-system calls.
43:
44: # Usage within code
45: # Note: NEVER run as system("/home/httpd/perl/lcpasswd NAME OLDPWD NEWPWD")
46: #
47: # $exitcode=system("/home/httpd/perl/lcpasswd","NAME","OLDPWD","NEWPWD")/256;
48: # print "uh-oh" if $exitcode;
49:
50: # These are the exit codes.
51: # ( (0,"ok"),
52: # (1,"User ID mismatch. This program must be run as user 'www'"),
53: # (2,"Error. This program needs 3 command-line arguments (username, old password, new password)."),
54: # (3,"Error. Three lines need to be entered into standard input."),
55: # (4,"Error. Too many other simultaneous password change requests being made."),
56: # (5,"Error. User $username does not exist."),
57: # (6,"Error. Invalid entry of current password."),
58: # (7,"Error. Root was not successfully enabled."),
59: # (8,"Error. Cannot open /etc/passwd."),
60: # (9,"Error. The user name specified has invalid characters."),
61: # (10,"Error. A password entry had an invalid character.") )
62:
63: # Security
64: $ENV{'PATH'}='/bin:/usr/bin'; # Nullify path information except for what smbpasswd needs
65: $ENV{'BASH_ENV'}=''; # Nullify shell environment information.
66:
67: # Do not print error messages if there are command-line arguments
68: my $noprint=0;
69: if (@ARGV) {
70: $noprint=1;
71: }
72:
73: # Read in /etc/passwd, and make sure this process is running from user=www
74: open (IN, '</etc/passwd');
75: my @lines=<IN>;
76: close IN;
77: my $wwwid;
78: for my $l (@lines) {
79: chop $l;
80: my @F=split(/\:/,$l);
81: if ($F[0] eq 'www') {$wwwid=$F[2];}
82: }
83: if ($wwwid!=$<) {
84: print("User ID mismatch. This program must be run as user 'www'\n") unless $noprint;
85: exit 1;
86: }
87: &disable_root_capability;
88:
89: # Handle case of another lcpasswd process
90: unless (&try_to_lock('/tmp/lock_lcpasswd')) {
91: print "Error. Too many other simultaneous password change requests being made.\n" unless $noprint;
92: exit 4;
93: }
94:
95: # Gather input. Should only be 3 values.
96: my @input;
97: if (@ARGV==3) {
98: @input=@ARGV;
99: }
100: elsif (@ARGV) {
101: print("Error. This program needs 3 command-line arguments (username, old password, new password).\n") unless $noprint;
102: unlink('/tmp/lock_lcpasswd');
103: exit 2;
104: }
105: else {
106: @input=<>;
107: if (@input!=3) {
108: print("Error. Three lines need to be entered into standard input.\n") unless $noprint;
109: unlink('/tmp/lock_lcpasswd');
110: exit 3;
111: }
112: map {chop} @input;
113: }
114:
115: my ($username,$oldpwd,$newpwd)=@input;
116: $username=~/^(\w+)$/;
117: my $safeusername=$1;
118: if ($username ne $safeusername) {
119: print "Error. The user name specified has invalid characters.\n";
120: unlink('/tmp/lock_lcpasswd');
121: exit 9;
122: }
123: my $pbad=0;
124: map {if (($_<32)&&($_>126)){$pbad=1;}} (split(//,$oldpwd));
125: map {if (($_<32)&&($_>126)){$pbad=1;}} (split(//,$newpwd));
126: if ($pbad) {
127: print "Error. A password entry had an invalid character.\n";
128: unlink('/tmp/lock_lcpasswd');
129: exit 10;
130: }
131:
132: # Grab the line corresponding to username
133: my ($userid,$useroldcryptpwd);
134: my @F; my @U;
135: for my $l (@lines) {
136: @F=split(/\:/,$l);
137: if ($F[0] eq $username) {($userid,$useroldcryptpwd)=($F[2],$F[1]); @U=@F;}
138: }
139:
140: # Verify existence of user
141: if (!defined($userid)) {
142: print "Error. User $username does not exist.\n" unless $noprint;
143: unlink('/tmp/lock_lcpasswd');
144: exit 5;
145: }
146:
147: # Verify password entry
148: if (crypt($oldpwd,$useroldcryptpwd) ne $useroldcryptpwd) {
149: print "Error. Invalid entry of current password.\n" unless $noprint;
150: unlink('/tmp/lock_lcpasswd');
151: exit 6;
152: }
153:
154: # Construct new password entry (random salt)
155: my $newcryptpwd=crypt($newpwd,(join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64]));
156: $U[1]=$newcryptpwd;
157: my $userline=join(':',@U);
158: my $rootid=&enable_root_capability;
159: if ($rootid!=0) {
160: print "Error. Root was not successfully enabled.\n" unless $noprint;
161: unlink('/tmp/lock_lcpasswd');
162: exit 7;
163: }
164: open PASSWORDFILE, '>/etc/passwd' or (print("Error. Cannot open /etc/passwd.\n") && unlink('/tmp/lock_lcpasswd') && exit(8));
165: for my $l (@lines) {
166: @F=split(/\:/,$l);
167: if ($F[0] eq $username) {print PASSWORDFILE "$userline\n";}
168: else {print PASSWORDFILE "$l\n";}
169: }
170: close PASSWORDFILE;
171:
172: ($>,$<)=(0,0); # fool smbpasswd here to think this is not a setuid environment
173: unless (-e '/etc/smbpasswd') {
174: open (OUT,'>/etc/smbpasswd'); close OUT;
175: }
176: my $smbexist=0;
177: open (IN, '</etc/smbpasswd');
178: my @lines=<IN>;
179: close IN;
180: for my $l (@lines) {
181: chop $l;
182: my @F=split(/\:/,$l);
183: if ($F[0] eq $username) {$smbexist=1;}
184: }
185: unless ($smbexist) {
186: open(OUT,'>>/etc/smbpasswd');
187: print OUT join(':',($safeusername,$userid,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX','','/home/'.$safeusername,'/bin/bash')) . "\n";
188: close OUT;
189: }
190: open(OUT,"|/usr/bin/smbpasswd -s $safeusername>/dev/null");
191: print OUT $newpwd; print OUT "\n";
192: print OUT $newpwd; print OUT "\n";
193: close OUT;
194: $<=$wwwid; # unfool the program
195: &disable_root_capability;
196: unlink('/tmp/lock_lcpasswd');
197: exit 0;
198:
199: # ----------------------------------------------------------- have setuid script run as root
200: sub enable_root_capability {
201: if ($wwwid==$>) {
202: ($<,$>)=($>,$<);
203: ($(,$))=($),$();
204: }
205: else {
206: # root capability is already enabled
207: }
208: return $>;
209: }
210:
211: # ----------------------------------------------------------- have setuid script run as www
212: sub disable_root_capability {
213: if ($wwwid==$<) {
214: ($<,$>)=($>,$<);
215: ($(,$))=($),$();
216: }
217: else {
218: # root capability is already disabled
219: }
220: }
221:
222: # ----------------------------------- make sure that another lcpasswd process isn't running
223: sub try_to_lock {
224: my ($lockfile)=@_;
225: my $currentpid;
226: my $lastpid;
227: # Do not manipulate lock file as root
228: if ($>==0) {
229: return 0;
230: }
231: # Try to generate lock file.
232: # Wait 3 seconds. If same process id is in
233: # lock file, then assume lock file is stale, and
234: # go ahead. If process id's fluctuate, try
235: # for a maximum of 10 times.
236: for (0..10) {
237: if (-e $lockfile) {
238: open(LOCK,"<$lockfile");
239: $currentpid=<LOCK>;
240: close LOCK;
241: if ($currentpid==$lastpid) {
242: last;
243: }
244: sleep 3;
245: $lastpid=$currentpid;
246: }
247: else {
248: last;
249: }
250: if ($_==10) {
251: return 0;
252: }
253: }
254: open(LOCK,">$lockfile");
255: print LOCK $$;
256: close LOCK;
257: return 1;
258: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>