1 /* pam_lastlog module */
6 * Written by Andrew Morgan <morgan@linux.kernel.org> 1996/3/11
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.
13 #include <security/_pam_aconf.h>
27 #include <sys/types.h>
32 #include <pwdb/pwdb_public.h> /* use POSIX front end */
35 #if defined(hpux) || defined(sunos) || defined(solaris)
36 # ifndef _PATH_LASTLOG
37 # define _PATH_LASTLOG "/usr/adm/lastlog"
38 # endif /* _PATH_LASTLOG */
40 # define UT_HOSTSIZE 16
41 # endif /* UT_HOSTSIZE */
43 # define UT_LINESIZE 12
44 # endif /* UT_LINESIZE */
49 char ll_line[UT_LINESIZE];
50 char ll_host[UT_HOSTSIZE]; /* same as in utmp */
54 /* XXX - time before ignoring lock. Is 1 sec enough? */
55 #define LASTLOG_IGNORE_LOCK_TIME 1
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))
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.
78 #define PAM_SM_SESSION
80 #include <security/pam_modules.h>
81 #include <security/_pam_macros.h>
82 #include <security/_pam_modutil.h>
86 static void _log_err(int err, const char *format, ...)
90 va_start(args, format);
91 openlog("PAM-lastlog", LOG_CONS|LOG_PID, LOG_AUTH);
92 vsyslog(err, format, args);
97 /* argument parsing */
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 */
106 static int _pam_parse(int flags, int argc, const char **argv)
108 int ctrl=(LASTLOG_DATE|LASTLOG_HOST|LASTLOG_LINE);
110 /* does the appliction require quiet? */
111 if (flags & PAM_SILENT) {
112 ctrl |= LASTLOG_QUIET;
115 /* step through arguments */
116 for (; argc-- > 0; ++argv) {
118 /* generic options */
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;
133 _log_err(LOG_ERR,"unknown option; %s",*argv);
137 D(("ctrl = %o", ctrl));
141 /* a front end for conversations */
143 static int converse(pam_handle_t *pamh, int ctrl, int nargs
144 , struct pam_message **message
145 , struct pam_response **response)
148 struct pam_conv *conv;
150 D(("begin to converse"));
152 retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ;
153 if ( retval == PAM_SUCCESS && conv) {
155 retval = conv->conv(nargs, ( const struct pam_message ** ) message
156 , response, conv->appdata_ptr);
158 D(("returned from application's conversation function"));
160 if (retval != PAM_SUCCESS && (ctrl & LASTLOG_DEBUG) ) {
161 _log_err(LOG_DEBUG, "conversation failure [%s]"
162 , pam_strerror(pamh, retval));
166 _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
167 , pam_strerror(pamh, retval));
168 if (retval == PAM_SUCCESS)
169 retval = PAM_BAD_ITEM; /* conv was NULL */
172 D(("ready to return from module conversation"));
174 return retval; /* propagate error status */
177 static int make_remark(pam_handle_t *pamh, int ctrl, const char *remark)
181 if (!(ctrl & LASTLOG_QUIET)) {
182 struct pam_message msg[1], *mesg[1];
183 struct pam_response *resp=NULL;
186 msg[0].msg_style = PAM_TEXT_INFO;
189 retval = converse(pamh, ctrl, 1, mesg, &resp);
193 _pam_drop_reply(resp, 1);
196 D(("keeping quiet"));
197 retval = PAM_SUCCESS;
200 D(("returning %s", pam_strerror(pamh, retval)));
205 * Values for the announce flags..
208 static int last_login_date(pam_handle_t *pamh, int announce, uid_t uid)
210 struct flock last_lock;
211 struct lastlog last_login;
212 int retval = PAM_SESSION_ERR;
215 /* obtain the last login date and all the relevant info */
216 last_fd = open(_PATH_LASTLOG, O_RDWR);
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);
222 retval = PAM_PERM_DENIED;
226 /* read the lastlogin file - for this uid */
227 (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET);
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);
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);
241 win = (_pammodutil_read (last_fd, (char *) &last_login,
242 sizeof(last_login)) == sizeof(last_login));
244 last_lock.l_type = F_UNLCK;
245 (void) fcntl(last_fd, F_SETLK, &last_lock); /* unlock */
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);
252 memset(&last_login, 0, sizeof(last_login));
256 (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET);
258 if (!(announce & LASTLOG_QUIET)) {
259 if (last_login.ll_time) {
264 ll_time = last_login.ll_time;
265 the_time = ctime(&ll_time);
266 the_time[-1+strlen(the_time)] = '\0'; /* delete '\n' */
268 remark = malloc(LASTLOG_MAXSIZE);
269 if (remark == NULL) {
270 D(("no memory for last login remark"));
271 retval = PAM_BUF_ERR;
275 /* printing prefix */
276 at = sprintf(remark, "%s", LASTLOG_INTRO);
278 /* we want the date? */
279 if (announce & LASTLOG_DATE) {
280 at += sprintf(remark+at, LASTLOG_TIME, the_time);
283 /* we want & have the host? */
284 if ((announce & LASTLOG_HOST)
285 && (last_login.ll_host[0] != '\0')) {
286 char format[2*sizeof(_LASTLOG_HOST_FORMAT)];
288 (void) sprintf(format, _LASTLOG_HOST_FORMAT
290 D(("format: %s", format));
291 at += sprintf(remark+at, format, last_login.ll_host);
292 _pam_overwrite(format);
295 /* we want and have the terminal? */
296 if ((announce & LASTLOG_LINE)
297 && (last_login.ll_line[0] != '\0')) {
298 char format[2*sizeof(_LASTLOG_LINE_FORMAT)];
300 (void) sprintf(format, _LASTLOG_LINE_FORMAT
302 D(("format: %s", format));
303 at += sprintf(remark+at, format, last_login.ll_line);
304 _pam_overwrite(format);
307 /* display requested combo */
308 sprintf(remark+at, "%s", LASTLOG_TAIL);
310 retval = make_remark(pamh, announce, remark);
312 /* free all the stuff malloced */
313 _pam_overwrite(remark);
316 } else if ((!last_login.ll_time) && (announce & LASTLOG_NEVER)) {
317 D(("this is the first time this user has logged in"));
318 retval = make_remark(pamh, announce, LASTLOG_NEVER_WELCOME);
321 D(("no text was requested"));
322 retval = PAM_SUCCESS;
325 /* write latest value */
328 const char *remote_host=NULL
329 , *terminal_line=DEFAULT_TERM;
331 /* set this login date */
332 D(("set the most recent login time"));
334 (void) time(&ll_time); /* set the time */
335 last_login.ll_time = ll_time;
337 /* set the remote host */
338 (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host);
339 if (remote_host == NULL) {
340 remote_host = DEFAULT_HOST;
343 /* copy to last_login */
344 strncpy(last_login.ll_host, remote_host,
345 sizeof(last_login.ll_host));
346 last_login.ll_host[sizeof(last_login.ll_host) - 1] = '\0';
349 /* set the terminal line */
350 (void) pam_get_item(pamh, PAM_TTY, (const void **)&terminal_line);
351 D(("terminal = %s", terminal_line));
352 if (terminal_line == NULL) {
353 terminal_line = DEFAULT_TERM;
354 } else if ( !strncmp("/dev/", terminal_line, 5) ) {
355 /* strip leading "/dev/" from tty.. */
358 D(("terminal = %s", terminal_line));
360 /* copy to last_login */
361 strncpy(last_login.ll_line, terminal_line,
362 sizeof(last_login.ll_line));
363 last_login.ll_host[sizeof(last_login.ll_host) - 1] = '\0';
364 terminal_line = NULL;
366 D(("locking last_log file"));
368 /* now we try to lock this file-record exclusively; non-blocking */
369 memset(&last_lock, 0, sizeof(last_lock));
370 last_lock.l_type = F_WRLCK;
371 last_lock.l_whence = SEEK_SET;
372 last_lock.l_start = sizeof(last_login) * (off_t) uid;
373 last_lock.l_len = sizeof(last_login);
375 if ( fcntl(last_fd, F_SETLK, &last_lock) < 0 ) {
376 D(("locking %s failed..(waiting a little)", _PATH_LASTLOG));
377 _log_err(LOG_ALERT, "%s file is locked/write", _PATH_LASTLOG);
378 sleep(LASTLOG_IGNORE_LOCK_TIME);
381 D(("writing to the last_log file"));
382 _pammodutil_write (last_fd, (char *) &last_login,
383 sizeof (last_login));
385 last_lock.l_type = F_UNLCK;
386 (void) fcntl(last_fd, F_SETLK, &last_lock); /* unlock */
389 close(last_fd); /* all done */
391 D(("all done with last login"));
394 /* reset the last login structure */
395 memset(&last_login, 0, sizeof(last_login));
400 /* --- authentication management functions (only) --- */
403 int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc
408 const struct passwd *pwd;
412 * this module gets the uid of the PAM_USER. Uses it to display
413 * last login info and then updates the lastlog for that user.
416 ctrl = _pam_parse(flags, argc, argv);
420 retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
421 if (retval != PAM_SUCCESS || user == NULL || *user == '\0') {
422 _log_err(LOG_NOTICE, "user unknown");
423 return PAM_USER_UNKNOWN;
428 pwd = _pammodutil_getpwnam (pamh, user);
430 D(("couldn't identify user %s", user));
431 return PAM_CRED_INSUFFICIENT;
434 pwd = NULL; /* tidy up */
436 /* process the current login attempt (indicate last) */
438 retval = last_login_date(pamh, ctrl, uid);
440 /* indicate success or failure */
442 uid = -1; /* forget this */
448 int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
456 /* static module data */
458 struct pam_module _pam_lastlog_modstruct = {
464 pam_sm_close_session,
470 /* end of module definition */