xref: /freebsd/usr.bin/last/last.c (revision 7c1b51d6dc2e165ae7333373513b080f17cf79bd)
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. 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
31 static 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
37 static const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
41 
42 #include <sys/param.h>
43 #include <sys/capsicum.h>
44 #include <sys/queue.h>
45 #include <sys/stat.h>
46 
47 #include <capsicum_helpers.h>
48 #include <err.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <langinfo.h>
52 #include <locale.h>
53 #include <paths.h>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <timeconv.h>
60 #include <unistd.h>
61 #include <utmpx.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 
67 typedef 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;
76 static ARG	*arglist;			/* head of linked list */
77 
78 static SLIST_HEAD(, idtab) idlist;
79 
80 struct 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 
86 static const	char *crmsg;			/* cause of last reboot */
87 static time_t	currentout;			/* current logout value */
88 static long	maxrec;				/* records to display */
89 static const	char *file = NULL;		/* utx.log file */
90 static int	sflag = 0;			/* show delta in seconds */
91 static int	width = 5;			/* show seconds in delta */
92 static int	yflag;				/* show year */
93 static int      d_first;
94 static int	snapfound = 0;			/* found snapshot entry? */
95 static time_t	snaptime;			/* if != 0, we will only
96 						 * report users logged in
97 						 * at this snapshot time
98 						 */
99 
100 static void	 addarg(int, char *);
101 static time_t	 dateconv(char *);
102 static void	 doentry(struct utmpx *);
103 static void	 hostconv(char *);
104 static void	 printentry(struct utmpx *, struct idtab *);
105 static char	*ttyconv(char *);
106 static int	 want(struct utmpx *);
107 static void	 usage(void);
108 static void	 wtmp(void);
109 
110 static void
111 usage(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 
119 int
120 main(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 (caph_limit_stdio() < 0)
182 		err(1, "can't limit stdio rights");
183 
184 	caph_cache_catpages();
185 	caph_cache_tzdata();
186 
187 	/* Cache UTX database. */
188 	if (setutxdb(UTXDB_LOG, file) != 0)
189 		err(1, "%s", file != NULL ? file : "(default utx db)");
190 
191 	if (cap_enter() < 0 && errno != ENOSYS)
192 		err(1, "cap_enter");
193 
194 	if (sflag && width == 8) usage();
195 
196 	if (argc) {
197 		setlinebuf(stdout);
198 		for (argv += optind; *argv; ++argv) {
199 			if (strcmp(*argv, "reboot") == 0)
200 				addarg(REBOOT_TYPE, *argv);
201 #define	COMPATIBILITY
202 #ifdef	COMPATIBILITY
203 			/* code to allow "last p5" to work */
204 			addarg(TTY_TYPE, ttyconv(*argv));
205 #endif
206 			addarg(USER_TYPE, *argv);
207 		}
208 	}
209 	wtmp();
210 	exit(0);
211 }
212 
213 /*
214  * wtmp --
215  *	read through the utx.log file
216  */
217 static void
218 wtmp(void)
219 {
220 	struct utmpx *buf = NULL;
221 	struct utmpx *ut;
222 	static unsigned int amount = 0;
223 	time_t t;
224 	char ct[80];
225 	struct tm *tm;
226 
227 	SLIST_INIT(&idlist);
228 	(void)time(&t);
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 				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 	while (amount > 0)
245 		doentry(&buf[--amount]);
246 	free(buf);
247 	tm = localtime(&t);
248 	(void) strftime(ct, sizeof(ct), "%+", tm);
249 	printf("\n%s begins %s\n", ((file == NULL) ? "utx.log" : file), ct);
250 }
251 
252 /*
253  * doentry --
254  *	process a single utx.log entry
255  */
256 static void
257 doentry(struct utmpx *bp)
258 {
259 	struct idtab *tt;
260 
261 	/* the machine stopped */
262 	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
263 		/* everybody just logged out */
264 		while ((tt = SLIST_FIRST(&idlist)) != NULL) {
265 			SLIST_REMOVE_HEAD(&idlist, list);
266 			free(tt);
267 		}
268 		currentout = -bp->ut_tv.tv_sec;
269 		crmsg = bp->ut_type != SHUTDOWN_TIME ?
270 		    "crash" : "shutdown";
271 		/*
272 		 * if we're in snapshot mode, we want to exit if this
273 		 * shutdown/reboot appears while we we are tracking the
274 		 * active range
275 		 */
276 		if (snaptime && snapfound)
277 			exit(0);
278 		/*
279 		 * don't print shutdown/reboot entries unless flagged for
280 		 */
281 		if (!snaptime && want(bp))
282 			printentry(bp, NULL);
283 		return;
284 	}
285 	/* date got set */
286 	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
287 		if (want(bp) && !snaptime)
288 			printentry(bp, NULL);
289 		return;
290 	}
291 
292 	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
293 		return;
294 
295 	/* find associated identifier */
296 	SLIST_FOREACH(tt, &idlist, list)
297 	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
298 		    break;
299 
300 	if (tt == NULL) {
301 		/* add new one */
302 		tt = malloc(sizeof(struct idtab));
303 		if (tt == NULL)
304 			errx(1, "malloc failure");
305 		tt->logout = currentout;
306 		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
307 		SLIST_INSERT_HEAD(&idlist, tt, list);
308 	}
309 
310 	/*
311 	 * print record if not in snapshot mode and wanted
312 	 * or in snapshot mode and in snapshot range
313 	 */
314 	if (bp->ut_type == USER_PROCESS && (want(bp) ||
315 	    (bp->ut_tv.tv_sec < snaptime &&
316 	    (tt->logout > snaptime || tt->logout < 1)))) {
317 		snapfound = 1;
318 		printentry(bp, tt);
319 	}
320 	tt->logout = bp->ut_tv.tv_sec;
321 }
322 
323 /*
324  * printentry --
325  *	output an entry
326  *
327  * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
328  * logout type (crash/shutdown) as appropriate.
329  */
330 static void
331 printentry(struct utmpx *bp, struct idtab *tt)
332 {
333 	char ct[80];
334 	struct tm *tm;
335 	time_t	delta;				/* time difference */
336 	time_t	t;
337 
338 	if (maxrec != -1 && !maxrec--)
339 		exit(0);
340 	t = bp->ut_tv.tv_sec;
341 	tm = localtime(&t);
342 	(void) strftime(ct, sizeof(ct), d_first ?
343 	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
344 	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
345 	switch (bp->ut_type) {
346 	case BOOT_TIME:
347 		printf("%-42s", "boot time");
348 		break;
349 	case SHUTDOWN_TIME:
350 		printf("%-42s", "shutdown time");
351 		break;
352 	case OLD_TIME:
353 		printf("%-42s", "old time");
354 		break;
355 	case NEW_TIME:
356 		printf("%-42s", "new time");
357 		break;
358 	case USER_PROCESS:
359 		printf("%-10s %-8s %-22.22s",
360 		    bp->ut_user, bp->ut_line, bp->ut_host);
361 		break;
362 	}
363 	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
364 	if (tt == NULL)
365 		return;
366 	if (!tt->logout) {
367 		puts("  still logged in");
368 		return;
369 	}
370 	if (tt->logout < 0) {
371 		tt->logout = -tt->logout;
372 		printf("- %s", crmsg);
373 	} else {
374 		tm = localtime(&tt->logout);
375 		(void) strftime(ct, sizeof(ct), "%R", tm);
376 		printf("- %s", ct);
377 	}
378 	delta = tt->logout - bp->ut_tv.tv_sec;
379 	if (sflag) {
380 		printf("  (%8ld)\n", (long)delta);
381 	} else {
382 		tm = gmtime(&delta);
383 		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
384 		if (delta < 86400)
385 			printf("  (%s)\n", ct);
386 		else
387 			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
388 	}
389 }
390 
391 /*
392  * want --
393  *	see if want this entry
394  */
395 static int
396 want(struct utmpx *bp)
397 {
398 	ARG *step;
399 
400 	if (snaptime)
401 		return (NO);
402 
403 	if (!arglist)
404 		return (YES);
405 
406 	for (step = arglist; step; step = step->next)
407 		switch(step->type) {
408 		case REBOOT_TYPE:
409 			if (bp->ut_type == BOOT_TIME ||
410 			    bp->ut_type == SHUTDOWN_TIME)
411 				return (YES);
412 			break;
413 		case HOST_TYPE:
414 			if (!strcasecmp(step->name, bp->ut_host))
415 				return (YES);
416 			break;
417 		case TTY_TYPE:
418 			if (!strcmp(step->name, bp->ut_line))
419 				return (YES);
420 			break;
421 		case USER_TYPE:
422 			if (!strcmp(step->name, bp->ut_user))
423 				return (YES);
424 			break;
425 		}
426 	return (NO);
427 }
428 
429 /*
430  * addarg --
431  *	add an entry to a linked list of arguments
432  */
433 static void
434 addarg(int type, char *arg)
435 {
436 	ARG *cur;
437 
438 	if ((cur = malloc(sizeof(ARG))) == NULL)
439 		errx(1, "malloc failure");
440 	cur->next = arglist;
441 	cur->type = type;
442 	cur->name = arg;
443 	arglist = cur;
444 }
445 
446 /*
447  * hostconv --
448  *	convert the hostname to search pattern; if the supplied host name
449  *	has a domain attached that is the same as the current domain, rip
450  *	off the domain suffix since that's what login(1) does.
451  */
452 static void
453 hostconv(char *arg)
454 {
455 	static int first = 1;
456 	static char *hostdot, name[MAXHOSTNAMELEN];
457 	char *argdot;
458 
459 	if (!(argdot = strchr(arg, '.')))
460 		return;
461 	if (first) {
462 		first = 0;
463 		if (gethostname(name, sizeof(name)))
464 			err(1, "gethostname");
465 		hostdot = strchr(name, '.');
466 	}
467 	if (hostdot && !strcasecmp(hostdot, argdot))
468 		*argdot = '\0';
469 }
470 
471 /*
472  * ttyconv --
473  *	convert tty to correct name.
474  */
475 static char *
476 ttyconv(char *arg)
477 {
478 	char *mval;
479 
480 	/*
481 	 * kludge -- we assume that all tty's end with
482 	 * a two character suffix.
483 	 */
484 	if (strlen(arg) == 2) {
485 		/* either 6 for "ttyxx" or 8 for "console" */
486 		if ((mval = malloc(8)) == NULL)
487 			errx(1, "malloc failure");
488 		if (!strcmp(arg, "co"))
489 			(void)strcpy(mval, "console");
490 		else {
491 			(void)strcpy(mval, "tty");
492 			(void)strcpy(mval + 3, arg);
493 		}
494 		return (mval);
495 	}
496 	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
497 		return (arg + 5);
498 	return (arg);
499 }
500 
501 /*
502  * dateconv --
503  * 	Convert the snapshot time in command line given in the format
504  * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
505  * 	Derived from atime_arg1() in usr.bin/touch/touch.c
506  */
507 static time_t
508 dateconv(char *arg)
509 {
510         time_t timet;
511         struct tm *t;
512         int yearset;
513         char *p;
514 
515         /* Start with the current time. */
516         if (time(&timet) < 0)
517                 err(1, "time");
518         if ((t = localtime(&timet)) == NULL)
519                 err(1, "localtime");
520 
521         /* [[CC]YY]MMDDhhmm[.SS] */
522         if ((p = strchr(arg, '.')) == NULL)
523                 t->tm_sec = 0; 		/* Seconds defaults to 0. */
524         else {
525                 if (strlen(p + 1) != 2)
526                         goto terr;
527                 *p++ = '\0';
528                 t->tm_sec = ATOI2(p);
529         }
530 
531         yearset = 0;
532         switch (strlen(arg)) {
533         case 12:                	/* CCYYMMDDhhmm */
534                 t->tm_year = ATOI2(arg);
535                 t->tm_year *= 100;
536                 yearset = 1;
537                 /* FALLTHROUGH */
538         case 10:                	/* YYMMDDhhmm */
539                 if (yearset) {
540                         yearset = ATOI2(arg);
541                         t->tm_year += yearset;
542                 } else {
543                         yearset = ATOI2(arg);
544                         if (yearset < 69)
545                                 t->tm_year = yearset + 2000;
546                         else
547                                 t->tm_year = yearset + 1900;
548                 }
549                 t->tm_year -= 1900;     /* Convert to UNIX time. */
550                 /* FALLTHROUGH */
551         case 8:				/* MMDDhhmm */
552                 t->tm_mon = ATOI2(arg);
553                 --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
554                 t->tm_mday = ATOI2(arg);
555                 t->tm_hour = ATOI2(arg);
556                 t->tm_min = ATOI2(arg);
557                 break;
558         case 4:				/* hhmm */
559                 t->tm_hour = ATOI2(arg);
560                 t->tm_min = ATOI2(arg);
561                 break;
562         default:
563                 goto terr;
564         }
565         t->tm_isdst = -1;       	/* Figure out DST. */
566         timet = mktime(t);
567         if (timet == -1)
568 terr:           errx(1,
569         "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
570         return timet;
571 }
572