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