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