1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 *	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
29 *	  All Rights Reserved
30 */
31
32/*
33 * University Copyright- Copyright (c) 1982, 1986, 1988
34 * The Regents of the University of California
35 * All Rights Reserved
36 *
37 * University Acknowledgment- Portions of this document are derived from
38 * software developed by the University of California, Berkeley, and its
39 * contributors.
40 */
41
42#pragma ident	"%Z%%M%	%I%	%E% SMI"
43
44/*
45 * last
46 */
47#include <sys/types.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <unistd.h>
51#include <strings.h>
52#include <signal.h>
53#include <sys/stat.h>
54#include <pwd.h>
55#include <fcntl.h>
56#include <utmpx.h>
57#include <locale.h>
58#include <ctype.h>
59
60/*
61 * NMAX, LMAX and HMAX are set to these values for now. They
62 * should be much higher because of the max allowed limit in
63 * utmpx.h
64 */
65#define	NMAX	8
66#define	LMAX	12
67#define	HMAX	(sizeof (((struct utmpx *)0)->ut_host))
68#define	SECDAY	(24*60*60)
69#define	CHUNK_SIZE 256
70
71#define	lineq(a, b)	(strncmp(a, b, LMAX) == 0)
72#define	nameq(a, b)	(strncmp(a, b, NMAX) == 0)
73#define	hosteq(a, b)	(strncmp(a, b, HMAX) == 0)
74#define	linehostnameq(a, b, c, d) \
75	    (lineq(a, b)&&hosteq(a+LMAX+1, c)&&nameq(a+LMAX+HMAX+2, d))
76
77#define	USAGE	"usage: last [-n number] [-f filename] [-a ] [name | tty] ...\n"
78
79/* Beware: These are set in main() to exclude the executable name.  */
80static char	**argv;
81static int	argc;
82static char	**names;
83static int	names_num;
84
85static struct	utmpx buf[128];
86
87/*
88 * ttnames and logouts are allocated in the blocks of
89 * CHUNK_SIZE lines whenever needed. The count of the
90 * current size is maintained in the variable "lines"
91 * The variable bootxtime is used to hold the time of
92 * the last BOOT_TIME
93 * All elements of the logouts are initialised to bootxtime
94 * everytime the buffer is reallocated.
95 */
96
97static char	**ttnames;
98static time_t	*logouts;
99static time_t	bootxtime;
100static int	lines;
101static char	timef[128];
102static char	hostf[HMAX + 1];
103
104static char *strspl(char *, char *);
105static void onintr(int);
106static void reallocate_buffer();
107static void memory_alloc(int);
108static int want(struct utmpx *, char **, char **);
109static void record_time(time_t *, int *, int, struct utmpx *);
110
111int
112main(int ac, char **av)
113{
114	int i, j;
115	int aflag = 0;
116	int fpos;	/* current position in time format buffer */
117	int chrcnt;	/* # of chars formatted by current sprintf */
118	int bl, wtmp;
119	char *ct;
120	char *ut_host;
121	char *ut_user;
122	struct utmpx *bp;
123	time_t otime;
124	struct stat stb;
125	int print = 0;
126	char *crmsg = (char *)0;
127	long outrec = 0;
128	long maxrec = 0x7fffffffL;
129	char *wtmpfile = "/var/adm/wtmpx";
130	size_t hostf_len;
131
132	(void) setlocale(LC_ALL, "");
133#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
134#define	TEXT_DOMAIN "SYS_TEST"		/* Use this only if it weren't. */
135#endif
136	(void) textdomain(TEXT_DOMAIN);
137
138	(void) time(&buf[0].ut_xtime);
139	ac--, av++;
140	argc = ac;
141	argv = av;
142	names = malloc(argc * sizeof (char *));
143	if (names == NULL) {
144		perror("last");
145		exit(2);
146	}
147	names_num = 0;
148	for (i = 0; i < argc; i++) {
149		if (argv[i][0] == '-') {
150
151			/* -[0-9]*   sets max # records to print */
152			if (isdigit(argv[i][1])) {
153				maxrec = atoi(argv[i]+1);
154				continue;
155			}
156
157			for (j = 1; argv[i][j] != '\0'; ++j) {
158				switch (argv[i][j]) {
159
160				/* -f name sets filename of wtmp file */
161				case 'f':
162					if (argv[i][j+1] != '\0') {
163						wtmpfile = &argv[i][j+1];
164					} else if (i+1 < argc) {
165						wtmpfile = argv[++i];
166					} else {
167						(void) fprintf(stderr,
168						    gettext("last: argument to "
169						    "-f is missing\n"));
170						(void) fprintf(stderr,
171						    gettext(USAGE));
172						exit(1);
173					}
174					goto next_word;
175
176				/* -n number sets max # records to print */
177				case 'n': {
178					char *arg;
179
180					if (argv[i][j+1] != '\0') {
181						arg = &argv[i][j+1];
182					} else if (i+1 < argc) {
183						arg = argv[++i];
184					} else {
185						(void) fprintf(stderr,
186						    gettext("last: argument to "
187						    "-n is missing\n"));
188						(void) fprintf(stderr,
189						    gettext(USAGE));
190						exit(1);
191					}
192
193					if (!isdigit(*arg)) {
194						(void) fprintf(stderr,
195						    gettext("last: argument to "
196						    "-n is not a number\n"));
197						(void) fprintf(stderr,
198						    gettext(USAGE));
199						exit(1);
200					}
201					maxrec = atoi(arg);
202					goto next_word;
203				}
204
205				/* -a displays hostname last on the line */
206				case 'a':
207					aflag++;
208					break;
209
210				default:
211					(void) fprintf(stderr, gettext(USAGE));
212					exit(1);
213				}
214			}
215
216next_word:
217			continue;
218		}
219
220		if (strlen(argv[i]) > 2 || strcmp(argv[i], "~") == 0 ||
221		    getpwnam(argv[i]) != NULL) {
222			/* Not a tty number. */
223			names[names_num] = argv[i];
224			++names_num;
225		} else {
226			/* tty number.  Prepend "tty". */
227			names[names_num] = strspl("tty", argv[i]);
228			++names_num;
229		}
230	}
231
232	wtmp = open(wtmpfile, 0);
233	if (wtmp < 0) {
234		perror(wtmpfile);
235		exit(1);
236	}
237	(void) fstat(wtmp, &stb);
238	bl = (stb.st_size + sizeof (buf)-1) / sizeof (buf);
239	if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
240		(void) signal(SIGINT, onintr);
241		(void) signal(SIGQUIT, onintr);
242	}
243	lines = CHUNK_SIZE;
244	ttnames = calloc(lines, sizeof (char *));
245	logouts = calloc(lines, sizeof (time_t));
246	if (ttnames == NULL || logouts == NULL) {
247		(void) fprintf(stderr, gettext("Out of memory \n "));
248		exit(2);
249	}
250		for (bl--; bl >= 0; bl--) {
251		(void) lseek(wtmp, (off_t)(bl * sizeof (buf)), 0);
252		bp = &buf[read(wtmp, buf, sizeof (buf)) / sizeof (buf[0]) - 1];
253		for (; bp >= buf; bp--) {
254			if (want(bp, &ut_host, &ut_user)) {
255				for (i = 0; i <= lines; i++) {
256				if (i == lines)
257				    reallocate_buffer();
258				if (ttnames[i] == NULL) {
259				    memory_alloc(i);
260					/*
261					 * LMAX+HMAX+NMAX+3 bytes have been
262					 * allocated for ttnames[i].
263					 * If bp->ut_line is longer than LMAX,
264					 * ut_host is longer than HMAX,
265					 * and ut_user is longer than NMAX,
266					 * truncate it to fit ttnames[i].
267					 */
268					(void) strlcpy(ttnames[i], bp->ut_line,
269						LMAX+1);
270					(void) strlcpy(ttnames[i]+LMAX+1,
271						ut_host, HMAX+1);
272					(void) strlcpy(ttnames[i]+LMAX+HMAX+2,
273						ut_user, NMAX+1);
274						record_time(&otime, &print,
275							i, bp);
276						break;
277					} else if (linehostnameq(ttnames[i],
278					    bp->ut_line, ut_host, ut_user)) {
279						record_time(&otime,
280						    &print, i, bp);
281						break;
282					}
283				}
284			}
285			if (print) {
286				if (strncmp(bp->ut_line, "ftp", 3) == 0)
287					bp->ut_line[3] = '\0';
288				if (strncmp(bp->ut_line, "uucp", 4) == 0)
289					bp->ut_line[4] = '\0';
290
291				ct = ctime(&bp->ut_xtime);
292				(void) printf(gettext("%-*.*s  %-*.*s "),
293				    NMAX, NMAX, bp->ut_name,
294				    LMAX, LMAX, bp->ut_line);
295				hostf_len = strlen(bp->ut_host);
296				(void) snprintf(hostf, sizeof (hostf),
297				    "%-*.*s", hostf_len, hostf_len,
298				    bp->ut_host);
299				fpos = snprintf(timef, sizeof (timef),
300					"%10.10s %5.5s ",
301				    ct, 11 + ct);
302				if (!lineq(bp->ut_line, "system boot") &&
303				    !lineq(bp->ut_line, "system down")) {
304					if (otime == 0 &&
305					    bp->ut_type == USER_PROCESS) {
306
307	if (fpos < sizeof (timef)) {
308		/* timef still has room */
309		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
310			gettext("  still logged in"));
311	}
312
313					} else {
314					time_t delta;
315					if (otime < 0) {
316						otime = -otime;
317						/*
318						 * TRANSLATION_NOTE
319						 * See other notes on "down"
320						 * and "- %5.5s".
321						 * "-" means "until".  This
322						 * is displayed after the
323						 * starting time as in:
324						 * 	16:20 - down
325						 * You probably don't want to
326						 * translate this.  Should you
327						 * decide to translate this,
328						 * translate "- %5.5s" too.
329						 */
330
331	if (fpos < sizeof (timef)) {
332		/* timef still has room */
333		chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos,
334			gettext("- %s"), crmsg);
335		fpos += chrcnt;
336	}
337
338					} else {
339
340	if (fpos < sizeof (timef)) {
341		/* timef still has room */
342		chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos,
343			gettext("- %5.5s"), ctime(&otime) + 11);
344		fpos += chrcnt;
345	}
346
347					}
348					delta = otime - bp->ut_xtime;
349					if (delta < SECDAY) {
350
351	if (fpos < sizeof (timef)) {
352		/* timef still has room */
353		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
354			gettext("  (%5.5s)"), asctime(gmtime(&delta)) + 11);
355	}
356
357					} else {
358
359	if (fpos < sizeof (timef)) {
360		/* timef still has room */
361		(void) snprintf(timef + fpos, sizeof (timef) - fpos,
362			gettext(" (%ld+%5.5s)"), delta / SECDAY,
363		    asctime(gmtime(&delta)) + 11);
364	}
365
366					}
367				}
368				}
369				if (aflag)
370					(void) printf("%-35.35s %-.*s\n",
371					    timef, strlen(hostf), hostf);
372				else
373					(void) printf("%-16.16s %-.35s\n",
374					    hostf, timef);
375				(void) fflush(stdout);
376				if (++outrec >= maxrec)
377					exit(0);
378			}
379			/*
380			 * when the system is down or crashed.
381			 */
382			if (bp->ut_type == BOOT_TIME) {
383				for (i = 0; i < lines; i++)
384					logouts[i] = -bp->ut_xtime;
385				bootxtime = -bp->ut_xtime;
386				/*
387				 * TRANSLATION_NOTE
388				 * Translation of this "down " will replace
389				 * the %s in "- %s".  "down" is used instead
390				 * of the real time session was ended, probably
391				 * because the session ended by a sudden crash.
392				 */
393				crmsg = gettext("down ");
394			}
395			print = 0;	/* reset the print flag */
396		}
397	}
398	ct = ctime(&buf[0].ut_xtime);
399	(void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct, ct + 11);
400
401	/* free() called to prevent lint warning about names */
402	free(names);
403
404	return (0);
405}
406
407static void
408reallocate_buffer()
409{
410	int j;
411	static char	**tmpttnames;
412	static time_t	*tmplogouts;
413
414	lines += CHUNK_SIZE;
415	tmpttnames = realloc(ttnames, sizeof (char *)*lines);
416	tmplogouts = realloc(logouts, sizeof (time_t)*lines);
417	if (tmpttnames == NULL || tmplogouts == NULL) {
418		(void) fprintf(stderr, gettext("Out of memory \n"));
419		exit(2);
420	} else {
421	    ttnames = tmpttnames;
422	    logouts = tmplogouts;
423	}
424	for (j = lines-CHUNK_SIZE; j < lines; j++) {
425		ttnames[j] = NULL;
426		logouts[j] = bootxtime;
427	}
428}
429
430static void
431memory_alloc(int i)
432{
433	ttnames[i] = (char *)malloc(LMAX + HMAX + NMAX + 3);
434	if (ttnames[i] == NULL) {
435		(void) fprintf(stderr, gettext("Out of memory \n "));
436		exit(2);
437	}
438}
439
440static void
441onintr(int signo)
442{
443	char *ct;
444
445	if (signo == SIGQUIT)
446		(void) signal(SIGQUIT, (void(*)())onintr);
447	ct = ctime(&buf[0].ut_xtime);
448	(void) printf(gettext("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11);
449	(void) fflush(stdout);
450	if (signo == SIGINT)
451		exit(1);
452}
453
454static int
455want(struct utmpx *bp, char **host, char **user)
456{
457	char **name;
458	int i;
459	char *zerostr = "\0";
460
461	*host = zerostr; *user = zerostr;
462
463		/* if ut_line = dtremote for the users who did dtremote login */
464	if (strncmp(bp->ut_line, "dtremote", 8) == 0) {
465		*host = bp->ut_host;
466		*user = bp->ut_user;
467	}
468		/* if ut_line = dtlocal for the users who did a dtlocal login */
469	else if (strncmp(bp->ut_line, "dtlocal", 7) == 0) {
470		*host = bp->ut_host;
471		*user = bp->ut_user;
472	}
473		/*
474		 * Both dtremote and dtlocal can have multiple entries in
475		 * /var/adm/wtmpx with these values, so the user and host
476		 * entries are also checked
477		 */
478	if ((bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME))
479		(void) strcpy(bp->ut_user, "reboot");
480
481	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS &&
482	    bp->ut_type != BOOT_TIME && bp->ut_type != DOWN_TIME)
483		return (0);
484
485	if (bp->ut_user[0] == '.')
486		return (0);
487
488	if (names_num == 0) {
489		if (bp->ut_line[0] != '\0')
490			return (1);
491	} else {
492		name = names;
493		for (i = 0; i < names_num; i++, name++) {
494			if (nameq(*name, bp->ut_name) ||
495			    lineq(*name, bp->ut_line) ||
496			    (lineq(*name, "ftp") &&
497			    (strncmp(bp->ut_line, "ftp", 3) == 0))) {
498				return (1);
499			}
500		}
501	}
502	return (0);
503}
504
505static char *
506strspl(char *left, char *right)
507{
508	size_t ressize = strlen(left) + strlen(right) + 1;
509
510	char *res = malloc(ressize);
511
512	if (res == NULL) {
513		perror("last");
514		exit(2);
515	}
516	(void) strlcpy(res, left, ressize);
517	(void) strlcat(res, right, ressize);
518	return (res);
519}
520
521static void
522record_time(time_t *otime, int *print, int i, struct utmpx *bp)
523{
524	*otime = logouts[i];
525	logouts[i] = bp->ut_xtime;
526	if ((bp->ut_type == USER_PROCESS && bp->ut_user[0] != '\0') ||
527	    (bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME))
528		*print = 1;
529	else
530		*print = 0;
531}
532