diff options
Diffstat (limited to 'src/userdel.c')
-rw-r--r-- | src/userdel.c | 1355 |
1 files changed, 1355 insertions, 0 deletions
diff --git a/src/userdel.c b/src/userdel.c new file mode 100644 index 0000000..cc951e5 --- /dev/null +++ b/src/userdel.c @@ -0,0 +1,1355 @@ +/* + * Copyright (c) 1991 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2000, Marek Michałkiewicz + * Copyright (c) 2000 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2012, Nicolas François + * 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. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT + * HOLDERS 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. + */ + +#include <config.h> + +#ident "$Id$" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/stat.h> +#ifdef ACCT_TOOLS_SETUID +#ifdef USE_PAM +#include "pam_defs.h" +#endif /* USE_PAM */ +#endif /* ACCT_TOOLS_SETUID */ +#include "defines.h" +#include "getdef.h" +#include "groupio.h" +#include "nscd.h" +#include "sssd.h" +#include "prototypes.h" +#include "pwauth.h" +#include "pwio.h" +#include "shadowio.h" +#ifdef SHADOWGRP +#include "sgroupio.h" +#endif /* SHADOWGRP */ +#ifdef WITH_TCB +#include <tcb.h> +#include "tcbfuncs.h" +#endif /* WITH_TCB */ +/*@-exitarg@*/ +#include "exitcodes.h" +#ifdef ENABLE_SUBIDS +#include "subordinateio.h" +#endif /* ENABLE_SUBIDS */ + +/* + * exit status values + */ +#define E_PW_UPDATE 1 /* can't update password file */ +#define E_NOTFOUND 6 /* specified user doesn't exist */ +#define E_USER_BUSY 8 /* user currently logged in */ +#define E_GRP_UPDATE 10 /* can't update group file */ +#define E_HOMEDIR 12 /* can't remove home directory */ +#define E_SE_UPDATE 14 /* can't update SELinux user mapping */ +#ifdef ENABLE_SUBIDS +#define E_SUB_UID_UPDATE 16 /* can't update the subordinate uid file */ +#define E_SUB_GID_UPDATE 18 /* can't update the subordinate gid file */ +#endif /* ENABLE_SUBIDS */ + +/* + * Global variables + */ +const char *Prog; + +static char *user_name; +static uid_t user_id; +static gid_t user_gid; +static char *user_home; + +static bool fflg = false; +static bool rflg = false; +#ifdef WITH_SELINUX +static bool Zflg = false; +#endif +static bool Rflg = false; + +static bool is_shadow_pwd; + +#ifdef SHADOWGRP +static bool is_shadow_grp; +static bool sgr_locked = false; +#endif /* SHADOWGRP */ +static bool pw_locked = false; +static bool gr_locked = false; +static bool spw_locked = false; +#ifdef ENABLE_SUBIDS +static bool is_sub_uid; +static bool is_sub_gid; +static bool sub_uid_locked = false; +static bool sub_gid_locked = false; +#endif /* ENABLE_SUBIDS */ + +static const char* prefix = ""; + +/* local function prototypes */ +static void usage (int status); +static void update_groups (void); +static void remove_usergroup (void); +static void close_files (void); +static void fail_exit (int); +static void open_files (void); +static void update_user (void); +static void user_cancel (const char *); + +#ifdef EXTRA_CHECK_HOME_DIR +static bool path_prefix (const char *, const char *); +#endif /* EXTRA_CHECK_HOME_DIR */ +static int is_owner (uid_t, const char *); +static int remove_mailbox (void); +#ifdef WITH_TCB +static int remove_tcbdir (const char *user_name, uid_t user_id); +#endif /* WITH_TCB */ + +/* + * usage - display usage message and exit + */ +static void usage (int status) +{ + FILE *usageout = (E_SUCCESS != status) ? stderr : stdout; + (void) fprintf (usageout, + _("Usage: %s [options] LOGIN\n" + "\n" + "Options:\n"), + Prog); + (void) fputs (_(" -f, --force force removal of files,\n" + " even if not owned by user\n"), + usageout); + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -r, --remove remove home directory and mail spool\n"), usageout); + (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); + (void) fputs (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\n"), usageout); +#ifdef WITH_SELINUX + (void) fputs (_(" -Z, --selinux-user remove any SELinux user mapping for the user\n"), usageout); +#endif /* WITH_SELINUX */ + (void) fputs ("\n", usageout); + exit (status); +} + +/* + * update_groups - delete user from secondary group set + * + * update_groups() takes the user name that was given and searches + * the group files for membership in any group. + * + * we also check to see if they have any groups they own (the same + * name is their user name) and delete them too (only if USERGROUPS_ENAB + * is enabled). + */ +static void update_groups (void) +{ + const struct group *grp; + struct group *ngrp; + +#ifdef SHADOWGRP + const struct sgrp *sgrp; + struct sgrp *nsgrp; +#endif /* SHADOWGRP */ + + /* + * Scan through the entire group file looking for the groups that + * the user is a member of. + */ + for (gr_rewind (), grp = gr_next (); NULL != grp; grp = gr_next ()) { + + /* + * See if the user specified this group as one of their + * concurrent groups. + */ + if (!is_on_list (grp->gr_mem, user_name)) { + continue; + } + + /* + * Delete the username from the list of group members and + * update the group entry to reflect the change. + */ + ngrp = __gr_dup (grp); + if (NULL == ngrp) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, gr_dbname ()); + exit (13); /* XXX */ + } + ngrp->gr_mem = del_list (ngrp->gr_mem, user_name); + if (gr_update (ngrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), ngrp->gr_name); + exit (E_GRP_UPDATE); + } + + /* + * Update the DBM group file with the new entry as well. + */ +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user from group", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); +#endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete '%s' from group '%s'\n", + user_name, ngrp->gr_name)); + } + + if (getdef_bool ("USERGROUPS_ENAB")) { + remove_usergroup (); + } + +#ifdef SHADOWGRP + if (!is_shadow_grp) { + return; + } + + /* + * Scan through the entire shadow group file looking for the groups + * that the user is a member of. Both the administrative list and + * the ordinary membership list is checked. + */ + for (sgr_rewind (), sgrp = sgr_next (); + NULL != sgrp; + sgrp = sgr_next ()) { + bool was_member, was_admin; + + /* + * See if the user specified this group as one of their + * concurrent groups. + */ + was_member = is_on_list (sgrp->sg_mem, user_name); + was_admin = is_on_list (sgrp->sg_adm, user_name); + + if (!was_member && !was_admin) { + continue; + } + + nsgrp = __sgr_dup (sgrp); + if (NULL == nsgrp) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, sgr_dbname ()); + exit (13); /* XXX */ + } + + if (was_member) { + nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name); + } + + if (was_admin) { + nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name); + } + + if (sgr_update (nsgrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), nsgrp->sg_name); + exit (E_GRP_UPDATE); + } +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user from shadow group", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); +#endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete '%s' from shadow group '%s'\n", + user_name, nsgrp->sg_name)); + } +#endif /* SHADOWGRP */ +} + +/* + * remove_usergroup - delete the user's group if it is a usergroup + * + * An usergroup is removed if + * + it has the same name as the user + * + it is the primary group of the user + * + it has no other members + * + it is not the primary group of any other user + */ +static void remove_usergroup (void) +{ + const struct group *grp; + const struct passwd *pwd = NULL; + + grp = gr_locate (user_name); + if (NULL == grp) { + /* This user has no usergroup. */ + return; + } + + if (grp->gr_gid != user_gid) { + fprintf (stderr, + _("%s: group %s not removed because it is not the primary group of user %s.\n"), + Prog, grp->gr_name, user_name); + return; + } + + if (NULL != grp->gr_mem[0]) { + /* The usergroup has other members. */ + fprintf (stderr, + _("%s: group %s not removed because it has other members.\n"), + Prog, grp->gr_name); + return; + } + + if (!fflg) { + /* + * Scan the passwd file to check if this group is still + * used as a primary group. + */ + prefix_setpwent (); + while ((pwd = prefix_getpwent ()) != NULL) { + if (strcmp (pwd->pw_name, user_name) == 0) { + continue; + } + if (pwd->pw_gid == grp->gr_gid) { + fprintf (stderr, + _("%s: group %s is the primary group of another user and is not removed.\n"), + Prog, grp->gr_name); + break; + } + } + prefix_endpwent (); + } + + if (NULL == pwd) { + /* + * We can remove this group, it is not the primary + * group of any remaining user. + */ + if (gr_remove (user_name) == 0) { + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, user_name, gr_dbname ()); + fail_exit (E_GRP_UPDATE); + } + +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_GROUP, Prog, + "deleting group", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); +#endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, + "removed group '%s' owned by '%s'\n", + user_name, user_name)); + +#ifdef SHADOWGRP + if (sgr_locate (user_name) != NULL) { + if (sgr_remove (user_name) == 0) { + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, user_name, sgr_dbname ()); + fail_exit (E_GRP_UPDATE); + } +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_GROUP, Prog, + "deleting shadow group", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); +#endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, + "removed shadow group '%s' owned by '%s'\n", + user_name, user_name)); + + } +#endif /* SHADOWGRP */ + } +} + +/* + * close_files - close all of the files that were opened + * + * close_files() closes all of the files that were opened for this + * new user. This causes any modified entries to be written out. + */ +static void close_files (void) +{ + if (pw_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ())); + fail_exit (E_PW_UPDATE); + } + if (pw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + pw_locked = false; + + if (is_shadow_pwd) { + if (spw_close () == 0) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ())); + fail_exit (E_PW_UPDATE); + } + if (spw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + /* continue */ + } + spw_locked = false; + } + + if (gr_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ())); + fail_exit (E_GRP_UPDATE); + } + if (gr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ())); + /* continue */ + } + gr_locked = false; + +#ifdef SHADOWGRP + if (is_shadow_grp) { + if (sgr_close () == 0) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ())); + fail_exit (E_GRP_UPDATE); + } + + if (sgr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ())); + /* continue */ + } + sgr_locked = false; + } +#endif /* SHADOWGRP */ + +#ifdef ENABLE_SUBIDS + if (is_sub_uid) { + if (sub_uid_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ())); + fail_exit (E_SUB_UID_UPDATE); + } + if (sub_uid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + sub_uid_locked = false; + } + + if (is_sub_gid) { + if (sub_gid_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ())); + fail_exit (E_SUB_GID_UPDATE); + } + if (sub_gid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + sub_gid_locked = false; + } +#endif /* ENABLE_SUBIDS */ +} + +/* + * fail_exit - exit with a failure code after unlocking the files + */ +static void fail_exit (int code) +{ + if (pw_locked) { + if (pw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + } + if (gr_locked) { + if (gr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ())); + /* continue */ + } + } + if (spw_locked) { + if (spw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + /* continue */ + } + } +#ifdef SHADOWGRP + if (sgr_locked) { + if (sgr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ())); + /* continue */ + } + } +#endif /* SHADOWGRP */ +#ifdef ENABLE_SUBIDS + if (sub_uid_locked) { + if (sub_uid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + } + if (sub_gid_locked) { + if (sub_gid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + } +#endif /* ENABLE_SUBIDS */ + +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + + exit (code); +} + +/* + * open_files - lock and open the password files + * + * open_files() opens the two password files. + */ + +static void open_files (void) +{ + if (pw_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, pw_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking password file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + pw_locked = true; + if (pw_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, pw_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening password file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + if (is_shadow_pwd) { + if (spw_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, spw_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking shadow password file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + spw_locked = true; + if (spw_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, spw_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening shadow password file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + } + if (gr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + gr_locked = true; + if (gr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } +#ifdef SHADOWGRP + if (is_shadow_grp) { + if (sgr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking shadow group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + sgr_locked= true; + if (sgr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), + Prog, sgr_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening shadow group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + } +#endif /* SHADOWGRP */ +#ifdef ENABLE_SUBIDS + if (is_sub_uid) { + if (sub_uid_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_uid_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking subordinate user file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_SUB_UID_UPDATE); + } + sub_uid_locked = true; + if (sub_uid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, sub_uid_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening subordinate user file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_SUB_UID_UPDATE); + } + } + if (is_sub_gid) { + if (sub_gid_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_gid_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "locking subordinate group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_SUB_GID_UPDATE); + } + sub_gid_locked = true; + if (sub_gid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, sub_gid_dbname ()); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "opening subordinate group file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_SUB_GID_UPDATE); + } + } +#endif /* ENABLE_SUBIDS */ +} + +/* + * update_user - delete the user entries + * + * update_user() deletes the password file entries for this user + * and will update the group entries as required. + */ +static void update_user (void) +{ + if (pw_remove (user_name) == 0) { + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, user_name, pw_dbname ()); + fail_exit (E_PW_UPDATE); + } + if ( is_shadow_pwd + && (spw_locate (user_name) != NULL) + && (spw_remove (user_name) == 0)) { + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, user_name, spw_dbname ()); + fail_exit (E_PW_UPDATE); + } +#ifdef ENABLE_SUBIDS + if (is_sub_uid && sub_uid_remove(user_name, 0, ULONG_MAX) == 0) { + fprintf (stderr, + _("%s: cannot remove entry %lu from %s\n"), + Prog, (unsigned long)user_id, sub_uid_dbname ()); + fail_exit (E_SUB_UID_UPDATE); + } + if (is_sub_gid && sub_gid_remove(user_name, 0, ULONG_MAX) == 0) { + fprintf (stderr, + _("%s: cannot remove entry %lu from %s\n"), + Prog, (unsigned long)user_id, sub_gid_dbname ()); + fail_exit (E_SUB_GID_UPDATE); + } +#endif /* ENABLE_SUBIDS */ +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user entries", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); +#endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete user '%s'\n", user_name)); +} + +/* + * user_cancel - cancel cron and at jobs + * + * user_cancel calls a script for additional cleanups like removal of + * cron, at, or print jobs. + */ + +static void user_cancel (const char *user) +{ + const char *cmd; + const char *argv[3]; + int status; + + cmd = getdef_str ("USERDEL_CMD"); + if (NULL == cmd) { + return; + } + argv[0] = cmd; + argv[1] = user; + argv[2] = (char *)0; + (void) run_command (cmd, argv, NULL, &status); +} + +#ifdef EXTRA_CHECK_HOME_DIR +static bool path_prefix (const char *s1, const char *s2) +{ + return ( (strncmp (s2, s1, strlen (s1)) == 0) + && ( ('\0' == s2[strlen (s1)]) + || ('/' == s2[strlen (s1)]))); +} +#endif /* EXTRA_CHECK_HOME_DIR */ + +/* + * is_owner - Check if path is owned by uid + * + * Return + * 1: path exists and is owned by uid + * 0: path is not owned by uid, or a failure occurred + * -1: path does not exist + */ +static int is_owner (uid_t uid, const char *path) +{ + struct stat st; + + errno = 0; + if (stat (path, &st) != 0) { + if ((ENOENT == errno) || (ENOTDIR == errno)) { + /* The file or directory does not exist */ + return -1; + } else { + return 0; + } + } + return (st.st_uid == uid) ? 1 : 0; +} + +static int remove_mailbox (void) +{ + const char *maildir; + char* mailfile; + int i; + int errors = 0; + size_t len; + + maildir = getdef_str ("MAIL_DIR"); +#ifdef MAIL_SPOOL_DIR + if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) { + maildir = MAIL_SPOOL_DIR; + } +#endif /* MAIL_SPOOL_DIR */ + if (NULL == maildir) { + return 0; + } + + len = strlen (prefix) + strlen (maildir) + strlen (user_name) + 2; + mailfile = xmalloc (len); + + if (prefix[0]) { + (void) snprintf (mailfile, len, "%s/%s/%s", + prefix, maildir, user_name); + } + else { + (void) snprintf (mailfile, len, "%s/%s", + maildir, user_name); + } + mailfile[len-1] = '\0'; + + if (access (mailfile, F_OK) != 0) { + if (ENOENT == errno) { + fprintf (stderr, + _("%s: %s mail spool (%s) not found\n"), + Prog, user_name, mailfile); + free(mailfile); + return 0; + } else { + fprintf (stderr, + _("%s: warning: can't remove %s: %s\n"), + Prog, mailfile, strerror (errno)); + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + free(mailfile); + return -1; + } + } + + if (fflg) { + if (unlink (mailfile) != 0) { + fprintf (stderr, + _("%s: warning: can't remove %s: %s\n"), + Prog, mailfile, strerror (errno)); + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + errors = 1; + /* continue */ + } +#ifdef WITH_AUDIT + else + { + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); + } +#endif /* WITH_AUDIT */ + free(mailfile); + return errors; + } + i = is_owner (user_id, mailfile); + if (i == 0) { + fprintf (stderr, + _("%s: %s not owned by %s, not removing\n"), + Prog, mailfile, user_name); + SYSLOG ((LOG_ERR, + "%s not owned by %s, not removed", + mailfile, strerror (errno))); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + free(mailfile); + return 1; + } else if (i == -1) { + free(mailfile); + return 0; /* mailbox doesn't exist */ + } + if (unlink (mailfile) != 0) { + fprintf (stderr, + _("%s: warning: can't remove %s: %s\n"), + Prog, mailfile, strerror (errno)); + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + errors = 1; + /* continue */ + } +#ifdef WITH_AUDIT + else + { + audit_logger (AUDIT_DEL_USER, Prog, + "deleting mail file", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); + } +#endif /* WITH_AUDIT */ + free(mailfile); + return errors; +} + +#ifdef WITH_TCB +static int remove_tcbdir (const char *user_name, uid_t user_id) +{ + char *buf; + int ret = 0; + size_t buflen = (sizeof TCB_DIR) + strlen (user_name) + 2; + + if (!getdef_bool ("USE_TCB")) { + return 0; + } + + buf = malloc (buflen); + if (NULL == buf) { + fprintf (stderr, _("%s: Can't allocate memory, " + "tcb entry for %s not removed.\n"), + Prog, user_name); + return 1; + } + snprintf (buf, buflen, TCB_DIR "/%s", user_name); + if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) { + fprintf (stderr, _("%s: Cannot drop privileges: %s\n"), + Prog, strerror (errno)); + shadowtcb_gain_priv (); + free (buf); + return 1; + } + /* Only remove directory contents with dropped privileges. + * We will regain them and remove the user's tcb directory afterwards. + */ + if (remove_tree (buf, false) != 0) { + fprintf (stderr, _("%s: Cannot remove the content of %s: %s\n"), + Prog, buf, strerror (errno)); + shadowtcb_gain_priv (); + free (buf); + return 1; + } + shadowtcb_gain_priv (); + free (buf); + if (shadowtcb_remove (user_name) == SHADOWTCB_FAILURE) { + fprintf (stderr, _("%s: Cannot remove tcb files for %s: %s\n"), + Prog, user_name, strerror (errno)); + ret = 1; + } + return ret; +} +#endif /* WITH_TCB */ + +/* + * main - userdel command + */ +int main (int argc, char **argv) +{ + int errors = 0; /* Error in the removal of the home directory */ + +#ifdef ACCT_TOOLS_SETUID +#ifdef USE_PAM + pam_handle_t *pamh = NULL; + int retval; +#endif /* USE_PAM */ +#endif /* ACCT_TOOLS_SETUID */ + + /* + * Get my name so that I can use it to report errors. + */ + Prog = Basename (argv[0]); + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + process_root_flag ("-R", argc, argv); + prefix = process_prefix_flag ("-P", argc, argv); + + OPENLOG ("userdel"); +#ifdef WITH_AUDIT + audit_help_open (); +#endif /* WITH_AUDIT */ + + { + /* + * Parse the command line options. + */ + int c; + static struct option long_options[] = { + {"force", no_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"remove", no_argument, NULL, 'r'}, + {"root", required_argument, NULL, 'R'}, + {"prefix", required_argument, NULL, 'P'}, +#ifdef WITH_SELINUX + {"selinux-user", no_argument, NULL, 'Z'}, +#endif /* WITH_SELINUX */ + {NULL, 0, NULL, '\0'} + }; + while ((c = getopt_long (argc, argv, +#ifdef WITH_SELINUX + "fhrR:P:Z", +#else /* !WITH_SELINUX */ + "fhrR:P:", +#endif /* !WITH_SELINUX */ + long_options, NULL)) != -1) { + switch (c) { + case 'f': /* force remove even if not owned by user */ + fflg = true; + break; + case 'h': + usage (E_SUCCESS); + break; + case 'r': /* remove home dir and mailbox */ + rflg = true; + break; + case 'R': /* no-op, handled in process_root_flag () */ + Rflg = true; + break; + case 'P': /* no-op, handled in process_prefix_flag () */ + break; +#ifdef WITH_SELINUX + case 'Z': + if (prefix[0]) { + fprintf (stderr, + _("%s: -Z cannot be used with --prefix\n"), + Prog); + exit (E_BAD_ARG); + } + if (is_selinux_enabled () > 0) { + Zflg = true; + } else { + fprintf (stderr, + _("%s: -Z requires SELinux enabled kernel\n"), + Prog); + + exit (E_BAD_ARG); + } + break; +#endif /* WITH_SELINUX */ + default: + usage (E_USAGE); + } + } + } + + if ((optind + 1) != argc) { + usage (E_USAGE); + } + +#ifdef ACCT_TOOLS_SETUID +#ifdef USE_PAM + { + struct passwd *pampw; + pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */ + if (pampw == NULL) { + fprintf (stderr, + _("%s: Cannot determine your user name.\n"), + Prog); + exit (E_PW_UPDATE); + } + + retval = pam_start ("userdel", pampw->pw_name, &conv, &pamh); + } + + if (PAM_SUCCESS == retval) { + retval = pam_authenticate (pamh, 0); + } + + if (PAM_SUCCESS == retval) { + retval = pam_acct_mgmt (pamh, 0); + } + + if (PAM_SUCCESS != retval) { + fprintf (stderr, _("%s: PAM: %s\n"), + Prog, pam_strerror (pamh, retval)); + SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval))); + if (NULL != pamh) { + (void) pam_end (pamh, retval); + } + exit (E_PW_UPDATE); + } + (void) pam_end (pamh, retval); +#endif /* USE_PAM */ +#endif /* ACCT_TOOLS_SETUID */ + + is_shadow_pwd = spw_file_present (); +#ifdef SHADOWGRP + is_shadow_grp = sgr_file_present (); +#endif /* SHADOWGRP */ +#ifdef ENABLE_SUBIDS + is_sub_uid = sub_uid_file_present (); + is_sub_gid = sub_gid_file_present (); +#endif /* ENABLE_SUBIDS */ + + /* + * Start with a quick check to see if the user exists. + */ + user_name = argv[argc - 1]; + { + const struct passwd *pwd; + + pw_open(O_RDONLY); + pwd = pw_locate (user_name); /* we care only about local users */ + if (NULL == pwd) { + pw_close(); + fprintf (stderr, _("%s: user '%s' does not exist\n"), + Prog, user_name); +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user not found", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + exit (E_NOTFOUND); + } + user_id = pwd->pw_uid; + user_gid = pwd->pw_gid; + + if(prefix[0]) { + + size_t len = strlen(prefix) + strlen(pwd->pw_dir) + 2; + int wlen; + user_home = xmalloc(len); + wlen = snprintf(user_home, len, "%s/%s", prefix, pwd->pw_dir); + assert (wlen == (int) len -1); + } + else { + user_home = xstrdup (pwd->pw_dir); + } + pw_close(); + } +#ifdef WITH_TCB + if (shadowtcb_set_user (user_name) == SHADOWTCB_FAILURE) { + exit (E_NOTFOUND); + } +#endif /* WITH_TCB */ +#ifdef USE_NIS + + /* + * Now make sure it isn't an NIS user. + */ + if (__ispwNIS ()) { + char *nis_domain; + char *nis_master; + + fprintf (stderr, + _("%s: user %s is a NIS user\n"), Prog, user_name); + if ( !yp_get_default_domain (&nis_domain) + && !yp_master (nis_domain, "passwd.byname", &nis_master)) { + fprintf (stderr, + _("%s: %s is the NIS master\n"), + Prog, nis_master); + } + exit (E_NOTFOUND); + } +#endif /* USE_NIS */ + /* + * Check to make certain the user isn't logged in. + * Note: This is a best effort basis. The user may log in between, + * a cron job may be started on her behalf, etc. + */ + if ((prefix[0] == '\0') && !Rflg && user_busy (user_name, user_id) != 0) { + if (!fflg) { +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user logged in", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + exit (E_USER_BUSY); + } + } + + /* + * Do the hard stuff - open the files, create the user entries, + * create the home directory, then close and update the files. + */ + open_files (); + update_user (); + update_groups (); + + if (rflg) { + errors += remove_mailbox (); + } + if (rflg) { + int home_owned = is_owner (user_id, user_home); + if (-1 == home_owned) { + fprintf (stderr, + _("%s: %s home directory (%s) not found\n"), + Prog, user_name, user_home); + rflg = 0; + } else if ((0 == home_owned) && !fflg) { + fprintf (stderr, + _("%s: %s not owned by %s, not removing\n"), + Prog, user_home, user_name); + rflg = 0; + errors++; + /* continue */ + } + } + +#ifdef EXTRA_CHECK_HOME_DIR + /* This may be slow, the above should be good enough. */ + if (rflg && !fflg) { + struct passwd *pwd; + /* + * For safety, refuse to remove the home directory if it + * would result in removing some other user's home + * directory. Still not perfect so be careful, but should + * prevent accidents if someone has /home or / as home + * directory... --marekm + */ + prefix_setpwent (); + while ((pwd = prefix_getpwent ())) { + if (strcmp (pwd->pw_name, user_name) == 0) { + continue; + } + if (path_prefix (user_home, pwd->pw_dir)) { + fprintf (stderr, + _("%s: not removing directory %s (would remove home of user %s)\n"), + Prog, user_home, pwd->pw_name); + rflg = false; + errors++; + /* continue */ + break; + } + } + prefix_endpwent (); + } +#endif /* EXTRA_CHECK_HOME_DIR */ + + if (rflg) { +#ifdef WITH_BTRFS + int is_subvolume = btrfs_is_subvolume (user_home); + if (is_subvolume < 0) { + errors++; + /* continue */ + } + else if (is_subvolume > 0) { + if (btrfs_remove_subvolume (user_home)) { + fprintf (stderr, + _("%s: error removing subvolume %s\n"), + Prog, user_home); + errors++; + /* continue */ + } + } + else +#endif + if (remove_tree (user_home, true) != 0) { + fprintf (stderr, + _("%s: error removing directory %s\n"), + Prog, user_home); + errors++; + /* continue */ + } +#ifdef WITH_AUDIT + else + { + audit_logger (AUDIT_DEL_USER, Prog, + "deleting home directory", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_SUCCESS); + } +#endif /* WITH_AUDIT */ + } +#ifdef WITH_AUDIT + if (0 != errors) { + audit_logger (AUDIT_DEL_USER, Prog, + "deleting home directory", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + } +#endif /* WITH_AUDIT */ + +#ifdef WITH_SELINUX + if (Zflg) { + if (del_seuser (user_name) != 0) { + fprintf (stderr, + _("%s: warning: the user name %s to SELinux user mapping removal failed.\n"), + Prog, user_name); +#ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, + "removing SELinux user mapping", + user_name, (unsigned int) user_id, + SHADOW_AUDIT_FAILURE); +#endif /* WITH_AUDIT */ + fail_exit (E_SE_UPDATE); + } + } +#endif /* WITH_SELINUX */ + + /* + * Cancel any crontabs or at jobs. Have to do this before we remove + * the entry from /etc/passwd. + */ + if(prefix[0] == '\0') + user_cancel (user_name); + close_files (); + +#ifdef WITH_TCB + errors += remove_tcbdir (user_name, user_id); +#endif /* WITH_TCB */ + + nscd_flush_cache ("passwd"); + nscd_flush_cache ("group"); + sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP); + + return ((0 != errors) ? E_HOMEDIR : E_SUCCESS); +} + |