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