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