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