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