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