]> granicus.if.org Git - shadow/commitdiff
* src/chage.c: Fix typo: s/maximim/maximum/
authornekral-guest <nekral-guest@5a98b0ae-9ef6-0310-add3-de5d479b70d7>
Mon, 31 Dec 2007 04:29:30 +0000 (04:29 +0000)
committernekral-guest <nekral-guest@5a98b0ae-9ef6-0310-add3-de5d479b70d7>
Mon, 31 Dec 2007 04:29:30 +0000 (04:29 +0000)
* src/chage.c: New function: fail_exit(). Change most of the exit()
to a fail_exit, which makes sure the files are unlocked (new global
variables: pw_locked, spw_locked), the PAM transaction is ended, and
the failure is logged to libaudit (use a global user_name and user_uid
for logging).
* src/chage.c: Compilation fix for PAM support (pamh needs to be
global since the function split).
* src/chage.c: Document process_flags(), check_flags(), check_perms(),
open_files(), and close_files().
* src/chage.c: Split update_age() and get_defaults() out of main()
* src/chage.c: Drop the privileges just after opening the files.
* src/chage.c: Do not log to audit only if the user has an entry in
the shadow file.
* NEWS, src/chage.c (open_files): Also open the password file for
writing. This fix chage when the user only has a password entry (and
no shadow entries).
* src/chage.c (get_defaults): Use default values that don't change the
behavior of the account for the fields that are not specified when the
user has no shadow entry.

ChangeLog
NEWS
src/chage.c

index 41692eb565904ec75fe7ecd7513588312f39d4f9..ef15f02655a5d98cb00c50bca90cebdc937ef395 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2007-12-31  Nicolas François  <nicolas.francois@centraliens.net>
+
+       * src/chage.c: Fix typo: s/maximim/maximum/
+       * src/chage.c: New function: fail_exit(). Change most of the exit()
+       to a fail_exit, which makes sure the files are unlocked (new global
+       variables: pw_locked, spw_locked), the PAM transaction is ended, and
+       the failure is logged to libaudit (use a global user_name and user_uid
+       for logging).
+       * src/chage.c: Compilation fix for PAM support (pamh needs to be
+       global since the function split).
+       * src/chage.c: Document process_flags(), check_flags(), check_perms(),
+       open_files(), and close_files().
+       * src/chage.c: Split update_age() and get_defaults() out of main()
+       * src/chage.c: Drop the privileges just after opening the files.
+       * src/chage.c: Do not log to audit only if the user has an entry in
+       the shadow file.
+       * NEWS, src/chage.c (open_files): Also open the password file for
+       writing. This fix chage when the user only has a password entry (and
+       no shadow entries). Use default values that don't change the behavior
+       of the account for the fields that are not specified.
+
 2007-12-30  Nicolas François  <nicolas.francois@centraliens.net>
 
        * src/groupadd.c: Compilation fix for PAM support (pamh needs to be
diff --git a/NEWS b/NEWS
index da9d44b603f4844a515394f5659dc90f5ba80acc..727e5165c3d3f2bc1b1b9ea93ec72dc042225395 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,9 @@ shadow-4.1.0 -> shadow-4.1.1                                          UNRELEASED
   * The new users are no more added to the list of members of their groups
     because the membership is already set by their primary group.
   * Added support for gshadow.
+- chage
+  * Fix bug which forbid to set the aging information of an account with a
+    passwd entry, but no shadow entry.
 
 shadow-4.0.18.2 -> shadow-4.1.0                                                09-12-2008
 
index ab55e0128d06915fb378014628487737ed9e2990..a6e91a7e6e1cc365083e42ac47427c58ee5d957a 100644 (file)
@@ -63,10 +63,16 @@ static int
     Iflg = 0,                  /* set password inactive after expiration */
     lflg = 0,                  /* show account aging information */
     mflg = 0,                  /* set minimum number of days before password change */
-    Mflg = 0,                  /* set maximim number of days before password change */
+    Mflg = 0,                  /* set maximum number of days before password change */
     Wflg = 0;                  /* set expiration warning days */
 static int amroot = 0;
 
+static int pw_locked = 0;      /* Indicate if the password file is locked */
+static int spw_locked = 0;     /* Indicate if the shadow file is locked */
+/* The name and UID of the user being worked on */
+static char user_name[BUFSIZ] = "";
+static uid_t user_uid = -1;
+
 static long mindays;
 static long maxdays;
 static long lastday;
@@ -74,6 +80,10 @@ static long warndays;
 static long inactdays;
 static long expdays;
 
+#ifdef USE_PAM
+static pam_handle_t *pamh = NULL;
+#endif
+
 #define        EPOCH           "1969-12-31"
 
 /* local function prototypes */
@@ -87,6 +97,40 @@ static void check_flags (int argc, int opt_index);
 static void check_perms (void);
 static void open_files (int readonly);
 static void close_files (void);
+static void fail_exit (int code);
+
+/*
+ * fail_exit - do some cleanup and exit with the given error code
+ */
+static void fail_exit (int code)
+{
+       if (spw_locked) {
+               spw_unlock ();
+       }
+       if (pw_locked) {
+               pw_unlock ();
+       }
+       closelog ();
+
+#ifdef WITH_AUDIT
+       if (E_SUCCESS != code) {
+               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
+                             user_name, user_uid, 0);
+       }
+#endif
+
+#ifdef USE_PAM
+       if (NULL != pamh) {
+               /* If there is a PAM error, pam_end will be called by the
+                * caller.
+                * We always end the pam transaction with PAM_SUCCESS here.
+                */
+               pam_end (pamh, PAM_SUCCESS);
+       }
+#endif
+
+       exit (code);
+}
 
 /*
  * isnum - determine whether or not a string is a number
@@ -317,6 +361,11 @@ static void list_fields (void)
                warndays);
 }
 
+/*
+ * process_flags - parse the command line options
+ *
+ *     It will not return if an error is encountered.
+ */
 static void process_flags (int argc, char **argv)
 {
        /*
@@ -386,10 +435,13 @@ static void process_flags (int argc, char **argv)
        check_flags (argc, optind);
 }
 
-
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ *     It will not return if an error is encountered.
+ */
 static void check_flags (int argc, int opt_index)
 {
-
        /*
         * Make certain the flags do not conflict and that there is a user
         * name on the command line.
@@ -407,11 +459,23 @@ static void check_flags (int argc, int opt_index)
        }
 }
 
-/* Additional check done later */
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ *     Non-root users are only allowed to display their aging information.
+ *     (we will later make sure that the user is only listing her aging
+ *     information)
+ *
+ *     With PAM support, the setuid bit can be set on groupadd to allow
+ *     non-root users to groups.
+ *     Without PAM support, only users who can write in the group databases
+ *     can add groups.
+ *
+ *     It will not return if the user is not allowed.
+ */
 static void check_perms (void)
 {
 #ifdef USE_PAM
-       pam_handle_t *pamh = NULL;
        struct passwd *pampw;
        int retval;
 #endif
@@ -424,11 +488,7 @@ static void check_perms (void)
 
        if (!amroot && !lflg) {
                fprintf (stderr, _("%s: Permission denied.\n"), Prog);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age", NULL,
-                             getuid (), 0);
-#endif
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
        }
 
 #ifdef USE_PAM
@@ -459,23 +519,39 @@ static void check_perms (void)
 
        if (retval != PAM_SUCCESS) {
                fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
-               exit (E_NOPERM);
+               pamh = NULL;
+               fail_exit (E_NOPERM);
        }
 #endif                         /* USE_PAM */
 }
 
+/*
+ * open_files - open the shadow database
+ *
+ *     The password database is also needed (only for reading).
+ *     In read-only mode, the shadow database is not locked and is opened
+ *     only for reading.
+ */
 static void open_files (int readonly)
 {
        /*
-        * open the password file. This loads all of the password
+        * Lock and open the password file. This loads all of the password
         * file entries into memory. Then we get a pointer to the password
         * file entry for the requested user.
         */
-       if (pw_open (O_RDONLY) == 0) {
+       if (!readonly && (pw_lock () == 0)) {
+               fprintf (stderr,
+                        _("%s: can't lock password file\n"), Prog);
+               SYSLOG ((LOG_ERR, "failed locking %s", PASSWD_FILE));
+               fail_exit (E_NOPERM);
+       }
+       if (!readonly) {
+               pw_locked = 1;
+       }
+       if (pw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
                fprintf (stderr, _("%s: can't open password file\n"), Prog);
                SYSLOG ((LOG_ERR, "failed opening %s", PASSWD_FILE));
-               closelog ();
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
        }
 
        /*
@@ -488,27 +564,22 @@ static void open_files (int readonly)
                fprintf (stderr,
                         _("%s: can't lock shadow password file\n"), Prog);
                SYSLOG ((LOG_ERR, "failed locking %s", SHADOW_FILE));
-               closelog ();
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age", name,
-                             getuid (), 0);
-#endif
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
+       }
+       if (!readonly) {
+               spw_locked = 1;
        }
        if (spw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
                fprintf (stderr,
                         _("%s: can't open shadow password file\n"), Prog);
-               spw_unlock ();
                SYSLOG ((LOG_ERR, "failed opening %s", SHADOW_FILE));
-               closelog ();
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age", name,
-                             getuid (), 0);
-#endif
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
        }
 }
 
+/*
+ * close_files - close and unlock the password/shadow databases
+ */
 static void close_files (void)
 {
        /*
@@ -518,14 +589,8 @@ static void close_files (void)
        if (spw_close () == 0) {
                fprintf (stderr,
                         _("%s: can't rewrite shadow password file\n"), Prog);
-               spw_unlock ();
                SYSLOG ((LOG_ERR, "failed rewriting %s", SHADOW_FILE));
-               closelog ();
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                             pw->pw_name, getuid (), 0);
-#endif
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
        }
 
        /*
@@ -534,16 +599,123 @@ static void close_files (void)
         */
        if (pw_close () == 0) {
                fprintf (stderr, _("%s: can't rewrite password file\n"), Prog);
-               spw_unlock ();
                SYSLOG ((LOG_ERR, "failed rewriting %s", PASSWD_FILE));
-               closelog ();
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                             pw->pw_name, getuid (), 0);
-#endif
-               exit (E_NOPERM);
+               fail_exit (E_NOPERM);
        }
        spw_unlock ();
+       spw_locked = 0;
+       pw_unlock ();
+       pw_locked = 0;
+}
+
+/*
+ * update_age - update the aging information in the database
+ *
+ *     It will not return in case of error
+ */
+static void update_age (const struct spwd *sp, const struct passwd *pw)
+{
+       struct spwd spwent;
+
+       /*
+        * There was no shadow entry. The new entry will have the encrypted
+        * password transferred from the normal password file along with the
+        * aging information.
+        */
+       if (NULL == sp) {
+               struct passwd pwent = *pw;
+
+               memzero (&spwent, sizeof spwent);
+               spwent.sp_namp = xstrdup (pw->pw_name);
+               spwent.sp_pwdp = xstrdup (pw->pw_passwd);
+               spwent.sp_flag = -1;
+
+               pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+               if (pw_update (&pwent) == 0) {
+                       fprintf (stderr,
+                                _("%s: can't update password file\n"), Prog);
+                       SYSLOG ((LOG_ERR, "failed updating %s", PASSWD_FILE));
+                       fail_exit (E_NOPERM);
+               }
+       } else {
+               spwent.sp_namp = xstrdup (sp->sp_namp);
+               spwent.sp_pwdp = xstrdup (sp->sp_pwdp);
+               spwent.sp_flag = sp->sp_flag;
+       }
+
+       /*
+        * Copy the fields back to the shadow file entry and write the
+        * modified entry back to the shadow file. Closing the shadow and
+        * password files will commit any changes that have been made.
+        */
+       spwent.sp_max = maxdays;
+       spwent.sp_min = mindays;
+       spwent.sp_lstchg = lastday;
+       spwent.sp_warn = warndays;
+       spwent.sp_inact = inactdays;
+       spwent.sp_expire = expdays;
+
+       if (spw_update (&spwent) == 0) {
+               fprintf (stderr,
+                        _("%s: can't update shadow password file\n"), Prog);
+               SYSLOG ((LOG_ERR, "failed updating %s", SHADOW_FILE));
+               fail_exit (E_NOPERM);
+       }
+
+}
+
+/*
+ * get_defaults - get the value of the fields not set from the command line
+ */
+static void get_defaults (const struct spwd *sp)
+{
+       /*
+        * Set the fields that aren't being set from the command line from
+        * the password file.
+        */
+       if (NULL != sp) {
+               if (!Mflg) {
+                       maxdays = sp->sp_max;
+               }
+               if (!mflg) {
+                       mindays = sp->sp_min;
+               }
+               if (!dflg) {
+                       lastday = sp->sp_lstchg;
+               }
+               if (!Wflg) {
+                       warndays = sp->sp_warn;
+               }
+               if (!Iflg) {
+                       inactdays = sp->sp_inact;
+               }
+               if (!Eflg) {
+                       expdays = sp->sp_expire;
+               }
+       } else {
+               /*
+                * Use default values that will not change the behavior of the
+                * account.
+                */
+               if (!Mflg) {
+                       maxdays = -1;
+               }
+               if (!mflg) {
+                       mindays = -1;
+               }
+               if (!dflg) {
+                       lastday = 0;
+               }
+               if (!Wflg) {
+                       warndays = -1;
+               }
+               if (!Iflg) {
+                       inactdays = -1;
+               }
+               if (!Eflg) {
+                       expdays = -1;
+               }
+       }
 }
 
 /*
@@ -570,12 +742,9 @@ static void close_files (void)
 int main (int argc, char **argv)
 {
        const struct spwd *sp;
-       struct spwd spwent;
        uid_t ruid;
        gid_t rgid;
        const struct passwd *pw;
-       struct passwd pwent;
-       char name[BUFSIZ];
 
 #ifdef WITH_AUDIT
        audit_help_open ();
@@ -615,6 +784,12 @@ int main (int argc, char **argv)
        }
 
        open_files (lflg);
+       /* Drop privileges */
+       if (lflg && (setregid (rgid, rgid) || setreuid (ruid, ruid))) {
+               fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
+                        Prog, strerror (errno));
+               fail_exit (E_NOPERM);
+       }
 
        pw = pw_locate (argv[optind]);
        if (NULL == pw) {
@@ -624,104 +799,27 @@ int main (int argc, char **argv)
                exit (E_NOPERM);
        }
 
-       pwent = *pw;
-       STRFCPY (name, pwent.pw_name);
-
-       /* Drop privileges */
-       if (lflg && (setregid (rgid, rgid) || setreuid (ruid, ruid))) {
-               fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
-                        Prog, strerror (errno));
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age", name,
-                             getuid (), 0);
-#endif
-               exit (E_NOPERM);
-       }
+       STRFCPY (user_name, pw->pw_name);
+       user_uid = pw->pw_uid;
 
        sp = spw_locate (argv[optind]);
-
-       /*
-        * Set the fields that aren't being set from the command line from
-        * the password file.
-        */
-       if (NULL != sp) {
-               spwent = *sp;
-
-               if (!Mflg) {
-                       maxdays = spwent.sp_max;
-               }
-               if (!mflg) {
-                       mindays = spwent.sp_min;
-               }
-               if (!dflg) {
-                       lastday = spwent.sp_lstchg;
-               }
-               if (!Wflg) {
-                       warndays = spwent.sp_warn;
-               }
-               if (!Iflg) {
-                       inactdays = spwent.sp_inact;
-               }
-               if (!Eflg) {
-                       expdays = spwent.sp_expire;
-               }
-#ifdef WITH_AUDIT
-               if (Mflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change max age", pw->pw_name, pw->pw_uid,
-                                     1);
-               }
-               if (mflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change min age", pw->pw_name, pw->pw_uid,
-                                     1);
-               }
-               if (dflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change last change date", pw->pw_name,
-                                     pw->pw_uid, 1);
-               }
-               if (Wflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change passwd warning", pw->pw_name,
-                                     pw->pw_uid, 1);
-               }
-               if (Iflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change inactive days", pw->pw_name,
-                                     pw->pw_uid, 1);
-               }
-               if (Eflg) {
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "change passwd expiration", pw->pw_name,
-                                     pw->pw_uid, 1);
-               }
-#endif
-       }
+       get_defaults(sp);
 
        /*
         * Print out the expiration fields if the user has requested the
         * list option.
         */
-
        if (lflg) {
-               if (!amroot && (ruid != pwent.pw_uid)) {
-#ifdef WITH_AUDIT
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                                     pw->pw_name, pw->pw_uid, 0);
-#endif
+               if (!amroot && (ruid != user_uid)) {
                        fprintf (stderr, _("%s: Permission denied.\n"), Prog);
-                       closelog ();
-                       exit (E_NOPERM);
+                       fail_exit (E_NOPERM);
                }
 #ifdef WITH_AUDIT
                audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "display aging info",
-                             pw->pw_name, pw->pw_uid, 1);
+                             user_name, user_uid, 1);
 #endif
                list_fields ();
-               spw_unlock ();
-               closelog ();
-               exit (E_SUCCESS);
+               fail_exit (E_SUCCESS);
        }
 
        /*
@@ -729,82 +827,60 @@ int main (int argc, char **argv)
         * user interactively change them.
         */
        if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) {
-               printf (_("Changing the aging information for %s\n"), name);
+               printf (_("Changing the aging information for %s\n"),
+                       user_name);
                if (new_fields () == 0) {
                        fprintf (stderr, _("%s: error changing fields\n"),
                                 Prog);
-                       spw_unlock ();
-                       closelog ();
-#ifdef WITH_AUDIT
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                                     pw->pw_name, getuid (), 0);
-#endif
-                       exit (E_NOPERM);
+                       fail_exit (E_NOPERM);
                }
 #ifdef WITH_AUDIT
                else {
                        audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
                                      "change all aging information",
-                                     pw->pw_name, getuid (), 1);
+                                     user_name, user_uid, 1);
                }
 #endif
-       }
-       /*
-        * There was no shadow entry. The new entry will have the encrypted
-        * password transferred from the normal password file along with the
-        * aging information.
-        */
-       if (NULL == sp) {
-               sp = &spwent;
-               memzero (&spwent, sizeof spwent);
-
-               spwent.sp_namp = xstrdup (pwent.pw_name);
-               spwent.sp_pwdp = xstrdup (pwent.pw_passwd);
-               spwent.sp_flag = -1;
-
-               pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
-               if (pw_update (&pwent) == 0) {
-                       fprintf (stderr,
-                                _("%s: can't update password file\n"), Prog);
-                       spw_unlock ();
-                       SYSLOG ((LOG_ERR, "failed updating %s", PASSWD_FILE));
-                       closelog ();
+       } else {
 #ifdef WITH_AUDIT
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                                     pw->pw_name, getuid (), 0);
-#endif
-                       exit (E_NOPERM);
+               if (Mflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change max age", user_name,
+                                     user_uid, 1);
+               }
+               if (mflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change min age", user_name,
+                                     user_uid, 1);
+               }
+               if (dflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change last change date", user_name,
+                                     user_uid, 1);
+               }
+               if (Wflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change passwd warning", user_name,
+                                     user_uid, 1);
+               }
+               if (Iflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change inactive days", user_name,
+                                     user_uid, 1);
+               }
+               if (Eflg) {
+                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+                                     "change passwd expiration", user_name,
+                                     user_uid, 1);
                }
-       }
-
-       /*
-        * Copy the fields back to the shadow file entry and write the
-        * modified entry back to the shadow file. Closing the shadow and
-        * password files will commit any changes that have been made.
-        */
-       spwent.sp_max = maxdays;
-       spwent.sp_min = mindays;
-       spwent.sp_lstchg = lastday;
-       spwent.sp_warn = warndays;
-       spwent.sp_inact = inactdays;
-       spwent.sp_expire = expdays;
-
-       if (spw_update (&spwent) == 0) {
-               fprintf (stderr,
-                        _("%s: can't update shadow password file\n"), Prog);
-               spw_unlock ();
-               SYSLOG ((LOG_ERR, "failed updating %s", SHADOW_FILE));
-               closelog ();
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "change age",
-                             pw->pw_name, getuid (), 0);
 #endif
-               exit (E_NOPERM);
        }
 
+       update_age (sp, pw);
+
        close_files ();
 
-       SYSLOG ((LOG_INFO, "changed password expiry for %s", name));
+       SYSLOG ((LOG_INFO, "changed password expiry for %s", user_name));
 
 #ifdef USE_PAM
        pam_end (pamh, PAM_SUCCESS);