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