]> git.cameronkatri.com Git - pw-darwin.git/blob - adduser/rmuser.perl
Don't show on the screen just securely entered password
[pw-darwin.git] / adduser / rmuser.perl
1 #!/usr/bin/perl
2 # -*- perl -*-
3 # Copyright 1995, 1996 Guy Helmer, Madison, South Dakota 57042.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer as
11 # the first lines of this file unmodified.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions and the following disclaimer in the
14 # documentation and/or other materials provided with the distribution.
15 # 3. The name of the author may not be used to endorse or promote products
16 # derived from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR
19 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 # IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
22 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #
29 # rmuser - Perl script to remove users
30 #
31 # Guy Helmer <ghelmer@alpha.dsu.edu>, 07/17/96
32 #
33 # $Id: removeuser.perl,v 1.2 1996/08/11 13:03:25 wosch Exp $
34
35 sub LOCK_SH {0x01;}
36 sub LOCK_EX {0x02;}
37 sub LOCK_NB {0x04;}
38 sub LOCK_UN {0x08;}
39 sub F_SETFD {2;}
40
41 $ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin";
42 umask(022);
43 $whoami = $0;
44 $passwd_file = "/etc/master.passwd";
45 $new_passwd_file = "${passwd_file}.new.$$";
46 $group_file = "/etc/group";
47 $new_group_file = "${group_file}.new.$$";
48 $mail_dir = "/var/mail";
49 $crontab_dir = "/var/cron/tabs";
50 $atjob_dir = "/var/at/jobs";
51
52 #$debug = 1;
53
54 sub cleanup {
55 local($sig) = @_;
56
57 print STDERR "Caught signal SIG$sig -- cleaning up.\n";
58 &unlockpw;
59 if (-e $new_passwd_file) {
60 unlink $new_passwd_file;
61 }
62 exit(0);
63 }
64
65 sub lockpw {
66 # Open the password file for reading
67 if (!open(MASTER_PW, "$passwd_file")) {
68 print STDERR "${whoami}: Error: Couldn't open ${passwd_file}: $!\n";
69 exit(1);
70 }
71 # Set the close-on-exec flag just in case
72 fcntl(MASTER_PW, &F_SETFD, 1);
73 # Apply an advisory lock the password file
74 if (!flock(MASTER_PW, &LOCK_EX|&LOCK_NB)) {
75 print STDERR "Couldn't lock ${passwd_file}: $!\n";
76 exit(1);
77 }
78 }
79
80 sub unlockpw {
81 flock(MASTER_PW, &LOCK_UN);
82 }
83
84 $SIG{'INT'} = 'cleanup';
85 $SIG{'QUIT'} = 'cleanup';
86 $SIG{'HUP'} = 'cleanup';
87 $SIG{'TERM'} = 'cleanup';
88
89 if ($#ARGV > 0) {
90 print STDERR "usage: ${whoami} [username]\n";
91 exit(1);
92 }
93
94 if ($< != 0) {
95 print STDERR "${whoami}: Error: you must be root to use ${whoami}\n";
96 exit(1);
97 }
98
99 &lockpw;
100
101 if ($#ARGV == 0) {
102 # Username was given as a parameter
103 $login_name = pop(@ARGV);
104 } else {
105 # Get the user name from the user
106 $login_name = &get_login_name;
107 }
108
109 if (($pw_ent = &check_login_name($login_name)) eq '0') {
110 print STDERR "${whoami}: Error: User ${login_name} not in password database\n";
111 &unlockpw;
112 exit 1;
113 }
114
115 ($name, $password, $uid, $gid, $class, $change, $expire, $gecos, $home_dir,
116 $shell) = split(/:/, $pw_ent);
117
118 if ($uid == 0) {
119 print "${whoami}: Sorry, I'd rather not remove a user with a uid of 0.\n";
120 &unlockpw;
121 exit 1;
122 }
123
124 print "Matching password entry:\n\n$pw_ent\n\n";
125
126 $ans = &get_yn("Is this the entry you wish to remove? ");
127
128 if ($ans eq 'N') {
129 print "User ${login_name} not removed.\n";
130 &unlockpw;
131 exit 0;
132 }
133
134 #
135 # Get owner of user's home directory; don't remove home dir if not
136 # owned by $login_name
137
138 $remove_directory = 1;
139
140 if (-l $home_dir) {
141 $real_home_dir = &resolvelink($home_dir);
142 } else {
143 $real_home_dir = $home_dir;
144 }
145
146 #
147 # If home_dir is a symlink and points to something that isn't a directory,
148 # or if home_dir is not a symlink and is not a directory, don't remove
149 # home_dir -- seems like a good thing to do, but probably isn't necessary...
150 if (((-l $home_dir) && ((-e $real_home_dir) && !(-d $real_home_dir))) ||
151 (!(-l $home_dir) && !(-d $home_dir))) {
152 print STDERR "${whoami}: Home ${home_dir} is not a directory, so it won't be removed\n";
153 $remove_directory = 0;
154 }
155
156 if (length($real_home_dir) && -d $real_home_dir) {
157 $dir_owner = (stat($real_home_dir))[4]; # UID
158 if ($dir_owner != $uid) {
159 print STDERR "${whoami}: Home dir ${real_home_dir} is not owned by ${login_name} (uid ${dir_owner})\n";
160 $remove_directory = 0;
161 }
162 }
163
164 if ($remove_directory) {
165 $ans = &get_yn("Remove user's home directory ($home_dir)? ");
166 if ($ans eq 'N') {
167 $remove_directory = 0;
168 }
169 }
170
171 #exit 0 if $debug;
172
173 #
174 # Remove the user's crontab, if there is one
175 # (probably needs to be done before password databases are updated)
176
177 if (-e "$crontab_dir/$login_name") {
178 print STDERR "Removing user's crontab:";
179 system('/usr/bin/crontab', '-u', $login_name, '-r');
180 print STDERR " done.\n";
181 }
182
183 #
184 # Remove the user's at jobs, if any
185 # (probably also needs to be done before password databases are updated)
186
187 &remove_at_jobs($login_name, $uid);
188
189 #
190 # Copy master password file to new file less removed user's entry
191
192 &update_passwd_file;
193
194 #
195 # Remove the user from all groups in /etc/group
196
197 &update_group_file($login_name);
198
199 #
200 # Remove the user's home directory
201
202 if ($remove_directory) {
203 print STDERR "Removing user's home directory ($home_dir):";
204 &remove_dir($home_dir);
205 print STDERR " done.\n";
206 }
207
208 #
209 # Remove the user's incoming mail file
210
211 if (-e "$mail_dir/$login_name" || -l "$mail_dir/$login_name") {
212 print STDERR "Removing user's incoming mail file ($mail_dir/$login_name):";
213 unlink "$mail_dir/$login_name" ||
214 print STDERR "\n${whoami}: warning: unlink on $mail_dir/$login_name failed ($!) - continuing\n";
215 print STDERR " done.\n";
216 }
217
218 #
219 # All done!
220
221 exit 0;
222
223 sub get_login_name {
224 #
225 # Get new user's name
226 local($done, $login_name);
227
228 for ($done = 0; ! $done; ) {
229 print "Enter login name for user to remove: ";
230 $login_name = <>;
231 chop $login_name;
232 if (!($login_name =~ /[A-Za-z0-9_]/)) {
233 print STDERR "Sorry, login name must contain alphanumeric characters only.\n";
234 } elsif (length($login_name) > 8 || length($login_name) == 0) {
235 print STDERR "Sorry, login name must be eight characters or less.\n";
236 } else {
237 $done = 1;
238 }
239 }
240
241 print "User name is ${login_name}\n" if $debug;
242 return($login_name);
243 }
244
245 sub check_login_name {
246 #
247 # Check to see whether login name is in password file
248 local($login_name) = @_;
249 local($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire,
250 $Mgecos, $Mhome_dir, $Mshell);
251 local($i);
252
253 seek(MASTER_PW, 0, 0);
254 while ($i = <MASTER_PW>) {
255 chop $i;
256 ($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire,
257 $Mgecos, $Mhome_dir, $Mshell) = split(/:/, $i);
258 if ($Mname eq $login_name) {
259 seek(MASTER_PW, 0, 0);
260 return($i); # User is in password database
261 }
262 }
263 seek(MASTER_PW, 0, 0);
264
265 return '0'; # User wasn't found
266 }
267
268 sub get_yn {
269 #
270 # Get a yes or no answer; return 'Y' or 'N'
271 local($prompt) = @_;
272 local($done, $ans);
273
274 for ($done = 0; ! $done; ) {
275 print $prompt;
276 $ans = <>;
277 chop $ans;
278 $ans =~ tr/a-z/A-Z/;
279 if (!($ans =~ /^[YN]/)) {
280 print STDERR "Please answer (y)es or (n)o.\n";
281 } else {
282 $done = 1;
283 }
284 }
285
286 return(substr($ans, 0, 1));
287 }
288
289 sub update_passwd_file {
290 local($skipped, $i);
291
292 print STDERR "Updating password file,";
293 seek(MASTER_PW, 0, 0);
294 open(NEW_PW, ">$new_passwd_file") ||
295 die "\n${whoami}: Error: Couldn't open file ${new_passwd_file}:\n $!\n";
296 chmod(0600, $new_passwd_file) ||
297 print STDERR "\n${whoami}: warning: couldn't set mode of $new_passwd_file to 0600 ($!)\n\tcontinuing, but please check mode of /etc/master.passwd!\n";
298 $skipped = 0;
299 while ($i = <MASTER_PW>) {
300 if ($i =~ /\n$/) {
301 chop $i;
302 }
303 if ($i ne $pw_ent) {
304 print NEW_PW "$i\n";
305 } else {
306 print STDERR "Dropped entry for $login_name\n" if $debug;
307 $skipped = 1;
308 }
309 }
310 close(NEW_PW);
311 seek(MASTER_PW, 0, 0);
312
313 if ($skipped == 0) {
314 print STDERR "\n${whoami}: Whoops! Didn't find ${login_name}'s entry second time around!\n";
315 unlink($new_passwd_file) ||
316 print STDERR "\n${whoami}: warning: couldn't unlink $new_passwd_file ($!)\n\tPlease investigate, as this file should not be left in the filesystem\n";
317 &unlockpw;
318 exit 1;
319 }
320
321 #
322 # Run pwd_mkdb to install the updated password files and databases
323
324 print STDERR " updating databases,";
325 system('/usr/sbin/pwd_mkdb', '-p', ${new_passwd_file});
326 print STDERR " done.\n";
327
328 close(MASTER_PW); # Not useful anymore
329 }
330
331 sub update_group_file {
332 local($login_name) = @_;
333
334 local($i, $j, $grmember_list, $new_grent);
335 local($grname, $grpass, $grgid, $grmember_list, @grmembers);
336
337 print STDERR "Updating group file:";
338 open(GROUP, $group_file) ||
339 die "\n${whoami}: Error: couldn't open ${group_file}: $!\n";
340 if (!flock(GROUP, &LOCK_EX|&LOCK_NB)) {
341 print STDERR "\n${whoami}: Error: couldn't lock ${group_file}: $!\n";
342 exit 1;
343 }
344 local($group_perms, $group_uid, $group_gid) =
345 (stat(GROUP))[2, 4, 5]; # File Mode, uid, gid
346 open(NEW_GROUP, ">$new_group_file") ||
347 die "\n${whoami}: Error: couldn't open ${new_group_file}: $!\n";
348 chmod($group_perms, $new_group_file) ||
349 printf STDERR "\n${whoami}: warning: could not set permissions of new group file to %o ($!)\n\tContinuing, but please check permissions of $group_file!\n", $group_perms;
350 chown($group_uid, $group_gid, $new_group_file) ||
351 print STDERR "\n${whoami}: warning: could not set owner/group of new group file to ${group_uid}/${group_gid} ($!)\n\rContinuing, but please check ownership of $group_file!\n";
352 while ($i = <GROUP>) {
353 if (!($i =~ /$login_name/)) {
354 # Line doesn't contain any references to the user, so just add it
355 # to the new file
356 print NEW_GROUP $i;
357 } else {
358 #
359 # Remove the user from the group
360 if ($i =~ /\n$/) {
361 chop $i;
362 }
363 ($grname, $grpass, $grgid, $grmember_list) = split(/:/, $i);
364 @grmembers = split(/,/, $grmember_list);
365 undef @new_grmembers;
366 local(@new_grmembers);
367 foreach $j (@grmembers) {
368 if ($j ne $login_name) {
369 push(new_grmembers, $j);
370 } elsif ($debug) {
371 print STDERR "Removing $login_name from group $grname\n";
372 }
373 }
374 if ($grname eq $login_name && $#new_grmembers == -1) {
375 # Remove a user's personal group if empty
376 print STDERR "Removing group $grname -- personal group is empty\n";
377 } else {
378 $grmember_list = join(',', @new_grmembers);
379 $new_grent = join(':', $grname, $grpass, $grgid, $grmember_list);
380 print NEW_GROUP "$new_grent\n";
381 }
382 }
383 }
384 close(NEW_GROUP);
385 rename($new_group_file, $group_file) || # Replace old group file with new
386 die "\n${whoami}: error: couldn't rename $new_group_file to $group_file ($!)\n";
387 close(GROUP); # File handle is worthless now
388 print STDERR " done.\n";
389 }
390
391 sub remove_dir {
392 # Remove the user's home directory
393 local($dir) = @_;
394 local($linkdir);
395
396 if (-l $dir) {
397 $linkdir = &resolvelink($dir);
398 # Remove the symbolic link
399 unlink($dir) ||
400 warn "${whoami}: Warning: could not unlink symlink $dir: $!\n";
401 if (!(-e $linkdir)) {
402 #
403 # Dangling symlink - just return now
404 return;
405 }
406 # Set dir to be the resolved pathname
407 $dir = $linkdir;
408 }
409 if (!(-d $dir)) {
410 print STDERR "${whoami}: Warning: $dir is not a directory\n";
411 unlink($dir) || warn "${whoami}: Warning: could not unlink $dir: $!\n";
412 return;
413 }
414 system('/bin/rm', '-rf', $dir);
415 }
416
417 sub remove_at_jobs {
418 local($login_name, $uid) = @_;
419 local($i, $owner, $found);
420
421 $found = 0;
422 opendir(ATDIR, $atjob_dir) || return;
423 while ($i = readdir(ATDIR)) {
424 next if $i eq '.';
425 next if $i eq '..';
426 next if $i eq '.lockfile';
427
428 $owner = (stat("$atjob_dir/$i"))[4]; # UID
429 if ($uid == $owner) {
430 if (!$found) {
431 print STDERR "Removing user's at jobs:";
432 $found = 1;
433 }
434 # Use atrm to remove the job
435 print STDERR " $i";
436 system('/usr/bin/atrm', $i);
437 }
438 }
439 closedir(ATDIR);
440 if ($found) {
441 print STDERR " done.\n";
442 }
443 }
444
445 sub resolvelink {
446 local($path) = @_;
447 local($l);
448
449 while (-l $path && -e $path) {
450 if (!defined($l = readlink($path))) {
451 die "${whoami}: readlink on $path failed (but it should have worked!): $!\n";
452 }
453 if ($l =~ /^\//) {
454 # Absolute link
455 $path = $l;
456 } else {
457 # Relative link
458 $path =~ s/\/[^\/]+\/?$/\/$l/; # Replace last component of path
459 }
460 }
461 return $path;
462 }