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