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