Import Linux-PAM.
[profile/ivi/pam.git] / modules / pam_lastlog / pam_lastlog.c
1 /* pam_lastlog module */
2
3 /*
4  * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/3/11
5  *
6  * This module does the necessary work to display the last login
7  * time+date for this user, it then updates this entry for the
8  * present (login) service.
9  */
10
11 #include "config.h"
12
13 #include <fcntl.h>
14 #include <time.h>
15 #include <errno.h>
16 #ifdef HAVE_UTMP_H
17 # include <utmp.h>
18 #else
19 # include <lastlog.h>
20 #endif
21 #include <pwd.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <syslog.h>
28 #include <unistd.h>
29
30 #if defined(hpux) || defined(sunos) || defined(solaris)
31 # ifndef _PATH_LASTLOG
32 #  define _PATH_LASTLOG "/usr/adm/lastlog"
33 # endif /* _PATH_LASTLOG */
34 # ifndef UT_HOSTSIZE
35 #  define UT_HOSTSIZE 16
36 # endif /* UT_HOSTSIZE */
37 # ifndef UT_LINESIZE
38 #  define UT_LINESIZE 12
39 # endif /* UT_LINESIZE */
40 #endif
41 #if defined(hpux)
42 struct lastlog {
43     time_t  ll_time;
44     char    ll_line[UT_LINESIZE];
45     char    ll_host[UT_HOSTSIZE];            /* same as in utmp */
46 };
47 #endif /* hpux */
48
49 #ifndef _PATH_BTMP
50 # define _PATH_BTMP "/var/log/btmp"
51 #endif
52
53 /* XXX - time before ignoring lock. Is 1 sec enough? */
54 #define LASTLOG_IGNORE_LOCK_TIME     1
55
56 #define DEFAULT_HOST     ""  /* "[no.where]" */
57 #define DEFAULT_TERM     ""  /* "tt???" */
58
59 /*
60  * here, we make a definition for the externally accessible function
61  * in this file (this definition is required for static a module
62  * but strongly encouraged generally) it is used to instruct the
63  * modules include file to define the function prototypes.
64  */
65
66 #define PAM_SM_SESSION
67
68 #include <security/pam_modules.h>
69 #include <security/_pam_macros.h>
70 #include <security/pam_modutil.h>
71 #include <security/pam_ext.h>
72
73 /* argument parsing */
74
75 #define LASTLOG_DATE        01  /* display the date of the last login */
76 #define LASTLOG_HOST        02  /* display the last host used (if set) */
77 #define LASTLOG_LINE        04  /* display the last terminal used */
78 #define LASTLOG_NEVER      010  /* display a welcome message for first login */
79 #define LASTLOG_DEBUG      020  /* send info to syslog(3) */
80 #define LASTLOG_QUIET      040  /* keep quiet about things */
81 #define LASTLOG_WTMP      0100  /* log to wtmp as well as lastlog */
82 #define LASTLOG_BTMP      0200  /* display failed login info from btmp */
83 #define LASTLOG_UPDATE    0400  /* update the lastlog and wtmp files (default) */
84
85 static int
86 _pam_parse(pam_handle_t *pamh, int flags, int argc, const char **argv)
87 {
88     int ctrl=(LASTLOG_DATE|LASTLOG_HOST|LASTLOG_LINE|LASTLOG_WTMP|LASTLOG_UPDATE);
89
90     /* does the appliction require quiet? */
91     if (flags & PAM_SILENT) {
92         ctrl |= LASTLOG_QUIET;
93     }
94
95     /* step through arguments */
96     for (; argc-- > 0; ++argv) {
97
98         /* generic options */
99
100         if (!strcmp(*argv,"debug")) {
101             ctrl |= LASTLOG_DEBUG;
102         } else if (!strcmp(*argv,"nodate")) {
103             ctrl &= ~LASTLOG_DATE;
104         } else if (!strcmp(*argv,"noterm")) {
105             ctrl &= ~LASTLOG_LINE;
106         } else if (!strcmp(*argv,"nohost")) {
107             ctrl &= ~LASTLOG_HOST;
108         } else if (!strcmp(*argv,"silent")) {
109             ctrl |= LASTLOG_QUIET;
110         } else if (!strcmp(*argv,"never")) {
111             ctrl |= LASTLOG_NEVER;
112         } else if (!strcmp(*argv,"nowtmp")) {
113             ctrl &= ~LASTLOG_WTMP;
114         } else if (!strcmp(*argv,"noupdate")) {
115             ctrl &= ~(LASTLOG_WTMP|LASTLOG_UPDATE);
116         } else if (!strcmp(*argv,"showfailed")) {
117             ctrl |= LASTLOG_BTMP;
118         } else {
119             pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
120         }
121     }
122
123     D(("ctrl = %o", ctrl));
124     return ctrl;
125 }
126
127 static const char *
128 get_tty(pam_handle_t *pamh)
129 {
130     const void *void_terminal_line = NULL;
131     const char *terminal_line;
132
133     if (pam_get_item(pamh, PAM_TTY, &void_terminal_line) != PAM_SUCCESS
134         || void_terminal_line == NULL) {
135         terminal_line = DEFAULT_TERM;
136     } else {
137         terminal_line = void_terminal_line;
138     }
139     if (!strncmp("/dev/", terminal_line, 5)) {
140         /* strip leading "/dev/" from tty. */
141         terminal_line += 5;
142     }
143     D(("terminal = %s", terminal_line));
144     return terminal_line;
145 }
146
147 static int
148 last_login_read(pam_handle_t *pamh, int announce, int last_fd, uid_t uid, time_t *lltime)
149 {
150     struct flock last_lock;
151     struct lastlog last_login;
152     int retval = PAM_SUCCESS;
153     char the_time[256];
154     char *date = NULL;
155     char *host = NULL;
156     char *line = NULL;
157
158     memset(&last_lock, 0, sizeof(last_lock));
159     last_lock.l_type = F_RDLCK;
160     last_lock.l_whence = SEEK_SET;
161     last_lock.l_start = sizeof(last_login) * (off_t) uid;
162     last_lock.l_len = sizeof(last_login);
163
164     if (fcntl(last_fd, F_SETLK, &last_lock) < 0) {
165         D(("locking %s failed..(waiting a little)", _PATH_LASTLOG));
166         pam_syslog(pamh, LOG_WARNING,
167                    "file %s is locked/read", _PATH_LASTLOG);
168         sleep(LASTLOG_IGNORE_LOCK_TIME);
169     }
170
171     if (pam_modutil_read(last_fd, (char *) &last_login,
172                          sizeof(last_login)) != sizeof(last_login)) {
173         memset(&last_login, 0, sizeof(last_login));
174     }
175
176     last_lock.l_type = F_UNLCK;
177     (void) fcntl(last_fd, F_SETLK, &last_lock);        /* unlock */
178
179     *lltime = last_login.ll_time;
180     if (!last_login.ll_time) {
181         if (announce & LASTLOG_DEBUG) {
182             pam_syslog(pamh, LOG_DEBUG,
183                        "first login for user with uid %lu",
184                        (unsigned long int)uid);
185         }
186     }
187
188     if (!(announce & LASTLOG_QUIET)) {
189
190         if (last_login.ll_time) {
191
192             /* we want the date? */
193             if (announce & LASTLOG_DATE) {
194                 struct tm *tm, tm_buf;
195                 time_t ll_time;
196
197                 ll_time = last_login.ll_time;
198                 tm = localtime_r (&ll_time, &tm_buf);
199                 strftime (the_time, sizeof (the_time),
200                 /* TRANSLATORS: "strftime options for date of last login" */
201                           _(" %a %b %e %H:%M:%S %Z %Y"), tm);
202
203                 date = the_time;
204             }
205
206             /* we want & have the host? */
207             if ((announce & LASTLOG_HOST)
208                 && (last_login.ll_host[0] != '\0')) {
209                 /* TRANSLATORS: " from <host>" */
210                 if (asprintf(&host, _(" from %.*s"), UT_HOSTSIZE,
211                              last_login.ll_host) < 0) {
212                     pam_syslog(pamh, LOG_ERR, "out of memory");
213                     retval = PAM_BUF_ERR;
214                     goto cleanup;
215                 }
216             }
217
218             /* we want and have the terminal? */
219             if ((announce & LASTLOG_LINE)
220                 && (last_login.ll_line[0] != '\0')) {
221                 /* TRANSLATORS: " on <terminal>" */
222                 if (asprintf(&line, _(" on %.*s"), UT_LINESIZE,
223                              last_login.ll_line) < 0) {
224                     pam_syslog(pamh, LOG_ERR, "out of memory");
225                     retval = PAM_BUF_ERR;
226                     goto cleanup;
227                 }
228             }
229
230             if (date != NULL || host != NULL || line != NULL)
231                     /* TRANSLATORS: "Last login: <date> from <host> on <terminal>" */
232                     retval = pam_info(pamh, _("Last login:%s%s%s"),
233                               date ? date : "",
234                               host ? host : "",
235                               line ? line : "");
236         } else if (announce & LASTLOG_NEVER) {
237                 D(("this is the first time this user has logged in"));
238                 retval = pam_info(pamh, "%s", _("Welcome to your new account!"));
239         }
240     }
241
242     /* cleanup */
243  cleanup:
244     memset(&last_login, 0, sizeof(last_login));
245     _pam_overwrite(date);
246     _pam_overwrite(host);
247     _pam_drop(host);
248     _pam_overwrite(line);
249     _pam_drop(line);
250
251     return retval;
252 }
253
254 static int
255 last_login_write(pam_handle_t *pamh, int announce, int last_fd,
256                  uid_t uid, const char *user)
257 {
258     struct flock last_lock;
259     struct lastlog last_login;
260     time_t ll_time;
261     const void *void_remote_host = NULL;
262     const char *remote_host;
263     const char *terminal_line;
264     int retval = PAM_SUCCESS;
265
266     /* rewind */
267     if (lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET) < 0) {
268         pam_syslog(pamh, LOG_ERR, "failed to lseek %s: %m", _PATH_LASTLOG);
269         return PAM_SERVICE_ERR;
270     }
271
272     /* set this login date */
273     D(("set the most recent login time"));
274     (void) time(&ll_time);    /* set the time */
275     last_login.ll_time = ll_time;
276
277     /* set the remote host */
278     if (pam_get_item(pamh, PAM_RHOST, &void_remote_host) != PAM_SUCCESS
279         || void_remote_host == NULL) {
280         remote_host = DEFAULT_HOST;
281     } else {
282         remote_host = void_remote_host;
283     }
284
285     /* copy to last_login */
286     last_login.ll_host[0] = '\0';
287     strncat(last_login.ll_host, remote_host, sizeof(last_login.ll_host)-1);
288
289     /* set the terminal line */
290     terminal_line = get_tty(pamh);
291
292     /* copy to last_login */
293     last_login.ll_line[0] = '\0';
294     strncat(last_login.ll_line, terminal_line, sizeof(last_login.ll_line)-1);
295     terminal_line = NULL;
296
297     D(("locking lastlog file"));
298
299     /* now we try to lock this file-record exclusively; non-blocking */
300     memset(&last_lock, 0, sizeof(last_lock));
301     last_lock.l_type = F_WRLCK;
302     last_lock.l_whence = SEEK_SET;
303     last_lock.l_start = sizeof(last_login) * (off_t) uid;
304     last_lock.l_len = sizeof(last_login);
305
306     if (fcntl(last_fd, F_SETLK, &last_lock) < 0) {
307         D(("locking %s failed..(waiting a little)", _PATH_LASTLOG));
308         pam_syslog(pamh, LOG_WARNING, "file %s is locked/write", _PATH_LASTLOG);
309         sleep(LASTLOG_IGNORE_LOCK_TIME);
310     }
311
312     D(("writing to the lastlog file"));
313     if (pam_modutil_write (last_fd, (char *) &last_login,
314                            sizeof (last_login)) != sizeof(last_login)) {
315         pam_syslog(pamh, LOG_ERR, "failed to write %s: %m", _PATH_LASTLOG);
316         retval = PAM_SERVICE_ERR;
317     }
318
319     last_lock.l_type = F_UNLCK;
320     (void) fcntl(last_fd, F_SETLK, &last_lock);        /* unlock */
321     D(("unlocked"));
322
323     if (announce & LASTLOG_WTMP) {
324         /* write wtmp entry for user */
325         logwtmp(last_login.ll_line, user, remote_host);
326     }
327
328     /* cleanup */
329     memset(&last_login, 0, sizeof(last_login));
330
331     return retval;
332 }
333
334 static int
335 last_login_date(pam_handle_t *pamh, int announce, uid_t uid, const char *user, time_t *lltime)
336 {
337     int retval;
338     int last_fd;
339
340     /* obtain the last login date and all the relevant info */
341     last_fd = open(_PATH_LASTLOG, announce&LASTLOG_UPDATE ? O_RDWR : O_RDONLY);
342     if (last_fd < 0) {
343         if (errno == ENOENT) {
344              last_fd = open(_PATH_LASTLOG, O_RDWR|O_CREAT,
345                             S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
346              if (last_fd < 0) {
347                   pam_syslog(pamh, LOG_ERR,
348                              "unable to create %s: %m", _PATH_LASTLOG);
349                   D(("unable to create %s file", _PATH_LASTLOG));
350                   return PAM_SERVICE_ERR;
351              }
352              pam_syslog(pamh, LOG_WARNING,
353                         "file %s created", _PATH_LASTLOG);
354              D(("file %s created", _PATH_LASTLOG));
355         } else {
356           pam_syslog(pamh, LOG_ERR, "unable to open %s: %m", _PATH_LASTLOG);
357           D(("unable to open %s file", _PATH_LASTLOG));
358           return PAM_SERVICE_ERR;
359         }
360     }
361
362     if (lseek(last_fd, sizeof(struct lastlog) * (off_t) uid, SEEK_SET) < 0) {
363         pam_syslog(pamh, LOG_ERR, "failed to lseek %s: %m", _PATH_LASTLOG);
364         D(("unable to lseek %s file", _PATH_LASTLOG));
365         return PAM_SERVICE_ERR;
366     }
367
368     retval = last_login_read(pamh, announce, last_fd, uid, lltime);
369     if (retval != PAM_SUCCESS)
370       {
371         close(last_fd);
372         D(("error while reading lastlog file"));
373         return retval;
374       }
375
376     if (announce & LASTLOG_UPDATE) {
377         retval = last_login_write(pamh, announce, last_fd, uid, user);
378     }
379
380     close(last_fd);
381     D(("all done with last login"));
382
383     return retval;
384 }
385
386 static int
387 last_login_failed(pam_handle_t *pamh, int announce, const char *user, time_t lltime)
388 {
389     int retval;
390     int fd;
391     struct utmp ut;
392     struct utmp utuser;
393     int failed = 0;
394     char the_time[256];
395     char *date = NULL;
396     char *host = NULL;
397     char *line = NULL;
398
399     if (strlen(user) > UT_NAMESIZE) {
400         pam_syslog(pamh, LOG_WARNING, "username too long, output might be inaccurate");
401     }
402
403     /* obtain the failed login attempt records from btmp */
404     fd = open(_PATH_BTMP, O_RDONLY);
405     if (fd < 0) {
406         int save_errno = errno;
407         pam_syslog(pamh, LOG_ERR, "unable to open %s: %m", _PATH_BTMP);
408         D(("unable to open %s file", _PATH_BTMP));
409         if (save_errno == ENOENT)
410           return PAM_SUCCESS;
411         else
412           return PAM_SERVICE_ERR;
413     }
414
415     while ((retval=pam_modutil_read(fd, (void *)&ut,
416                          sizeof(ut))) == sizeof(ut)) {
417         if (ut.ut_tv.tv_sec >= lltime && strncmp(ut.ut_user, user, UT_NAMESIZE) == 0) {
418             memcpy(&utuser, &ut, sizeof(utuser));
419             failed++;
420         }
421     }
422
423     if (failed) {
424         /* we want the date? */
425         if (announce & LASTLOG_DATE) {
426             struct tm *tm, tm_buf;
427             time_t lf_time;
428
429             lf_time = utuser.ut_tv.tv_sec;
430             tm = localtime_r (&lf_time, &tm_buf);
431             strftime (the_time, sizeof (the_time),
432                 /* TRANSLATORS: "strftime options for date of last login" */
433                 _(" %a %b %e %H:%M:%S %Z %Y"), tm);
434
435             date = the_time;
436         }
437
438         /* we want & have the host? */
439         if ((announce & LASTLOG_HOST)
440                 && (utuser.ut_host[0] != '\0')) {
441             /* TRANSLATORS: " from <host>" */
442             if (asprintf(&host, _(" from %.*s"), UT_HOSTSIZE,
443                     utuser.ut_host) < 0) {
444                 pam_syslog(pamh, LOG_ERR, "out of memory");
445                 retval = PAM_BUF_ERR;
446                 goto cleanup;
447             }
448         }
449
450         /* we want and have the terminal? */
451         if ((announce & LASTLOG_LINE)
452                 && (utuser.ut_line[0] != '\0')) {
453             /* TRANSLATORS: " on <terminal>" */
454             if (asprintf(&line, _(" on %.*s"), UT_LINESIZE,
455                         utuser.ut_line) < 0) {
456                 pam_syslog(pamh, LOG_ERR, "out of memory");
457                 retval = PAM_BUF_ERR;
458                 goto cleanup;
459             }
460         }
461
462         if (line != NULL || date != NULL || host != NULL) {
463             /* TRANSLATORS: "Last failed login: <date> from <host> on <terminal>" */
464             pam_info(pamh, _("Last failed login:%s%s%s"),
465                               date ? date : "",
466                               host ? host : "",
467                               line ? line : "");
468         }
469
470         _pam_drop(line);
471 #if defined HAVE_DNGETTEXT && defined ENABLE_NLS
472         retval = asprintf (&line, dngettext(PACKAGE,
473                 "There was %d failed login attempt since the last successful login.",
474                 "There were %d failed login attempts since the last successful login.",
475                 failed),
476             failed);
477 #else
478         if (failed == 1)
479             retval = asprintf(&line,
480                 _("There was %d failed login attempt since the last successful login."),
481                 failed);
482         else
483             retval = asprintf(&line,
484                 /* TRANSLATORS: only used if dngettext is not supported */
485                 _("There were %d failed login attempts since the last successful login."),
486                 failed);
487 #endif
488         if (retval >= 0)
489                 retval = pam_info(pamh, "%s", line);
490         else {
491                 retval = PAM_BUF_ERR;
492                 line = NULL;
493         }
494     }
495
496 cleanup:
497     free(host);
498     free(line);
499     close(fd);
500     D(("all done with btmp"));
501
502     return retval;
503 }
504
505 /* --- authentication management functions (only) --- */
506
507 PAM_EXTERN int
508 pam_sm_open_session(pam_handle_t *pamh, int flags,
509                     int argc, const char **argv)
510 {
511     int retval, ctrl;
512     const void *user;
513     const struct passwd *pwd;
514     uid_t uid;
515     time_t lltime = 0;
516
517     /*
518      * this module gets the uid of the PAM_USER. Uses it to display
519      * last login info and then updates the lastlog for that user.
520      */
521
522     ctrl = _pam_parse(pamh, flags, argc, argv);
523
524     /* which user? */
525
526     retval = pam_get_item(pamh, PAM_USER, &user);
527     if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0') {
528         pam_syslog(pamh, LOG_NOTICE, "user unknown");
529         return PAM_USER_UNKNOWN;
530     }
531
532     /* what uid? */
533
534     pwd = pam_modutil_getpwnam (pamh, user);
535     if (pwd == NULL) {
536         D(("couldn't identify user %s", user));
537         return PAM_USER_UNKNOWN;
538     }
539     uid = pwd->pw_uid;
540     pwd = NULL;                                         /* tidy up */
541
542     /* process the current login attempt (indicate last) */
543
544     retval = last_login_date(pamh, ctrl, uid, user, &lltime);
545
546     if ((ctrl & LASTLOG_BTMP) && retval == PAM_SUCCESS) {
547             retval = last_login_failed(pamh, ctrl, user, lltime);
548     }
549
550     /* indicate success or failure */
551
552     uid = -1;                                           /* forget this */
553
554     return retval;
555 }
556
557 PAM_EXTERN int
558 pam_sm_close_session (pam_handle_t *pamh, int flags,
559                       int argc, const char **argv)
560 {
561     const char *terminal_line;
562
563     if (!(_pam_parse(pamh, flags, argc, argv) & LASTLOG_WTMP))
564         return PAM_SUCCESS;
565
566     terminal_line = get_tty(pamh);
567
568     /* Wipe out utmp logout entry */
569     logwtmp(terminal_line, "", "");
570
571     return PAM_SUCCESS;
572 }
573
574 #ifdef PAM_STATIC
575
576 /* static module data */
577
578 struct pam_module _pam_lastlog_modstruct = {
579      "pam_lastlog",
580      NULL,
581      NULL,
582      NULL,
583      pam_sm_open_session,
584      pam_sm_close_session,
585      NULL,
586 };
587
588 #endif
589
590 /* end of module definition */