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