GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/last/last.c Lines: 0 249 0.0 %
Date: 2017-11-07 Branches: 0 196 0.0 %

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