sync with FreeBSD, except for the damned tcl script.
authordownsj <downsj@openbsd.org>
Sun, 8 Dec 1996 13:57:06 +0000 (13:57 +0000)
committerdownsj <downsj@openbsd.org>
Sun, 8 Dec 1996 13:57:06 +0000 (13:57 +0000)
usr.sbin/adduser/Makefile
usr.sbin/adduser/adduser.perl
usr.sbin/adduser/rmgroup.8 [new file with mode: 0644]
usr.sbin/adduser/rmgroup.sh [new file with mode: 0644]
usr.sbin/adduser/rmuser.8 [new file with mode: 0644]
usr.sbin/adduser/rmuser.perl [new file with mode: 0644]

index bd7175c..22f7b88 100644 (file)
@@ -1,9 +1,13 @@
-#      $OpenBSD: Makefile,v 1.1 1996/09/28 05:58:34 downsj Exp $
+#      $OpenBSD: Makefile,v 1.2 1996/12/08 13:57:06 downsj Exp $
+#      $From: Makefile,v 1.9 1996/11/17 03:51:26 wosch Exp $
 
-MAN=   adduser.8 adduser_proc.8
+SCRIPTS= adduser.perl rmuser.perl rmgroup.sh
+MAN=    adduser.8 adduser_proc.8 rmuser.8 rmgroup.8
 
 beforeinstall:
-       install ${COPY} -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
-           ${.CURDIR}/adduser.perl ${DESTDIR}${BINDIR}/adduser
+.for script in ${SCRIPTS}
+       ${INSTALL} -c -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+           ${.CURDIR}/${script} ${DESTDIR}${BINDIR}/${script:R}
+.endfor
 
 .include <bsd.prog.mk>
index fe0152b..5f5c919 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 #
-#      $OpenBSD: adduser.perl,v 1.1 1996/09/28 05:58:35 downsj Exp $
+#      $OpenBSD: adduser.perl,v 1.2 1996/12/08 13:57:07 downsj Exp $
 #
 # Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
 # All rights reserved.
@@ -26,7 +26,7 @@
 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.
 #
-# $From: adduser.perl,v 1.19 1996/09/17 19:34:56 wosch Exp $
+# $From: adduser.perl,v 1.22 1996/12/07 21:25:12 ache Exp $
 
 
 # read variables
@@ -257,7 +257,7 @@ sub passwd_read {
        print "User $p_username: illegal shell: ``$sh''\n"
            if ($verbose && $sh &&
                !$shell{&basename($sh)} &&
-               $p_username !~ /^(bin|uucp|falcon|nobody)$/ &&
+               $p_username !~ /^(news|xten|bin|nobody|uucp)$/ &&
                $sh !~ /\/(pppd|sliplogin)$/);
        $uid{$p_uid} = $p_username;
        $pwgid{$p_gid} = $p_username;
@@ -309,7 +309,7 @@ sub new_users_name {
     local($name);
 
     while(1) {
-       $name = &confirm_list("Enter username", 1, "a-z0-9", "");
+       $name = &confirm_list("Enter username", 1, "A-Za-z0-9_", "");
        if (length($name) > 8) {
            warn "Username is longer than 8 chars\a\n";
            next;
@@ -1188,8 +1188,8 @@ sub message_create {
 
 \$fullname,
 
-your account ``\$name'' was created. Your password is ``\$password''.
-Please expire your password. Have fun!
+your account ``\$name'' was created.
+Have fun!
 
 See also chpass(1), finger(1), passwd(1)
 EOF
diff --git a/usr.sbin/adduser/rmgroup.8 b/usr.sbin/adduser/rmgroup.8
new file mode 100644 (file)
index 0000000..787987f
--- /dev/null
@@ -0,0 +1,53 @@
+.\"    $OpenBSD: rmgroup.8,v 1.1 1996/12/08 13:57:08 downsj Exp $
+.\"
+.\" Copyright (c) 1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $From: rmgroup.8,v 1.1 1996/11/04 17:21:11 wosch Exp $
+
+.Dd Oct, 30, 1996
+.Dt RMGROUP 8
+.Os
+.Sh NAME
+.Nm rmgroup
+.Nd delete a Unix group
+.Sh SYNOPSIS
+.Nm 
+.Ar group
+.Sh DESCRIPTION
+.Nm 
+delete a Unix group from group database. 
+.Nm 
+do not delete the system groups wheel, daemon, kmem, sys, tty,
+operator, bin, nogroup, nobody,
+and not groups with gid 0.
+.Sh SEE ALSO
+.Xr group 5 ,
+.Xr adduser 8 ,
+.Xr addgroup 8 ,
+.Xr rmuser 8
+.Sh HISTORY
+The
+.Nm
+command appeared in FreeBSD 2.2.
diff --git a/usr.sbin/adduser/rmgroup.sh b/usr.sbin/adduser/rmgroup.sh
new file mode 100644 (file)
index 0000000..b9018b9
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# $OpenBSD: rmgroup.sh,v 1.1 1996/12/08 13:57:08 downsj Exp $
+#
+# Copyright (c) 1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+# All rights reserved.
+#
+# rmgroup - delete a Unix group
+#
+# $From: rmgroup.sh,v 1.1 1996/10/30 20:58:47 wosch Exp $
+
+PATH=/bin:/usr/bin; export PATH
+db=/etc/group
+
+case "$1" in
+       ""|-*)  echo "usage: rmgroup group"; exit 1;;
+       wheel|daemon|kmem|sys|tty|operator|bin|nogroup|nobody)
+               echo "Do not remove system group: $1"; exit 2;;
+       *) group="$1";;
+esac
+
+if egrep -q -- "^$group:" $db; then
+       if egrep -q -- "^$group:\*:0:" $db; then
+               echo "Do not remove group with gid 0: $group"
+               exit 2
+       fi
+       egrep -v -- "^$group:" $db > $db.new &&
+               cp -pf $db $db.bak &&
+               mv -f  $db.new $db
+else 
+       echo "Group \"$group\" does not exists in $db."; exit 1
+fi
diff --git a/usr.sbin/adduser/rmuser.8 b/usr.sbin/adduser/rmuser.8
new file mode 100644 (file)
index 0000000..2632602
--- /dev/null
@@ -0,0 +1,106 @@
+.\"    $OpenBSD: rmuser.8,v 1.1 1996/12/08 13:57:09 downsj Exp $
+.\"
+.\" Copyright 1995, 1996
+.\"     Guy Helmer, Madison, South Dakota 57042.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer as
+.\"    the first lines of this file unmodified.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote products
+.\"    derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\"    $From: rmuser.8,v 1.1 1996/11/17 03:51:31 wosch Exp $
+.\"
+.Dd July 16, 1996
+.Dt RMUSER 8
+.Os
+.Sh NAME
+.Nm rmuser
+.Nd remove users from the system
+.Sh SYNOPSIS
+.Nm rmuser
+.Op Ar username
+.Sh DESCRIPTION
+The utility
+.Nm rmuser
+removes a user's 
+.Xr crontab 1
+entry (if any) and any 
+.Xr at 1
+jobs belonging to the user,
+then removes a user from the system's local password file, removes
+the user's home directory if it is owned by the user, and removes
+the user's incoming mail file if it exists.  The username is removed
+from any groups to which it belongs in the file
+.Pa /etc/group .
+If a group becomes empty and the group name is the same as the username,
+the group is removed (this complements
+.Xr adduser 8 's
+per-user unique groups).
+.Pp
+.Nm rmuser
+politely refuses to remove users whose uid is 0 (typically root), since
+it seemed like a good idea at the time
+.Nm rmuser
+was written.
+.Pp
+.Nm rmuser
+shows the selected user's password file entry and asks for confirmation
+that you wish to remove the user.  If the user's home directory is owned
+by the user (and not by any other user),
+.Nm rmuser
+asks whether you wish to remove the user's home directory and everything
+below.
+.Pp
+Available options:
+.Pp
+.Bl -tag -width username
+.It Ar \&username
+Identifies the user to be removed; if not present,
+.Nm rmuser
+interactively asks for the user to be removed.
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/master.passwd
+.It Pa /etc/passwd
+.It Pa /etc/group
+.It Pa /etc/spwd.db
+.It Pa /etc/pwd.db
+.El
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr chpass 1 ,
+.Xr crontab 1 ,
+.Xr finger 1 ,
+.Xr passwd 1 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr addgroup 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr rmgroup 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+command appeared in FreeBSD 2.1.5.
+
+.\" .Sh AUTHOR
+.\" Guy Helmer, Madison, South Dakota
diff --git a/usr.sbin/adduser/rmuser.perl b/usr.sbin/adduser/rmuser.perl
new file mode 100644 (file)
index 0000000..d19add5
--- /dev/null
@@ -0,0 +1,465 @@
+#!/usr/bin/perl
+# -*- perl -*-
+#
+# $OpenBSD: rmuser.perl,v 1.1 1996/12/08 13:57:09 downsj Exp $
+#
+# Copyright 1995, 1996 Guy Helmer, Madison, South Dakota 57042.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer as
+#    the first lines of this file unmodified.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+#    derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# rmuser - Perl script to remove users
+#
+# Guy Helmer <ghelmer@alpha.dsu.edu>, 07/17/96
+#
+#      $From: rmuser.perl,v 1.2 1996/12/07 21:25:12 ache Exp $
+
+sub LOCK_SH {0x01;}
+sub LOCK_EX {0x02;}
+sub LOCK_NB {0x04;}
+sub LOCK_UN {0x08;}
+sub F_SETFD {2;}
+
+$ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin";
+umask(022);
+$whoami = $0;
+$passwd_file = "/etc/master.passwd";
+$new_passwd_file = "${passwd_file}.new.$$";
+$group_file = "/etc/group";
+$new_group_file = "${group_file}.new.$$";
+$mail_dir = "/var/mail";
+$crontab_dir = "/var/cron/tabs";
+$atjob_dir = "/var/at/jobs";
+
+#$debug = 1;
+
+sub cleanup {
+    local($sig) = @_;
+
+    print STDERR "Caught signal SIG$sig -- cleaning up.\n";
+    &unlockpw;
+    if (-e $new_passwd_file) {
+       unlink $new_passwd_file;
+    }
+    exit(0);
+}
+
+sub lockpw {
+    # Open the password file for reading
+    if (!open(MASTER_PW, "$passwd_file")) {
+       print STDERR "${whoami}: Error: Couldn't open ${passwd_file}: $!\n";
+       exit(1);
+    }
+    # Set the close-on-exec flag just in case
+    fcntl(MASTER_PW, &F_SETFD, 1);
+    # Apply an advisory lock the password file
+    if (!flock(MASTER_PW, &LOCK_EX|&LOCK_NB)) {
+       print STDERR "Couldn't lock ${passwd_file}: $!\n";
+       exit(1);
+    }
+}
+
+sub unlockpw {
+    flock(MASTER_PW, &LOCK_UN);
+}
+
+$SIG{'INT'} = 'cleanup';
+$SIG{'QUIT'} = 'cleanup';
+$SIG{'HUP'} = 'cleanup';
+$SIG{'TERM'} = 'cleanup';
+
+if ($#ARGV > 0) {
+    print STDERR "usage: ${whoami} [username]\n";
+    exit(1);
+}
+
+if ($< != 0) {
+    print STDERR "${whoami}: Error: you must be root to use ${whoami}\n";
+    exit(1);
+}
+
+&lockpw;
+
+if ($#ARGV == 0) {
+    # Username was given as a parameter
+    $login_name = pop(@ARGV);
+} else {
+    # Get the user name from the user
+    $login_name = &get_login_name;
+}
+
+if (($pw_ent = &check_login_name($login_name)) eq '0') {
+    print STDERR "${whoami}: Error: User ${login_name} not in password database\n";
+    &unlockpw;
+    exit 1;
+}
+
+($name, $password, $uid, $gid, $class, $change, $expire, $gecos, $home_dir,
+ $shell) = split(/:/, $pw_ent);
+
+if ($uid == 0) {
+    print "${whoami}: Sorry, I'd rather not remove a user with a uid of 0.\n";
+    &unlockpw;
+    exit 1;
+}
+
+print "Matching password entry:\n\n$pw_ent\n\n";
+
+$ans = &get_yn("Is this the entry you wish to remove? ");
+
+if ($ans eq 'N') {
+    print "User ${login_name} not removed.\n";
+    &unlockpw;
+    exit 0;
+}
+
+#
+# Get owner of user's home directory; don't remove home dir if not
+# owned by $login_name
+
+$remove_directory = 1;
+
+if (-l $home_dir) {
+    $real_home_dir = &resolvelink($home_dir);
+} else {
+    $real_home_dir = $home_dir;
+}
+
+#
+# If home_dir is a symlink and points to something that isn't a directory,
+# or if home_dir is not a symlink and is not a directory, don't remove
+# home_dir -- seems like a good thing to do, but probably isn't necessary...
+if (((-l $home_dir) && ((-e $real_home_dir) && !(-d $real_home_dir))) ||
+    (!(-l $home_dir) && !(-d $home_dir))) {
+    print STDERR "${whoami}: Home ${home_dir} is not a directory, so it won't be removed\n";
+    $remove_directory = 0;
+}
+
+if (length($real_home_dir) && -d $real_home_dir) {
+    $dir_owner = (stat($real_home_dir))[4]; # UID
+    if ($dir_owner != $uid) {
+       print STDERR "${whoami}: Home dir ${real_home_dir} is not owned by ${login_name} (uid ${dir_owner})\n";
+       $remove_directory = 0;
+    }
+}
+
+if ($remove_directory) {
+    $ans = &get_yn("Remove user's home directory ($home_dir)? ");
+    if ($ans eq 'N') {
+       $remove_directory = 0;
+    }
+}
+
+#exit 0 if $debug;
+
+#
+# Remove the user's crontab, if there is one
+# (probably needs to be done before password databases are updated)
+
+if (-e "$crontab_dir/$login_name") {
+    print STDERR "Removing user's crontab:";
+    system('/usr/bin/crontab', '-u', $login_name, '-r');
+    print STDERR " done.\n";
+}
+
+#
+# Remove the user's at jobs, if any
+# (probably also needs to be done before password databases are updated)
+
+&remove_at_jobs($login_name, $uid);
+
+#
+# Copy master password file to new file less removed user's entry
+
+&update_passwd_file;
+
+#
+# Remove the user from all groups in /etc/group
+
+&update_group_file($login_name);
+
+#
+# Remove the user's home directory
+
+if ($remove_directory) {
+    print STDERR "Removing user's home directory ($home_dir):";
+    &remove_dir($home_dir);
+    print STDERR " done.\n";
+}
+
+#
+# Remove the user's incoming mail file
+
+if (-e "$mail_dir/$login_name" || -l "$mail_dir/$login_name") {
+    print STDERR "Removing user's incoming mail file ($mail_dir/$login_name):";
+    unlink "$mail_dir/$login_name" ||
+       print STDERR "\n${whoami}: warning: unlink on $mail_dir/$login_name failed ($!) - continuing\n";
+    print STDERR " done.\n";
+}
+
+#
+# All done!
+
+exit 0;
+
+sub get_login_name {
+    #
+    # Get new user's name
+    local($done, $login_name);
+
+    for ($done = 0; ! $done; ) {
+       print "Enter login name for user to remove: ";
+       $login_name = <>;
+       chop $login_name;
+       if (!($login_name =~ /[A-Za-z0-9_]/)) {
+           print STDERR "Sorry, login name must contain alphanumeric characters only.\n";
+       } elsif (length($login_name) > 16 || length($login_name) == 0) {
+           print STDERR "Sorry, login name must be 16 characters or less.\n";
+       } else {
+           $done = 1;
+       }
+    }
+
+    print "User name is ${login_name}\n" if $debug;
+    return($login_name);
+}
+
+sub check_login_name {
+    #
+    # Check to see whether login name is in password file
+    local($login_name) = @_;
+    local($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire,
+         $Mgecos, $Mhome_dir, $Mshell);
+    local($i);
+
+    seek(MASTER_PW, 0, 0);
+    while ($i = <MASTER_PW>) {
+       chop $i;
+       ($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire,
+        $Mgecos, $Mhome_dir, $Mshell) = split(/:/, $i);
+       if ($Mname eq $login_name) {
+           seek(MASTER_PW, 0, 0);
+           return($i);         # User is in password database
+       }
+    }
+    seek(MASTER_PW, 0, 0);
+
+    return '0';                        # User wasn't found
+}
+
+sub get_yn {
+    #
+    # Get a yes or no answer; return 'Y' or 'N'
+    local($prompt) = @_;
+    local($done, $ans);
+
+    for ($done = 0; ! $done; ) {
+       print $prompt;
+       $ans = <>;
+       chop $ans;
+       $ans =~ tr/a-z/A-Z/;
+       if (!($ans =~ /^[YN]/)) {
+           print STDERR "Please answer (y)es or (n)o.\n";
+       } else {
+           $done = 1;
+       }
+    }
+
+    return(substr($ans, 0, 1));
+}
+
+sub update_passwd_file {
+    local($skipped, $i);
+
+    print STDERR "Updating password file,";
+    seek(MASTER_PW, 0, 0);
+    open(NEW_PW, ">$new_passwd_file") ||
+       die "\n${whoami}: Error: Couldn't open file ${new_passwd_file}:\n $!\n";
+    chmod(0600, $new_passwd_file) ||
+       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";
+    $skipped = 0;
+    while ($i = <MASTER_PW>) {
+       if ($i =~ /\n$/) {
+           chop $i;
+       }
+       if ($i ne $pw_ent) {
+           print NEW_PW "$i\n";
+       } else {
+           print STDERR "Dropped entry for $login_name\n" if $debug;
+           $skipped = 1;
+       }
+    }
+    close(NEW_PW);
+    seek(MASTER_PW, 0, 0);
+
+    if ($skipped == 0) {
+       print STDERR "\n${whoami}: Whoops! Didn't find ${login_name}'s entry second time around!\n";
+       unlink($new_passwd_file) ||
+           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";
+       &unlockpw;
+       exit 1;
+    }
+
+    #
+    # Run pwd_mkdb to install the updated password files and databases
+
+    print STDERR " updating databases,";
+    system('/usr/sbin/pwd_mkdb', '-p', ${new_passwd_file});
+    print STDERR " done.\n";
+
+    close(MASTER_PW);          # Not useful anymore
+}
+
+sub update_group_file {
+    local($login_name) = @_;
+
+    local($i, $j, $grmember_list, $new_grent);
+    local($grname, $grpass, $grgid, $grmember_list, @grmembers);
+
+    print STDERR "Updating group file:";
+    open(GROUP, $group_file) ||
+       die "\n${whoami}: Error: couldn't open ${group_file}: $!\n";
+    if (!flock(GROUP, &LOCK_EX|&LOCK_NB)) {
+       print STDERR "\n${whoami}: Error: couldn't lock ${group_file}: $!\n";
+       exit 1;
+    }
+    local($group_perms, $group_uid, $group_gid) =
+       (stat(GROUP))[2, 4, 5]; # File Mode, uid, gid
+    open(NEW_GROUP, ">$new_group_file") ||
+       die "\n${whoami}: Error: couldn't open ${new_group_file}: $!\n";
+    chmod($group_perms, $new_group_file) ||
+       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;
+    chown($group_uid, $group_gid, $new_group_file) ||
+       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";
+    while ($i = <GROUP>) {
+       if (!($i =~ /$login_name/)) {
+           # Line doesn't contain any references to the user, so just add it
+           # to the new file
+           print NEW_GROUP $i;
+       } else {
+           #
+           # Remove the user from the group
+           if ($i =~ /\n$/) {
+               chop $i;
+           }
+           ($grname, $grpass, $grgid, $grmember_list) = split(/:/, $i);
+           @grmembers = split(/,/, $grmember_list);
+           undef @new_grmembers;
+           local(@new_grmembers);
+           foreach $j (@grmembers) {
+               if ($j ne $login_name) {
+                   push(new_grmembers, $j);
+               } elsif ($debug) {
+                   print STDERR "Removing $login_name from group $grname\n";
+               }
+           }
+           if ($grname eq $login_name && $#new_grmembers == -1) {
+               # Remove a user's personal group if empty
+               print STDERR "Removing group $grname -- personal group is empty\n";
+           } else {
+               $grmember_list = join(',', @new_grmembers);
+               $new_grent = join(':', $grname, $grpass, $grgid, $grmember_list);
+               print NEW_GROUP "$new_grent\n";
+           }
+       }
+    }
+    close(NEW_GROUP);
+    rename($new_group_file, $group_file) || # Replace old group file with new
+       die "\n${whoami}: error: couldn't rename $new_group_file to $group_file ($!)\n";
+    close(GROUP);                      # File handle is worthless now
+    print STDERR " done.\n";
+}
+
+sub remove_dir {
+    # Remove the user's home directory
+    local($dir) = @_;
+    local($linkdir);
+
+    if (-l $dir) {
+       $linkdir = &resolvelink($dir);
+       # Remove the symbolic link
+       unlink($dir) ||
+           warn "${whoami}: Warning: could not unlink symlink $dir: $!\n";
+       if (!(-e $linkdir)) {
+           #
+           # Dangling symlink - just return now
+           return;
+       }
+       # Set dir to be the resolved pathname
+       $dir = $linkdir;
+    }
+    if (!(-d $dir)) {
+       print STDERR "${whoami}: Warning: $dir is not a directory\n";
+       unlink($dir) || warn "${whoami}: Warning: could not unlink $dir: $!\n";
+       return;
+    }
+    system('/bin/rm', '-rf', $dir);
+}
+
+sub remove_at_jobs {
+    local($login_name, $uid) = @_;
+    local($i, $owner, $found);
+
+    $found = 0;
+    opendir(ATDIR, $atjob_dir) || return;
+    while ($i = readdir(ATDIR)) {
+       next if $i eq '.';
+       next if $i eq '..';
+       next if $i eq '.lockfile';
+
+       $owner = (stat("$atjob_dir/$i"))[4]; # UID
+       if ($uid == $owner) {
+           if (!$found) {
+               print STDERR "Removing user's at jobs:";
+               $found = 1;
+           }
+           # Use atrm to remove the job
+           print STDERR " $i";
+           system('/usr/bin/atrm', $i);
+       }
+    }
+    closedir(ATDIR);
+    if ($found) {
+       print STDERR " done.\n";
+    }
+}
+
+sub resolvelink {
+    local($path) = @_;
+    local($l);
+
+    while (-l $path && -e $path) {
+       if (!defined($l = readlink($path))) {
+           die "${whoami}: readlink on $path failed (but it should have worked!): $!\n";
+       }
+       if ($l =~ /^\//) {
+           # Absolute link
+           $path = $l;
+       } else {
+           # Relative link
+           $path =~ s/\/[^\/]+\/?$/\/$l/; # Replace last component of path
+       }
+    }
+    return $path;
+}