xref: /freebsd/usr.bin/last/last.c (revision e72055b7feba695a760d45f01f0f8268b1cb4a74)
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
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/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 
65 typedef struct arg {
66 	char	*name;				/* argument */
67 #define	HOST_TYPE	-2
68 #define	TTY_TYPE	-3
69 #define	USER_TYPE	-4
70 	int	type;				/* type of arg */
71 	struct arg	*next;			/* linked list pointer */
72 } ARG;
73 static ARG	*arglist;			/* head of linked list */
74 
75 static SLIST_HEAD(, idtab) idlist;
76 
77 struct idtab {
78 	time_t	logout;				/* log out time */
79 	char	id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
80 	SLIST_ENTRY(idtab) list;
81 };
82 
83 static const	char *crmsg;			/* cause of last reboot */
84 static time_t	currentout;			/* current logout value */
85 static long	maxrec;				/* records to display */
86 static const	char *file = NULL;		/* utx.log file */
87 static int	sflag = 0;			/* show delta in seconds */
88 static int	width = 5;			/* show seconds in delta */
89 static int	yflag;				/* show year */
90 static int      d_first;
91 static int	snapfound = 0;			/* found snapshot entry? */
92 static time_t	snaptime;			/* if != 0, we will only
93 						 * report users logged in
94 						 * at this snapshot time
95 						 */
96 
97 static void	 addarg(int, char *);
98 static time_t	 dateconv(char *);
99 static void	 doentry(struct utmpx *);
100 static void	 hostconv(char *);
101 static void	 printentry(struct utmpx *, struct idtab *);
102 static char	*ttyconv(char *);
103 static int	 want(struct utmpx *);
104 static void	 usage(void);
105 static void	 wtmp(void);
106 
107 static void
108 usage(void)
109 {
110 	(void)fprintf(stderr,
111 "usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
112 "            [-n maxrec] [-t tty] [user ...]\n");
113 	exit(1);
114 }
115 
116 int
117 main(int argc, char *argv[])
118 {
119 	int ch;
120 	char *p;
121 
122 	(void) setlocale(LC_TIME, "");
123 	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
124 
125 	maxrec = -1;
126 	snaptime = 0;
127 	while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
128 		switch (ch) {
129 		case '0': case '1': case '2': case '3': case '4':
130 		case '5': case '6': case '7': case '8': case '9':
131 			/*
132 			 * kludge: last was originally designed to take
133 			 * a number after a dash.
134 			 */
135 			if (maxrec == -1) {
136 				p = strchr(argv[optind - 1], ch);
137 				if (p == NULL)
138 					p = strchr(argv[optind], ch);
139 				maxrec = atol(p);
140 				if (!maxrec)
141 					exit(0);
142 			}
143 			break;
144 		case 'd':
145 			snaptime = dateconv(optarg);
146 			break;
147 		case 'f':
148 			file = optarg;
149 			break;
150 		case 'h':
151 			hostconv(optarg);
152 			addarg(HOST_TYPE, optarg);
153 			break;
154 		case 'n':
155 			errno = 0;
156 			maxrec = strtol(optarg, &p, 10);
157 			if (p == optarg || *p != '\0' || errno != 0 ||
158 			    maxrec <= 0)
159 				errx(1, "%s: bad line count", optarg);
160 			break;
161 		case 's':
162 			sflag++;	/* Show delta as seconds */
163 			break;
164 		case 't':
165 			addarg(TTY_TYPE, ttyconv(optarg));
166 			break;
167 		case 'w':
168 			width = 8;
169 			break;
170 		case 'y':
171 			yflag++;
172 			break;
173 		case '?':
174 		default:
175 			usage();
176 		}
177 
178 	if (sflag && width == 8) usage();
179 
180 	if (argc) {
181 		setlinebuf(stdout);
182 		for (argv += optind; *argv; ++argv) {
183 #define	COMPATIBILITY
184 #ifdef	COMPATIBILITY
185 			/* code to allow "last p5" to work */
186 			addarg(TTY_TYPE, ttyconv(*argv));
187 #endif
188 			addarg(USER_TYPE, *argv);
189 		}
190 	}
191 	wtmp();
192 	exit(0);
193 }
194 
195 /*
196  * wtmp --
197  *	read through the utx.log file
198  */
199 static void
200 wtmp(void)
201 {
202 	struct utmpx *buf = NULL;
203 	struct utmpx *ut;
204 	static unsigned int amount = 0;
205 	time_t t;
206 	char ct[80];
207 	struct tm *tm;
208 
209 	SLIST_INIT(&idlist);
210 	(void)time(&t);
211 
212 	/* Load the last entries from the file. */
213 	if (setutxdb(UTXDB_LOG, file) != 0)
214 		err(1, "%s", file);
215 	while ((ut = getutxent()) != NULL) {
216 		if (amount % 128 == 0) {
217 			buf = realloc(buf, (amount + 128) * sizeof *ut);
218 			if (buf == NULL)
219 				err(1, "realloc");
220 		}
221 		memcpy(&buf[amount++], ut, sizeof *ut);
222 		if (t > ut->ut_tv.tv_sec)
223 			t = ut->ut_tv.tv_sec;
224 	}
225 	endutxent();
226 
227 	/* Display them in reverse order. */
228 	while (amount > 0)
229 		doentry(&buf[--amount]);
230 
231 	tm = localtime(&t);
232 	(void) strftime(ct, sizeof(ct), "%+", tm);
233 	printf("\n%s begins %s\n", ((file == NULL) ? "utx.log" : file), ct);
234 }
235 
236 /*
237  * doentry --
238  *	process a single utx.log entry
239  */
240 static void
241 doentry(struct utmpx *bp)
242 {
243 	struct idtab *tt;
244 
245 	/* the machine stopped */
246 	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
247 		/* everybody just logged out */
248 		while ((tt = SLIST_FIRST(&idlist)) != NULL) {
249 			SLIST_REMOVE_HEAD(&idlist, list);
250 			free(tt);
251 		}
252 		currentout = -bp->ut_tv.tv_sec;
253 		crmsg = bp->ut_type != SHUTDOWN_TIME ?
254 		    "crash" : "shutdown";
255 		/*
256 		 * if we're in snapshot mode, we want to exit if this
257 		 * shutdown/reboot appears while we we are tracking the
258 		 * active range
259 		 */
260 		if (snaptime && snapfound)
261 			exit(0);
262 		/*
263 		 * don't print shutdown/reboot entries unless flagged for
264 		 */
265 		if (!snaptime && want(bp))
266 			printentry(bp, NULL);
267 		return;
268 	}
269 	/* date got set */
270 	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
271 		if (want(bp) && !snaptime)
272 			printentry(bp, NULL);
273 		return;
274 	}
275 
276 	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
277 		return;
278 
279 	/* find associated identifier */
280 	SLIST_FOREACH(tt, &idlist, list)
281 	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
282 		    break;
283 
284 	if (tt == NULL) {
285 		/* add new one */
286 		tt = malloc(sizeof(struct idtab));
287 		if (tt == NULL)
288 			errx(1, "malloc failure");
289 		tt->logout = currentout;
290 		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
291 		SLIST_INSERT_HEAD(&idlist, tt, list);
292 	}
293 
294 	/*
295 	 * print record if not in snapshot mode and wanted
296 	 * or in snapshot mode and in snapshot range
297 	 */
298 	if (bp->ut_type == USER_PROCESS && (want(bp) ||
299 	    (bp->ut_tv.tv_sec < snaptime &&
300 	    (tt->logout > snaptime || tt->logout < 1)))) {
301 		snapfound = 1;
302 		printentry(bp, tt);
303 	}
304 	tt->logout = bp->ut_tv.tv_sec;
305 }
306 
307 /*
308  * printentry --
309  *	output an entry
310  *
311  * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
312  * logout type (crash/shutdown) as appropriate.
313  */
314 static void
315 printentry(struct utmpx *bp, struct idtab *tt)
316 {
317 	char ct[80];
318 	struct tm *tm;
319 	time_t	delta;				/* time difference */
320 	time_t	t;
321 
322 	if (maxrec != -1 && !maxrec--)
323 		exit(0);
324 	t = bp->ut_tv.tv_sec;
325 	tm = localtime(&t);
326 	(void) strftime(ct, sizeof(ct), d_first ?
327 	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
328 	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
329 	switch (bp->ut_type) {
330 	case BOOT_TIME:
331 		printf("%-42s", "boot time");
332 		break;
333 	case SHUTDOWN_TIME:
334 		printf("%-42s", "shutdown time");
335 		break;
336 	case OLD_TIME:
337 		printf("%-42s", "old time");
338 		break;
339 	case NEW_TIME:
340 		printf("%-42s", "new time");
341 		break;
342 	case USER_PROCESS:
343 		printf("%-10s %-8s %-22.22s",
344 		    bp->ut_user, bp->ut_line, bp->ut_host);
345 		break;
346 	}
347 	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
348 	if (tt == NULL)
349 		return;
350 	if (!tt->logout) {
351 		puts("  still logged in");
352 		return;
353 	}
354 	if (tt->logout < 0) {
355 		tt->logout = -tt->logout;
356 		printf("- %s", crmsg);
357 	} else {
358 		tm = localtime(&tt->logout);
359 		(void) strftime(ct, sizeof(ct), "%R", tm);
360 		printf("- %s", ct);
361 	}
362 	delta = tt->logout - bp->ut_tv.tv_sec;
363 	if (sflag) {
364 		printf("  (%8ld)\n", (long)delta);
365 	} else {
366 		tm = gmtime(&delta);
367 		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
368 		if (delta < 86400)
369 			printf("  (%s)\n", ct);
370 		else
371 			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
372 	}
373 }
374 
375 /*
376  * want --
377  *	see if want this entry
378  */
379 static int
380 want(struct utmpx *bp)
381 {
382 	ARG *step;
383 
384 	if (snaptime)
385 		return (NO);
386 
387 	if (!arglist)
388 		return (YES);
389 
390 	for (step = arglist; step; step = step->next)
391 		switch(step->type) {
392 		case HOST_TYPE:
393 			if (!strcasecmp(step->name, bp->ut_host))
394 				return (YES);
395 			break;
396 		case TTY_TYPE:
397 			if (!strcmp(step->name, bp->ut_line))
398 				return (YES);
399 			break;
400 		case USER_TYPE:
401 			if (!strcmp(step->name, bp->ut_user))
402 				return (YES);
403 			break;
404 		}
405 	return (NO);
406 }
407 
408 /*
409  * addarg --
410  *	add an entry to a linked list of arguments
411  */
412 static void
413 addarg(int type, char *arg)
414 {
415 	ARG *cur;
416 
417 	if ((cur = malloc(sizeof(ARG))) == NULL)
418 		errx(1, "malloc failure");
419 	cur->next = arglist;
420 	cur->type = type;
421 	cur->name = arg;
422 	arglist = cur;
423 }
424 
425 /*
426  * hostconv --
427  *	convert the hostname to search pattern; if the supplied host name
428  *	has a domain attached that is the same as the current domain, rip
429  *	off the domain suffix since that's what login(1) does.
430  */
431 static void
432 hostconv(char *arg)
433 {
434 	static int first = 1;
435 	static char *hostdot, name[MAXHOSTNAMELEN];
436 	char *argdot;
437 
438 	if (!(argdot = strchr(arg, '.')))
439 		return;
440 	if (first) {
441 		first = 0;
442 		if (gethostname(name, sizeof(name)))
443 			err(1, "gethostname");
444 		hostdot = strchr(name, '.');
445 	}
446 	if (hostdot && !strcasecmp(hostdot, argdot))
447 		*argdot = '\0';
448 }
449 
450 /*
451  * ttyconv --
452  *	convert tty to correct name.
453  */
454 static char *
455 ttyconv(char *arg)
456 {
457 	char *mval;
458 
459 	/*
460 	 * kludge -- we assume that all tty's end with
461 	 * a two character suffix.
462 	 */
463 	if (strlen(arg) == 2) {
464 		/* either 6 for "ttyxx" or 8 for "console" */
465 		if ((mval = malloc(8)) == NULL)
466 			errx(1, "malloc failure");
467 		if (!strcmp(arg, "co"))
468 			(void)strcpy(mval, "console");
469 		else {
470 			(void)strcpy(mval, "tty");
471 			(void)strcpy(mval + 3, arg);
472 		}
473 		return (mval);
474 	}
475 	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
476 		return (arg + 5);
477 	return (arg);
478 }
479 
480 /*
481  * dateconv --
482  * 	Convert the snapshot time in command line given in the format
483  * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
484  * 	Derived from atime_arg1() in usr.bin/touch/touch.c
485  */
486 static time_t
487 dateconv(char *arg)
488 {
489         time_t timet;
490         struct tm *t;
491         int yearset;
492         char *p;
493 
494         /* Start with the current time. */
495         if (time(&timet) < 0)
496                 err(1, "time");
497         if ((t = localtime(&timet)) == NULL)
498                 err(1, "localtime");
499 
500         /* [[CC]YY]MMDDhhmm[.SS] */
501         if ((p = strchr(arg, '.')) == NULL)
502                 t->tm_sec = 0; 		/* Seconds defaults to 0. */
503         else {
504                 if (strlen(p + 1) != 2)
505                         goto terr;
506                 *p++ = '\0';
507                 t->tm_sec = ATOI2(p);
508         }
509 
510         yearset = 0;
511         switch (strlen(arg)) {
512         case 12:                	/* CCYYMMDDhhmm */
513                 t->tm_year = ATOI2(arg);
514                 t->tm_year *= 100;
515                 yearset = 1;
516                 /* FALLTHROUGH */
517         case 10:                	/* YYMMDDhhmm */
518                 if (yearset) {
519                         yearset = ATOI2(arg);
520                         t->tm_year += yearset;
521                 } else {
522                         yearset = ATOI2(arg);
523                         if (yearset < 69)
524                                 t->tm_year = yearset + 2000;
525                         else
526                                 t->tm_year = yearset + 1900;
527                 }
528                 t->tm_year -= 1900;     /* Convert to UNIX time. */
529                 /* FALLTHROUGH */
530         case 8:				/* MMDDhhmm */
531                 t->tm_mon = ATOI2(arg);
532                 --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
533                 t->tm_mday = ATOI2(arg);
534                 t->tm_hour = ATOI2(arg);
535                 t->tm_min = ATOI2(arg);
536                 break;
537         case 4:				/* hhmm */
538                 t->tm_hour = ATOI2(arg);
539                 t->tm_min = ATOI2(arg);
540                 break;
541         default:
542                 goto terr;
543         }
544         t->tm_isdst = -1;       	/* Figure out DST. */
545         timet = mktime(t);
546         if (timet == -1)
547 terr:           errx(1,
548         "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
549         return timet;
550 }
551