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