]> granicus.if.org Git - shadow/commitdiff
* src/faillog.c: Added support for the specification of a range of
authornekral-guest <nekral-guest@5a98b0ae-9ef6-0310-add3-de5d479b70d7>
Fri, 13 Mar 2009 22:49:20 +0000 (22:49 +0000)
committernekral-guest <nekral-guest@5a98b0ae-9ef6-0310-add3-de5d479b70d7>
Fri, 13 Mar 2009 22:49:20 +0000 (22:49 +0000)
users with -u.
* src/faillog.c: Do not call print_one() for users which do not
exist.
* src/faillog.c: Make sure the user's entry is not outside the
faillog file and initialize the faillog structure in that case.
* src/faillog.c: Move print_one() closer to print().
* src/faillog.c: reset(), setmax(), set_locktime() can also change
entries of user which do not exist.
* src/faillog.c: reset(), setmax() and set_locktime() shall not
create entries for users which have no entries if the value has to
be set to 0.
* src/faillog.c: reset(), setmax() and set_locktime(): better
handling of users whose entry is outside the faillog file.
* src/faillog.c: Improved option handling. Options can now be
specified in any order.
* src/faillog.c: Improved warnings when options are not
compatible or when the faillog cannot be open with the right mode.
* src/faillog.c: Only fstat the faillog file once.
* man/faillog.8.xml: Improved documentation.

ChangeLog
NEWS
man/faillog.8.xml
src/faillog.c

index 2365f8fcdbc045b6c7990da81ad3ec53b0fce6d4..d3996e9fe0836ec9573efc75c32721f7e547097b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
        * src/chgpasswd.c: Fix the test for getlong() failure.
        * src/useradd.c, man/useradd.8.xml: Added long name for the -l
        option: --no-log-init.
+       * src/faillog.c: Added support for the specification of a range of
+       users with -u.
+       * src/faillog.c: Do not call print_one() for users which do not
+       exist.
+       * src/faillog.c: Make sure the user's entry is not outside the
+       faillog file and initialize the faillog structure in that case.
+       * src/faillog.c: Move print_one() closer to print().
+       * src/faillog.c: reset(), setmax(), set_locktime() can also change
+       entries of user which do not exist.
+       * src/faillog.c: reset(), setmax() and set_locktime() shall not
+       create entries for users which have no entries if the value has to
+       be set to 0.
+       * src/faillog.c: reset(), setmax() and set_locktime(): better
+       handling of users whose entry is outside the faillog file.
+       * src/faillog.c: Improved option handling. Options can now be
+       specified in any order.
+       * src/faillog.c: Improved warnings when options are not
+       compatible or when the faillog cannot be open with the right mode.
+       * src/faillog.c: Only fstat the faillog file once.
+       * man/faillog.8.xml: Improved documentation.
 
 2009-03-13  Nicolas François  <nicolas.francois@centraliens.net>
 
diff --git a/NEWS b/NEWS
index 0e83c781eb28d127f46d21fae0df1ea69ec1187e..5b4b535cc4c3cb551521c4c3ca10077830cba9ae 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,12 @@ shadow-4.1.2.2 -> shadow-4.1.3                                               UNRELEASED
 - Translations
   * New Kazakh translation.
 
+- faillog
+  * Accept users specified as a numerical UID, or ranges of users (-user,
+    user-, user1-user2).
+  * -l, -m, and -r now apply not only to existing users, but to all the
+    specified UIDs.
+  * Options can be specified in any order.
 - gpasswd
   * Added support for long options --add (-a), --delete (-d),
     --remove-password (-r), --restrict (-R), --administrators (-A), and
index cb6723bbbff550f6b7d054ea2ee92f77824a21e8..faf208066e894281302d817b8318d11674550457 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
    Copyright (c) 1989 - 1994, Julianne Frances Haugh
-   Copyright (c) 2007 - 2008, Nicolas François
+   Copyright (c) 2007 - 2009, Nicolas François
    All rights reserved.
   
    Redistribution and use in source and binary forms, with or without
       <varlistentry>
        <term><option>-a</option>, <option>--all</option></term>
        <listitem>
-         <para>Display faillog records for all users.</para>
+         <para>
+           Display (or act on) faillog records for all users having an
+           entry in the <filename>faillog</filename> database.
+         </para>
        </listitem>
       </varlistentry>
       <varlistentry>
            Lock account to <replaceable>SEC</replaceable>
            seconds after failed login.
          </para>
+         <para>
+           Write access to <filename>/var/log/faillog</filename>
+           is required for this option.
+         </para>
        </listitem>
       </varlistentry>
       <varlistentry>
        <listitem>
          <para>
            Set maximum number of login failures after the account is
-           disabled to <replaceable>MAX</replaceable>. Selecting
+           disabled to <replaceable>MAX</replaceable>.
+         </para>
+         <para>
+           Selecting a
            <replaceable>MAX</replaceable> value of 0 has the effect of not
-           placing a limit on the number of failed logins. The maximum
+           placing a limit on the number of failed logins.
+         </para>
+         <para>
+           The maximum
            failure count should always be 0 for <emphasis>root</emphasis>
            to prevent a denial of services attack against the system.
          </para>
+         <para>
+           Write access to <filename>/var/log/faillog</filename>
+           is required for this option.
+         </para>
        </listitem>
       </varlistentry>
       <varlistentry>
          <para>
            Reset the counters of login failures or one record if used with
            the <option>-u</option> <replaceable>LOGIN</replaceable>
-           option. Write access to <filename>/var/log/faillog</filename>
+           option.
+         </para>
+         <para>
+           Write access to <filename>/var/log/faillog</filename>
            is required for this option.
          </para>
        </listitem>
        <listitem>
          <para>
            Display faillog records more recent than
-           <replaceable>DAYS</replaceable>. The <option>-t</option>
-           flag overrides the use of <option>-u</option>.
+           <replaceable>DAYS</replaceable>.
          </para>
        </listitem>
       </varlistentry>
       <varlistentry>
        <term>
-         <option>-u</option>, <option>--user</option> <replaceable>LOGIN</replaceable>
+         <option>-u</option>, <option>--user</option>
+         <replaceable>LOGIN</replaceable>|<replaceable>RANGE</replaceable>
        </term>
        <listitem>
          <para>
            Display faillog record or maintains failure counters and limits
            (if used with <option>-l</option>, <option>-m</option> or
-           <option>-r</option> options) only for user with
-           <replaceable>LOGIN</replaceable>.
+           <option>-r</option> options) only for the specified user(s).
+         </para>
+         <para>
+           The users can be specified by a login name, a numerical user
+           ID, or a <replaceable>RANGE</replaceable> of users.  This
+           <replaceable>RANGE</replaceable> of users can be specified
+           with a min and max values
+           (<replaceable>UID_MIN-UID_MAX</replaceable>), a max value
+           (<replaceable>-UID_MAX</replaceable>), or a min value
+           (<replaceable>UID_MIN-</replaceable>).
          </para>
        </listitem>
       </varlistentry>
     </variablelist>
+
+    <para>
+      When none of the <option>-l</option>, <option>-m</option>, or
+      <option>-r</option> options are used, <command>faillog</command>
+      displays the faillog record of the specified user(s).
+    </para>
+    <para>
+      NOTE: in display mode, only the records of users which currently
+      exist in the system are displayed.  In the other modes (when the
+      <option>-l</option>, <option>-m</option>, or <option>-r</option>
+      options are used), the records of the user, or the range of users,
+      or all the users that may have an entry in the faillog database will
+      be changed.  This is useful to reset records of users that have been
+      deleted or set a policy in advance for a range of users.
+    </para>
   </refsect1>
 
   <refsect1 id='caveats'>
index e91afdb5e31a9102960f324f6ff95943817fef77..c2c516b291425ad95373cab17ab39699cf1ad91f 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 1989 - 1993, Julianne Frances Haugh
  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
  * Copyright (c) 2002 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2009, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,6 +40,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
+#include <assert.h>
 #include "defines.h"
 #include "exitcodes.h"
 #include "faillog.h"
  * Global variables
  */
 static FILE *fail;             /* failure file stream */
-static uid_t user;             /* one single user, specified on command line */
-static int days;               /* number of days to consider for print command */
 static time_t seconds;         /* that number of days in seconds */
-
-static bool
-    aflg = false,              /* set if all users are to be printed always */
-    uflg = false,              /* set if user is a valid user id */
-    tflg = false;              /* print is restricted to most recent days */
+static unsigned long umin;     /* if uflg and has_umin, only display users with uid >= umin */
+static bool has_umin = false;
+static unsigned long umax;     /* if uflg and has_umax, only display users with uid <= umax */
+static bool has_umax = false;
+static bool errors = false;
+
+static bool aflg = false;      /* set if all users are to be printed always */
+static bool uflg = false;      /* set if user is a valid user id */
+static bool tflg = false;      /* print is restricted to most recent days */
+static bool lflg = false;      /* set the locktime */
+static bool mflg = false;      /* set maximum failed login counters */
+static bool rflg = false;      /* reset the counters of login failures */
 
 static struct stat statbuf;    /* fstat buffer for file size */
 
@@ -68,7 +74,7 @@ static void usage (void)
                 "Options:\n"
                 "  -a, --all                     display faillog records for all users\n"
                 "  -h, --help                    display this help message and exit\n"
-                "  -l, --lock-time SEC           after failed login lock accout to SEC seconds\n"
+                "  -l, --lock-time SEC           after failed login lock account to SEC seconds\n"
                 "  -m, --maximum MAX             set maximum failed login counters to MAX\n"
                 "  -r, --reset                   reset the counters of login failures\n"
                 "  -t, --time DAYS               display faillog records more recent than DAYS\n"
@@ -79,259 +85,388 @@ static void usage (void)
        exit (E_USAGE);
 }
 
-static void print_one (const struct faillog *fl, uid_t uid)
+static void print_one (const struct passwd *pw, bool force)
 {
        static bool once = false;
-       char *cp;
        struct tm *tm;
+       off_t offset;
+       struct faillog fl;
        time_t now;
-       struct passwd *pwent;
 
 #ifdef HAVE_STRFTIME
+       char *cp;
        char ptime[80];
 #endif
 
-       if (!once) {
-               puts (_("Login       Failures Maximum Latest                   On\n"));
-               once = true;
+       if (NULL == pw) {
+               return;
        }
-       pwent = getpwuid (uid); /* local, no need for xgetpwuid */
-       (void) time (&now);
-       tm = localtime (&fl->fail_time);
-#ifdef HAVE_STRFTIME
-       strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
-       cp = ptime;
-#endif
-       if (NULL != pwent) {
-               printf ("%-9s   %5d    %5d   ",
-                       pwent->pw_name, fl->fail_cnt, fl->fail_max);
-               if ((time_t) 0 != fl->fail_time) {
-                       /* FIXME: cp is not defined ifndef HAVE_STRFTIME */
-                       printf ("%s  %s", cp, fl->fail_line);
-                       if (0 != fl->fail_locktime) {
-                               if (   ((fl->fail_time+fl->fail_locktime) > now)
-                                   && (0 != fl->fail_cnt)) {
-                                       printf (_(" [%lus left]"),
-                                               (unsigned long) fl->fail_time + fl->fail_locktime - now);
-                               } else {
-                                       printf (_(" [%lds lock]"),
-                                               fl->fail_locktime);
-                               }
-                       }
+
+       offset = pw->pw_uid * sizeof (fl);
+       if (offset <= (statbuf.st_size - sizeof (fl))) {
+               /* fseeko errors are not really relevant for us. */
+               assert ( fseeko (fail, offset, SEEK_SET) == 0 );
+               /* faillog is a sparse file. Even if no entries were
+                * entered for this user, which should be able to get the
+                * empty entry in this case.
+                */
+               if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+                       fprintf (stderr,
+                                _("faillog: Failed to get the entry for UID %d\n"),
+                                pw->pw_uid);
+                       return;
                }
-               putchar ('\n');
+       } else {
+               /* Outsize of the faillog file.
+                * Behave as if there were a missing entry (same behavior
+                * as if we were reading an non existing entry in the
+                * sparse faillog file).
+                */
+               memzero (&fl, sizeof (fl));
        }
-}
-
-static int reset_one (uid_t uid)
-{
-       off_t offset;
-       struct faillog faillog;
 
-       offset = uid * sizeof faillog;
-       if (fstat (fileno (fail), &statbuf) != 0) {
-               perror (FAILLOG_FILE);
-               return 0;
-       }
-       if (offset >= statbuf.st_size) {
-               return 0;
+       /* Nothing to report */
+       if (!force && (0 == fl.fail_time)) {
+               return;
        }
 
-       if (fseeko (fail, offset, SEEK_SET) != 0) {
-               perror (FAILLOG_FILE);
-               return 0;
-       }
-       if (fread ((char *) &faillog, sizeof faillog, 1, fail) != 1) {
-               if (feof (fail) == 0) {
-                       perror (FAILLOG_FILE);
-               }
+       (void) time(&now);
 
-               return 0;
-       }
-       if (0 == faillog.fail_cnt) {
-               return 1;       /* don't fill in no holes ... */
+       /* Filter out entries that do not match with the -t option */
+       if (tflg && ((now - fl.fail_time) > seconds)) {
+               return;
        }
 
-       faillog.fail_cnt = 0;
+       /* Print the header only once */
+       if (!once) {
+               puts (_("Login       Failures Maximum Latest                   On\n"));
+               once = true;
+       }
 
-       if (   (fseeko (fail, offset, SEEK_SET) == 0)
-           && (fwrite ((char *) &faillog, sizeof faillog, 1, fail) == 1)) {
-               fflush (fail);
-               return 1;
-       } else {
-               perror (FAILLOG_FILE);
+       tm = localtime (&fl.fail_time);
+#ifdef HAVE_STRFTIME
+       strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
+       cp = ptime;
+#endif
+       printf ("%-9s   %5d    %5d   ",
+               pw->pw_name, fl.fail_cnt, fl.fail_max);
+       /* FIXME: cp is not defined ifndef HAVE_STRFTIME */
+       printf ("%s  %s", cp, fl.fail_line);
+       if (0 != fl.fail_locktime) {
+               if (   ((fl.fail_time + fl.fail_locktime) > now)
+                   && (0 != fl.fail_cnt)) {
+                       printf (_(" [%lus left]"),
+                               (unsigned long) fl.fail_time + fl.fail_locktime - now);
+               } else {
+                       printf (_(" [%lds lock]"),
+                               fl.fail_locktime);
+               }
        }
-       return 0;
+       putchar ('\n');
 }
 
-static void reset (void)
+static void print (void)
 {
-       uid_t uid;
-
-       if (uflg) {
-               reset_one (user);
+       if (uflg && has_umin && has_umax && (umin==umax)) {
+               print_one (getpwuid ((uid_t)umin), true);
        } else {
+               /* We only print records for existing users.
+                * Loop based on the user database instead of reading the
+                * whole file. We will have to query the database anyway
+                * so except for very small ranges and large user
+                * database, this should not be a performance issue.
+                */
                struct passwd *pwent;
 
                setpwent ();
                while ( (pwent = getpwent ()) != NULL ) {
-                       reset_one (pwent->pw_uid);
+                       if (   uflg
+                           && (   (has_umin && (pwent->pw_uid < (uid_t)umin))
+                               || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
+                               continue;
+                       }
+                       print_one (pwent, aflg);
                }
                endpwent ();
        }
 }
 
-static void print (void)
+/*
+ * reset_one - Reset the fail count for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool reset_one (uid_t uid)
 {
-       uid_t uid;
        off_t offset;
-       struct faillog faillog;
-
-       if (uflg) {
-               offset = user * sizeof faillog;
-               if (fstat (fileno (fail), &statbuf) != 0) {
-                       perror (FAILLOG_FILE);
-                       return;
-               }
-               if (offset >= statbuf.st_size) {
-                       return;
-               }
-
-               fseeko (fail, (off_t) user * sizeof faillog, SEEK_SET);
-               if (fread ((char *) &faillog, sizeof faillog, 1, fail) == 1) {
-                       print_one (&faillog, user);
-               } else {
-                       perror (FAILLOG_FILE);
+       struct faillog fl;
+
+       offset = uid * sizeof (fl);
+       if (offset <= (statbuf.st_size - sizeof (fl))) {
+               /* fseeko errors are not really relevant for us. */
+               assert ( fseeko (fail, offset, SEEK_SET) == 0 );
+               /* faillog is a sparse file. Even if no entries were
+                * entered for this user, which should be able to get the
+                * empty entry in this case.
+                */
+               if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+                       fprintf (stderr,
+                                _("faillog: Failed to get the entry for UID %d\n"),
+                                uid);
+                       return true;
                }
        } else {
-               for (uid = 0;
-                    fread ((char *) &faillog, sizeof faillog, 1, fail) == 1;
-                    uid++) {
+               /* Outsize of the faillog file.
+                * Behave as if there were a missing entry (same behavior
+                * as if we were reading an non existing entry in the
+                * sparse faillog file).
+                */
+               memzero (&fl, sizeof (fl));
+       }
 
-                       if (!aflg && (0 == faillog.fail_cnt)) {
-                               continue;
-                       }
+       if (0 == fl.fail_cnt) {
+               /* If the count is already null, do not write in the file.
+                * This avoids writing 0 when no entries were present for
+                * the user.
+                */
+               return false;
+       }
 
-                       if (!aflg && tflg &&
-                           ((NOW - faillog.fail_time) > seconds)) {
-                               continue;
-                       }
+       fl.fail_cnt = 0;
 
-                       if (aflg && (0 == faillog.fail_time)) {
-                               continue;
-                       }
+       if (   (fseeko (fail, offset, SEEK_SET) == 0)
+           && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
+               (void) fflush (fail);
+               return false;
+       }
 
-                       print_one (&faillog, uid);
+       fprintf (stderr,
+                _("faillog: Failed to reset fail count for UID %d\n"),
+                uid);
+       return true;
+}
+
+static void reset (void)
+{
+       if (uflg && has_umin && has_umax && (umin==umax)) {
+               if (reset_one ((uid_t)umin)) {
+                       errors = true;
+               }
+       } else {
+               /* Reset all entries in the specified range.
+                * Non existing entries will not be touched.
+                * Entries for non existing users are also reset.
+                */
+               uid_t uid = 0;
+               uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+
+               /* Make sure we stay in the umin-umax range if specified */
+               if (has_umin) {
+                       uid = (uid_t)umin;
+               }
+               if (has_umax && (uid_t)umax < uidmax) {
+                       uidmax = (uid_t)umax;
+               }
+
+               while (uid < uidmax) {
+                       if (reset_one (uid)) {
+                               errors = true;
+                       }
+                       uid++;
                }
        }
 }
 
-static void setmax_one (uid_t uid, int max)
+/*
+ * setmax_one - Set the maximum failed login counter for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool setmax_one (uid_t uid, int max)
 {
        off_t offset;
-       struct faillog faillog;
-
-       offset = uid * sizeof faillog;
-
-       if (fseeko (fail, offset, SEEK_SET) != 0) {
-               perror (FAILLOG_FILE);
-               return;
-       }
-       if (fread ((char *) &faillog, sizeof faillog, 1, fail) != 1) {
-               if (feof (fail) == 0) {
-                       perror (FAILLOG_FILE);
+       struct faillog fl;
+
+       offset = (off_t) uid * sizeof (fl);
+       if (offset <= (statbuf.st_size - sizeof (fl))) {
+               /* fseeko errors are not really relevant for us. */
+               assert ( fseeko (fail, offset, SEEK_SET) == 0 );
+               /* faillog is a sparse file. Even if no entries were
+                * entered for this user, which should be able to get the
+                * empty entry in this case.
+                */
+               if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+                       fprintf (stderr,
+                                _("faillog: Failed to get the entry for UID %d\n"),
+                                uid);
+                       return true;
                }
-               memzero (&faillog, sizeof faillog);
+       } else {
+               /* Outsize of the faillog file.
+                * Behave as if there were a missing entry (same behavior
+                * as if we were reading an non existing entry in the
+                * sparse faillog file).
+                */
+               memzero (&fl, sizeof (fl));
+       }
+
+       if (max == fl.fail_max) {
+               /* If the max is already set to the right value, do not
+                * write in the file.
+                * This avoids writing 0 when no entries were present for
+                * the user and the max argument is 0.
+                */
+               return false;
        }
-       faillog.fail_max = max;
+
+       fl.fail_max = max;
 
        if (   (fseeko (fail, offset, SEEK_SET) == 0)
-           && (fwrite ((char *) &faillog, sizeof faillog, 1, fail) == 1)) {
+           && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
                fflush (fail);
-       } else {
-               perror (FAILLOG_FILE);
+               return false;
        }
+
+       fprintf (stderr,
+                _("faillog: Failed to set max for UID %d\n"),
+                uid);
+       return true;
 }
 
 static void setmax (int max)
 {
-       struct passwd *pwent;
-
-       if (uflg) {
-               setmax_one (user, max);
+       if (uflg && has_umin && has_umax && (umin==umax)) {
+               if (setmax_one ((uid_t)umin, max)) {
+                       errors = true;
+               }
        } else {
-               setpwent ();
-               while ( (pwent = getpwent ()) != NULL ) {
-                       setmax_one (pwent->pw_uid, max);
+               /* Set max for all entries in the specified range.
+                * If max is unchanged for an entry, the entry is not touched.
+                * If max is null, and no entries exist for this user, no
+                * entries will be created.
+                * Entries for non existing user are also taken into
+                * account (in order to define policy for future users).
+                */
+               uid_t uid = 0;
+               uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+
+               /* Make sure we stay in the umin-umax range if specified */
+               if (has_umin) {
+                       uid = (uid_t)umin;
+               }
+               if (has_umax && (uid_t)umax < uidmax) {
+                       uidmax = (uid_t)umax;
+               }
+
+               while (uid < uidmax) {
+                       if (setmax_one (uid, max)) {
+                               errors = true;
+                       }
+                       uid++;
                }
-               endpwent ();
        }
 }
 
-static void set_locktime_one (uid_t uid, long locktime)
+/*
+ * set_locktime_one - Set the locktime for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool set_locktime_one (uid_t uid, long locktime)
 {
        off_t offset;
-       struct faillog faillog;
-
-       offset = uid * sizeof faillog;
-
-       if (fseeko (fail, offset, SEEK_SET) != 0) {
-               perror (FAILLOG_FILE);
-               return;
-       }
-       if (fread ((char *) &faillog, sizeof faillog, 1, fail) != 1) {
-               if (feof (fail) == 0) {
-                       perror (FAILLOG_FILE);
+       struct faillog fl;
+
+       offset = (off_t) uid * sizeof (fl);
+       if (offset <= (statbuf.st_size - sizeof (fl))) {
+               /* fseeko errors are not really relevant for us. */
+               assert ( fseeko (fail, offset, SEEK_SET) == 0 );
+               /* faillog is a sparse file. Even if no entries were
+                * entered for this user, which should be able to get the
+                * empty entry in this case.
+                */
+               if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+                       fprintf (stderr,
+                                _("faillog: Failed to get the entry for UID %d\n"),
+                                uid);
+                       return true;
                }
-               memzero (&faillog, sizeof faillog);
+       } else {
+               /* Outsize of the faillog file.
+                * Behave as if there were a missing entry (same behavior
+                * as if we were reading an non existing entry in the
+                * sparse faillog file).
+                */
+               memzero (&fl, sizeof (fl));
        }
-       faillog.fail_locktime = locktime;
+
+       if (locktime == fl.fail_locktime) {
+               /* If the max is already set to the right value, do not
+                * write in the file.
+                * This avoids writing 0 when no entries were present for
+                * the user and the max argument is 0.
+                */
+               return false;
+       }
+
+       fl.fail_locktime = locktime;
 
        if (fseeko (fail, offset, SEEK_SET) == 0
-           && fwrite ((char *) &faillog, sizeof faillog, 1, fail) == 1) {
+           && fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1) {
                fflush (fail);
-       } else {
-               perror (FAILLOG_FILE);
+               return false;
        }
+
+       fprintf (stderr,
+                _("faillog: Failed to set locktime for UID %d\n"),
+                uid);
+       return true;
 }
 
-/*
- * XXX - this needs to be written properly some day, right now it is
- * a quick cut-and-paste hack from the above two functions.  --marekm
- */
 static void set_locktime (long locktime)
 {
-       struct passwd *pwent;
-
-       if (uflg) {
-               set_locktime_one (user, locktime);
+       if (uflg && has_umin && has_umax && (umin==umax)) {
+               if (set_locktime_one ((uid_t)umin, locktime)) {
+                       errors = true;
+               }
        } else {
-               setpwent ();
-               while ( (pwent = getpwent ()) != NULL ) {
-                       set_locktime_one (pwent->pw_uid, locktime);
+               /* Set locktime for all entries in the specified range.
+                * If locktime is unchanged for an entry, the entry is not touched.
+                * If locktime is null, and no entries exist for this user, no
+                * entries will be created.
+                * Entries for non existing user are also taken into
+                * account (in order to define policy for future users).
+                */
+               uid_t uid = 0;
+               uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+
+               /* Make sure we stay in the umin-umax range if specified */
+               if (has_umin) {
+                       uid = (uid_t)umin;
+               }
+               if (has_umax && (uid_t)umax < uidmax) {
+                       uidmax = (uid_t)umax;
+               }
+
+               while (uid < uidmax) {
+                       if (set_locktime_one (uid, locktime)) {
+                               errors = true;
+                       }
+                       uid++;
                }
-               endpwent ();
        }
 }
 
 int main (int argc, char **argv)
 {
-       bool anyflag = false;
+       long fail_locktime;
+       long fail_max;
+       long days;
 
        (void) setlocale (LC_ALL, "");
        (void) bindtextdomain (PACKAGE, LOCALEDIR);
        (void) textdomain (PACKAGE);
 
-       /* try to open for read/write, if that fails - read only */
-       fail = fopen (FAILLOG_FILE, "r+");
-       if (NULL == fail) {
-               fail = fopen (FAILLOG_FILE, "r");
-       }
-       if (NULL == fail) {
-               perror (FAILLOG_FILE);
-               exit (1);
-       }
-
        {
                int option_index = 0;
                int c;
@@ -345,54 +480,76 @@ int main (int argc, char **argv)
                        {"user", required_argument, NULL, 'u'},
                        {NULL, 0, NULL, '\0'}
                };
-
-               while ((c =
-                       getopt_long (argc, argv, "ahl:m:rt:u:",
-                                    long_options, &option_index)) != -1) {
+               while ((c = getopt_long (argc, argv, "ahl:m:rt:u:",
+                                        long_options, &option_index)) != -1) {
                        switch (c) {
                        case 'a':
                                aflg = true;
-                               if (uflg) {
-                                       usage ();
-                               }
                                break;
                        case 'h':
                                usage ();
                                break;
                        case 'l':
-                               set_locktime ((long) atoi (optarg));
-                               anyflag = true;
+                               if (getlong (optarg, &fail_locktime) == 0) {
+                                       fprintf (stderr,
+                                                _("%s: invalid numeric argument '%s'\n"),
+                                                "faillog", optarg);
+                                       usage ();
+                               }
+                               lflg = true;
                                break;
                        case 'm':
-                               setmax (atoi (optarg));
-                               anyflag = true;
+                               if (getlong (optarg, &fail_max) == 0) {
+                                       fprintf (stderr,
+                                                _("%s: invalid numeric argument '%s'\n"),
+                                                "faillog", optarg);
+                                       usage ();
+                               }
+                               mflg = true;
                                break;
                        case 'r':
-                               reset ();
-                               anyflag = true;
+                               rflg = true;
                                break;
                        case 't':
-                               days = atoi (optarg);
+                               if (getlong (optarg, &days) == 0) {
+                                       fprintf (stderr,
+                                                _("%s: invalid numeric argument '%s'\n"),
+                                                "faillog", optarg);
+                                       usage ();
+                               }
                                seconds = (time_t) days * DAY;
                                tflg = true;
                                break;
                        case 'u':
                        {
+                               /*
+                                * The user can be:
+                                *  - a login name
+                                *  - numerical
+                                *  - a numerical login ID
+                                *  - a range (-x, x-, x-y)
+                                */
                                struct passwd *pwent;
-                               if (aflg) {
-                                       usage ();
-                               }
 
+                               uflg = true;
                                /* local, no need for xgetpwnam */
                                pwent = getpwnam (optarg);
-                               if (NULL == pwent) {
-                                       fprintf (stderr,
-                                                _("Unknown User: %s\n"),
-                                                optarg);
-                                       exit (1);
+                               if (NULL != pwent) {
+                                       umin = (unsigned long) pwent->pw_uid;
+                                       has_umin = true;
+                                       umax = umin;
+                                       has_umax = true;
+                               } else {
+                                       if (getrange (optarg,
+                                                     &umin, &has_umin,
+                                                     &umax, &has_umax) == 0) {
+                                               fprintf (stderr,
+                                                        _("lastlog: Unknown user or range: %s\n"),
+                                                        optarg);
+                                               exit (EXIT_FAILURE);
+                                       }
                                }
-                               uflg = true;
-                               user = pwent->pw_uid;
+
                                break;
                        }
                        default:
@@ -401,17 +558,52 @@ int main (int argc, char **argv)
                }
        }
 
-       /* no flags implies -a -p (= print information for all users)  */
-       if (!(anyflag || aflg || tflg || uflg)) {
-               aflg = true;
+       if (aflg && uflg) {
+               usage ();
+       }
+       if (tflg && (lflg || mflg || rflg)) {
+               usage ();
+       }
+
+       /* Open the faillog database */
+       if (lflg || mflg || rflg) {
+               fail = fopen (FAILLOG_FILE, "r+");
+       } else {
+               fail = fopen (FAILLOG_FILE, "r");
+       }
+       if (NULL == fail) {
+               fprintf (stderr,
+                        _("faillog: Cannot open %s: %s\n"),
+                        FAILLOG_FILE, strerror (errno));
+               exit (EXIT_FAILURE);
+       }
+
+       /* Get the size of the faillog */
+       if (fstat (fileno (fail), &statbuf) != 0) {
+               fprintf (stderr,
+                        _("faillog: Cannot get the size of %s: %s\n"),
+                        FAILLOG_FILE, strerror (errno));
+               exit (EXIT_FAILURE);
+       }
+
+       if (lflg) {
+               set_locktime (fail_locktime);
+       }
+
+       if (mflg) {
+               setmax (fail_max);
+       }
+
+       if (rflg) {
+               reset ();
        }
-       /* (-a or -t days or -u user) and no other flags implies -p
-          (= print information for selected users) */
-       if (!anyflag && (aflg || tflg || uflg)) {
+
+       if (!(lflg || mflg || rflg)) {
                print ();
        }
+
        fclose (fail);
 
-       exit (E_SUCCESS);
+       exit (errors ? EXIT_FAILURE : EXIT_SUCCESS);
 }