xref: /freebsd/usr.bin/last/last.c (revision 984485a02eb3e63b4170dd911b72de38b35b2289)
1 /*
2  * Copyright (c) 1987, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1987, 1993, 1994\n\
37 	The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39 
40 #ifndef lint
41 static const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
42 #endif /* not lint */
43 #include <sys/cdefs.h>
44 __FBSDID("$FreeBSD$");
45 
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <langinfo.h>
53 #include <locale.h>
54 #include <paths.h>
55 #include <signal.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <time.h>
60 #include <timeconv.h>
61 #include <unistd.h>
62 #include <utmpx.h>
63 #include <sys/queue.h>
64 
65 #define	NO	0				/* false/no */
66 #define	YES	1				/* true/yes */
67 #define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
68 
69 typedef struct arg {
70 	char	*name;				/* argument */
71 #define	HOST_TYPE	-2
72 #define	TTY_TYPE	-3
73 #define	USER_TYPE	-4
74 	int	type;				/* type of arg */
75 	struct arg	*next;			/* linked list pointer */
76 } ARG;
77 ARG	*arglist;				/* head of linked list */
78 
79 LIST_HEAD(idlisthead, idtab) idlist;
80 
81 struct idtab {
82 	time_t	logout;				/* log out time */
83 	char	id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
84 	LIST_ENTRY(idtab) list;
85 };
86 
87 static const	char *crmsg;			/* cause of last reboot */
88 static time_t	currentout;			/* current logout value */
89 static long	maxrec;				/* records to display */
90 static const	char *file = NULL;		/* wtmp file */
91 static int	sflag = 0;			/* show delta in seconds */
92 static int	width = 5;			/* show seconds in delta */
93 static int	yflag;				/* show year */
94 static int      d_first;
95 static int	snapfound = 0;			/* found snapshot entry? */
96 static time_t	snaptime;			/* if != 0, we will only
97 						 * report users logged in
98 						 * at this snapshot time
99 						 */
100 
101 void	 addarg(int, char *);
102 time_t	 dateconv(char *);
103 void	 doentry(struct utmpx *);
104 void	 hostconv(char *);
105 void	 printentry(struct utmpx *, struct idtab *);
106 char	*ttyconv(char *);
107 int	 want(struct utmpx *);
108 void	 usage(void);
109 void	 wtmp(void);
110 
111 void
112 usage(void)
113 {
114 	(void)fprintf(stderr,
115 "usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
116 "            [-n maxrec] [-t tty] [user ...]\n");
117 	exit(1);
118 }
119 
120 int
121 main(int argc, char *argv[])
122 {
123 	int ch;
124 	char *p;
125 
126 	(void) setlocale(LC_TIME, "");
127 	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
128 
129 	maxrec = -1;
130 	snaptime = 0;
131 	while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
132 		switch (ch) {
133 		case '0': case '1': case '2': case '3': case '4':
134 		case '5': case '6': case '7': case '8': case '9':
135 			/*
136 			 * kludge: last was originally designed to take
137 			 * a number after a dash.
138 			 */
139 			if (maxrec == -1) {
140 				p = strchr(argv[optind - 1], ch);
141 				if (p == NULL)
142 					p = strchr(argv[optind], ch);
143 				maxrec = atol(p);
144 				if (!maxrec)
145 					exit(0);
146 			}
147 			break;
148 		case 'd':
149 			snaptime = dateconv(optarg);
150 			break;
151 		case 'f':
152 			file = optarg;
153 			break;
154 		case 'h':
155 			hostconv(optarg);
156 			addarg(HOST_TYPE, optarg);
157 			break;
158 		case 'n':
159 			errno = 0;
160 			maxrec = strtol(optarg, &p, 10);
161 			if (p == optarg || *p != '\0' || errno != 0 ||
162 			    maxrec <= 0)
163 				errx(1, "%s: bad line count", optarg);
164 			break;
165 		case 's':
166 			sflag++;	/* Show delta as seconds */
167 			break;
168 		case 't':
169 			addarg(TTY_TYPE, ttyconv(optarg));
170 			break;
171 		case 'w':
172 			width = 8;
173 			break;
174 		case 'y':
175 			yflag++;
176 			break;
177 		case '?':
178 		default:
179 			usage();
180 		}
181 
182 	if (sflag && width == 8) usage();
183 
184 	if (argc) {
185 		setlinebuf(stdout);
186 		for (argv += optind; *argv; ++argv) {
187 #define	COMPATIBILITY
188 #ifdef	COMPATIBILITY
189 			/* code to allow "last p5" to work */
190 			addarg(TTY_TYPE, ttyconv(*argv));
191 #endif
192 			addarg(USER_TYPE, *argv);
193 		}
194 	}
195 	wtmp();
196 	exit(0);
197 }
198 
199 #define	MAXUTXENTRIES	1024
200 
201 /*
202  * wtmp --
203  *	read through the wtmp file
204  */
205 void
206 wtmp(void)
207 {
208 	struct utmpx buf[MAXUTXENTRIES];
209 	struct utmpx *ut;
210 	static unsigned int first = 0, amount = 0;
211 	time_t t;
212 	char ct[80];
213 	struct tm *tm;
214 
215 	LIST_INIT(&idlist);
216 	(void)time(&t);
217 
218 	/* Load the last entries from the file. */
219 	if (setutxdb(UTXDB_LOG, file) != 0)
220 		err(1, "%s", file);
221 	while ((ut = getutxent()) != NULL) {
222 		memcpy(&buf[(first + amount) % MAXUTXENTRIES], ut, sizeof *ut);
223 		if (amount == MAXUTXENTRIES)
224 			first++;
225 		else
226 			amount++;
227 		if (t > ut->ut_tv.tv_sec)
228 			t = ut->ut_tv.tv_sec;
229 	}
230 	endutxent();
231 
232 	/* Display them in reverse order. */
233 	while (amount > 0)
234 		doentry(&buf[(first + amount--) % MAXUTXENTRIES]);
235 
236 	tm = localtime(&t);
237 	(void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
238 	printf("%s", ct);
239 }
240 
241 /*
242  * doentry --
243  *	process a single wtmp entry
244  */
245 void
246 doentry(struct utmpx *bp)
247 {
248 	struct idtab	*tt, *ttx;		/* idlist entry */
249 
250 	/* the machine stopped */
251 	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
252 		/* everybody just logged out */
253 		for (tt = LIST_FIRST(&idlist); tt;) {
254 			LIST_REMOVE(tt, list);
255 			ttx = tt;
256 			tt = LIST_NEXT(tt, list);
257 			free(ttx);
258 		}
259 		currentout = -bp->ut_tv.tv_sec;
260 		crmsg = bp->ut_type != SHUTDOWN_TIME ?
261 		    "crash" : "shutdown";
262 		/*
263 		 * if we're in snapshot mode, we want to exit if this
264 		 * shutdown/reboot appears while we we are tracking the
265 		 * active range
266 		 */
267 		if (snaptime && snapfound)
268 			exit(0);
269 		/*
270 		 * don't print shutdown/reboot entries unless flagged for
271 		 */
272 		if (!snaptime && want(bp))
273 			printentry(bp, NULL);
274 		return;
275 	}
276 	/* date got set */
277 	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
278 		if (want(bp) && !snaptime)
279 			printentry(bp, NULL);
280 		return;
281 	}
282 
283 	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
284 		return;
285 
286 	/* find associated identifier */
287 	LIST_FOREACH(tt, &idlist, list)
288 	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
289 		    break;
290 
291 	if (tt == NULL) {
292 		/* add new one */
293 		tt = malloc(sizeof(struct idtab));
294 		if (tt == NULL)
295 			errx(1, "malloc failure");
296 		tt->logout = currentout;
297 		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
298 		LIST_INSERT_HEAD(&idlist, tt, list);
299 	}
300 
301 	/*
302 	 * print record if not in snapshot mode and wanted
303 	 * or in snapshot mode and in snapshot range
304 	 */
305 	if (bp->ut_type == USER_PROCESS && (want(bp) ||
306 	    (bp->ut_tv.tv_sec < snaptime &&
307 	    (tt->logout > snaptime || tt->logout < 1)))) {
308 		snapfound = 1;
309 		printentry(bp, tt);
310 	}
311 	tt->logout = bp->ut_tv.tv_sec;
312 }
313 
314 /*
315  * printentry --
316  *	output an entry
317  *
318  * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
319  * logout type (crash/shutdown) as appropriate.
320  */
321 void
322 printentry(struct utmpx *bp, struct idtab *tt)
323 {
324 	char ct[80];
325 	struct tm *tm;
326 	time_t	delta;				/* time difference */
327 	time_t	t;
328 
329 	if (maxrec != -1 && !maxrec--)
330 		exit(0);
331 	t = bp->ut_tv.tv_sec;
332 	tm = localtime(&t);
333 	(void) strftime(ct, sizeof(ct), d_first ?
334 	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
335 	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
336 	switch (bp->ut_type) {
337 	case BOOT_TIME:
338 		printf("%-42s", "boot time");
339 		break;
340 	case SHUTDOWN_TIME:
341 		printf("%-42s", "shutdown time");
342 		break;
343 	case OLD_TIME:
344 		printf("%-42s", "old time");
345 		break;
346 	case NEW_TIME:
347 		printf("%-42s", "new time");
348 		break;
349 	case USER_PROCESS:
350 		printf("%-10s %-8s %-22.22s",
351 		    bp->ut_user, bp->ut_line, bp->ut_host);
352 		break;
353 	}
354 	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
355 	if (tt == NULL)
356 		return;
357 	if (!tt->logout) {
358 		puts("  still logged in");
359 		return;
360 	}
361 	if (tt->logout < 0) {
362 		tt->logout = -tt->logout;
363 		printf("- %s", crmsg);
364 	} else {
365 		tm = localtime(&tt->logout);
366 		(void) strftime(ct, sizeof(ct), "%R", tm);
367 		printf("- %s", ct);
368 	}
369 	delta = tt->logout - bp->ut_tv.tv_sec;
370 	if (sflag) {
371 		printf("  (%8ld)\n", (long)delta);
372 	} else {
373 		tm = gmtime(&delta);
374 		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
375 		if (delta < 86400)
376 			printf("  (%s)\n", ct);
377 		else
378 			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
379 	}
380 }
381 
382 /*
383  * want --
384  *	see if want this entry
385  */
386 int
387 want(struct utmpx *bp)
388 {
389 	ARG *step;
390 
391 	if (snaptime)
392 		return (NO);
393 
394 	if (!arglist)
395 		return (YES);
396 
397 	for (step = arglist; step; step = step->next)
398 		switch(step->type) {
399 		case HOST_TYPE:
400 			if (!strcasecmp(step->name, bp->ut_host))
401 				return (YES);
402 			break;
403 		case TTY_TYPE:
404 			if (!strcmp(step->name, bp->ut_line))
405 				return (YES);
406 			break;
407 		case USER_TYPE:
408 			if (!strcmp(step->name, bp->ut_user))
409 				return (YES);
410 			break;
411 		}
412 	return (NO);
413 }
414 
415 /*
416  * addarg --
417  *	add an entry to a linked list of arguments
418  */
419 void
420 addarg(int type, char *arg)
421 {
422 	ARG *cur;
423 
424 	if ((cur = malloc(sizeof(ARG))) == NULL)
425 		errx(1, "malloc failure");
426 	cur->next = arglist;
427 	cur->type = type;
428 	cur->name = arg;
429 	arglist = cur;
430 }
431 
432 /*
433  * hostconv --
434  *	convert the hostname to search pattern; if the supplied host name
435  *	has a domain attached that is the same as the current domain, rip
436  *	off the domain suffix since that's what login(1) does.
437  */
438 void
439 hostconv(char *arg)
440 {
441 	static int first = 1;
442 	static char *hostdot, name[MAXHOSTNAMELEN];
443 	char *argdot;
444 
445 	if (!(argdot = strchr(arg, '.')))
446 		return;
447 	if (first) {
448 		first = 0;
449 		if (gethostname(name, sizeof(name)))
450 			err(1, "gethostname");
451 		hostdot = strchr(name, '.');
452 	}
453 	if (hostdot && !strcasecmp(hostdot, argdot))
454 		*argdot = '\0';
455 }
456 
457 /*
458  * ttyconv --
459  *	convert tty to correct name.
460  */
461 char *
462 ttyconv(char *arg)
463 {
464 	char *mval;
465 
466 	/*
467 	 * kludge -- we assume that all tty's end with
468 	 * a two character suffix.
469 	 */
470 	if (strlen(arg) == 2) {
471 		/* either 6 for "ttyxx" or 8 for "console" */
472 		if ((mval = malloc(8)) == NULL)
473 			errx(1, "malloc failure");
474 		if (!strcmp(arg, "co"))
475 			(void)strcpy(mval, "console");
476 		else {
477 			(void)strcpy(mval, "tty");
478 			(void)strcpy(mval + 3, arg);
479 		}
480 		return (mval);
481 	}
482 	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
483 		return (arg + 5);
484 	return (arg);
485 }
486 
487 /*
488  * dateconv --
489  * 	Convert the snapshot time in command line given in the format
490  * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
491  * 	Derived from atime_arg1() in usr.bin/touch/touch.c
492  */
493 time_t
494 dateconv(char *arg)
495 {
496         time_t timet;
497         struct tm *t;
498         int yearset;
499         char *p;
500 
501         /* Start with the current time. */
502         if (time(&timet) < 0)
503                 err(1, "time");
504         if ((t = localtime(&timet)) == NULL)
505                 err(1, "localtime");
506 
507         /* [[CC]YY]MMDDhhmm[.SS] */
508         if ((p = strchr(arg, '.')) == NULL)
509                 t->tm_sec = 0; 		/* Seconds defaults to 0. */
510         else {
511                 if (strlen(p + 1) != 2)
512                         goto terr;
513                 *p++ = '\0';
514                 t->tm_sec = ATOI2(p);
515         }
516 
517         yearset = 0;
518         switch (strlen(arg)) {
519         case 12:                	/* CCYYMMDDhhmm */
520                 t->tm_year = ATOI2(arg);
521                 t->tm_year *= 100;
522                 yearset = 1;
523                 /* FALLTHROUGH */
524         case 10:                	/* YYMMDDhhmm */
525                 if (yearset) {
526                         yearset = ATOI2(arg);
527                         t->tm_year += yearset;
528                 } else {
529                         yearset = ATOI2(arg);
530                         if (yearset < 69)
531                                 t->tm_year = yearset + 2000;
532                         else
533                                 t->tm_year = yearset + 1900;
534                 }
535                 t->tm_year -= 1900;     /* Convert to UNIX time. */
536                 /* FALLTHROUGH */
537         case 8:				/* MMDDhhmm */
538                 t->tm_mon = ATOI2(arg);
539                 --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
540                 t->tm_mday = ATOI2(arg);
541                 t->tm_hour = ATOI2(arg);
542                 t->tm_min = ATOI2(arg);
543                 break;
544         case 4:				/* hhmm */
545                 t->tm_hour = ATOI2(arg);
546                 t->tm_min = ATOI2(arg);
547                 break;
548         default:
549                 goto terr;
550         }
551         t->tm_isdst = -1;       	/* Figure out DST. */
552         timet = mktime(t);
553         if (timet == -1)
554 terr:           errx(1,
555         "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
556         return timet;
557 }
558