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