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