]> granicus.if.org Git - shadow/blobdiff - src/usermod.c
* src/usermod.c (update_gshadow): is_member was computed twice.
[shadow] / src / usermod.c
index aee90f8cfacdd9ca1d4541643a6b12153fbdff17..4d7d0988d71f663b3263df840345ba0c9e5a6ec5 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 1991 - 1994, Julianne Frances Haugh
  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
  * Copyright (c) 2000 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2010, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 #include <grp.h>
 #include <lastlog.h>
 #include <pwd.h>
+#ifdef ACCT_TOOLS_SETUID
 #ifdef USE_PAM
 #include "pam_defs.h"
 #endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include "sgroupio.h"
 #endif
 #include "shadowio.h"
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+
 /*
  * exit status values
  * for E_GRP_UPDATE and E_NOSPACE (not used yet), other update requests
  * will be implemented (as documented in the Solaris 2.x man page).
  */
+/*@-exitarg@*/
 #define E_SUCCESS      0       /* success */
 #define E_PW_UPDATE    1       /* can't update password file */
 #define E_USAGE                2       /* invalid command syntax */
 /*
  * Global variables
  */
+const char *Prog;
+
 static char *user_name;
-static char *user_newname;
+static char *user_newname = NULL;
 static char *user_pass;
 static uid_t user_id;
 static uid_t user_newid;
 static gid_t user_gid;
 static gid_t user_newgid;
 static char *user_comment;
-static char *user_newcomment;
+static char *user_newcomment = NULL;
 static char *user_home;
-static char *user_newhome;
+static char *user_newhome = NULL;
 static char *user_shell;
-static char *user_newshell;
+#ifdef WITH_SELINUX
+static const char *user_selinux = "";
+#endif
+static char *user_newshell = NULL;
 static long user_expire;
 static long user_newexpire;
 static long user_inactive;
@@ -102,8 +114,6 @@ static long user_newinactive;
 static long sys_ngroups;
 static char **user_groups;     /* NULL-terminated list */
 
-static char *Prog;
-
 static bool
     aflg = false,              /* append to existing secondary group set */
     cflg = false,              /* new comment (GECOS) field */
@@ -118,6 +128,9 @@ static bool
     oflg = false,              /* permit non-unique user ID to be specified with -u */
     pflg = false,              /* new encrypted password */
     sflg = false,              /* new shell program */
+#ifdef WITH_SELINUX
+    Zflg = false,              /* new selinux user */
+#endif
     uflg = false,              /* specify new user ID */
     Uflg = false;              /* unlock the password */
 
@@ -139,8 +152,11 @@ static bool sgr_locked = false;
 static void date_to_str (char *buf, size_t maxsize,
                          long int date, const char *negativ);
 static int get_groups (char *);
-static void usage (void);
+static /*@noreturn@*/void usage (int status);
 static void new_pwent (struct passwd *);
+#ifdef WITH_SELINUX
+static void selinux_update_mapping (void);
+#endif
 
 static void new_spent (struct spwd *);
 static void fail_exit (int);
@@ -151,8 +167,6 @@ static void update_gshadow (void);
 #endif
 static void grp_update (void);
 
-static long get_number (const char *);
-static uid_t get_id (const char *);
 static void process_flags (int, char **);
 static void close_files (void);
 static void open_files (void);
@@ -184,22 +198,6 @@ static void date_to_str (char *buf, size_t maxsize,
        }
        buf[maxsize - 1] = '\0';
 }
-/*
- * Had to move this over from useradd.c since we have groups named
- * "56k-family"... ergh.
- * --Pac.
- */
-static struct group *getgr_nam_gid (const char *grname)
-{
-       long val;
-       char *errptr;
-
-       val = strtol (grname, &errptr, 10);
-       if (*grname != '\0' && *errptr == '\0' && errno != ERANGE && val >= 0) {
-               return xgetgrgid ((gid_t) val);
-       }
-       return xgetgrnam (grname);
-}
 
 /*
  * get_groups - convert a list of group names to an array of group IDs
@@ -250,8 +248,8 @@ static int get_groups (char *list)
                 * string name.
                 */
                if (NULL == grp) {
-                       fprintf (stderr, _("%s: unknown group %s\n"),
-                                Prog, list);
+                       fprintf (stderr, _("%s: group '%s' does not exist\n"),
+                                Prog, list);
                        errors++;
                }
                list = cp;
@@ -271,8 +269,8 @@ static int get_groups (char *list)
                 */
                if (__isgrNIS ()) {
                        fprintf (stderr,
-                                _("%s: group '%s' is a NIS group.\n"),
-                                Prog, grp->gr_name);
+                                _("%s: group '%s' is a NIS group.\n"),
+                                Prog, grp->gr_name);
                        continue;
                }
 #endif
@@ -305,9 +303,10 @@ static int get_groups (char *list)
 /*
  * usage - display usage message and exit
  */
-static void usage (void)
+static /*@noreturn@*/void usage (int status)
 {
-       fputs (_("Usage: usermod [options] LOGIN\n"
+       fprintf ((E_SUCCESS != status) ? stderr : stdout,
+                _("Usage: usermod [options] LOGIN\n"
                 "\n"
                 "Options:\n"
                 "  -c, --comment COMMENT         new value of the GECOS field\n"
@@ -330,8 +329,15 @@ static void usage (void)
                 "  -s, --shell SHELL             new login shell for the user account\n"
                 "  -u, --uid UID                 new UID for the user account\n"
                 "  -U, --unlock                  unlock the user account\n"
-                "\n"), stderr);
-       exit (E_USAGE);
+                "%s"
+                "\n"),
+#ifdef WITH_SELINUX
+                _("  -Z, --selinux-user            new SELinux user mapping for the user account\n")
+#else
+                ""
+#endif
+                );
+       exit (status);
 }
 
 /*
@@ -357,9 +363,9 @@ static char *new_pw_passwd (char *pw_pass)
 
                if (pw_pass[1] == '\0') {
                        fprintf (stderr,
-                                _("%s: unlocking the user's password would result in a passwordless account.\n"
-                                  "You should set a password with usermod -p to unlock this user's password.\n"),
-                                Prog);
+                                _("%s: unlocking the user's password would result in a passwordless account.\n"
+                                  "You should set a password with usermod -p to unlock this user's password.\n"),
+                                Prog);
                        return pw_pass;
                }
 
@@ -395,18 +401,24 @@ static char *new_pw_passwd (char *pw_pass)
 static void new_pwent (struct passwd *pwent)
 {
        if (lflg) {
+               if (pw_locate (user_newname) != NULL) {
+                       fprintf (stderr,
+                                _("%s: user '%s' already exists in %s\n"),
+                                Prog, user_newname, pw_dbname ());
+                       fail_exit (E_NAME_IN_USE);
+               }
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
                              "changing name",
                              user_newname, (unsigned int) user_newid, 1);
 #endif
-               SYSLOG ((LOG_INFO, "change user name '%s' to '%s'",
-                        pwent->pw_name, user_newname));
+               SYSLOG ((LOG_INFO,
+                        "change user name '%s' to '%s'",
+                        pwent->pw_name, user_newname));
                pwent->pw_name = xstrdup (user_newname);
        }
        if (!is_shadow_pwd) {
-               pwent->pw_passwd =
-                   new_pw_passwd (pwent->pw_passwd);
+               pwent->pw_passwd = new_pw_passwd (pwent->pw_passwd);
        }
 
        if (uflg) {
@@ -416,8 +428,8 @@ static void new_pwent (struct passwd *pwent)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user '%s' UID from '%d' to '%d'",
-                        pwent->pw_name, pwent->pw_uid, user_newid));
+                        "change user '%s' UID from '%d' to '%d'",
+                        pwent->pw_name, pwent->pw_uid, user_newid));
                pwent->pw_uid = user_newid;
        }
        if (gflg) {
@@ -427,8 +439,8 @@ static void new_pwent (struct passwd *pwent)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user '%s' GID from '%d' to '%d'",
-                        pwent->pw_name, pwent->pw_gid, user_newgid));
+                        "change user '%s' GID from '%d' to '%d'",
+                        pwent->pw_name, pwent->pw_gid, user_newgid));
                pwent->pw_gid = user_newgid;
        }
        if (cflg) {
@@ -447,8 +459,8 @@ static void new_pwent (struct passwd *pwent)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user '%s' home from '%s' to '%s'",
-                        pwent->pw_name, pwent->pw_dir, user_newhome));
+                        "change user '%s' home from '%s' to '%s'",
+                        pwent->pw_name, pwent->pw_dir, user_newhome));
                pwent->pw_dir = user_newhome;
        }
        if (sflg) {
@@ -457,8 +469,9 @@ static void new_pwent (struct passwd *pwent)
                              "changing user shell",
                              user_newname, (unsigned int) user_newid, 1);
 #endif
-               SYSLOG ((LOG_INFO, "change user '%s' shell from '%s' to '%s'",
-                        pwent->pw_name, pwent->pw_shell, user_newshell));
+               SYSLOG ((LOG_INFO,
+                        "change user '%s' shell from '%s' to '%s'",
+                        pwent->pw_name, pwent->pw_shell, user_newshell));
                pwent->pw_shell = user_newshell;
        }
 }
@@ -472,6 +485,12 @@ static void new_pwent (struct passwd *pwent)
 static void new_spent (struct spwd *spent)
 {
        if (lflg) {
+               if (spw_locate (user_newname) != NULL) {
+                       fprintf (stderr,
+                                _("%s: user '%s' already exists in %s\n"),
+                                Prog, user_newname, spw_dbname ());
+                       fail_exit (E_NAME_IN_USE);
+               }
                spent->sp_namp = xstrdup (user_newname);
        }
 
@@ -482,8 +501,8 @@ static void new_spent (struct spwd *spent)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user '%s' inactive from '%ld' to '%ld'",
-                        spent->sp_namp, spent->sp_inact, user_newinactive));
+                        "change user '%s' inactive from '%ld' to '%ld'",
+                        spent->sp_namp, spent->sp_inact, user_newinactive));
                spent->sp_inact = user_newinactive;
        }
        if (eflg) {
@@ -499,13 +518,18 @@ static void new_spent (struct spwd *spent)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user '%s' expiration from '%s' to '%s'",
-                        spent->sp_namp, old_exp, new_exp));
+                        "change user '%s' expiration from '%s' to '%s'",
+                        spent->sp_namp, old_exp, new_exp));
                spent->sp_expire = user_newexpire;
        }
        spent->sp_pwdp = new_pw_passwd (spent->sp_pwdp);
        if (pflg) {
                spent->sp_lstchg = (long) time ((time_t *) 0) / SCALE;
+               if (0 == spent->sp_lstchg) {
+                       /* Better disable aging than requiring a password
+                        * change */
+                       spent->sp_lstchg = -1;
+               }
        }
 }
 
@@ -515,18 +539,34 @@ static void new_spent (struct spwd *spent)
 static void fail_exit (int code)
 {
        if (gr_locked) {
-               gr_unlock ();
+               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 */
+               }
        }
 #ifdef SHADOWGRP
        if (sgr_locked) {
-               sgr_unlock ();
+               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
        if (spw_locked) {
-               spw_unlock ();
+               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 */
+               }
        }
        if (pw_locked) {
-               pw_unlock ();
+               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 */
+               }
        }
 
 #ifdef WITH_AUDIT
@@ -576,9 +616,9 @@ static void update_group (void)
                if (was_member && (!Gflg || is_member)) {
                        if (lflg) {
                                ngrp->gr_mem = del_list (ngrp->gr_mem,
-                                                        user_name);
+                                                        user_name);
                                ngrp->gr_mem = add_list (ngrp->gr_mem,
-                                                        user_newname);
+                                                        user_newname);
                                changed = true;
 #ifdef WITH_AUDIT
                                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
@@ -586,9 +626,9 @@ static void update_group (void)
                                              user_newname, AUDIT_NO_ID, 1);
 #endif
                                SYSLOG ((LOG_INFO,
-                                        "change '%s' to '%s' in group '%s'",
-                                        user_name, user_newname,
-                                        ngrp->gr_name));
+                                        "change '%s' to '%s' in group '%s'",
+                                        user_name, user_newname,
+                                        ngrp->gr_name));
                        }
                } else if (was_member && !aflg && Gflg && !is_member) {
                        ngrp->gr_mem = del_list (ngrp->gr_mem, user_name);
@@ -598,8 +638,9 @@ static void update_group (void)
                                      "removing group member",
                                      user_name, AUDIT_NO_ID, 1);
 #endif
-                       SYSLOG ((LOG_INFO, "delete '%s' from group '%s'",
-                                user_name, ngrp->gr_name));
+                       SYSLOG ((LOG_INFO,
+                                "delete '%s' from group '%s'",
+                                user_name, ngrp->gr_name));
                } else if (!was_member && Gflg && is_member) {
                        ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname);
                        changed = true;
@@ -618,9 +659,9 @@ static void update_group (void)
                changed = false;
                if (gr_update (ngrp) == 0) {
                        fprintf (stderr,
-                                _("%s: error adding new entry '%s' in %s\n"),
-                                Prog, ngrp->gr_name, gr_dbname ());
-                       SYSLOG ((LOG_ERR, "error adding new entry '%s' in %s", ngrp->gr_name, gr_dbname ()));
+                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                Prog, gr_dbname (), ngrp->gr_name);
+                       SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", gr_dbname (), ngrp->gr_name));
                        fail_exit (E_GRP_UPDATE);
                }
        }
@@ -658,7 +699,6 @@ static void update_gshadow (void)
                 * See if the user specified this group as one of their
                 * concurrent groups.
                 */
-               is_member = Gflg && is_on_list (user_groups, sgrp->sg_name);
                is_member = Gflg && (   (was_member && aflg)
                                     || is_on_list (user_groups, sgrp->sg_name));
 
@@ -684,15 +724,15 @@ static void update_gshadow (void)
                                      user_name, AUDIT_NO_ID, 1);
 #endif
                        SYSLOG ((LOG_INFO,
-                                "change admin '%s' to '%s' in shadow group '%s'",
-                                user_name, user_newname, nsgrp->sg_name));
+                                "change admin '%s' to '%s' in shadow group '%s'",
+                                user_name, user_newname, nsgrp->sg_name));
                }
                if (was_member && (!Gflg || is_member)) {
                        if (lflg) {
                                nsgrp->sg_mem = del_list (nsgrp->sg_mem,
-                                                         user_name);
+                                                         user_name);
                                nsgrp->sg_mem = add_list (nsgrp->sg_mem,
-                                                         user_newname);
+                                                         user_newname);
                                changed = true;
 #ifdef WITH_AUDIT
                                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
@@ -700,9 +740,9 @@ static void update_gshadow (void)
                                              user_name, AUDIT_NO_ID, 1);
 #endif
                                SYSLOG ((LOG_INFO,
-                                        "change '%s' to '%s' in shadow group '%s'",
-                                        user_name, user_newname,
-                                        nsgrp->sg_name));
+                                        "change '%s' to '%s' in shadow group '%s'",
+                                        user_name, user_newname,
+                                        nsgrp->sg_name));
                        }
                } else if (was_member && !aflg && Gflg && !is_member) {
                        nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name);
@@ -713,8 +753,8 @@ static void update_gshadow (void)
                                      user_name, AUDIT_NO_ID, 1);
 #endif
                        SYSLOG ((LOG_INFO,
-                                "delete '%s' from shadow group '%s'",
-                                user_name, nsgrp->sg_name));
+                                "delete '%s' from shadow group '%s'",
+                                user_name, nsgrp->sg_name));
                } else if (!was_member && Gflg && is_member) {
                        nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname);
                        changed = true;
@@ -737,10 +777,10 @@ static void update_gshadow (void)
                 */
                if (sgr_update (nsgrp) == 0) {
                        fprintf (stderr,
-                                _("%s: error adding new entry '%s' in '%s\n"),
-                                Prog, nsgrp->sg_name, sgr_dbname ());
-                       SYSLOG ((LOG_ERR, "error adding new entry '%s' in %s",
-                               nsgrp->sg_name, sgr_dbname ()));
+                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                Prog, sgr_dbname (), nsgrp->sg_name);
+                       SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'",
+                                sgr_dbname (), nsgrp->sg_name));
                        fail_exit (E_GRP_UPDATE);
                }
        }
@@ -763,34 +803,6 @@ static void grp_update (void)
 #endif
 }
 
-static long get_number (const char *numstr)
-{
-       long val;
-       char *errptr;
-
-       val = strtol (numstr, &errptr, 10);
-       if (('\0' != *errptr) || (ERANGE == errno)) {
-               fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
-                        numstr);
-               exit (E_BAD_ARG);
-       }
-       return val;
-}
-
-static uid_t get_id (const char *uidstr)
-{
-       long val;
-       char *errptr;
-
-       val = strtol (uidstr, &errptr, 10);
-       if (('\0' != *errptr) || (ERANGE == errno) || (val < 0)) {
-               fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
-                        uidstr);
-               exit (E_BAD_ARG);
-       }
-       return (uid_t) val;
-}
-
 /*
  * process_flags - perform command line argument setting
  *
@@ -804,65 +816,6 @@ static void process_flags (int argc, char **argv)
 
        bool anyflag = false;
 
-       if ((1 == argc) || ('-' == argv[argc - 1][0])) {
-               usage ();
-       }
-
-       {
-               const struct passwd *pwd;
-               /* local, no need for xgetpwnam */
-               pwd = getpwnam (argv[argc - 1]);
-               if (NULL == pwd) {
-                       fprintf (stderr, _("%s: user '%s' does not exist\n"),
-                                Prog, argv[argc - 1]);
-                       exit (E_NOTFOUND);
-               }
-
-               user_name = argv[argc - 1];
-               user_id = pwd->pw_uid;
-               user_gid = pwd->pw_gid;
-               user_comment = xstrdup (pwd->pw_gecos);
-               user_home = xstrdup (pwd->pw_dir);
-               user_shell = xstrdup (pwd->pw_shell);
-       }
-       user_newname = user_name;
-       user_newid = user_id;
-       user_newgid = user_gid;
-       user_newcomment = user_comment;
-       user_newhome = user_home;
-       user_newshell = user_shell;
-
-#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
-
-       {
-               const struct spwd *spwd = NULL;
-               /* local, no need for xgetspnam */
-               if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
-                       user_expire = spwd->sp_expire;
-                       user_inactive = spwd->sp_inact;
-                       user_newexpire = user_expire;
-                       user_newinactive = user_inactive;
-               }
-       }
-
        {
                /*
                 * Parse the command line options.
@@ -882,14 +835,21 @@ static void process_flags (int argc, char **argv)
                        {"move-home", no_argument, NULL, 'm'},
                        {"non-unique", no_argument, NULL, 'o'},
                        {"password", required_argument, NULL, 'p'},
+#ifdef WITH_SELINUX
+                       {"selinux-user", required_argument, NULL, 'Z'},
+#endif
                        {"shell", required_argument, NULL, 's'},
                        {"uid", required_argument, NULL, 'u'},
                        {"unlock", no_argument, NULL, 'U'},
                        {NULL, 0, NULL, '\0'}
                };
-               while ((c =
-                       getopt_long (argc, argv, "ac:d:e:f:g:G:hl:Lmop:s:u:U",
-                                    long_options, NULL)) != -1) {
+               while ((c = getopt_long (argc, argv,
+#ifdef WITH_SELINUX
+                                        "ac:d:e:f:g:G:hl:Lmop:s:u:UZ:",
+#else
+                                        "ac:d:e:f:g:G:hl:Lmop:s:u:U",
+#endif
+                                        long_options, NULL)) != -1) {
                        switch (c) {
                        case 'a':
                                aflg = true;
@@ -897,8 +857,8 @@ static void process_flags (int argc, char **argv)
                        case 'c':
                                if (!VALID (optarg)) {
                                        fprintf (stderr,
-                                                _("%s: invalid field '%s'\n"),
-                                                Prog, optarg);
+                                                _("%s: invalid field '%s'\n"),
+                                                Prog, optarg);
                                        exit (E_BAD_ARG);
                                }
                                user_newcomment = optarg;
@@ -907,8 +867,8 @@ static void process_flags (int argc, char **argv)
                        case 'd':
                                if (!VALID (optarg)) {
                                        fprintf (stderr,
-                                                _("%s: invalid field '%s'\n"),
-                                                Prog, optarg);
+                                                _("%s: invalid field '%s'\n"),
+                                                Prog, optarg);
                                        exit (E_BAD_ARG);
                                }
                                dflg = true;
@@ -917,11 +877,10 @@ static void process_flags (int argc, char **argv)
                        case 'e':
                                if ('\0' != *optarg) {
                                        user_newexpire = strtoday (optarg);
-                                       if (user_newexpire == -1) {
+                                       if (user_newexpire < -1) {
                                                fprintf (stderr,
-                                                        _
-                                                        ("%s: invalid date '%s'\n"),
-                                                        Prog, optarg);
+                                                        _("%s: invalid date '%s'\n"),
+                                                        Prog, optarg);
                                                exit (E_BAD_ARG);
                                        }
                                        user_newexpire *= DAY / SCALE;
@@ -931,15 +890,21 @@ static void process_flags (int argc, char **argv)
                                eflg = true;
                                break;
                        case 'f':
-                               user_newinactive = get_number (optarg);
+                               if (   (getlong (optarg, &user_newinactive) == 0)
+                                   || (user_newinactive < -1)) {
+                                       fprintf (stderr,
+                                                _("%s: invalid numeric argument '%s'\n"),
+                                                Prog, optarg);
+                                       usage (E_USAGE);
+                               }
                                fflg = true;
                                break;
                        case 'g':
                                grp = getgr_nam_gid (optarg);
                                if (NULL == grp) {
                                        fprintf (stderr,
-                                                _("%s: unknown group %s\n"),
-                                                Prog, optarg);
+                                                _("%s: group '%s' does not exist\n"),
+                                                Prog, optarg);
                                        exit (E_NOTFOUND);
                                }
                                user_newgid = grp->gr_gid;
@@ -951,11 +916,14 @@ static void process_flags (int argc, char **argv)
                                }
                                Gflg = true;
                                break;
+                       case 'h':
+                               usage (E_SUCCESS);
+                               /* @notreached@ */break;
                        case 'l':
                                if (!is_valid_user_name (optarg)) {
                                        fprintf (stderr,
-                                                _("%s: invalid field '%s'\n"),
-                                                Prog, optarg);
+                                                _("%s: invalid field '%s'\n"),
+                                                Prog, optarg);
                                        exit (E_BAD_ARG);
                                }
                                lflg = true;
@@ -977,30 +945,144 @@ static void process_flags (int argc, char **argv)
                        case 's':
                                if (!VALID (optarg)) {
                                        fprintf (stderr,
-                                                _("%s: invalid field '%s'\n"),
-                                                Prog, optarg);
+                                                _("%s: invalid field '%s'\n"),
+                                                Prog, optarg);
                                        exit (E_BAD_ARG);
                                }
                                user_newshell = optarg;
                                sflg = true;
                                break;
                        case 'u':
-                               user_newid = get_id (optarg);
+                               if (   (get_uid (optarg, &user_newid) ==0)
+                                   || (user_newid == (uid_t)-1)) {
+                                       fprintf (stderr,
+                                                _("%s: invalid user ID '%s'\n"),
+                                                Prog, optarg);
+                                       exit (E_BAD_ARG);
+                               }
                                uflg = true;
                                break;
                        case 'U':
                                Uflg = true;
                                break;
+#ifdef WITH_SELINUX
+                       case 'Z':
+                               if (is_selinux_enabled () > 0) {
+                                       user_selinux = optarg;
+                                       Zflg = true;
+                               } else {
+                                       fprintf (stderr,
+                                                _("%s: -Z requires SELinux enabled kernel\n"),
+                                                Prog);
+                                       exit (E_BAD_ARG);
+                               }
+                               break;
+#endif
                        default:
-                               usage ();
+                               usage (E_USAGE);
                        }
                        anyflag = true;
                }
        }
 
+       if (optind != argc - 1) {
+               usage (E_USAGE);
+       }
+
+       user_name = argv[argc - 1];
+
+       {
+               const struct passwd *pwd;
+               /* local, no need for xgetpwnam */
+               pwd = getpwnam (user_name);
+               if (NULL == pwd) {
+                       fprintf (stderr,
+                                _("%s: user '%s' does not exist\n"),
+                                Prog, user_name);
+                       exit (E_NOTFOUND);
+               }
+
+               user_id = pwd->pw_uid;
+               user_gid = pwd->pw_gid;
+               user_comment = xstrdup (pwd->pw_gecos);
+               user_home = xstrdup (pwd->pw_dir);
+               user_shell = xstrdup (pwd->pw_shell);
+       }
+
+       /* user_newname, user_newid, user_newgid can be used even when the
+        * options where not specified. */
+       if (!lflg) {
+               user_newname = user_name;
+       }
+       if (!uflg) {
+               user_newid = user_id;
+       }
+       if (!gflg) {
+               user_newgid = user_gid;
+       }
+
+#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
+
+       {
+               const struct spwd *spwd = NULL;
+               /* local, no need for xgetspnam */
+               if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
+                       user_expire = spwd->sp_expire;
+                       user_inactive = spwd->sp_inact;
+               }
+       }
+
        if (!anyflag) {
-               fprintf (stderr, _("%s: no flags given\n"), Prog);
-               exit (E_USAGE);
+               fprintf (stderr, _("%s: no options\n"), Prog);
+               usage (E_USAGE);
+       }
+
+       if (aflg && (!Gflg)) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-a", "-G");
+               usage (E_USAGE);
+       }
+
+       if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
+               fprintf (stderr,
+                        _("%s: the -L, -p, and -U flags are exclusive\n"),
+                        Prog);
+               usage (E_USAGE);
+       }
+
+       if (oflg && !uflg) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-o", "-u");
+               usage (E_USAGE);
+       }
+
+       if (mflg && !dflg) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-m", "-d");
+               usage (E_USAGE);
        }
 
        if (user_newid == user_id) {
@@ -1010,7 +1092,8 @@ static void process_flags (int argc, char **argv)
        if (user_newgid == user_gid) {
                gflg = false;
        }
-       if (strcmp (user_newshell, user_shell) == 0) {
+       if (   (NULL != user_newshell)
+           && (strcmp (user_newshell, user_shell) == 0)) {
                sflg = false;
        }
        if (strcmp (user_newname, user_name) == 0) {
@@ -1022,74 +1105,46 @@ static void process_flags (int argc, char **argv)
        if (user_newexpire == user_expire) {
                eflg = false;
        }
-       if (strcmp (user_newhome, user_home) == 0) {
+       if (   (NULL != user_newhome)
+           && (strcmp (user_newhome, user_home) == 0)) {
                dflg = false;
                mflg = false;
        }
-       if (strcmp (user_newcomment, user_comment) == 0) {
+       if (   (NULL != user_newcomment)
+           && (strcmp (user_newcomment, user_comment) == 0)) {
                cflg = false;
        }
 
-       if (!(Uflg || uflg || sflg || pflg || oflg || mflg || Lflg ||
-             lflg || Gflg || gflg || fflg || eflg || dflg || cflg)) {
+       if (!(Uflg || uflg || sflg || pflg || mflg || Lflg ||
+             lflg || Gflg || gflg || fflg || eflg || dflg || cflg
+#ifdef WITH_SELINUX
+             || Zflg
+#endif
+       )) {
                fprintf (stderr, _("%s: no changes\n"), Prog);
                exit (E_SUCCESS);
        }
 
        if (!is_shadow_pwd && (eflg || fflg)) {
                fprintf (stderr,
-                        _
-                        ("%s: shadow passwords required for -e and -f\n"),
-                        Prog);
-               exit (E_USAGE);
-       }
-
-       if (optind != argc - 1) {
-               usage ();
-       }
-
-       if (aflg && (!Gflg)) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-a", "-G");
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
-               fprintf (stderr,
-                        _("%s: the -L, -p, and -U flags are exclusive\n"),
-                        Prog);
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if (oflg && !uflg) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-o", "-u");
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if (mflg && !dflg) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-m", "-d");
-               usage ();
+                        _("%s: shadow passwords required for -e and -f\n"),
+                        Prog);
                exit (E_USAGE);
        }
 
        /* local, no need for xgetpwnam */
        if (lflg && (getpwnam (user_newname) != NULL)) {
-               fprintf (stderr, _("%s: user '%s' already exists\n"), Prog, user_newname);
+               fprintf (stderr,
+                        _("%s: user '%s' already exists\n"),
+                        Prog, user_newname);
                exit (E_NAME_IN_USE);
        }
 
        /* local, no need for xgetpwuid */
        if (uflg && !oflg && (getpwuid (user_newid) != NULL)) {
-               fprintf (stderr, _("%s: UID '%lu' already exists\n"),
-                        Prog, (unsigned long) user_newid);
+               fprintf (stderr,
+                        _("%s: UID '%lu' already exists\n"),
+                        Prog, (unsigned long) user_newid);
                exit (E_UID_IN_USE);
        }
 }
@@ -1104,12 +1159,18 @@ static void close_files (void)
 {
        if (pw_close () == 0) {
                fprintf (stderr,
-                        _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+                        _("%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 (is_shadow_pwd && (spw_close () == 0)) {
                fprintf (stderr,
-                        _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
+                        _("%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);
        }
 
@@ -1118,26 +1179,62 @@ static void close_files (void)
                        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);
                }
 #ifdef SHADOWGRP
-               if (is_shadow_grp && (sgr_close () == 0)) {
-                       fprintf (stderr,
-                                _("%s: failure while writing changes to %s\n"),
-                                Prog, sgr_dbname ());
-                       fail_exit (E_GRP_UPDATE);
-               }
                if (is_shadow_grp) {
-                       sgr_unlock ();
+                       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 */
+                       }
                }
 #endif
-               gr_unlock ();
+               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 (is_shadow_pwd) {
-               spw_unlock ();
+               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 */
+               }
+       }
+       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_unlock ();
 
        pw_locked = false;
        spw_locked = false;
@@ -1166,24 +1263,28 @@ static void open_files (void)
 {
        if (pw_lock () == 0) {
                fprintf (stderr,
-                        _("%s: cannot lock %s\n"), Prog, pw_dbname ());
+                        _("%s: cannot lock %s; try again later.\n"),
+                        Prog, pw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
        pw_locked = true;
        if (pw_open (O_RDWR) == 0) {
                fprintf (stderr,
-                        _("%s: cannot open %s\n"), Prog, pw_dbname ());
+                        _("%s: cannot open %s\n"),
+                        Prog, pw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
        if (is_shadow_pwd && (spw_lock () == 0)) {
                fprintf (stderr,
-                        _("%s: cannot lock %s\n"), Prog, spw_dbname ());
+                        _("%s: cannot lock %s; try again later.\n"),
+                        Prog, spw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
        spw_locked = true;
        if (is_shadow_pwd && (spw_open (O_RDWR) == 0)) {
                fprintf (stderr,
-                        _("%s: cannot open %s\n"), Prog, spw_dbname ());
+                        _("%s: cannot open %s\n"),
+                        Prog, spw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
 
@@ -1194,25 +1295,29 @@ static void open_files (void)
                 */
                if (gr_lock () == 0) {
                        fprintf (stderr,
-                                _("%s: cannot lock %s\n"), Prog, gr_dbname ());
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, gr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
                gr_locked = true;
                if (gr_open (O_RDWR) == 0) {
                        fprintf (stderr,
-                                _("%s: cannot open %s\n"), Prog, gr_dbname ());
+                                _("%s: cannot open %s\n"),
+                                Prog, gr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 #ifdef SHADOWGRP
                if (is_shadow_grp && (sgr_lock () == 0)) {
                        fprintf (stderr,
-                                _("%s: cannot lock %s\n"), Prog, sgr_dbname ());
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, sgr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
                sgr_locked = true;
                if (is_shadow_grp && (sgr_open (O_RDWR) == 0)) {
                        fprintf (stderr,
-                                _("%s: cannot open %s\n"), Prog, sgr_dbname ());
+                                _("%s: cannot open %s\n"),
+                                Prog, sgr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 #endif
@@ -1238,7 +1343,8 @@ static void usr_update (void)
         */
        pwd = pw_locate (user_name);
        if (NULL == pwd) {
-               fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
+               fprintf (stderr,
+                        _("%s: user '%s' does not exist in %s\n"),
                         Prog, user_name, pw_dbname ());
                fail_exit (E_NOTFOUND);
        }
@@ -1259,8 +1365,8 @@ static void usr_update (void)
            || Lflg || Uflg) {
                if (pw_update (&pwent) == 0) {
                        fprintf (stderr,
-                                _("%s: cannot add new entry '%s' in %s\n"),
-                                Prog, pwent.pw_name, pw_dbname ());
+                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                Prog, pw_dbname (), pwent.pw_name);
                        fail_exit (E_PW_UPDATE);
                }
                if (lflg && (pw_remove (user_name) == 0)) {
@@ -1273,8 +1379,8 @@ static void usr_update (void)
        if ((NULL != spwd) && (lflg || eflg || fflg || pflg || Lflg || Uflg)) {
                if (spw_update (&spent) == 0) {
                        fprintf (stderr,
-                                _("%s: cannot add new entry '%s' in %s\n"),
-                                Prog, spent.sp_namp, spw_dbname ());
+                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                Prog, spw_dbname (), spent.sp_namp);
                        fail_exit (E_PW_UPDATE);
                }
                if (lflg && (spw_remove (user_name) == 0)) {
@@ -1296,41 +1402,56 @@ static void move_home (void)
 {
        struct stat sb;
 
-       if (mflg && (stat (user_home, &sb) == 0)) {
+       if (access (user_newhome, F_OK) == 0) {
+               /*
+                * If the new home directory already exist, the user
+                * should not use -m.
+                */
+               fprintf (stderr,
+                        _("%s: directory %s exists\n"),
+                        Prog, user_newhome);
+               fail_exit (E_HOMEDIR);
+       }
+
+       if (stat (user_home, &sb) == 0) {
                /*
                 * Don't try to move it if it is not a directory
                 * (but /dev/null for example).  --marekm
                 */
                if (!S_ISDIR (sb.st_mode)) {
-                       return;
+                       fprintf (stderr,
+                                _("%s: The previous home directory (%s) was "
+                                  "not a directory. It is not removed and no "
+                                  "home directories are created.\n"),
+                                Prog, user_home);
+                       fail_exit (E_HOMEDIR);
                }
 
-               if (access (user_newhome, F_OK) == 0) {
-                       fprintf (stderr, _("%s: directory %s exists\n"),
-                                Prog, user_newhome);
-                       fail_exit (E_HOMEDIR);
-               } else if (rename (user_home, user_newhome) != 0) {
-                       if (errno == EXDEV) {
-                               if (mkdir (user_newhome, sb.st_mode & 0777) != 0) {
-                                       fprintf (stderr,
-                                                _("%s: can't create %s\n"),
-                                                Prog, user_newhome);
-                               }
-                               if (chown (user_newhome, sb.st_uid, sb.st_gid) != 0) {
-                                       fprintf (stderr,
-                                                _("%s: can't chown %s\n"),
-                                                Prog, user_newhome);
-                                       rmdir (user_newhome);
-                                       fail_exit (E_HOMEDIR);
-                               }
-                               if (copy_tree (user_home, user_newhome,
-                                              uflg ? (long int)user_newid : -1,
-                                              gflg ? (long int)user_newgid : -1) == 0) {
-                                       if (remove_tree (user_home) != 0) {
+               if (rename (user_home, user_newhome) == 0) {
+                       /* FIXME: rename above may have broken symlinks
+                        *        pointing to the user's home directory
+                        *        with an absolute path. */
+                       if (chown_tree (user_newhome,
+                                       user_id,  uflg ? user_newid  : (uid_t)-1,
+                                       user_gid, gflg ? user_newgid : (gid_t)-1) != 0) {
+                               fprintf (stderr,
+                                        _("%s: Failed to change ownership of the home directory"),
+                                        Prog);
+                               fail_exit (E_HOMEDIR);
+                       }
+                       return;
+               } else {
+                       if (EXDEV == errno) {
+                               if (copy_tree (user_home, user_newhome, true,
+                                              true,
+                                              user_id,
+                                              uflg ? user_newid : (uid_t)-1,
+                                              user_gid,
+                                              gflg ? user_newgid : (gid_t)-1) == 0) {
+                                       if (remove_tree (user_home, true) != 0) {
                                                fprintf (stderr,
-                                                        _
-                                                        ("%s: warning: failed to completely remove old home directory %s"),
-                                                        Prog, user_home);
+                                                        _("%s: warning: failed to completely remove old home directory %s"),
+                                                        Prog, user_home);
                                        }
 #ifdef WITH_AUDIT
                                        audit_logger (AUDIT_USER_CHAUTHTOK,
@@ -1343,9 +1464,7 @@ static void move_home (void)
                                        return;
                                }
 
-                               /* TODO: do some cleanup if the copy
-                                *       was started */
-                               (void) remove_tree (user_newhome);
+                               (void) remove_tree (user_newhome, true);
                        }
                        fprintf (stderr,
                                 _("%s: cannot rename directory %s to %s\n"),
@@ -1358,16 +1477,6 @@ static void move_home (void)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
        }
-       if (uflg || gflg) {
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "changing home directory owner",
-                             user_newname, (unsigned int) user_newid, 1);
-#endif
-               chown (dflg ? user_newhome : user_home,
-                      uflg ? user_newid : user_id,
-                      gflg ? user_newgid : user_gid);
-       }
 }
 
 /*
@@ -1402,6 +1511,7 @@ static void update_lastlog (void)
                /* Copy the old entry to its new location */
                if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                    || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
+                   || (fsync (fd) != 0)
                    || (close (fd) != 0)) {
                        fprintf (stderr,
                                 _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
@@ -1418,6 +1528,7 @@ static void update_lastlog (void)
                        memzero (&ll, sizeof (ll));
                        if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                            || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
+                           || (fsync (fd) != 0)
                            || (close (fd) != 0)) {
                                fprintf (stderr,
                                         _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
@@ -1461,6 +1572,7 @@ static void update_faillog (void)
                /* Copy the old entry to its new location */
                if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                    || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
+                   || (fsync (fd) != 0)
                    || (close (fd) != 0)) {
                        fprintf (stderr,
                                 _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
@@ -1473,7 +1585,7 @@ static void update_faillog (void)
                /* Check if the new UID already has an entry */
                if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
                    && (read (fd, &fl, sizeof fl) == (ssize_t) sizeof fl)) {
-                       /* Reset the new uid's lastlog entry */
+                       /* Reset the new uid's faillog entry */
                        memzero (&fl, sizeof (fl));
                        if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                            || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
@@ -1536,12 +1648,12 @@ static void move_mailbox (void)
        if (st.st_uid != user_id) {
                /* better leave it alone */
                fprintf (stderr, _("%s: warning: %s not owned by %s\n"),
-                        Prog, mailfile, user_name);
+                        Prog, mailfile, user_name);
                close (fd);
                return;
        }
        if (uflg) {
-               if (fchown (fd, user_newid, (gid_t) - 1) < 0) {
+               if (fchown (fd, user_newid, (gid_t) -1) < 0) {
                        perror (_("failed to change mailbox owner"));
                }
 #ifdef WITH_AUDIT
@@ -1557,7 +1669,7 @@ static void move_mailbox (void)
 
        if (lflg) {
                snprintf (newmailfile, sizeof newmailfile, "%s/%s",
-                         maildir, user_newname);
+                         maildir, user_newname);
                if (   (link (mailfile, newmailfile) != 0)
                    || (unlink (mailfile) != 0)) {
                        perror (_("failed to rename mailbox"));
@@ -1578,10 +1690,12 @@ static void move_mailbox (void)
  */
 int main (int argc, char **argv)
 {
+#ifdef ACCT_TOOLS_SETUID
 #ifdef USE_PAM
        pam_handle_t *pamh = NULL;
        int retval;
-#endif
+#endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
 
 #ifdef WITH_AUDIT
        audit_help_open ();
@@ -1609,20 +1723,28 @@ int main (int argc, char **argv)
 
        process_flags (argc, argv);
 
-#ifdef USE_PAM
-       retval = PAM_SUCCESS;
+       /*
+        * The home directory, the username and the user's UID should not
+        * be changed while the user is logged in.
+        */
+       if (   (uflg || lflg || dflg)
+           && (user_busy (user_name, user_id) != 0)) {
+               exit (E_USER_BUSY);
+       }
 
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
        {
                struct passwd *pampw;
                pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
                if (pampw == NULL) {
-                       retval = PAM_USER_UNKNOWN;
+                       fprintf (stderr,
+                                _("%s: Cannot determine your user name.\n"),
+                                Prog);
+                       exit (1);
                }
 
-               if (PAM_SUCCESS == retval) {
-                       retval = pam_start ("usermod", pampw->pw_name,
-                                           &conv, &pamh);
-               }
+               retval = pam_start ("usermod", pampw->pw_name, &conv, &pamh);
        }
 
        if (PAM_SUCCESS == retval) {
@@ -1633,12 +1755,21 @@ int main (int argc, char **argv)
                retval = pam_acct_mgmt (pamh, 0);
        }
 
-       if (PAM_SUCCESS != retval) {
+       if (NULL != pamh) {
                (void) pam_end (pamh, retval);
+       }
+       if (PAM_SUCCESS != retval) {
                fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
                exit (1);
        }
 #endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
+
+#ifdef WITH_TCB
+       if (shadowtcb_set_user (user_name) == SHADOWTCB_FAILURE) {
+               exit (E_PW_UPDATE);
+       }
+#endif
 
        /*
         * Do the hard stuff - open the files, change the user entries,
@@ -1654,9 +1785,22 @@ int main (int argc, char **argv)
        }
        close_files ();
 
+#ifdef WITH_TCB
+       if (   (lflg || uflg)
+           && (shadowtcb_move (user_newname, user_newid) == SHADOWTCB_FAILURE) ) {
+               exit (E_PW_UPDATE);
+       }
+#endif
+
        nscd_flush_cache ("passwd");
        nscd_flush_cache ("group");
 
+#ifdef WITH_SELINUX
+       if (Zflg) {
+               selinux_update_mapping ();
+       }
+#endif
+
        if (mflg) {
                move_home ();
        }
@@ -1670,21 +1814,65 @@ int main (int argc, char **argv)
        if (uflg) {
                update_lastlog ();
                update_faillog ();
+       }
 
-               /*
-                * Change the UID on all of the files owned by `user_id' to
-                * `user_newid' in the user's home directory.
-                */
-               chown_tree (dflg ? user_newhome : user_home,
-                           user_id, user_newid,
-                           user_gid, gflg ? user_newgid : user_gid);
+       if (!mflg && (uflg || gflg)) {
+               if (access (dflg ? user_newhome : user_home, F_OK) == 0) {
+                       /*
+                        * Change the UID on all of the files owned by
+                        * `user_id' to `user_newid' in the user's home
+                        * directory.
+                        *
+                        * move_home() already takes care of changing the
+                        * ownership.
+                        *
+                        */
+                       if (chown_tree (dflg ? user_newhome : user_home,
+                                       user_id,
+                                       uflg ? user_newid  : (uid_t)-1,
+                                       user_gid,
+                                       gflg ? user_newgid : (gid_t)-1) != 0) {
+                               fprintf (stderr,
+                                        _("%s: Failed to change ownership of the home directory"),
+                                        Prog);
+                               fail_exit (E_HOMEDIR);
+                       }
+               }
        }
 
-#ifdef USE_PAM
-       (void) pam_end (pamh, PAM_SUCCESS);
-#endif                         /* USE_PAM */
+       return E_SUCCESS;
+}
+
+#ifdef WITH_SELINUX
+static void selinux_update_mapping (void) {
+       const char *argv[7];
+
+       if (is_selinux_enabled () <= 0) {
+               return;
+       }
 
-       exit (E_SUCCESS);
-       /* NOT REACHED */
+       if ('\0' != *user_selinux) {
+               argv[0] = "/usr/sbin/semanage";
+               argv[1] = "login";
+               argv[2] = "-m";
+               argv[3] = "-s";
+               argv[4] = user_selinux;
+               argv[5] = user_name;
+               argv[6] = NULL;
+               if (safe_system (argv[0], argv, NULL, true) != 0) {
+                       argv[2] = "-a";
+                       if (safe_system (argv[0], argv, NULL, false) != 0) {
+                               fprintf (stderr,
+                                        _("%s: warning: the user name %s to %s SELinux user mapping failed.\n"),
+                                        Prog, user_name, user_selinux);
+#ifdef WITH_AUDIT
+                               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                             "modifying User mapping ",
+                                             user_name, (unsigned int) user_id, 0);
+#endif
+                       }
+               }
+       }
 }
+#endif