]> granicus.if.org Git - linux-pam/blob - modules/pam_lastlog/pam_lastlog.c
1e7a3b4f5ea5edc0ccff9b4539e237729e95b9cd
[linux-pam] / modules / pam_lastlog / pam_lastlog.c
1 /* pam_lastlog module */
2
3 /*
4  * $Id$
5  *
6  * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/3/11
7  *
8  * This module does the necessary work to display the last login
9  * time+date for this user, it then updates this entry for the
10  * present (login) service.
11  */
12
13 #include <security/_pam_aconf.h>
14
15 #include <fcntl.h>
16 #include <time.h>
17 #ifdef HAVE_UTMP_H
18 # include <utmp.h>
19 #else
20 # include <lastlog.h>
21 #endif
22 #include <pwd.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <syslog.h>
29 #include <unistd.h>
30
31 #ifdef WANT_PWDB
32 #include <pwdb/pwdb_public.h>                /* use POSIX front end */
33 #endif
34
35 #if defined(hpux) || defined(sunos) || defined(solaris)
36 # ifndef _PATH_LASTLOG
37 #  define _PATH_LASTLOG "/usr/adm/lastlog"
38 # endif /* _PATH_LASTLOG */
39 # ifndef UT_HOSTSIZE
40 #  define UT_HOSTSIZE 16
41 # endif /* UT_HOSTSIZE */
42 # ifndef UT_LINESIZE
43 #  define UT_LINESIZE 12
44 # endif /* UT_LINESIZE */
45 #endif
46 #if defined(hpux)
47 struct lastlog {
48     time_t  ll_time;
49     char    ll_line[UT_LINESIZE];
50     char    ll_host[UT_HOSTSIZE];            /* same as in utmp */
51 };
52 #endif /* hpux */
53
54 /* XXX - time before ignoring lock. Is 1 sec enough? */
55 #define LASTLOG_IGNORE_LOCK_TIME     1
56
57 #define DEFAULT_HOST     ""  /* "[no.where]" */
58 #define DEFAULT_TERM     ""  /* "tt???" */
59 #define LASTLOG_NEVER_WELCOME       "Welcome to your new account!"
60 #define LASTLOG_INTRO    "Last login:"
61 #define LASTLOG_TIME     " %s"
62 #define _LASTLOG_HOST_FORMAT   " from %%.%ds"
63 #define _LASTLOG_LINE_FORMAT   " on %%.%ds"
64 #define LASTLOG_TAIL     ""
65 #define LASTLOG_MAXSIZE  (sizeof(LASTLOG_INTRO)+0 \
66                           +sizeof(LASTLOG_TIME)+strlen(the_time) \
67                           +sizeof(_LASTLOG_HOST_FORMAT)+UT_HOSTSIZE \
68                           +sizeof(_LASTLOG_LINE_FORMAT)+UT_LINESIZE \
69                           +sizeof(LASTLOG_TAIL))
70
71 /*
72  * here, we make a definition for the externally accessible function
73  * in this file (this definition is required for static a module
74  * but strongly encouraged generally) it is used to instruct the
75  * modules include file to define the function prototypes.
76  */
77
78 #define PAM_SM_SESSION
79
80 #include <security/pam_modules.h>
81 #include <security/_pam_macros.h>
82
83 /* some syslogging */
84
85 static void _log_err(int err, const char *format, ...)
86 {
87     va_list args;
88
89     va_start(args, format);
90     openlog("PAM-lastlog", LOG_CONS|LOG_PID, LOG_AUTH);
91     vsyslog(err, format, args);
92     va_end(args);
93     closelog();
94 }
95
96 /* argument parsing */
97
98 #define LASTLOG_DATE        01  /* display the date of the last login */
99 #define LASTLOG_HOST        02  /* display the last host used (if set) */
100 #define LASTLOG_LINE        04  /* display the last terminal used */
101 #define LASTLOG_NEVER      010  /* display a welcome message for first login */
102 #define LASTLOG_DEBUG      020  /* send info to syslog(3) */
103 #define LASTLOG_QUIET      040  /* keep quiet about things */
104
105 static int _pam_parse(int flags, int argc, const char **argv)
106 {
107     int ctrl=(LASTLOG_DATE|LASTLOG_HOST|LASTLOG_LINE);
108
109     /* does the appliction require quiet? */
110     if (flags & PAM_SILENT) {
111         ctrl |= LASTLOG_QUIET;
112     }
113
114     /* step through arguments */
115     for (; argc-- > 0; ++argv) {
116
117         /* generic options */
118
119         if (!strcmp(*argv,"debug")) {
120             ctrl |= LASTLOG_DEBUG;
121         } else if (!strcmp(*argv,"nodate")) {
122             ctrl |= ~LASTLOG_DATE;
123         } else if (!strcmp(*argv,"noterm")) {
124             ctrl |= ~LASTLOG_LINE;
125         } else if (!strcmp(*argv,"nohost")) {
126             ctrl |= ~LASTLOG_HOST;
127         } else if (!strcmp(*argv,"silent")) {
128             ctrl |= LASTLOG_QUIET;
129         } else if (!strcmp(*argv,"never")) {
130             ctrl |= LASTLOG_NEVER;
131         } else {
132             _log_err(LOG_ERR,"unknown option; %s",*argv);
133         }
134     }
135
136     D(("ctrl = %o", ctrl));
137     return ctrl;
138 }
139
140 /* a front end for conversations */
141
142 static int converse(pam_handle_t *pamh, int ctrl, int nargs
143                     , struct pam_message **message
144                     , struct pam_response **response)
145 {
146     int retval;
147     struct pam_conv *conv;
148
149     D(("begin to converse"));
150
151     retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; 
152     if ( retval == PAM_SUCCESS && conv) {
153
154         retval = conv->conv(nargs, ( const struct pam_message ** ) message
155                             , response, conv->appdata_ptr);
156
157         D(("returned from application's conversation function"));
158
159         if (retval != PAM_SUCCESS && (ctrl & LASTLOG_DEBUG) ) {
160             _log_err(LOG_DEBUG, "conversation failure [%s]"
161                      , pam_strerror(pamh, retval));
162         }
163
164     } else {
165         _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
166                  , pam_strerror(pamh, retval));
167         if (retval == PAM_SUCCESS)
168                 retval = PAM_BAD_ITEM; /* conv was NULL */
169     }
170
171     D(("ready to return from module conversation"));
172
173     return retval;                  /* propagate error status */
174 }
175
176 static int make_remark(pam_handle_t *pamh, int ctrl, const char *remark)
177 {
178     int retval;
179
180     if (!(ctrl & LASTLOG_QUIET)) {
181         struct pam_message msg[1], *mesg[1];
182         struct pam_response *resp=NULL;
183
184         mesg[0] = &msg[0];
185         msg[0].msg_style = PAM_TEXT_INFO;
186         msg[0].msg = remark;
187
188         retval = converse(pamh, ctrl, 1, mesg, &resp);
189
190         msg[0].msg = NULL;
191         if (resp) {
192             _pam_drop_reply(resp, 1);
193         }
194     } else {
195         D(("keeping quiet"));
196         retval = PAM_SUCCESS;
197     }
198
199     D(("returning %s", pam_strerror(pamh, retval)));
200     return retval;
201 }
202
203 /*
204  * Values for the announce flags..
205  */
206
207 static int last_login_date(pam_handle_t *pamh, int announce, uid_t uid)
208 {
209     struct flock last_lock;
210     struct lastlog last_login;
211     int retval = PAM_SESSION_ERR;
212     int last_fd;
213
214     /* obtain the last login date and all the relevant info */
215     last_fd = open(_PATH_LASTLOG, O_RDWR);
216     if (last_fd < 0) {
217         D(("unable to open the %s file", _PATH_LASTLOG));
218         if (announce & LASTLOG_DEBUG) {
219             _log_err(LOG_DEBUG, "unable to open %s file", _PATH_LASTLOG);
220         }
221         retval = PAM_PERM_DENIED;
222     } else {
223         int win;
224
225         /* read the lastlogin file - for this uid */
226         (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET);
227
228         memset(&last_lock, 0, sizeof(last_lock));
229         last_lock.l_type = F_RDLCK;
230         last_lock.l_whence = SEEK_SET;
231         last_lock.l_start = sizeof(last_login) * (off_t) uid;
232         last_lock.l_len = sizeof(last_login);
233
234         if ( fcntl(last_fd, F_SETLK, &last_lock) < 0 ) {
235             D(("locking %s failed..(waiting a little)", _PATH_LASTLOG));
236             _log_err(LOG_ALERT, "%s file is locked/read", _PATH_LASTLOG);
237             sleep(LASTLOG_IGNORE_LOCK_TIME);
238         }
239
240         win = ( _pammodutil_read(last_fd, &last_login, sizeof(last_login))
241                 == sizeof(last_login) );
242
243         last_lock.l_type = F_UNLCK;
244         (void) fcntl(last_fd, F_SETLK, &last_lock);        /* unlock */
245
246         if (!win) {
247             D(("First login for user uid=%d", _PATH_LASTLOG, uid));
248             if (announce & LASTLOG_DEBUG) {
249                 _log_err(LOG_DEBUG, "creating lastlog for uid %d", uid);
250             }
251             memset(&last_login, 0, sizeof(last_login));
252         }
253
254         /* rewind */
255         (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET);
256
257         if (!(announce & LASTLOG_QUIET)) {
258             if (last_login.ll_time) {
259                 time_t ll_time;
260                 char *the_time;
261                 char *remark;
262
263                 ll_time = last_login.ll_time;
264                 the_time = ctime(&ll_time);
265                 the_time[-1+strlen(the_time)] = '\0';    /* delete '\n' */
266
267                 remark = malloc(LASTLOG_MAXSIZE);
268                 if (remark == NULL) {
269                     D(("no memory for last login remark"));
270                     retval = PAM_BUF_ERR;
271                 } else {
272                     int at;
273
274                     /* printing prefix */
275                     at = sprintf(remark, "%s", LASTLOG_INTRO);
276
277                     /* we want the date? */
278                     if (announce & LASTLOG_DATE) {
279                         at += sprintf(remark+at, LASTLOG_TIME, the_time);
280                     }
281
282                     /* we want & have the host? */
283                     if ((announce & LASTLOG_HOST)
284                         && (last_login.ll_host[0] != '\0')) {
285                         char format[2*sizeof(_LASTLOG_HOST_FORMAT)];
286
287                         (void) sprintf(format, _LASTLOG_HOST_FORMAT
288                                        , UT_HOSTSIZE);
289                         D(("format: %s", format));
290                         at += sprintf(remark+at, format, last_login.ll_host);
291                         _pam_overwrite(format);
292                     }
293
294                     /* we want and have the terminal? */
295                     if ((announce & LASTLOG_LINE)
296                         && (last_login.ll_line[0] != '\0')) {
297                         char format[2*sizeof(_LASTLOG_LINE_FORMAT)];
298
299                         (void) sprintf(format, _LASTLOG_LINE_FORMAT
300                                        , UT_LINESIZE);
301                         D(("format: %s", format));
302                         at += sprintf(remark+at, format, last_login.ll_line);
303                         _pam_overwrite(format);
304                     }
305
306                     /* display requested combo */
307                     sprintf(remark+at, "%s", LASTLOG_TAIL);
308
309                     retval = make_remark(pamh, announce, remark);
310
311                     /* free all the stuff malloced */
312                     _pam_overwrite(remark);
313                     _pam_drop(remark);
314                 }
315             } else if ((!last_login.ll_time) && (announce & LASTLOG_NEVER)) {
316                 D(("this is the first time this user has logged in"));
317                 retval = make_remark(pamh, announce, LASTLOG_NEVER_WELCOME);
318             }
319         } else {
320             D(("no text was requested"));
321             retval = PAM_SUCCESS;
322         }
323
324         /* write latest value */
325         {
326             time_t ll_time;
327             const char *remote_host=NULL
328                 , *terminal_line=DEFAULT_TERM;
329
330             /* set this login date */
331             D(("set the most recent login time"));
332
333             (void) time(&ll_time);    /* set the time */
334             last_login.ll_time = ll_time;
335
336             /* set the remote host */
337             (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host);
338             if (remote_host == NULL) {
339                 remote_host = DEFAULT_HOST;
340             }
341
342             /* copy to last_login */
343             strncpy(last_login.ll_host, remote_host,
344                     sizeof(last_login.ll_host));
345             last_login.ll_host[sizeof(last_login.ll_host) - 1] = '\0';
346             remote_host = NULL;
347
348             /* set the terminal line */
349             (void) pam_get_item(pamh, PAM_TTY, (const void **)&terminal_line);
350             D(("terminal = %s", terminal_line));
351             if (terminal_line == NULL) {
352                 terminal_line = DEFAULT_TERM;
353             } else if ( !strncmp("/dev/", terminal_line, 5) ) {
354                 /* strip leading "/dev/" from tty.. */
355                 terminal_line += 5;
356             }
357             D(("terminal = %s", terminal_line));
358
359             /* copy to last_login */
360             strncpy(last_login.ll_line, terminal_line,
361                     sizeof(last_login.ll_line));
362             last_login.ll_host[sizeof(last_login.ll_host) - 1] = '\0';
363             terminal_line = NULL;
364
365             D(("locking last_log file"));
366
367             /* now we try to lock this file-record exclusively; non-blocking */
368             memset(&last_lock, 0, sizeof(last_lock));
369             last_lock.l_type = F_WRLCK;
370             last_lock.l_whence = SEEK_SET;
371             last_lock.l_start = sizeof(last_login) * (off_t) uid;
372             last_lock.l_len = sizeof(last_login);
373
374             if ( fcntl(last_fd, F_SETLK, &last_lock) < 0 ) {
375                 D(("locking %s failed..(waiting a little)", _PATH_LASTLOG));
376                 _log_err(LOG_ALERT, "%s file is locked/write", _PATH_LASTLOG);
377                 sleep(LASTLOG_IGNORE_LOCK_TIME);
378             }
379
380             D(("writing to the last_log file"));
381             (void) _pammodutil_write(last_fd, &last_login, sizeof(last_login));
382
383             last_lock.l_type = F_UNLCK;
384             (void) fcntl(last_fd, F_SETLK, &last_lock);        /* unlock */
385             D(("unlocked"));
386
387             close(last_fd);                                  /* all done */
388         }
389         D(("all done with last login"));
390     }
391
392     /* reset the last login structure */
393     memset(&last_login, 0, sizeof(last_login));
394
395     return retval;
396 }
397
398 /* --- authentication management functions (only) --- */
399
400 PAM_EXTERN
401 int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc
402                         , const char **argv)
403 {
404     int retval, ctrl;
405     const char *user;
406     const struct passwd *pwd;
407     uid_t uid;
408
409     /*
410      * this module gets the uid of the PAM_USER. Uses it to display
411      * last login info and then updates the lastlog for that user.
412      */
413
414     ctrl = _pam_parse(flags, argc, argv);
415
416     /* which user? */
417
418     retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
419     if (retval != PAM_SUCCESS || user == NULL || *user == '\0') {
420         _log_err(LOG_NOTICE, "user unknown");
421         return PAM_USER_UNKNOWN;
422     }
423
424     /* what uid? */
425
426     pwd = getpwnam(user);
427     if (pwd == NULL) {
428         D(("couldn't identify user %s", user));
429         return PAM_CRED_INSUFFICIENT;
430     }
431     uid = pwd->pw_uid;
432     pwd = NULL;                                         /* tidy up */
433
434     /* process the current login attempt (indicate last) */
435
436     retval = last_login_date(pamh, ctrl, uid);
437
438     /* indicate success or failure */
439
440     uid = -1;                                           /* forget this */
441
442     return retval;
443 }
444
445 PAM_EXTERN
446 int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
447                          ,const char **argv)
448 {
449     return PAM_SUCCESS;
450 }
451
452 #ifdef PAM_STATIC
453
454 /* static module data */
455
456 struct pam_module _pam_lastlog_modstruct = {
457      "pam_lastlog",
458      NULL,
459      NULL,
460      NULL,
461      pam_sm_open_session,
462      pam_sm_close_session,
463      NULL,
464 };
465
466 #endif
467
468 /* end of module definition */