xref: /openbsd/usr.bin/last/last.c (revision 610f49f8)
1 /*	$OpenBSD: last.c,v 1.18 2002/02/04 17:40:59 mickey Exp $	*/
2 /*	$NetBSD: last.c,v 1.6 1994/12/24 16:49:02 cgd Exp $	*/
3 
4 /*
5  * Copyright (c) 1987, 1993, 1994
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #ifndef lint
38 static char copyright[] =
39 "@(#) Copyright (c) 1987, 1993, 1994\n\
40 	The Regents of the University of California.  All rights reserved.\n";
41 #endif /* not lint */
42 
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
46 #endif
47 static char rcsid[] = "$OpenBSD: last.c,v 1.18 2002/02/04 17:40:59 mickey Exp $";
48 #endif /* not lint */
49 
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 
53 #include <err.h>
54 #include <fcntl.h>
55 #include <paths.h>
56 #include <signal.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <time.h>
61 #include <tzfile.h>
62 #include <unistd.h>
63 #include <utmp.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 
69 static struct utmp	buf[1024];		/* utmp read buffer */
70 
71 typedef struct arg {
72 	char	*name;				/* argument */
73 #define	HOST_TYPE	-2
74 #define	TTY_TYPE	-3
75 #define	USER_TYPE	-4
76 	int	type;				/* type of arg */
77 	struct arg	*next;			/* linked list pointer */
78 } ARG;
79 ARG	*arglist;				/* head of linked list */
80 
81 typedef struct ttytab {
82 	time_t	logout;				/* log out time */
83 	char	tty[UT_LINESIZE + 1];		/* terminal name */
84 	struct ttytab	*next;			/* linked list pointer */
85 } TTY;
86 TTY	*ttylist;				/* head of linked list */
87 
88 static time_t	currentout;			/* current logout value */
89 static long	maxrec;				/* records to display */
90 static char	*file = _PATH_WTMP;		/* wtmp file */
91 static int	fulltime = 0;			/* Display seconds? */
92 static time_t	snaptime;			/* if != 0, we will only
93 						 * report users logged in
94 						 * at this snapshot time
95 						 */
96 static int calculate = 0;
97 static int seconds = 0;
98 
99 void	 addarg __P((int, char *));
100 TTY	*addtty __P((char *));
101 void	 hostconv __P((char *));
102 void	 onintr __P((int));
103 char	*ttyconv __P((char *));
104 time_t	 dateconv __P((char *));
105 int	 want __P((struct utmp *, int));
106 void	 wtmp __P((void));
107 void	 checkargs __P((void));
108 
109 #define NAME_WIDTH	8
110 #define HOST_WIDTH	24
111 
112 int
113 main(argc, argv)
114 	int argc;
115 	char *argv[];
116 {
117 	extern int optind;
118 	extern char *optarg;
119 	int ch;
120 	char *p;
121 
122 	maxrec = -1;
123 	snaptime = 0;
124 	while ((ch = getopt(argc, argv, "0123456789cf:h:st:d:T")) != -1)
125 		switch (ch) {
126 		case '0': case '1': case '2': case '3': case '4':
127 		case '5': case '6': case '7': case '8': case '9':
128 			/*
129 			 * kludge: last was originally designed to take
130 			 * a number after a dash.
131 			 */
132 			if (maxrec == -1) {
133 				p = argv[optind - 1];
134 				if (p[0] == '-' && p[1] == ch && !p[2])
135 					maxrec = atol(++p);
136 				else
137 					maxrec = atol(argv[optind] + 1);
138 				if (!maxrec)
139 					exit(0);
140 			}
141 			break;
142 		case 'c':
143 			calculate++;
144 			break;
145 		case 'f':
146 			file = optarg;
147 			break;
148 		case 'h':
149 			hostconv(optarg);
150 			addarg(HOST_TYPE, optarg);
151 			break;
152 		case 's':
153 			seconds++;
154 			break;
155 		case 't':
156 			addarg(TTY_TYPE, ttyconv(optarg));
157 			break;
158 		case 'd':
159 			snaptime = dateconv(optarg);
160 			break;
161 		case 'T':
162 			fulltime = 1;
163 			break;
164 		case '?':
165 		default:
166 			(void)fprintf(stderr,
167 			    "usage: last [-#] [-cns] [-f file] [-T] [-t tty]"
168 			    " [-h host] [-d [[CC]YY][MMDD]hhmm[.SS]"
169 			    " [user ...]\n");
170 			exit(1);
171 		}
172 
173 	if (argc) {
174 		setlinebuf(stdout);
175 		for (argv += optind; *argv; ++argv) {
176 #define	COMPATIBILITY
177 #ifdef	COMPATIBILITY
178 			/* code to allow "last p5" to work */
179 			addarg(TTY_TYPE, ttyconv(*argv));
180 #endif
181 			addarg(USER_TYPE, *argv);
182 		}
183 	}
184 
185 	checkargs();
186 	wtmp();
187 	exit(0);
188 }
189 
190 /*
191  * checkargs --
192  *	if snaptime is set, print warning if usernames, or -t or -h
193  *	flags are also provided
194  */
195 
196 void
197 checkargs()
198 {
199 	ARG	*step;
200 	int	ttyflag = 0;
201 
202 	if (!snaptime)
203 		return;
204 
205 	if (!arglist)
206 		return;
207 
208 	for (step = arglist; step; step = step->next)
209 		switch (step->type) {
210 		case HOST_TYPE:
211 			(void)fprintf(stderr,
212 			    "Warning: Ignoring hostname flag\n");
213 			break;
214 		case TTY_TYPE:
215 			if (!ttyflag) { /* don't print this twice */
216 				(void)fprintf(stderr,
217 				    "Warning: Ignoring tty flag\n");
218 				ttyflag = 1;
219 			}
220 			break;
221 		case USER_TYPE:
222 			(void)fprintf(stderr,
223 			    "Warning: Ignoring username[s]\n");
224 			break;
225 		default:
226 			/* PRINT NOTHING */
227 		}
228 }
229 
230 
231 /*
232  * wtmp --
233  *	read through the wtmp file
234  */
235 void
236 wtmp()
237 {
238 	struct utmp	*bp;		/* current structure */
239 	TTY	*T;			/* tty list entry */
240 	struct stat	stb;		/* stat of file for size */
241 	time_t	delta;			/* time difference */
242 	time_t	total;
243 	off_t	bl;
244 	int	timesize;		/* how long time string gonna be */
245 	int	bytes, wfd;
246 	char	*ct, *crmsg;
247 	int	snapfound = 0;		/* found snapshot entry? */
248 	if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
249 		err(1, "%s", file);
250 	bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
251 
252 	if (fulltime)
253 		timesize = 8;	/* HH:MM:SS */
254 	else
255 		timesize = 5;	/* HH:MM */
256 
257 	(void)time(&buf[0].ut_time);
258 	(void)signal(SIGINT, onintr);
259 	(void)signal(SIGQUIT, onintr);
260 
261 	while (--bl >= 0) {
262 		if (lseek(wfd, bl * sizeof(buf), SEEK_SET) == -1 ||
263 		    (bytes = read(wfd, buf, sizeof(buf))) == -1)
264 			err(1, "%s", file);
265 		for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
266 			/*
267 			 * if the terminal line is '~', the machine stopped.
268 			 * see utmp(5) for more info.
269 			 */
270 			if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
271 				/* everybody just logged out */
272 				for (T = ttylist; T; T = T->next)
273 					T->logout = -bp->ut_time;
274 				currentout = -bp->ut_time;
275 				crmsg = strncmp(bp->ut_name, "shutdown",
276 				    UT_NAMESIZE) ? "crash" : "shutdown";
277 				/*
278 				 * if we're in snapshot mode, we want to
279 				 * exit if this shutdown/reboot appears
280 				 * while we we are tracking the active
281 				 * range
282 				 */
283 				if (snaptime && snapfound)
284 					return;
285 				/*
286 				 * don't print shutdown/reboot entries
287 				 * unless flagged for
288 				 */
289 				if (want(bp, NO)) {
290 					if (seconds) {
291 						printf("%-*.*s %-*.*s %-*.*s %ld \n",
292 						    NAME_WIDTH, UT_NAMESIZE,
293 						    bp->ut_name, UT_LINESIZE,
294 						    UT_LINESIZE, bp->ut_line,
295 						    HOST_WIDTH, UT_HOSTSIZE,
296 						    bp->ut_host, (long)bp->ut_time);
297 					} else {
298 						ct = ctime(&bp->ut_time);
299 						printf("%-*.*s  %-*.*s %-*.*s %10.10s %*.*s \n",
300 						    NAME_WIDTH, UT_NAMESIZE,
301 						    bp->ut_name, UT_LINESIZE,
302 						    UT_LINESIZE, bp->ut_line,
303 						    HOST_WIDTH, UT_HOSTSIZE,
304 						    bp->ut_host, ct, timesize,
305 						    timesize, ct + 11);
306 					}
307 					if (maxrec != -1 && !--maxrec)
308 						return;
309 				}
310 				continue;
311 			}
312 			/*
313 			 * if the line is '{' or '|', date got set; see
314 			 * utmp(5) for more info.
315 			 */
316 			if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
317 			    && !bp->ut_line[1]) {
318 				if (want(bp, NO)) {
319 					if (seconds) {
320 				printf("%-*.*s %-*.*s %-*.*s %ld \n",
321 					NAME_WIDTH, UT_NAMESIZE, bp->ut_name,
322 					UT_LINESIZE, UT_LINESIZE, bp->ut_line,
323 					HOST_WIDTH, UT_HOSTSIZE, bp->ut_host,
324 					(long)bp->ut_time);
325 					} else {
326 						ct = ctime(&bp->ut_time);
327 				printf("%-*.*s  %-*.*s %-*.*s %10.10s %*.*s \n",
328 					NAME_WIDTH, UT_NAMESIZE, bp->ut_name,
329 					UT_LINESIZE, UT_LINESIZE, bp->ut_line,
330 					HOST_WIDTH, UT_HOSTSIZE, bp->ut_host,
331 					ct, timesize, timesize, ct + 11);
332 					}
333 					if (maxrec && !--maxrec)
334 						return;
335 				}
336 				continue;
337 			}
338 			/* find associated tty */
339 			for (T = ttylist;; T = T->next) {
340 				if (!T) {
341 					/* add new one */
342 					T = addtty(bp->ut_line);
343 					break;
344 				}
345 				if (!strncmp(T->tty, bp->ut_line, UT_LINESIZE))
346 					break;
347 			}
348 			/*
349 			 * print record if not in snapshot mode and wanted
350 			 * or in snapshot mode and in snapshot range
351 			 */
352 			if (bp->ut_name[0] &&
353 			    ((want(bp, YES)) ||
354 			     (bp->ut_time < snaptime &&
355 			      (T->logout > snaptime || !T->logout ||
356 			       T->logout < 0)))) {
357 				snapfound = 1;
358 				if (seconds) {
359 				printf("%-*.*s %-*.*s %-*.*s %ld ",
360 					NAME_WIDTH, UT_NAMESIZE, bp->ut_name,
361 					UT_LINESIZE, UT_LINESIZE, bp->ut_line,
362 					HOST_WIDTH, UT_HOSTSIZE, bp->ut_host,
363 					(long)bp->ut_time);
364 				} else {
365 					ct = ctime(&bp->ut_time);
366 				printf("%-*.*s  %-*.*s %-*.*s %10.10s %*.*s ",
367 					NAME_WIDTH, UT_NAMESIZE, bp->ut_name,
368 					UT_LINESIZE, UT_LINESIZE, bp->ut_line,
369 					HOST_WIDTH, UT_HOSTSIZE, bp->ut_host,
370 					ct, timesize, timesize, ct + 11);
371 				}
372 				if (!T->logout)
373 					puts("  still logged in");
374 				else {
375 					if (T->logout < 0) {
376 						T->logout = -T->logout;
377 						printf("- %s", crmsg);
378 					} else {
379 						if (seconds)
380 							printf("- %ld",
381 							    (long)T->logout);
382 						else
383 							printf("- %*.*s",
384 							    timesize, timesize,
385 							    ctime(&T->logout)+11);
386 					}
387 					delta = T->logout - bp->ut_time;
388 					if (seconds)
389 						printf("  (%ld)\n", (long)delta);
390 					else {
391 						if (delta < SECSPERDAY)
392 							printf("  (%*.*s)\n",
393 							    timesize, timesize,
394 							    asctime(gmtime(&delta))+11);
395 						else
396 							printf(" (%ld+%*.*s)\n",
397 							    delta / SECSPERDAY,
398 							    timesize, timesize,
399 							    asctime(gmtime(&delta))+11);
400 					}
401 					if (calculate)
402 						total += delta;
403 				}
404 				if (maxrec != -1 && !--maxrec)
405 					return;
406 			}
407 			T->logout = bp->ut_time;
408 		}
409 	}
410 	if (calculate) {
411 		if ((total / SECSPERDAY) > 0) {
412 			int days = (total / SECSPERDAY);
413 			total -= (days * SECSPERDAY);
414 
415 			printf("\nTotal time: %d days, %*.*s\n",
416 				days, timesize, timesize,
417 				asctime(gmtime(&total))+11);
418 		} else
419 			printf("\nTotal time: %*.*s\n",
420 				timesize, timesize,
421 				asctime(gmtime(&total))+11);
422 	}
423 	ct = ctime(&buf[0].ut_time);
424 	printf("\nwtmp begins %10.10s %*.*s %4.4s\n", ct, timesize, timesize,
425 	    ct + 11, ct + 20);
426 }
427 
428 /*
429  * want --
430  *	see if want this entry
431  */
432 int
433 want(bp, check)
434 	struct utmp *bp;
435 	int check;
436 {
437 	ARG *step;
438 
439 	if (check) {
440 		/*
441 		 * when uucp and ftp log in over a network, the entry in
442 		 * the utmp file is the name plus their process id.  See
443 		 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
444 		 */
445 		if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
446 			bp->ut_line[3] = '\0';
447 		else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
448 			bp->ut_line[4] = '\0';
449 	}
450 
451 	if (snaptime)		/* if snaptime is set, return NO */
452 		return (NO);
453 
454 	if (!arglist)
455 		return (YES);
456 
457 	for (step = arglist; step; step = step->next)
458 		switch(step->type) {
459 		case HOST_TYPE:
460 			if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
461 				return (YES);
462 			break;
463 		case TTY_TYPE:
464 			if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
465 				return (YES);
466 			break;
467 		case USER_TYPE:
468 			if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
469 				return (YES);
470 			break;
471 		}
472 
473 	return (NO);
474 }
475 
476 /*
477  * addarg --
478  *	add an entry to a linked list of arguments
479  */
480 void
481 addarg(type, arg)
482 	int type;
483 	char *arg;
484 {
485 	ARG *cur;
486 
487 	if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
488 		err(1, "malloc failure");
489 	cur->next = arglist;
490 	cur->type = type;
491 	cur->name = arg;
492 	arglist = cur;
493 }
494 
495 /*
496  * addtty --
497  *	add an entry to a linked list of ttys
498  */
499 TTY *
500 addtty(ttyname)
501 	char *ttyname;
502 {
503 	TTY *cur;
504 
505 	if (!(cur = (TTY *)malloc((u_int)sizeof(TTY))))
506 		err(1, "malloc failure");
507 	cur->next = ttylist;
508 	cur->logout = currentout;
509 	memmove(cur->tty, ttyname, UT_LINESIZE);
510 	return (ttylist = cur);
511 }
512 
513 /*
514  * hostconv --
515  *	convert the hostname to search pattern; if the supplied host name
516  *	has a domain attached that is the same as the current domain, rip
517  *	off the domain suffix since that's what login(1) does.
518  */
519 void
520 hostconv(arg)
521 	char *arg;
522 {
523 	static int first = 1;
524 	static char *hostdot, name[MAXHOSTNAMELEN];
525 	char *argdot;
526 
527 	if (!(argdot = strchr(arg, '.')))
528 		return;
529 	if (first) {
530 		first = 0;
531 		if (gethostname(name, sizeof(name)))
532 			err(1, "gethostname");
533 		hostdot = strchr(name, '.');
534 	}
535 	if (hostdot && !strcasecmp(hostdot, argdot))
536 		*argdot = '\0';
537 }
538 
539 /*
540  * ttyconv --
541  *	convert tty to correct name.
542  */
543 char *
544 ttyconv(arg)
545 	char *arg;
546 {
547 	char *mval;
548 
549 	/*
550 	 * kludge -- we assume that all tty's end with
551 	 * a two character suffix.
552 	 */
553 	if (strlen(arg) == 2) {
554 		/* either 6 for "ttyxx" or 8 for "console" */
555 		if (!(mval = malloc((u_int)8)))
556 			err(1, "malloc failure");
557 		if (!strcmp(arg, "co"))
558 			(void)strcpy(mval, "console");
559 		else {
560 			(void)strcpy(mval, "tty");
561 			(void)strcpy(mval + 3, arg);
562 		}
563 		return (mval);
564 	}
565 	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
566 		return (arg + 5);
567 	return (arg);
568 }
569 
570 /*
571  * dateconv --
572  * Convert the snapshot time in command line given in the format
573  *	[[CC]YY][MMDD]hhmm[.ss]] to a time_t.
574  *	Derived from atime_arg1() in usr.bin/touch/touch.c
575  */
576 time_t
577 dateconv(arg)
578 	char *arg;
579 {
580 	time_t timet;
581 	struct tm *t;
582 	int yearset;
583 	char *p;
584 
585 	/* Start with the current time. */
586 	if (time(&timet) < 0)
587 		err(1, "time");
588 	if ((t = localtime(&timet)) == NULL)
589 		err(1, "localtime");
590 
591 	/* [[yy]yy][mmdd]hhmm[.ss] */
592 	if ((p = strchr(arg, '.')) == NULL)
593 		t->tm_sec = 0;		/* Seconds defaults to 0. */
594 	else {
595 		if (strlen(p + 1) != 2)
596 			goto terr;
597 		*p++ = '\0';
598 		t->tm_sec = ATOI2(p);
599 	}
600 
601 	yearset = 0;
602 	switch (strlen(arg)) {
603 	case 12:			/* ccyymmddhhmm */
604 		t->tm_year = ATOI2(arg);
605 		t->tm_year *= 100;
606 		yearset = 1;
607 		/* FALLTHOUGH */
608 	case 10:			/* yymmddhhmm */
609 		if (yearset) {
610 			yearset = ATOI2(arg);
611 			t->tm_year += yearset;
612 		} else {
613 			yearset = ATOI2(arg);
614 			if (yearset < 69)
615 				t->tm_year = yearset + 2000;
616 			else
617 				t->tm_year = yearset + 1900;
618 		}
619 		t->tm_year -= 1900;     /* Convert to UNIX time. */
620 		/* FALLTHROUGH */
621 	case 8:				/* MMDDhhmm */
622 		t->tm_mon = ATOI2(arg);
623 		--t->tm_mon;		/* Convert from 01-12 to 00-11 */
624 		t->tm_mday = ATOI2(arg);
625 		t->tm_hour = ATOI2(arg);
626 		t->tm_min = ATOI2(arg);
627 		break;
628 	case 4:				/* hhmm */
629 		t->tm_hour = ATOI2(arg);
630 		t->tm_min = ATOI2(arg);
631 		break;
632 	default:
633 		goto terr;
634 	}
635 	t->tm_isdst = -1;		/* Figure out DST. */
636 	timet = mktime(t);
637 	if (timet == -1)
638 terr:	   errx(1,
639 	"out of range or illegal time specification: [[yy]yy][mmdd]hhmm[.ss]");
640 	return timet;
641 }
642 
643 
644 /*
645  * onintr --
646  *	on interrupt, we inform the user how far we've gotten
647  */
648 void
649 onintr(signo)
650 	int signo;
651 {
652 	char *ct;
653 
654 	/* XXX signal race */
655 	ct = ctime(&buf[0].ut_time);
656 	printf("\ninterrupted %10.10s %8.8s \n", ct, ct + 11);
657 	if (signo == SIGINT)
658 		exit(1);
659 	(void)fflush(stdout);			/* fix required for rsh */
660 }
661