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