[Pkg-shadow-commits] r2507 - in upstream/trunk: . man src

nekral-guest at alioth.debian.org nekral-guest at alioth.debian.org
Fri Mar 13 22:49:21 UTC 2009


Author: nekral-guest
Date: 2009-03-13 22:49:20 +0000 (Fri, 13 Mar 2009)
New Revision: 2507

Modified:
   upstream/trunk/ChangeLog
   upstream/trunk/NEWS
   upstream/trunk/man/faillog.8.xml
   upstream/trunk/src/faillog.c
Log:
	* 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.


Modified: upstream/trunk/ChangeLog
===================================================================
--- upstream/trunk/ChangeLog	2009-03-13 22:30:38 UTC (rev 2506)
+++ upstream/trunk/ChangeLog	2009-03-13 22:49:20 UTC (rev 2507)
@@ -10,6 +10,26 @@
 	* 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 at centraliens.net>
 

Modified: upstream/trunk/NEWS
===================================================================
--- upstream/trunk/NEWS	2009-03-13 22:30:38 UTC (rev 2506)
+++ upstream/trunk/NEWS	2009-03-13 22:49:20 UTC (rev 2507)
@@ -37,6 +37,12 @@
 - 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

Modified: upstream/trunk/man/faillog.8.xml
===================================================================
--- upstream/trunk/man/faillog.8.xml	2009-03-13 22:30:38 UTC (rev 2506)
+++ upstream/trunk/man/faillog.8.xml	2009-03-13 22:49:20 UTC (rev 2507)
@@ -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
@@ -70,7 +70,10 @@
       <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>
@@ -89,6 +92,10 @@
 	    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>
@@ -99,12 +106,22 @@
 	<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>
@@ -113,7 +130,10 @@
 	  <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>
@@ -125,25 +145,48 @@
 	<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'>

Modified: upstream/trunk/src/faillog.c
===================================================================
--- upstream/trunk/src/faillog.c	2009-03-13 22:30:38 UTC (rev 2506)
+++ upstream/trunk/src/faillog.c	2009-03-13 22:49:20 UTC (rev 2507)
@@ -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"
@@ -48,14 +49,19 @@
  * 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 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 */
-    uflg = false,		/* set if user is a valid user id */
-    tflg = false;		/* print is restricted to most recent days */
+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 @@
 	         "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 @@
 	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;
+	/* Nothing to report */
+	if (!force && (0 == fl.fail_time)) {
+		return;
 	}
-	if (offset >= statbuf.st_size) {
-		return 0;
-	}
 
-	if (fseeko (fail, offset, SEEK_SET) != 0) {
-		perror (FAILLOG_FILE);
-		return 0;
+	(void) time(&now);
+
+	/* Filter out entries that do not match with the -t option */
+	if (tflg && ((now - fl.fail_time) > seconds)) {
+		return;
 	}
-	if (fread ((char *) &faillog, sizeof faillog, 1, fail) != 1) {
-		if (feof (fail) == 0) {
-			perror (FAILLOG_FILE);
-		}
 
-		return 0;
+	/* Print the header only once */
+	if (!once) {
+		puts (_("Login       Failures Maximum Latest                   On\n"));
+		once = true;
 	}
-	if (0 == faillog.fail_cnt) {
-		return 1;	/* don't fill in no holes ... */
-	}
 
-	faillog.fail_cnt = 0;
-
-	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;
+	struct faillog fl;
 
-	if (uflg) {
-		offset = user * sizeof faillog;
-		if (fstat (fileno (fail), &statbuf) != 0) {
-			perror (FAILLOG_FILE);
-			return;
+	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;
 		}
-		if (offset >= statbuf.st_size) {
-			return;
-		}
+	} 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));
+	}
 
-		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);
+	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;
+	}
+
+	fl.fail_cnt = 0;
+
+	if (   (fseeko (fail, offset, SEEK_SET) == 0)
+	    && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
+		(void) fflush (fail);
+		return false;
+	}
+
+	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 {
-		for (uid = 0;
-		     fread ((char *) &faillog, sizeof faillog, 1, fail) == 1;
-		     uid++) {
+		/* 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);
 
-			if (!aflg && (0 == faillog.fail_cnt)) {
-				continue;
-			}
+		/* 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;
+		}
 
-			if (!aflg && tflg &&
-			    ((NOW - faillog.fail_time) > seconds)) {
-				continue;
+		while (uid < uidmax) {
+			if (reset_one (uid)) {
+				errors = true;
 			}
-
-			if (aflg && (0 == faillog.fail_time)) {
-				continue;
-			}
-
-			print_one (&faillog, uid);
+			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;
+	struct faillog fl;
 
-	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);
+	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_max = max;
 
+	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;
+	}
+
+	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 && has_umin && has_umax && (umin==umax)) {
+		if (setmax_one ((uid_t)umin, max)) {
+			errors = true;
+		}
+	} else {
+		/* 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);
 
-	if (uflg) {
-		setmax_one (user, max);
-	} else {
-		setpwent ();
-		while ( (pwent = getpwent ()) != NULL ) {
-			setmax_one (pwent->pw_uid, max);
+		/* Make sure we stay in the umin-umax range if specified */
+		if (has_umin) {
+			uid = (uid_t)umin;
 		}
-		endpwent ();
+		if (has_umax && (uid_t)umax < uidmax) {
+			uidmax = (uid_t)umax;
+		}
+
+		while (uid < uidmax) {
+			if (setmax_one (uid, max)) {
+				errors = true;
+			}
+			uid++;
+		}
 	}
 }
 
-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;
+	struct faillog fl;
 
-	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);
+	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 && has_umin && has_umax && (umin==umax)) {
+		if (set_locktime_one ((uid_t)umin, locktime)) {
+			errors = true;
+		}
+	} else {
+		/* 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);
 
-	if (uflg) {
-		set_locktime_one (user, locktime);
-	} else {
-		setpwent ();
-		while ( (pwent = getpwent ()) != NULL ) {
-			set_locktime_one (pwent->pw_uid, locktime);
+		/* Make sure we stay in the umin-umax range if specified */
+		if (has_umin) {
+			uid = (uid_t)umin;
 		}
-		endpwent ();
+		if (has_umax && (uid_t)umax < uidmax) {
+			uidmax = (uid_t)umax;
+		}
+
+		while (uid < uidmax) {
+			if (set_locktime_one (uid, locktime)) {
+				errors = true;
+			}
+			uid++;
+		}
 	}
 }
 
 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 @@
 			{"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 @@
 		}
 	}
 
-	/* no flags implies -a -p (= print information for all users)  */
-	if (!(anyflag || aflg || tflg || uflg)) {
-		aflg = true;
+	if (aflg && uflg) {
+		usage ();
 	}
-	/* (-a or -t days or -u user) and no other flags implies -p
-	   (= print information for selected users) */
-	if (!anyflag && (aflg || tflg || uflg)) {
+	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 ();
+	}
+
+	if (!(lflg || mflg || rflg)) {
 		print ();
 	}
+
 	fclose (fail);
 
-	exit (E_SUCCESS);
+	exit (errors ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 




More information about the Pkg-shadow-commits mailing list