]> granicus.if.org Git - shadow/blob - src/faillog.c
* src/faillog.c: Remove function calls from within assert().
[shadow] / src / faillog.c
1 /*
2  * Copyright (c) 1989 - 1993, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2002 - 2006, Tomasz Kłoczko
5  * Copyright (c) 2007 - 2009, Nicolas François
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the copyright holders or contributors may not be used to
17  *    endorse or promote products derived from this software without
18  *    specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <config.h>
34
35 #ident "$Id$"
36
37 #include <getopt.h>
38 #include <pwd.h>
39 #include <stdio.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <time.h>
43 #include <assert.h>
44 #include "defines.h"
45 #include "exitcodes.h"
46 #include "faillog.h"
47 #include "prototypes.h"
48 /*
49  * Global variables
50  */
51 static FILE *fail;              /* failure file stream */
52 static time_t seconds;          /* that number of days in seconds */
53 static unsigned long umin;      /* if uflg and has_umin, only display users with uid >= umin */
54 static bool has_umin = false;
55 static unsigned long umax;      /* if uflg and has_umax, only display users with uid <= umax */
56 static bool has_umax = false;
57 static bool errors = false;
58
59 static bool aflg = false;       /* set if all users are to be printed always */
60 static bool uflg = false;       /* set if user is a valid user id */
61 static bool tflg = false;       /* print is restricted to most recent days */
62 static bool lflg = false;       /* set the locktime */
63 static bool mflg = false;       /* set maximum failed login counters */
64 static bool rflg = false;       /* reset the counters of login failures */
65
66 static struct stat statbuf;     /* fstat buffer for file size */
67
68 #define NOW     (time((time_t *) 0))
69
70 static void usage (void)
71 {
72         fputs (_("Usage: faillog [options]\n"
73                  "\n"
74                  "Options:\n"
75                  "  -a, --all                     display faillog records for all users\n"
76                  "  -h, --help                    display this help message and exit\n"
77                  "  -l, --lock-time SEC           after failed login lock account to SEC seconds\n"
78                  "  -m, --maximum MAX             set maximum failed login counters to MAX\n"
79                  "  -r, --reset                   reset the counters of login failures\n"
80                  "  -t, --time DAYS               display faillog records more recent than DAYS\n"
81                  "  -u, --user LOGIN              display faillog record or maintains failure\n"
82                  "                                counters and limits (if used with -r, -m or -l\n"
83                  "                                options) only for user with LOGIN\n"
84                  "\n"), stderr);
85         exit (E_USAGE);
86 }
87
88 static void print_one (/*@null@*/const struct passwd *pw, bool force)
89 {
90         static bool once = false;
91         struct tm *tm;
92         off_t offset;
93         struct faillog fl;
94         time_t now;
95
96 #ifdef HAVE_STRFTIME
97         char *cp;
98         char ptime[80];
99 #endif
100
101         if (NULL == pw) {
102                 return;
103         }
104
105         offset = pw->pw_uid * sizeof (fl);
106         if (offset <= (statbuf.st_size - sizeof (fl))) {
107                 /* fseeko errors are not really relevant for us. */
108                 int err = fseeko (fail, offset, SEEK_SET);
109                 assert (0 == err);
110                 /* faillog is a sparse file. Even if no entries were
111                  * entered for this user, which should be able to get the
112                  * empty entry in this case.
113                  */
114                 if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
115                         fprintf (stderr,
116                                  _("faillog: Failed to get the entry for UID %lu\n"),
117                                  (unsigned long int)pw->pw_uid);
118                         return;
119                 }
120         } else {
121                 /* Outsize of the faillog file.
122                  * Behave as if there were a missing entry (same behavior
123                  * as if we were reading an non existing entry in the
124                  * sparse faillog file).
125                  */
126                 memzero (&fl, sizeof (fl));
127         }
128
129         /* Nothing to report */
130         if (!force && (0 == fl.fail_time)) {
131                 return;
132         }
133
134         (void) time(&now);
135
136         /* Filter out entries that do not match with the -t option */
137         if (tflg && ((now - fl.fail_time) > seconds)) {
138                 return;
139         }
140
141         /* Print the header only once */
142         if (!once) {
143                 puts (_("Login       Failures Maximum Latest                   On\n"));
144                 once = true;
145         }
146
147         tm = localtime (&fl.fail_time);
148 #ifdef HAVE_STRFTIME
149         strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
150         cp = ptime;
151 #endif
152         printf ("%-9s   %5d    %5d   ",
153                 pw->pw_name, fl.fail_cnt, fl.fail_max);
154         /* FIXME: cp is not defined ifndef HAVE_STRFTIME */
155         printf ("%s  %s", cp, fl.fail_line);
156         if (0 != fl.fail_locktime) {
157                 if (   ((fl.fail_time + fl.fail_locktime) > now)
158                     && (0 != fl.fail_cnt)) {
159                         printf (_(" [%lus left]"),
160                                 (unsigned long) fl.fail_time + fl.fail_locktime - now);
161                 } else {
162                         printf (_(" [%lds lock]"),
163                                 fl.fail_locktime);
164                 }
165         }
166         putchar ('\n');
167 }
168
169 static void print (void)
170 {
171         if (uflg && has_umin && has_umax && (umin==umax)) {
172                 print_one (getpwuid ((uid_t)umin), true);
173         } else {
174                 /* We only print records for existing users.
175                  * Loop based on the user database instead of reading the
176                  * whole file. We will have to query the database anyway
177                  * so except for very small ranges and large user
178                  * database, this should not be a performance issue.
179                  */
180                 struct passwd *pwent;
181
182                 setpwent ();
183                 while ( (pwent = getpwent ()) != NULL ) {
184                         if (   uflg
185                             && (   (has_umin && (pwent->pw_uid < (uid_t)umin))
186                                 || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
187                                 continue;
188                         }
189                         print_one (pwent, aflg);
190                 }
191                 endpwent ();
192         }
193 }
194
195 /*
196  * reset_one - Reset the fail count for one user
197  *
198  * This returns a boolean indicating if an error occurred.
199  */
200 static bool reset_one (uid_t uid)
201 {
202         off_t offset;
203         struct faillog fl;
204
205         offset = uid * sizeof (fl);
206         if (offset <= (statbuf.st_size - sizeof (fl))) {
207                 /* fseeko errors are not really relevant for us. */
208                 int err = fseeko (fail, offset, SEEK_SET);
209                 assert (0 == err);
210                 /* faillog is a sparse file. Even if no entries were
211                  * entered for this user, which should be able to get the
212                  * empty entry in this case.
213                  */
214                 if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
215                         fprintf (stderr,
216                                  _("faillog: Failed to get the entry for UID %lu\n"),
217                                  (unsigned long int)uid);
218                         return true;
219                 }
220         } else {
221                 /* Outsize of the faillog file.
222                  * Behave as if there were a missing entry (same behavior
223                  * as if we were reading an non existing entry in the
224                  * sparse faillog file).
225                  */
226                 memzero (&fl, sizeof (fl));
227         }
228
229         if (0 == fl.fail_cnt) {
230                 /* If the count is already null, do not write in the file.
231                  * This avoids writing 0 when no entries were present for
232                  * the user.
233                  */
234                 return false;
235         }
236
237         fl.fail_cnt = 0;
238
239         if (   (fseeko (fail, offset, SEEK_SET) == 0)
240             && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
241                 (void) fflush (fail);
242                 return false;
243         }
244
245         fprintf (stderr,
246                  _("faillog: Failed to reset fail count for UID %lu\n"),
247                  (unsigned long int)uid);
248         return true;
249 }
250
251 static void reset (void)
252 {
253         if (uflg && has_umin && has_umax && (umin==umax)) {
254                 if (reset_one ((uid_t)umin)) {
255                         errors = true;
256                 }
257         } else {
258                 /* Reset all entries in the specified range.
259                  * Non existing entries will not be touched.
260                  * Entries for non existing users are also reset.
261                  */
262                 uid_t uid = 0;
263                 uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
264
265                 /* Make sure we stay in the umin-umax range if specified */
266                 if (has_umin) {
267                         uid = (uid_t)umin;
268                 }
269                 if (has_umax && (uid_t)umax < uidmax) {
270                         uidmax = (uid_t)umax;
271                 }
272
273                 while (uid < uidmax) {
274                         if (reset_one (uid)) {
275                                 errors = true;
276                         }
277                         uid++;
278                 }
279         }
280 }
281
282 /*
283  * setmax_one - Set the maximum failed login counter for one user
284  *
285  * This returns a boolean indicating if an error occurred.
286  */
287 static bool setmax_one (uid_t uid, int max)
288 {
289         off_t offset;
290         struct faillog fl;
291
292         offset = (off_t) uid * sizeof (fl);
293         if (offset <= (statbuf.st_size - sizeof (fl))) {
294                 /* fseeko errors are not really relevant for us. */
295                 int err = fseeko (fail, offset, SEEK_SET);
296                 assert (0 == err);
297                 /* faillog is a sparse file. Even if no entries were
298                  * entered for this user, which should be able to get the
299                  * empty entry in this case.
300                  */
301                 if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
302                         fprintf (stderr,
303                                  _("faillog: Failed to get the entry for UID %lu\n"),
304                                  (unsigned long int)uid);
305                         return true;
306                 }
307         } else {
308                 /* Outsize of the faillog file.
309                  * Behave as if there were a missing entry (same behavior
310                  * as if we were reading an non existing entry in the
311                  * sparse faillog file).
312                  */
313                 memzero (&fl, sizeof (fl));
314         }
315
316         if (max == fl.fail_max) {
317                 /* If the max is already set to the right value, do not
318                  * write in the file.
319                  * This avoids writing 0 when no entries were present for
320                  * the user and the max argument is 0.
321                  */
322                 return false;
323         }
324
325         fl.fail_max = max;
326
327         if (   (fseeko (fail, offset, SEEK_SET) == 0)
328             && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
329                 (void) fflush (fail);
330                 return false;
331         }
332
333         fprintf (stderr,
334                  _("faillog: Failed to set max for UID %lu\n"),
335                  (unsigned long int)uid);
336         return true;
337 }
338
339 static void setmax (int max)
340 {
341         if (uflg && has_umin && has_umax && (umin==umax)) {
342                 if (setmax_one ((uid_t)umin, max)) {
343                         errors = true;
344                 }
345         } else {
346                 /* Set max for all entries in the specified range.
347                  * If max is unchanged for an entry, the entry is not touched.
348                  * If max is null, and no entries exist for this user, no
349                  * entries will be created.
350                  * Entries for non existing user are also taken into
351                  * account (in order to define policy for future users).
352                  */
353                 uid_t uid = 0;
354                 uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
355
356                 /* Make sure we stay in the umin-umax range if specified */
357                 if (has_umin) {
358                         uid = (uid_t)umin;
359                 }
360                 if (has_umax && (uid_t)umax < uidmax) {
361                         uidmax = (uid_t)umax;
362                 }
363
364                 while (uid < uidmax) {
365                         if (setmax_one (uid, max)) {
366                                 errors = true;
367                         }
368                         uid++;
369                 }
370         }
371 }
372
373 /*
374  * set_locktime_one - Set the locktime for one user
375  *
376  * This returns a boolean indicating if an error occurred.
377  */
378 static bool set_locktime_one (uid_t uid, long locktime)
379 {
380         off_t offset;
381         struct faillog fl;
382
383         offset = (off_t) uid * sizeof (fl);
384         if (offset <= (statbuf.st_size - sizeof (fl))) {
385                 /* fseeko errors are not really relevant for us. */
386                 int err = fseeko (fail, offset, SEEK_SET);
387                 assert (0 == err);
388                 /* faillog is a sparse file. Even if no entries were
389                  * entered for this user, which should be able to get the
390                  * empty entry in this case.
391                  */
392                 if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
393                         fprintf (stderr,
394                                  _("faillog: Failed to get the entry for UID %lu\n"),
395                                  (unsigned long int)uid);
396                         return true;
397                 }
398         } else {
399                 /* Outsize of the faillog file.
400                  * Behave as if there were a missing entry (same behavior
401                  * as if we were reading an non existing entry in the
402                  * sparse faillog file).
403                  */
404                 memzero (&fl, sizeof (fl));
405         }
406
407         if (locktime == fl.fail_locktime) {
408                 /* If the max is already set to the right value, do not
409                  * write in the file.
410                  * This avoids writing 0 when no entries were present for
411                  * the user and the max argument is 0.
412                  */
413                 return false;
414         }
415
416         fl.fail_locktime = locktime;
417
418         if (   (fseeko (fail, offset, SEEK_SET) == 0)
419             && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
420                 (void) fflush (fail);
421                 return false;
422         }
423
424         fprintf (stderr,
425                  _("faillog: Failed to set locktime for UID %lu\n"),
426                  (unsigned long int)uid);
427         return true;
428 }
429
430 static void set_locktime (long locktime)
431 {
432         if (uflg && has_umin && has_umax && (umin==umax)) {
433                 if (set_locktime_one ((uid_t)umin, locktime)) {
434                         errors = true;
435                 }
436         } else {
437                 /* Set locktime for all entries in the specified range.
438                  * If locktime is unchanged for an entry, the entry is not touched.
439                  * If locktime is null, and no entries exist for this user, no
440                  * entries will be created.
441                  * Entries for non existing user are also taken into
442                  * account (in order to define policy for future users).
443                  */
444                 uid_t uid = 0;
445                 uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
446
447                 /* Make sure we stay in the umin-umax range if specified */
448                 if (has_umin) {
449                         uid = (uid_t)umin;
450                 }
451                 if (has_umax && (uid_t)umax < uidmax) {
452                         uidmax = (uid_t)umax;
453                 }
454
455                 while (uid < uidmax) {
456                         if (set_locktime_one (uid, locktime)) {
457                                 errors = true;
458                         }
459                         uid++;
460                 }
461         }
462 }
463
464 int main (int argc, char **argv)
465 {
466         long fail_locktime;
467         long fail_max;
468         long days;
469
470         (void) setlocale (LC_ALL, "");
471         (void) bindtextdomain (PACKAGE, LOCALEDIR);
472         (void) textdomain (PACKAGE);
473
474         {
475                 int option_index = 0;
476                 int c;
477                 static struct option long_options[] = {
478                         {"all", no_argument, NULL, 'a'},
479                         {"help", no_argument, NULL, 'h'},
480                         {"lock-secs", required_argument, NULL, 'l'},
481                         {"maximum", required_argument, NULL, 'm'},
482                         {"reset", no_argument, NULL, 'r'},
483                         {"time", required_argument, NULL, 't'},
484                         {"user", required_argument, NULL, 'u'},
485                         {NULL, 0, NULL, '\0'}
486                 };
487                 while ((c = getopt_long (argc, argv, "ahl:m:rt:u:",
488                                          long_options, &option_index)) != -1) {
489                         switch (c) {
490                         case 'a':
491                                 aflg = true;
492                                 break;
493                         case 'h':
494                                 usage ();
495                                 break;
496                         case 'l':
497                                 if (getlong (optarg, &fail_locktime) == 0) {
498                                         fprintf (stderr,
499                                                  _("%s: invalid numeric argument '%s'\n"),
500                                                  "faillog", optarg);
501                                         usage ();
502                                 }
503                                 lflg = true;
504                                 break;
505                         case 'm':
506                                 if (getlong (optarg, &fail_max) == 0) {
507                                         fprintf (stderr,
508                                                  _("%s: invalid numeric argument '%s'\n"),
509                                                  "faillog", optarg);
510                                         usage ();
511                                 }
512                                 mflg = true;
513                                 break;
514                         case 'r':
515                                 rflg = true;
516                                 break;
517                         case 't':
518                                 if (getlong (optarg, &days) == 0) {
519                                         fprintf (stderr,
520                                                  _("%s: invalid numeric argument '%s'\n"),
521                                                  "faillog", optarg);
522                                         usage ();
523                                 }
524                                 seconds = (time_t) days * DAY;
525                                 tflg = true;
526                                 break;
527                         case 'u':
528                         {
529                                 /*
530                                  * The user can be:
531                                  *  - a login name
532                                  *  - numerical
533                                  *  - a numerical login ID
534                                  *  - a range (-x, x-, x-y)
535                                  */
536                                 struct passwd *pwent;
537
538                                 uflg = true;
539                                 /* local, no need for xgetpwnam */
540                                 pwent = getpwnam (optarg);
541                                 if (NULL != pwent) {
542                                         umin = (unsigned long) pwent->pw_uid;
543                                         has_umin = true;
544                                         umax = umin;
545                                         has_umax = true;
546                                 } else {
547                                         if (getrange (optarg,
548                                                       &umin, &has_umin,
549                                                       &umax, &has_umax) == 0) {
550                                                 fprintf (stderr,
551                                                          _("lastlog: Unknown user or range: %s\n"),
552                                                          optarg);
553                                                 exit (EXIT_FAILURE);
554                                         }
555                                 }
556
557                                 break;
558                         }
559                         default:
560                                 usage ();
561                         }
562                 }
563         }
564
565         if (aflg && uflg) {
566                 usage ();
567         }
568         if (tflg && (lflg || mflg || rflg)) {
569                 usage ();
570         }
571
572         /* Open the faillog database */
573         if (lflg || mflg || rflg) {
574                 fail = fopen (FAILLOG_FILE, "r+");
575         } else {
576                 fail = fopen (FAILLOG_FILE, "r");
577         }
578         if (NULL == fail) {
579                 fprintf (stderr,
580                          _("faillog: Cannot open %s: %s\n"),
581                          FAILLOG_FILE, strerror (errno));
582                 exit (EXIT_FAILURE);
583         }
584
585         /* Get the size of the faillog */
586         if (fstat (fileno (fail), &statbuf) != 0) {
587                 fprintf (stderr,
588                          _("faillog: Cannot get the size of %s: %s\n"),
589                          FAILLOG_FILE, strerror (errno));
590                 exit (EXIT_FAILURE);
591         }
592
593         if (lflg) {
594                 set_locktime (fail_locktime);
595         }
596
597         if (mflg) {
598                 setmax (fail_max);
599         }
600
601         if (rflg) {
602                 reset ();
603         }
604
605         if (!(lflg || mflg || rflg)) {
606                 print ();
607         }
608
609         fclose (fail);
610
611         exit (errors ? EXIT_FAILURE : EXIT_SUCCESS);
612 }
613