]> granicus.if.org Git - shadow/blobdiff - src/usermod.c
* src/usermod.c (update_gshadow): is_member was computed twice.
[shadow] / src / usermod.c
index d7ff6fdf3ee7240ec6cbb53f516fc8ff901c183d..4d7d0988d71f663b3263df840345ba0c9e5a6ec5 100644 (file)
@@ -1,5 +1,8 @@
 /*
- * Copyright 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2010, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * 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. Neither the name of Julianne F. Haugh nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
+ * 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 JULIE HAUGH 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 JULIE HAUGH 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.
+ * 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>
 #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;
@@ -99,43 +114,49 @@ static long user_newinactive;
 static long sys_ngroups;
 static char **user_groups;     /* NULL-terminated list */
 
-static char *Prog;
-
-static int
-    aflg = 0,                  /* append to existing secondary group set */
-    cflg = 0,                  /* new comment (GECOS) field */
-    dflg = 0,                  /* new home directory */
-    eflg = 0,                  /* days since 1970-01-01 when account becomes expired */
-    fflg = 0,                  /* days until account with expired password is locked */
-    gflg = 0,                  /* new primary group ID */
-    Gflg = 0,                  /* new secondary group set */
-    Lflg = 0,                  /* lock the password */
-    lflg = 0,                  /* new user name */
-    mflg = 0,                  /* create user's home directory if it doesn't exist */
-    oflg = 0,                  /* permit non-unique user ID to be specified with -u */
-    pflg = 0,                  /* new encrypted password */
-    sflg = 0,                  /* new shell program */
-    uflg = 0,                  /* specify new user ID */
-    Uflg = 0;                  /* unlock the password */
-
-static int is_shadow_pwd;
+static bool
+    aflg = false,              /* append to existing secondary group set */
+    cflg = false,              /* new comment (GECOS) field */
+    dflg = false,              /* new home directory */
+    eflg = false,              /* days since 1970-01-01 when account becomes expired */
+    fflg = false,              /* days until account with expired password is locked */
+    gflg = false,              /* new primary group ID */
+    Gflg = false,              /* new secondary group set */
+    Lflg = false,              /* lock the password */
+    lflg = false,              /* new user name */
+    mflg = false,              /* create user's home directory if it doesn't exist */
+    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 */
+
+static bool is_shadow_pwd;
 
 #ifdef SHADOWGRP
-static int is_shadow_grp;
+static bool is_shadow_grp;
 #endif
 
-static int pw_locked  = 0;
-static int spw_locked = 0;
-static int gr_locked  = 0;
+static bool pw_locked  = false;
+static bool spw_locked = false;
+static bool gr_locked  = false;
 #ifdef SHADOWGRP
-static int sgr_locked = 0;
+static bool sgr_locked = false;
 #endif
 
 
 /* local function prototypes */
+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);
@@ -146,33 +167,36 @@ 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);
 static void usr_update (void);
 static void move_home (void);
-static void update_files (void);
+static void update_lastlog (void);
+static void update_faillog (void);
 
 #ifndef NO_MOVE_MAILBOX
 static void move_mailbox (void);
 #endif
 
-/*
- * 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)
+static void date_to_str (char *buf, size_t maxsize,
+                         long int date, const char *negativ)
 {
-       long val;
-       char *errptr;
+       struct tm *tp;
 
-       val = strtol (grname, &errptr, 10);
-       if (*grname != '\0' && *errptr == '\0' && errno != ERANGE && val >= 0)
-               return xgetgrgid (val);
-       return xgetgrnam (grname);
+       if ((negativ != NULL) && (date < 0)) {
+               strncpy (buf, negativ, maxsize);
+       } else {
+               time_t t = (time_t) date;
+               tp = gmtime (&t);
+#ifdef HAVE_STRFTIME
+               strftime (buf, maxsize, "%Y-%m-%d", tp);
+#else
+               snprintf (buf, maxsize, "%04d-%02d-%02d",
+                         tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
+#endif                         /* HAVE_STRFTIME */
+       }
+       buf[maxsize - 1] = '\0';
 }
 
 /*
@@ -194,8 +218,9 @@ static int get_groups (char *list)
         */
        user_groups[0] = (char *) 0;
 
-       if (!*list)
+       if ('\0' == *list) {
                return 0;
+       }
 
        /*
         * So long as there is some data to be converted, strip off each
@@ -206,8 +231,11 @@ static int get_groups (char *list)
                /*
                 * Strip off a single name from the list
                 */
-               if ((cp = strchr (list, ',')))
-                       *cp++ = '\0';
+               cp = strchr (list, ',');
+               if (NULL != cp) {
+                       *cp = '\0';
+                       cp++;
+               }
 
                /*
                 * Names starting with digits are treated as numerical GID
@@ -219,9 +247,9 @@ static int get_groups (char *list)
                 * There must be a match, either by GID value or by
                 * string name.
                 */
-               if (!grp) {
-                       fprintf (stderr, _("%s: unknown group %s\n"),
-                                Prog, list);
+               if (NULL == grp) {
+                       fprintf (stderr, _("%s: group '%s' does not exist\n"),
+                                Prog, list);
                        errors++;
                }
                list = cp;
@@ -230,8 +258,9 @@ static int get_groups (char *list)
                 * If the group doesn't exist, don't dump core. Instead,
                 * try the next one.  --marekm
                 */
-               if (!grp)
+               if (NULL == grp) {
                        continue;
+               }
 
 #ifdef USE_NIS
                /*
@@ -240,17 +269,16 @@ 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
 
                if (ngroups == sys_ngroups) {
                        fprintf (stderr,
-                                _
-                                ("%s: too many groups specified (max %d).\n"),
-                                Prog, ngroups);
+                                _("%s: too many groups specified (max %d).\n"),
+                                Prog, ngroups);
                        break;
                }
 
@@ -258,15 +286,16 @@ static int get_groups (char *list)
                 * Add the group name to the user's list of groups.
                 */
                user_groups[ngroups++] = xstrdup (grp->gr_name);
-       } while (list);
+       } while (NULL != list);
 
        user_groups[ngroups] = (char *) 0;
 
        /*
         * Any errors in finding group names are fatal
         */
-       if (errors)
+       if (0 != errors) {
                return -1;
+       }
 
        return 0;
 }
@@ -274,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"
@@ -299,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);
 }
 
 /*
@@ -309,14 +346,15 @@ static void usage (void)
  */
 static char *new_pw_passwd (char *pw_pass)
 {
-       if (Lflg && pw_pass[0] != '!') {
+       if (Lflg && ('!' != pw_pass[0])) {
                char *buf = xmalloc (strlen (pw_pass) + 2);
 
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "updating passwd",
-                             user_newname, user_newid, 0);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "updating passwd",
+                             user_newname, (unsigned int) user_newid, 0);
 #endif
-               SYSLOG ((LOG_INFO, "lock user `%s' password", user_newname));
+               SYSLOG ((LOG_INFO, "lock user '%s' password", user_newname));
                strcpy (buf, "!");
                strcat (buf, pw_pass);
                pw_pass = buf;
@@ -325,28 +363,30 @@ static char *new_pw_passwd (char *pw_pass)
 
                if (pw_pass[1] == '\0') {
                        fprintf (stderr,
-                                _("%s: unlocking the user would result in a passwordless account.\n"
-                                  "You should set a password with usermod -p to unlock this user account.\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;
                }
 
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "updating password",
-                             user_newname, user_newid, 0);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "updating password",
+                             user_newname, (unsigned int) user_newid, 0);
 #endif
-               SYSLOG ((LOG_INFO, "unlock user `%s' password", user_newname));
+               SYSLOG ((LOG_INFO, "unlock user '%s' password", user_newname));
                s = pw_pass;
-               while (*s) {
+               while ('\0' != *s) {
                        *s = *(s + 1);
                        s++;
                }
        } else if (pflg) {
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password",
-                             user_newname, user_newid, 1);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "changing password",
+                             user_newname, (unsigned int) user_newid, 1);
 #endif
-               SYSLOG ((LOG_INFO, "change user `%s' password", user_newname));
+               SYSLOG ((LOG_INFO, "change user '%s' password", user_newname));
                pw_pass = xstrdup (user_pass);
        }
        return pw_pass;
@@ -361,43 +401,53 @@ 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, user_newid, 1);
+               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);
+       if (!is_shadow_pwd) {
+               pwent->pw_passwd = new_pw_passwd (pwent->pw_passwd);
+       }
 
        if (uflg) {
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing uid",
-                             user_newname, user_newid, 1);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "changing uid",
+                             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) {
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "changing primary group", user_newname,
-                             user_newid, 1);
+                             "changing primary group",
+                             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) {
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing comment",
-                             user_newname, user_newid, 1);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "changing comment",
+                             user_newname, (unsigned int) user_newid, 1);
 #endif
                pwent->pw_gecos = user_newcomment;
        }
@@ -405,21 +455,23 @@ static void new_pwent (struct passwd *pwent)
        if (dflg) {
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "changing home directory", user_newname,
-                             user_newid, 1);
+                             "changing home directory",
+                             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) {
 #ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing user shell",
-                             user_newname, user_newid, 1);
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "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;
        }
 }
@@ -432,74 +484,53 @@ static void new_pwent (struct passwd *pwent)
  */
 static void new_spent (struct spwd *spent)
 {
-       if (lflg)
+       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);
+       }
 
        if (fflg) {
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "changing inactive days", user_newname,
-                             user_newid, 1);
+                             "changing inactive days",
+                             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) {
-               /* XXX - dates might be better than numbers of days.  --marekm */
+               /* log dates rather than numbers of days. */
+               char new_exp[16], old_exp[16];
+               date_to_str (new_exp, sizeof(new_exp),
+                            user_newexpire * DAY, "never");
+               date_to_str (old_exp, sizeof(old_exp),
+                            user_expire * DAY, "never");
 #ifdef WITH_AUDIT
-               if (audit_fd >= 0) {
-                       time_t exp_t;
-                       struct tm *exp_tm;
-                       char new_exp[16], old_exp[16];
-
-                       if (user_newexpire == -1)
-                               new_exp[0] = '\0';
-                       else {
-                               exp_t = user_newexpire * DAY;
-                               exp_tm = gmtime (&exp_t);
-#ifdef HAVE_STRFTIME
-                               strftime (new_exp, sizeof (new_exp), "%Y-%m-%d",
-                                         exp_tm);
-#else
-                               memset (new_exp, 0, sizeof (new_exp));
-                               snprintf (new_exp, sizeof (new_exp) - 1,
-                                         "%04i-%02i-%02i",
-                                         exp_tm->tm_year + 1900,
-                                         exp_tm->tm_mon + 1, exp_tm->tm_mday);
-#endif
-                       }
-
-                       if (user_expire == -1)
-                               old_exp[0] = '\0';
-                       else {
-                               exp_t = user_expire * DAY;
-                               exp_tm = gmtime (&exp_t);
-#ifdef HAVE_STRFTIME
-                               strftime (old_exp, sizeof (old_exp), "%Y-%m-%d",
-                                         exp_tm);
-#else
-                               memset (old_exp, 0, sizeof (old_exp));
-                               snprintf (old_exp, sizeof (old_exp) - 1,
-                                         "%04i-%02i-%02i",
-                                         exp_tm->tm_year + 1900,
-                                         exp_tm->tm_mon + 1, exp_tm->tm_mday);
-#endif
-                       }
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "changing expiration date", user_newname,
-                                     user_newid, 1);
-               }
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                             "changing expiration date",
+                             user_newname, (unsigned int) user_newid, 1);
 #endif
                SYSLOG ((LOG_INFO,
-                        "change user `%s' expiration from `%ld' to `%ld'",
-                        spent->sp_namp, spent->sp_expire, user_newexpire));
+                        "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 = time ((time_t *) 0) / SCALE;
+       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;
+               }
+       }
 }
 
 /*
@@ -507,20 +538,41 @@ static void new_spent (struct spwd *spent)
  */
 static void fail_exit (int code)
 {
-       if (gr_locked)
-               gr_unlock ();
+       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 */
+               }
+       }
 #ifdef SHADOWGRP
-       if (sgr_locked)
-               sgr_unlock ();
+       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
-       if (spw_locked)
-               spw_unlock ();
-       if (pw_locked)
-               pw_unlock ();
+       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 */
+               }
+       }
+       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 */
+               }
+       }
 
 #ifdef WITH_AUDIT
-       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying account",
-                     user_name, -1, 0);
+       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                     "modifying account",
+                     user_name, AUDIT_NO_ID, 0);
 #endif
        exit (code);
 }
@@ -528,82 +580,88 @@ static void fail_exit (int code)
 
 static void update_group (void)
 {
-       int is_member;
-       int was_member;
-       int changed;
+       bool is_member;
+       bool was_member;
+       bool changed;
        const struct group *grp;
        struct group *ngrp;
 
-       changed = 0;
+       changed = false;
 
        /*
         * Scan through the entire group file looking for the groups that
         * the user is a member of.
         */
-       while ((grp = gr_next ())) {
+       while ((grp = gr_next ()) != NULL) {
                /*
                 * See if the user specified this group as one of their
                 * concurrent groups.
                 */
                was_member = is_on_list (grp->gr_mem, user_name);
-               is_member = Gflg && is_on_list (user_groups, grp->gr_name);
+               is_member = Gflg && (   (was_member && aflg)
+                                    || is_on_list (user_groups, grp->gr_name));
 
-               if (!was_member && !is_member)
+               if (!was_member && !is_member) {
                        continue;
+               }
 
                ngrp = __gr_dup (grp);
-               if (!ngrp) {
+               if (NULL == ngrp) {
                        fprintf (stderr,
-                                _("%s: Out of memory. Cannot update the group database.\n"),
-                                Prog);
+                                _("%s: Out of memory. Cannot update %s.\n"),
+                                Prog, gr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 
                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);
-                               changed = 1;
+                                                        user_newname);
+                               changed = true;
 #ifdef WITH_AUDIT
                                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                             "changing group member",
-                                             user_newname, -1, 1);
+                                             "changing group member",
+                                             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);
-                       changed = 1;
+                       changed = true;
 #ifdef WITH_AUDIT
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "removing group member", user_name, -1,
-                                     1);
+                                     "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 = 1;
+                       changed = true;
 #ifdef WITH_AUDIT
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "adding user to group", user_name, -1, 1);
+                                     "adding user to group",
+                                     user_name, AUDIT_NO_ID, 1);
 #endif
-                       SYSLOG ((LOG_INFO, "add `%s' to group `%s'",
+                       SYSLOG ((LOG_INFO, "add '%s' to group '%s'",
                                 user_newname, ngrp->gr_name));
                }
-               if (!changed)
+               if (!changed) {
                        continue;
+               }
 
-               changed = 0;
-               if (!gr_update (ngrp)) {
+               changed = false;
+               if (gr_update (ngrp) == 0) {
                        fprintf (stderr,
-                                _("%s: error adding new group entry\n"), Prog);
-                       SYSLOG ((LOG_ERR, "error adding new group entry"));
+                                _("%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);
                }
        }
@@ -612,20 +670,20 @@ static void update_group (void)
 #ifdef SHADOWGRP
 static void update_gshadow (void)
 {
-       int is_member;
-       int was_member;
-       int was_admin;
-       int changed;
+       bool is_member;
+       bool was_member;
+       bool was_admin;
+       bool changed;
        const struct sgrp *sgrp;
        struct sgrp *nsgrp;
 
-       changed = 0;
+       changed = false;
 
        /*
         * Scan through the entire shadow group file looking for the groups
         * that the user is a member of.
         */
-       while ((sgrp = sgr_next ())) {
+       while ((sgrp = sgr_next ()) != NULL) {
 
                /*
                 * See if the user was a member of this group
@@ -641,83 +699,88 @@ 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));
 
-               if (!was_member && !was_admin && !is_member)
+               if (!was_member && !was_admin && !is_member) {
                        continue;
+               }
 
                nsgrp = __sgr_dup (sgrp);
-               if (!nsgrp) {
+               if (NULL == nsgrp) {
                        fprintf (stderr,
-                                _("%s: Out of memory. Cannot update the shadow group database.\n"),
-                                Prog);
+                                _("%s: Out of memory. Cannot update %s.\n"),
+                                Prog, sgr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 
                if (was_admin && lflg) {
                        nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name);
                        nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname);
-                       changed = 1;
+                       changed = true;
 #ifdef WITH_AUDIT
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "changing admin name in shadow group",
-                                     user_name, -1, 1);
+                                     "changing admin name in shadow group",
+                                     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);
-                               changed = 1;
+                                                         user_newname);
+                               changed = true;
 #ifdef WITH_AUDIT
                                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                             "changing member in shadow group",
-                                             user_name, -1, 1);
+                                             "changing member in shadow group",
+                                             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);
-                       changed = 1;
+                       changed = true;
 #ifdef WITH_AUDIT
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "removing user from shadow group",
-                                     user_name, -1, 1);
+                                     "removing user from shadow group",
+                                     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 = 1;
+                       changed = true;
 #ifdef WITH_AUDIT
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "adding user to shadow group",
-                                     user_newname, -1, 1);
+                                     "adding user to shadow group",
+                                     user_newname, AUDIT_NO_ID, 1);
 #endif
-                       SYSLOG ((LOG_INFO, "add `%s' to shadow group `%s'",
+                       SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'",
                                 user_newname, nsgrp->sg_name));
                }
-               if (!changed)
+               if (!changed) {
                        continue;
+               }
 
-               changed = 0;
+               changed = false;
 
                /* 
                 * Update the group entry to reflect the changes.
                 */
-               if (!sgr_update (nsgrp)) {
+               if (sgr_update (nsgrp) == 0) {
                        fprintf (stderr,
-                                _("%s: error adding new shadow group entry\n"), Prog);
-                       SYSLOG ((LOG_ERR, "error adding shadow group entry"));
+                                _("%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);
                }
        }
@@ -734,37 +797,10 @@ static void grp_update (void)
 {
        update_group ();
 #ifdef SHADOWGRP
-       if (is_shadow_grp)
+       if (is_shadow_grp) {
                update_gshadow ();
-#endif
-}
-
-static long get_number (const char *numstr)
-{
-       long val;
-       char *errptr;
-
-       val = strtol (numstr, &errptr, 10);
-       if (*errptr || errno == ERANGE) {
-               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 (*errptr || errno == ERANGE || val < 0) {
-               fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
-                        uidstr);
-               exit (E_BAD_ARG);
        }
-       return val;
+#endif
 }
 
 /*
@@ -778,64 +814,7 @@ static void process_flags (int argc, char **argv)
 {
        const struct group *grp;
 
-       int anyflag = 0;
-
-       if (argc == 1 || argv[argc - 1][0] == '-')
-               usage ();
-
-       {
-               const struct passwd *pwd;
-               /* local, no need for xgetpwnam */
-               if (!(pwd = getpwnam (argv[argc - 1]))) {
-                       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))) {
-                       user_expire = spwd->sp_expire;
-                       user_inactive = spwd->sp_inact;
-                       user_newexpire = user_expire;
-                       user_newinactive = user_inactive;
-               }
-       }
+       bool anyflag = false;
 
        {
                /*
@@ -856,215 +835,316 @@ 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++;
+                               aflg = true;
                                break;
                        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;
-                               cflg++;
+                               cflg = true;
                                break;
                        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++;
+                               dflg = true;
                                user_newhome = optarg;
                                break;
                        case 'e':
-                               if (*optarg) {
+                               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;
-                               } else
+                               } else {
                                        user_newexpire = -1;
-                               eflg++;
+                               }
+                               eflg = true;
                                break;
                        case 'f':
-                               user_newinactive = get_number (optarg);
-                               fflg++;
+                               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 (!grp) {
+                               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;
-                               gflg++;
+                               gflg = true;
                                break;
                        case 'G':
-                               if (get_groups (optarg))
+                               if (get_groups (optarg) != 0) {
                                        exit (E_NOTFOUND);
-                               Gflg++;
+                               }
+                               Gflg = true;
                                break;
+                       case 'h':
+                               usage (E_SUCCESS);
+                               /* @notreached@ */break;
                        case 'l':
-                               if (!check_user_name (optarg)) {
+                               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++;
+                               lflg = true;
                                user_newname = optarg;
                                break;
                        case 'L':
-                               Lflg++;
+                               Lflg = true;
                                break;
                        case 'm':
-                               mflg++;
+                               mflg = true;
                                break;
                        case 'o':
-                               oflg++;
+                               oflg = true;
                                break;
                        case 'p':
                                user_pass = optarg;
-                               pflg++;
+                               pflg = true;
                                break;
                        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++;
+                               sflg = true;
                                break;
                        case 'u':
-                               user_newid = get_id (optarg);
-                               uflg++;
+                               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++;
+                               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++;
+                       anyflag = true;
                }
        }
 
-       if (anyflag == 0) {
-               fprintf (stderr, _("%s: no flags given\n"), Prog);
-               exit (E_USAGE);
+       if (optind != argc - 1) {
+               usage (E_USAGE);
        }
 
-       if (user_newid == user_id) {
-               uflg = 0;
-       }
-       if (user_newgid == user_gid) {
-               gflg = 0;
-       }
-       if (strcmp (user_newshell, user_shell) == 0) {
-               sflg = 0;
-       }
-       if (strcmp (user_newname, user_name) == 0) {
-               lflg = 0;
-       }
-       if (user_newinactive == user_inactive) {
-               fflg = 0;
+       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);
        }
-       if (user_newexpire == user_expire) {
-               eflg = 0;
+
+       /* user_newname, user_newid, user_newgid can be used even when the
+        * options where not specified. */
+       if (!lflg) {
+               user_newname = user_name;
        }
-       if (strcmp (user_newhome, user_home) == 0) {
-               dflg = 0;
+       if (!uflg) {
+               user_newid = user_id;
        }
-       if (strcmp (user_newcomment, user_comment) == 0) {
-               cflg = 0;
+       if (!gflg) {
+               user_newgid = user_gid;
        }
 
-       if (Uflg + uflg + sflg + pflg + oflg + mflg + Lflg + lflg + Gflg +
-           gflg + fflg + eflg + dflg + cflg == 0) {
-               fprintf (stderr, _("%s: no changes\n"), Prog);
-               exit (E_SUCCESS);
-       }
+#ifdef USE_NIS
+       /*
+        * Now make sure it isn't an NIS user.
+        */
+       if (__ispwNIS ()) {
+               char *nis_domain;
+               char *nis_master;
 
-       if (!is_shadow_pwd && (eflg || fflg)) {
                fprintf (stderr,
-                        _
-                        ("%s: shadow passwords required for -e and -f\n"),
-                        Prog);
-               exit (E_USAGE);
+                        _("%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
 
-       if (optind != argc - 1)
-               usage ();
+       {
+               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 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 ();
-               exit (E_USAGE);
+                        _("%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 ();
-               exit (E_USAGE);
+                        _("%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 ();
-               exit (E_USAGE);
+                        _("%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 ();
-               exit (E_USAGE);
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-m", "-d");
+               usage (E_USAGE);
        }
 
-       if (dflg && strcmp (user_home, user_newhome) == 0)
-               dflg = mflg = 0;
+       if (user_newid == user_id) {
+               uflg = false;
+               oflg = false;
+       }
+       if (user_newgid == user_gid) {
+               gflg = false;
+       }
+       if (   (NULL != user_newshell)
+           && (strcmp (user_newshell, user_shell) == 0)) {
+               sflg = false;
+       }
+       if (strcmp (user_newname, user_name) == 0) {
+               lflg = false;
+       }
+       if (user_newinactive == user_inactive) {
+               fflg = false;
+       }
+       if (user_newexpire == user_expire) {
+               eflg = false;
+       }
+       if (   (NULL != user_newhome)
+           && (strcmp (user_newhome, user_home) == 0)) {
+               dflg = false;
+               mflg = false;
+       }
+       if (   (NULL != user_newcomment)
+           && (strcmp (user_newcomment, user_comment) == 0)) {
+               cflg = false;
+       }
+
+       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 (uflg && user_id == user_newid)
-               uflg = oflg = 0;
+       if (!is_shadow_pwd && (eflg || fflg)) {
+               fprintf (stderr,
+                        _("%s: shadow passwords required for -e and -f\n"),
+                        Prog);
+               exit (E_USAGE);
+       }
 
        /* local, no need for xgetpwnam */
-       if (lflg && getpwnam (user_newname)) {
-               fprintf (stderr, _("%s: user %s exists\n"), Prog, user_newname);
+       if (lflg && (getpwnam (user_newname) != NULL)) {
+               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)) {
-               fprintf (stderr, _("%s: uid %lu is not unique\n"),
-                        Prog, (unsigned long) user_newid);
+       if (uflg && !oflg && (getpwuid (user_newid) != NULL)) {
+               fprintf (stderr,
+                        _("%s: UID '%lu' already exists\n"),
+                        Prog, (unsigned long) user_newid);
                exit (E_UID_IN_USE);
        }
 }
@@ -1077,44 +1157,90 @@ static void process_flags (int argc, char **argv)
  */
 static void close_files (void)
 {
-       if (!pw_close ()) {
-               fprintf (stderr, _("%s: cannot rewrite password file\n"), Prog);
+       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 (is_shadow_pwd && !spw_close ()) {
+       if (is_shadow_pwd && (spw_close () == 0)) {
                fprintf (stderr,
-                        _("%s: cannot rewrite shadow password file\n"), Prog);
+                        _("%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 (Gflg || lflg) {
-               if (!gr_close ()) {
-                       fprintf (stderr, _("%s: cannot rewrite group file\n"),
-                                Prog);
+               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);
                }
 #ifdef SHADOWGRP
-               if (is_shadow_grp && !sgr_close ()) {
-                       fprintf (stderr,
-                                _("%s: cannot rewrite shadow group file\n"),
-                                Prog);
-                       fail_exit (E_GRP_UPDATE);
+               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 */
+                       }
                }
-               if (is_shadow_grp)
-                       sgr_unlock ();
 #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 ();
-       pw_unlock ();
+       if (is_shadow_pwd) {
+               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_locked = 0;
-       spw_locked = 0;
-       gr_locked = 0;
+       pw_locked = false;
+       spw_locked = false;
+       gr_locked = false;
 #ifdef SHADOWGRP
-       sgr_locked = 0;
+       sgr_locked = false;
 #endif
 
        /*
@@ -1135,24 +1261,30 @@ static void close_files (void)
  */
 static void open_files (void)
 {
-       if (!pw_lock ()) {
-               fprintf (stderr, _("%s: unable to lock password file\n"), Prog);
+       if (pw_lock () == 0) {
+               fprintf (stderr,
+                        _("%s: cannot lock %s; try again later.\n"),
+                        Prog, pw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
-       pw_locked = 1;
-       if (!pw_open (O_RDWR)) {
-               fprintf (stderr, _("%s: unable to open password file\n"), Prog);
+       pw_locked = true;
+       if (pw_open (O_RDWR) == 0) {
+               fprintf (stderr,
+                        _("%s: cannot open %s\n"),
+                        Prog, pw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
-       if (is_shadow_pwd && !spw_lock ()) {
+       if (is_shadow_pwd && (spw_lock () == 0)) {
                fprintf (stderr,
-                        _("%s: cannot lock shadow password file\n"), Prog);
+                        _("%s: cannot lock %s; try again later.\n"),
+                        Prog, spw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
-       spw_locked = 1;
-       if (is_shadow_pwd && !spw_open (O_RDWR)) {
+       spw_locked = true;
+       if (is_shadow_pwd && (spw_open (O_RDWR) == 0)) {
                fprintf (stderr,
-                        _("%s: cannot open shadow password file\n"), Prog);
+                        _("%s: cannot open %s\n"),
+                        Prog, spw_dbname ());
                fail_exit (E_PW_UPDATE);
        }
 
@@ -1161,29 +1293,31 @@ static void open_files (void)
                 * Lock and open the group file. This will load all of the
                 * group entries.
                 */
-               if (!gr_lock ()) {
-                       fprintf (stderr, _("%s: error locking group file\n"),
-                                Prog);
+               if (gr_lock () == 0) {
+                       fprintf (stderr,
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, gr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
-               gr_locked = 1;
-               if (!gr_open (O_RDWR)) {
-                       fprintf (stderr, _("%s: error opening group file\n"),
-                                Prog);
+               gr_locked = true;
+               if (gr_open (O_RDWR) == 0) {
+                       fprintf (stderr,
+                                _("%s: cannot open %s\n"),
+                                Prog, gr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 #ifdef SHADOWGRP
-               if (is_shadow_grp && !sgr_lock ()) {
+               if (is_shadow_grp && (sgr_lock () == 0)) {
                        fprintf (stderr,
-                                _("%s: error locking shadow group file\n"),
-                                Prog);
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, sgr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
-               sgr_locked = 1;
-               if (is_shadow_grp && !sgr_open (O_RDWR)) {
+               sgr_locked = true;
+               if (is_shadow_grp && (sgr_open (O_RDWR) == 0)) {
                        fprintf (stderr,
-                                _("%s: error opening shadow group file\n"),
-                                Prog);
+                                _("%s: cannot open %s\n"),
+                                Prog, sgr_dbname ());
                        fail_exit (E_GRP_UPDATE);
                }
 #endif
@@ -1208,9 +1342,10 @@ static void usr_update (void)
         * Locate the entry in /etc/passwd, which MUST exist.
         */
        pwd = pw_locate (user_name);
-       if (!pwd) {
-               fprintf (stderr, _("%s: %s not found in /etc/passwd\n"),
-                        Prog, user_name);
+       if (NULL == pwd) {
+               fprintf (stderr,
+                        _("%s: user '%s' does not exist in %s\n"),
+                        Prog, user_name, pw_dbname ());
                fail_exit (E_NOTFOUND);
        }
        pwent = *pwd;
@@ -1221,39 +1356,37 @@ static void usr_update (void)
         * Locate the entry in /etc/shadow. It doesn't have to exist, and
         * won't be created if it doesn't.
         */
-       if (is_shadow_pwd && (spwd = spw_locate (user_name))) {
+       if (is_shadow_pwd && ((spwd = spw_locate (user_name)) != NULL)) {
                spent = *spwd;
                new_spent (&spent);
        }
 
        if (lflg || uflg || gflg || cflg || dflg || sflg || pflg
            || Lflg || Uflg) {
-               if (!pw_update (&pwent)) {
+               if (pw_update (&pwent) == 0) {
                        fprintf (stderr,
-                                _("%s: error changing password entry\n"),
-                                Prog);
+                                _("%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)) {
+               if (lflg && (pw_remove (user_name) == 0)) {
                        fprintf (stderr,
-                                _("%s: error removing password entry\n"),
-                                Prog);
+                                _("%s: cannot remove entry '%s' from %s\n"),
+                                Prog, user_name, pw_dbname ());
                        fail_exit (E_PW_UPDATE);
                }
        }
-       if (spwd && (lflg || eflg || fflg || pflg || Lflg || Uflg)) {
-               if (!spw_update (&spent)) {
+       if ((NULL != spwd) && (lflg || eflg || fflg || pflg || Lflg || Uflg)) {
+               if (spw_update (&spent) == 0) {
                        fprintf (stderr,
-                                _
-                                ("%s: error adding new shadow password entry\n"),
-                                Prog);
+                                _("%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)) {
+               if (lflg && (spw_remove (user_name) == 0)) {
                        fprintf (stderr,
-                                _
-                                ("%s: error removing shadow password entry\n"),
-                                Prog);
+                                _("%s: cannot remove entry '%s' from %s\n"),
+                                Prog, user_name, spw_dbname ());
                        fail_exit (E_PW_UPDATE);
                }
        }
@@ -1269,112 +1402,201 @@ 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;
-
-               if (access (user_newhome, F_OK) == 0) {
-                       fprintf (stderr, _("%s: directory %s exists\n"),
-                                Prog, user_newhome);
+               if (!S_ISDIR (sb.st_mode)) {
+                       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);
-               } else if (rename (user_home, user_newhome)) {
-                       if (errno == EXDEV) {
-                               if (mkdir (user_newhome, sb.st_mode & 0777)) {
-                                       fprintf (stderr,
-                                                _
-                                                ("%s: can't create %s\n"),
-                                                Prog, user_newhome);
-                               }
-                               if (chown (user_newhome, sb.st_uid, sb.st_gid)) {
-                                       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 ||
-                                           rmdir (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,
-                                                     Prog,
-                                                     "moving home directory",
-                                                     user_newname, user_newid,
-                                                     1);
+                                                     Prog,
+                                                     "moving home directory",
+                                                     user_newname,
+                                                     (unsigned int) user_newid,
+                                                     1);
 #endif
                                        return;
                                }
 
-                               (void) remove_tree (user_newhome);
-                               (void) rmdir (user_newhome);
+                               (void) remove_tree (user_newhome, true);
                        }
                        fprintf (stderr,
-                                _
-                                ("%s: cannot rename directory %s to %s\n"),
-                                Prog, user_home, user_newhome);
+                                _("%s: cannot rename directory %s to %s\n"),
+                                Prog, user_home, user_newhome);
                        fail_exit (E_HOMEDIR);
                }
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "moving home directory", user_newname, user_newid,
-                             1);
+                             "moving home directory",
+                             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,
-                             user_newid, 1);
-#endif
-               chown (dflg ? user_newhome : user_home,
-                      uflg ? user_newid : user_id,
-                      gflg ? user_newgid : user_gid);
-       }
 }
 
 /*
- * update_files - update the lastlog and faillog files
+ * update_lastlog - update the lastlog file
+ *
+ * Relocate the "lastlog" entries for the user. The old entry is
+ * left alone in case the UID was shared. It doesn't hurt anything
+ * to just leave it be.
  */
-static void update_files (void)
+static void update_lastlog (void)
 {
        struct lastlog ll;
-       struct faillog fl;
        int fd;
+       off_t off_uid = (off_t) user_id * sizeof ll;
+       off_t off_newuid = (off_t) user_newid * sizeof ll;
 
-       /*
-        * Relocate the "lastlog" entries for the user. The old entry is
-        * left alone in case the UID was shared. It doesn't hurt anything
-        * to just leave it be.
-        */
-       if ((fd = open (LASTLOG_FILE, O_RDWR)) != -1) {
-               lseek (fd, (off_t) user_id * sizeof ll, SEEK_SET);
-               if (read (fd, (char *) &ll, sizeof ll) == sizeof ll) {
-                       lseek (fd, (off_t) user_newid * sizeof ll, SEEK_SET);
-                       write (fd, (char *) &ll, sizeof ll);
+       if (access (LASTLOG_FILE, F_OK) != 0) {
+               return;
+       }
+
+       fd = open (LASTLOG_FILE, O_RDWR);
+
+       if (-1 == fd) {
+               fprintf (stderr,
+                        _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
+                        Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+               return;
+       }
+
+       if (   (lseek (fd, off_uid, SEEK_SET) == off_uid)
+           && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
+               /* 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"),
+                                Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+               }
+       } else {
+               /* Assume lseek or read failed because there is
+                * no entry for the old UID */
+
+               /* Check if the new UID already has an entry */
+               if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
+                   && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
+                       /* Reset the new uid's lastlog entry */
+                       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"),
+                                        Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+                       }
+               } else {
+                       (void) close (fd);
                }
-               close (fd);
        }
+}
 
-       /*
-        * Relocate the "faillog" entries in the same manner.
-        */
-       if ((fd = open (FAILLOG_FILE, O_RDWR)) != -1) {
-               lseek (fd, (off_t) user_id * sizeof fl, SEEK_SET);
-               if (read (fd, (char *) &fl, sizeof fl) == sizeof fl) {
-                       lseek (fd, (off_t) user_newid * sizeof fl, SEEK_SET);
-                       write (fd, (char *) &fl, sizeof fl);
+/*
+ * update_faillog - update the faillog file
+ *
+ * Relocate the "faillog" entries for the user. The old entry is
+ * left alone in case the UID was shared. It doesn't hurt anything
+ * to just leave it be.
+ */
+static void update_faillog (void)
+{
+       struct faillog fl;
+       int fd;
+       off_t off_uid = (off_t) user_id * sizeof fl;
+       off_t off_newuid = (off_t) user_newid * sizeof fl;
+
+       if (access (FAILLOG_FILE, F_OK) != 0) {
+               return;
+       }
+
+       fd = open (FAILLOG_FILE, O_RDWR);
+
+       if (-1 == fd) {
+               fprintf (stderr,
+                        _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
+                        Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+               return;
+       }
+
+       if (   (lseek (fd, off_uid, SEEK_SET) == off_uid)
+           && (read (fd, (char *) &fl, sizeof fl) == (ssize_t) sizeof fl)) {
+               /* 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"),
+                                Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+               }
+       } else {
+               /* Assume lseek or read failed because there is
+                * no entry for the old UID */
+
+               /* 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 faillog entry */
+                       memzero (&fl, sizeof (fl));
+                       if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
+                           || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
+                           || (close (fd) != 0)) {
+                               fprintf (stderr,
+                                        _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
+                                        Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+                       }
+               } else {
+                       (void) close (fd);
                }
-               close (fd);
        }
 }
 
@@ -1395,11 +1617,13 @@ static void move_mailbox (void)
 
        maildir = getdef_str ("MAIL_DIR");
 #ifdef MAIL_SPOOL_DIR
-       if (!maildir && !getdef_str ("MAIL_FILE"))
+       if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) {
                maildir = MAIL_SPOOL_DIR;
+       }
 #endif
-       if (!maildir)
+       if (NULL == maildir) {
                return;
+       }
 
        /*
         * O_NONBLOCK is to make sure open won't hang on mandatory locks.
@@ -1411,8 +1635,9 @@ static void move_mailbox (void)
        fd = open (mailfile, O_RDONLY | O_NONBLOCK, 0);
        if (fd < 0) {
                /* no need for warnings if the mailbox doesn't exist */
-               if (errno != ENOENT)
+               if (errno != ENOENT) {
                        perror (mailfile);
+               }
                return;
        }
        if (fstat (fd, &st) < 0) {
@@ -1423,19 +1648,19 @@ 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
                else {
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "changing mail file owner", user_newname,
-                                     user_newid, 1);
+                                     "changing mail file owner",
+                                     user_newname, (unsigned int) user_newid, 1);
                }
 #endif
        }
@@ -1444,15 +1669,16 @@ static void move_mailbox (void)
 
        if (lflg) {
                snprintf (newmailfile, sizeof newmailfile, "%s/%s",
-                         maildir, user_newname);
-               if (link (mailfile, newmailfile) || unlink (mailfile)) {
+                         maildir, user_newname);
+               if (   (link (mailfile, newmailfile) != 0)
+                   || (unlink (mailfile) != 0)) {
                        perror (_("failed to rename mailbox"));
                }
 #ifdef WITH_AUDIT
                else {
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "changing mail file name", user_newname,
-                                     user_newid, 1);
+                                     "changing mail file name",
+                                     user_newname, (unsigned int) user_newid, 1);
                }
 #endif
        }
@@ -1464,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 ();
@@ -1478,12 +1706,12 @@ int main (int argc, char **argv)
         */
        Prog = Basename (argv[0]);
 
-       setlocale (LC_ALL, "");
-       bindtextdomain (PACKAGE, LOCALEDIR);
-       textdomain (PACKAGE);
+       (void) setlocale (LC_ALL, "");
+       (void) bindtextdomain (PACKAGE, LOCALEDIR);
+       (void) textdomain (PACKAGE);
 
        sys_ngroups = sysconf (_SC_NGROUPS_MAX);
-       user_groups = malloc ((1 + sys_ngroups) * sizeof (char *));
+       user_groups = (char **) malloc (sizeof (char *) * (1 + sys_ngroups));
        user_groups[0] = (char *) 0;
 
        OPENLOG ("usermod");
@@ -1495,80 +1723,156 @@ 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 (retval == PAM_SUCCESS) {
-                       retval = pam_start ("usermod", pampw->pw_name,
-                                           &conv, &pamh);
-               }
+               retval = pam_start ("usermod", pampw->pw_name, &conv, &pamh);
        }
 
-       if (retval == PAM_SUCCESS) {
+       if (PAM_SUCCESS == retval) {
                retval = pam_authenticate (pamh, 0);
-               if (retval != PAM_SUCCESS) {
-                       pam_end (pamh, retval);
-               }
        }
 
-       if (retval == PAM_SUCCESS) {
+       if (PAM_SUCCESS == retval) {
                retval = pam_acct_mgmt (pamh, 0);
-               if (retval != PAM_SUCCESS) {
-                       pam_end (pamh, retval);
-               }
        }
 
-       if (retval != PAM_SUCCESS) {
+       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,
         * change the home directory, then close and update the files.
         */
        open_files ();
-       usr_update ();
-       if (Gflg || lflg)
+       if (   cflg || dflg || eflg || fflg || gflg || Lflg || lflg || pflg
+           || sflg || uflg || Uflg) {
+               usr_update ();
+       }
+       if (Gflg || lflg) {
                grp_update ();
+       }
        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");
 
-       if (mflg)
+#ifdef WITH_SELINUX
+       if (Zflg) {
+               selinux_update_mapping ();
+       }
+#endif
+
+       if (mflg) {
                move_home ();
+       }
 
 #ifndef NO_MOVE_MAILBOX
-       if (lflg || uflg)
+       if (lflg || uflg) {
                move_mailbox ();
+       }
 #endif
 
        if (uflg) {
-               update_files ();
+               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
-       if (retval == PAM_SUCCESS)
-               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
+