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