]> granicus.if.org Git - shadow/blobdiff - src/usermod.c
* src/usermod.c (update_gshadow): is_member was computed twice.
[shadow] / src / usermod.c
index 4c87f1539bb7b7148c728a66fc13f48f035d3bc4..4d7d0988d71f663b3263df840345ba0c9e5a6ec5 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 1991 - 1994, Julianne Frances Haugh
  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
  * Copyright (c) 2000 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2009, Nicolas François
+ * Copyright (c) 2007 - 2010, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -63,6 +63,9 @@
 #include "sgroupio.h"
 #endif
 #include "shadowio.h"
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
 
 /*
  * exit status values
 /*
  * Global variables
  */
-char *Prog;
+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;
 #ifdef WITH_SELINUX
 static const char *user_selinux = "";
 #endif
-static char *user_newshell;
+static char *user_newshell = NULL;
 static long user_expire;
 static long user_newexpire;
 static long user_inactive;
@@ -149,7 +152,7 @@ static bool sgr_locked = false;
 static void date_to_str (char *buf, size_t maxsize,
                          long int date, const char *negativ);
 static int get_groups (char *);
-static void usage (void);
+static /*@noreturn@*/void usage (int status);
 static void new_pwent (struct passwd *);
 #ifdef WITH_SELINUX
 static void selinux_update_mapping (void);
@@ -300,9 +303,9 @@ static int get_groups (char *list)
 /*
  * usage - display usage message and exit
  */
-static void usage (void)
+static /*@noreturn@*/void usage (int status)
 {
-       fprintf (stderr,
+       fprintf ((E_SUCCESS != status) ? stderr : stdout,
                 _("Usage: usermod [options] LOGIN\n"
                 "\n"
                 "Options:\n"
@@ -334,7 +337,7 @@ static void usage (void)
                 ""
 #endif
                 );
-       exit (E_USAGE);
+       exit (status);
 }
 
 /*
@@ -696,7 +699,6 @@ static void update_gshadow (void)
                 * See if the user specified this group as one of their
                 * concurrent groups.
                 */
-               is_member = Gflg && is_on_list (user_groups, sgrp->sg_name);
                is_member = Gflg && (   (was_member && aflg)
                                     || is_on_list (user_groups, sgrp->sg_name));
 
@@ -814,68 +816,6 @@ static void process_flags (int argc, char **argv)
 
        bool anyflag = false;
 
-       if ((1 == argc) || ('-' == argv[argc - 1][0])) {
-               usage ();
-       }
-
-       {
-               const struct passwd *pwd;
-               /* local, no need for xgetpwnam */
-               pwd = getpwnam (argv[argc - 1]);
-               if (NULL == pwd) {
-                       fprintf (stderr,
-                                _("%s: user '%s' does not exist\n"),
-                                Prog, argv[argc - 1]);
-                       exit (E_NOTFOUND);
-               }
-
-               user_name = argv[argc - 1];
-               user_id = pwd->pw_uid;
-               user_gid = pwd->pw_gid;
-               user_comment = xstrdup (pwd->pw_gecos);
-               user_home = xstrdup (pwd->pw_dir);
-               user_shell = xstrdup (pwd->pw_shell);
-       }
-       user_newname = user_name;
-       user_newid = user_id;
-       user_newgid = user_gid;
-       user_newcomment = user_comment;
-       user_newhome = user_home;
-       user_newshell = user_shell;
-
-#ifdef USE_NIS
-       /*
-        * Now make sure it isn't an NIS user.
-        */
-       if (__ispwNIS ()) {
-               char *nis_domain;
-               char *nis_master;
-
-               fprintf (stderr,
-                        _("%s: user %s is a NIS user\n"),
-                        Prog, user_name);
-
-               if (   !yp_get_default_domain (&nis_domain)
-                   && !yp_master (nis_domain, "passwd.byname", &nis_master)) {
-                       fprintf (stderr,
-                                _("%s: %s is the NIS master\n"),
-                                Prog, nis_master);
-               }
-               exit (E_NOTFOUND);
-       }
-#endif
-
-       {
-               const struct spwd *spwd = NULL;
-               /* local, no need for xgetspnam */
-               if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
-                       user_expire = spwd->sp_expire;
-                       user_inactive = spwd->sp_inact;
-                       user_newexpire = user_expire;
-                       user_newinactive = user_inactive;
-               }
-       }
-
        {
                /*
                 * Parse the command line options.
@@ -937,7 +877,7 @@ static void process_flags (int argc, char **argv)
                        case 'e':
                                if ('\0' != *optarg) {
                                        user_newexpire = strtoday (optarg);
-                                       if (user_newexpire == -1) {
+                                       if (user_newexpire < -1) {
                                                fprintf (stderr,
                                                         _("%s: invalid date '%s'\n"),
                                                         Prog, optarg);
@@ -955,7 +895,7 @@ static void process_flags (int argc, char **argv)
                                        fprintf (stderr,
                                                 _("%s: invalid numeric argument '%s'\n"),
                                                 Prog, optarg);
-                                       usage ();
+                                       usage (E_USAGE);
                                }
                                fflg = true;
                                break;
@@ -976,6 +916,9 @@ static void process_flags (int argc, char **argv)
                                }
                                Gflg = true;
                                break;
+                       case 'h':
+                               usage (E_SUCCESS);
+                               /* @notreached@ */break;
                        case 'l':
                                if (!is_valid_user_name (optarg)) {
                                        fprintf (stderr,
@@ -1036,15 +979,110 @@ static void process_flags (int argc, char **argv)
                                break;
 #endif
                        default:
-                               usage ();
+                               usage (E_USAGE);
                        }
                        anyflag = true;
                }
        }
 
+       if (optind != argc - 1) {
+               usage (E_USAGE);
+       }
+
+       user_name = argv[argc - 1];
+
+       {
+               const struct passwd *pwd;
+               /* local, no need for xgetpwnam */
+               pwd = getpwnam (user_name);
+               if (NULL == pwd) {
+                       fprintf (stderr,
+                                _("%s: user '%s' does not exist\n"),
+                                Prog, user_name);
+                       exit (E_NOTFOUND);
+               }
+
+               user_id = pwd->pw_uid;
+               user_gid = pwd->pw_gid;
+               user_comment = xstrdup (pwd->pw_gecos);
+               user_home = xstrdup (pwd->pw_dir);
+               user_shell = xstrdup (pwd->pw_shell);
+       }
+
+       /* user_newname, user_newid, user_newgid can be used even when the
+        * options where not specified. */
+       if (!lflg) {
+               user_newname = user_name;
+       }
+       if (!uflg) {
+               user_newid = user_id;
+       }
+       if (!gflg) {
+               user_newgid = user_gid;
+       }
+
+#ifdef USE_NIS
+       /*
+        * Now make sure it isn't an NIS user.
+        */
+       if (__ispwNIS ()) {
+               char *nis_domain;
+               char *nis_master;
+
+               fprintf (stderr,
+                        _("%s: user %s is a NIS user\n"),
+                        Prog, user_name);
+
+               if (   !yp_get_default_domain (&nis_domain)
+                   && !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+                       fprintf (stderr,
+                                _("%s: %s is the NIS master\n"),
+                                Prog, nis_master);
+               }
+               exit (E_NOTFOUND);
+       }
+#endif
+
+       {
+               const struct spwd *spwd = NULL;
+               /* local, no need for xgetspnam */
+               if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
+                       user_expire = spwd->sp_expire;
+                       user_inactive = spwd->sp_inact;
+               }
+       }
+
        if (!anyflag) {
-               fprintf (stderr, _("%s: no flags given\n"), Prog);
-               exit (E_USAGE);
+               fprintf (stderr, _("%s: no options\n"), Prog);
+               usage (E_USAGE);
+       }
+
+       if (aflg && (!Gflg)) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-a", "-G");
+               usage (E_USAGE);
+       }
+
+       if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
+               fprintf (stderr,
+                        _("%s: the -L, -p, and -U flags are exclusive\n"),
+                        Prog);
+               usage (E_USAGE);
+       }
+
+       if (oflg && !uflg) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-o", "-u");
+               usage (E_USAGE);
+       }
+
+       if (mflg && !dflg) {
+               fprintf (stderr,
+                        _("%s: %s flag is only allowed with the %s flag\n"),
+                        Prog, "-m", "-d");
+               usage (E_USAGE);
        }
 
        if (user_newid == user_id) {
@@ -1054,7 +1092,8 @@ static void process_flags (int argc, char **argv)
        if (user_newgid == user_gid) {
                gflg = false;
        }
-       if (strcmp (user_newshell, user_shell) == 0) {
+       if (   (NULL != user_newshell)
+           && (strcmp (user_newshell, user_shell) == 0)) {
                sflg = false;
        }
        if (strcmp (user_newname, user_name) == 0) {
@@ -1066,15 +1105,17 @@ static void process_flags (int argc, char **argv)
        if (user_newexpire == user_expire) {
                eflg = false;
        }
-       if (strcmp (user_newhome, user_home) == 0) {
+       if (   (NULL != user_newhome)
+           && (strcmp (user_newhome, user_home) == 0)) {
                dflg = false;
                mflg = false;
        }
-       if (strcmp (user_newcomment, user_comment) == 0) {
+       if (   (NULL != user_newcomment)
+           && (strcmp (user_newcomment, user_comment) == 0)) {
                cflg = false;
        }
 
-       if (!(Uflg || uflg || sflg || pflg || oflg || mflg || Lflg ||
+       if (!(Uflg || uflg || sflg || pflg || mflg || Lflg ||
              lflg || Gflg || gflg || fflg || eflg || dflg || cflg
 #ifdef WITH_SELINUX
              || Zflg
@@ -1091,42 +1132,6 @@ static void process_flags (int argc, char **argv)
                exit (E_USAGE);
        }
 
-       if (optind != argc - 1) {
-               usage ();
-       }
-
-       if (aflg && (!Gflg)) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-a", "-G");
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
-               fprintf (stderr,
-                        _("%s: the -L, -p, and -U flags are exclusive\n"),
-                        Prog);
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if (oflg && !uflg) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-o", "-u");
-               usage ();
-               exit (E_USAGE);
-       }
-
-       if (mflg && !dflg) {
-               fprintf (stderr,
-                        _("%s: %s flag is only allowed with the %s flag\n"),
-                        Prog, "-m", "-d");
-               usage ();
-               exit (E_USAGE);
-       }
-
        /* local, no need for xgetpwnam */
        if (lflg && (getpwnam (user_newname) != NULL)) {
                fprintf (stderr,
@@ -1397,38 +1402,53 @@ 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);
+                                _("%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) != 0) {
-                       if (errno == EXDEV) {
-                               if (mkdir (user_newhome, sb.st_mode & 0777) != 0) {
-                                       fprintf (stderr,
-                                                _("%s: can't create %s\n"),
-                                                Prog, user_newhome);
-                               }
-                               if (chown (user_newhome, sb.st_uid, sb.st_gid) != 0) {
-                                       fprintf (stderr,
-                                                _("%s: can't chown %s\n"),
-                                                Prog, user_newhome);
-                                       rmdir (user_newhome);
-                                       fail_exit (E_HOMEDIR);
-                               }
-                               if (copy_tree (user_home, user_newhome,
-                                              uflg ? (long int)user_newid : -1,
-                                              gflg ? (long int)user_newgid : -1) == 0) {
-                                       if (remove_tree (user_home) != 0) {
+               }
+
+               if (rename (user_home, user_newhome) == 0) {
+                       /* FIXME: rename above may have broken symlinks
+                        *        pointing to the user's home directory
+                        *        with an absolute path. */
+                       if (chown_tree (user_newhome,
+                                       user_id,  uflg ? user_newid  : (uid_t)-1,
+                                       user_gid, gflg ? user_newgid : (gid_t)-1) != 0) {
+                               fprintf (stderr,
+                                        _("%s: Failed to change ownership of the home directory"),
+                                        Prog);
+                               fail_exit (E_HOMEDIR);
+                       }
+                       return;
+               } else {
+                       if (EXDEV == errno) {
+                               if (copy_tree (user_home, user_newhome, true,
+                                              true,
+                                              user_id,
+                                              uflg ? user_newid : (uid_t)-1,
+                                              user_gid,
+                                              gflg ? user_newgid : (gid_t)-1) == 0) {
+                                       if (remove_tree (user_home, true) != 0) {
                                                fprintf (stderr,
                                                         _("%s: warning: failed to completely remove old home directory %s"),
                                                         Prog, user_home);
@@ -1444,9 +1464,7 @@ static void move_home (void)
                                        return;
                                }
 
-                               /* TODO: do some cleanup if the copy
-                                *       was started */
-                               (void) remove_tree (user_newhome);
+                               (void) remove_tree (user_newhome, true);
                        }
                        fprintf (stderr,
                                 _("%s: cannot rename directory %s to %s\n"),
@@ -1459,16 +1477,6 @@ static void move_home (void)
                              user_newname, (unsigned int) user_newid, 1);
 #endif
        }
-       if (uflg || gflg) {
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                             "changing home directory owner",
-                             user_newname, (unsigned int) user_newid, 1);
-#endif
-               chown (dflg ? user_newhome : user_home,
-                      uflg ? user_newid : user_id,
-                      gflg ? user_newgid : user_gid);
-       }
 }
 
 /*
@@ -1577,7 +1585,7 @@ static void update_faillog (void)
                /* Check if the new UID already has an entry */
                if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
                    && (read (fd, &fl, sizeof fl) == (ssize_t) sizeof fl)) {
-                       /* Reset the new uid's lastlog entry */
+                       /* Reset the new uid's faillog entry */
                        memzero (&fl, sizeof (fl));
                        if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                            || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
@@ -1645,7 +1653,7 @@ static void move_mailbox (void)
                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
@@ -1715,6 +1723,15 @@ int main (int argc, char **argv)
 
        process_flags (argc, argv);
 
+       /*
+        * 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
        {
@@ -1748,6 +1765,12 @@ int main (int argc, char **argv)
 #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.
@@ -1762,6 +1785,13 @@ int main (int argc, char **argv)
        }
        close_files ();
 
+#ifdef WITH_TCB
+       if (   (lflg || uflg)
+           && (shadowtcb_move (user_newname, user_newid) == SHADOWTCB_FAILURE) ) {
+               exit (E_PW_UPDATE);
+       }
+#endif
+
        nscd_flush_cache ("passwd");
        nscd_flush_cache ("group");
 
@@ -1784,14 +1814,30 @@ int main (int argc, char **argv)
        if (uflg) {
                update_lastlog ();
                update_faillog ();
+       }
 
-               /*
-                * Change the UID on all of the files owned by `user_id' to
-                * `user_newid' in the user's home directory.
-                */
-               chown_tree (dflg ? user_newhome : user_home,
-                           user_id, user_newid,
-                           user_gid, gflg ? user_newgid : user_gid);
+       if (!mflg && (uflg || gflg)) {
+               if (access (dflg ? user_newhome : user_home, F_OK) == 0) {
+                       /*
+                        * Change the UID on all of the files owned by
+                        * `user_id' to `user_newid' in the user's home
+                        * directory.
+                        *
+                        * move_home() already takes care of changing the
+                        * ownership.
+                        *
+                        */
+                       if (chown_tree (dflg ? user_newhome : user_home,
+                                       user_id,
+                                       uflg ? user_newid  : (uid_t)-1,
+                                       user_gid,
+                                       gflg ? user_newgid : (gid_t)-1) != 0) {
+                               fprintf (stderr,
+                                        _("%s: Failed to change ownership of the home directory"),
+                                        Prog);
+                               fail_exit (E_HOMEDIR);
+                       }
+               }
        }
 
        return E_SUCCESS;
@@ -1801,9 +1847,11 @@ int main (int argc, char **argv)
 static void selinux_update_mapping (void) {
        const char *argv[7];
 
-       if (is_selinux_enabled () <= 0) return;
+       if (is_selinux_enabled () <= 0) {
+               return;
+       }
 
-       if (*user_selinux) {
+       if ('\0' != *user_selinux) {
                argv[0] = "/usr/sbin/semanage";
                argv[1] = "login";
                argv[2] = "-m";
@@ -1811,9 +1859,9 @@ static void selinux_update_mapping (void) {
                argv[4] = user_selinux;
                argv[5] = user_name;
                argv[6] = NULL;
-               if (safe_system (argv[0], argv, NULL, 1)) {
+               if (safe_system (argv[0], argv, NULL, true) != 0) {
                        argv[2] = "-a";
-                       if (safe_system (argv[0], argv, NULL, 0)) {
+                       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);