1 /* @(#)last.c 8.2 (Berkeley) 4/2/94 */ 2 /* $NetBSD: last.c,v 1.15 2000/06/30 06:19:58 simonb 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/param.h> 34 #include <sys/stat.h> 35 36 #include <err.h> 37 #include <fcntl.h> 38 #include <paths.h> 39 #include <signal.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <time.h> 44 #include <tzfile.h> 45 #include <unistd.h> 46 #include <utmpx.h> 47 48 #ifndef UT_NAMESIZE 49 #define UT_NAMESIZE 8 50 #define UT_LINESIZE 8 51 #define UT_HOSTSIZE 16 52 #endif 53 #ifndef SIGNATURE 54 #define SIGNATURE -1 55 #endif 56 57 58 59 #define NO 0 /* false/no */ 60 #define YES 1 /* true/yes */ 61 62 #define TBUFLEN 30 /* length of time string buffer */ 63 #define TFMT "%a %b %d %R" /* strftime format string */ 64 #define LTFMT "%a %b %d %Y %T" /* strftime long format string */ 65 #define TFMTS "%R" /* strftime format string - time only */ 66 #define LTFMTS "%T" /* strftime long format string - " */ 67 68 /* fmttime() flags */ 69 #define FULLTIME 0x1 /* show year, seconds */ 70 #define TIMEONLY 0x2 /* show time only, not date */ 71 #define GMT 0x4 /* show time at GMT, for offsets only */ 72 73 #define MAXUTMP 1024; 74 75 typedef struct arg { 76 char *name; /* argument */ 77 #define HOST_TYPE -2 78 #define TTY_TYPE -3 79 #define USER_TYPE -4 80 int type; /* type of arg */ 81 struct arg *next; /* linked list pointer */ 82 } ARG; 83 static ARG *arglist; /* head of linked list */ 84 85 typedef struct ttytab { 86 time_t logout; /* log out time */ 87 char tty[128]; /* terminal name */ 88 struct ttytab *next; /* linked list pointer */ 89 } TTY; 90 static TTY *ttylist; /* head of linked list */ 91 92 static struct utmpx *bufx; 93 static time_t currentout; /* current logout value */ 94 static long maxrec; /* records to display */ 95 static int fulltime = 0; /* Display seconds? */ 96 97 static void addarg(int, char *); 98 static TTY *addtty(const char *); 99 static void hostconv(char *); 100 static char *ttyconv(char *); 101 static void wtmpx(const char *, int, int, int); 102 static char *fmttime(time_t, int); 103 static void usage(void); 104 static void onintrx(int); 105 static int wantx(struct utmpx *, int); 106 107 static 108 void usage(void) 109 { 110 fprintf(stderr, "Usage: %s [-#%s] [-T] [-f file]" 111 " [-h host] [-H hostsize] [-L linesize]\n" 112 "\t [-N namesize] [-t tty] [user ...]\n", getprogname(), 113 #if 0 /* XXX NOTYET_SUPPORT_UTMPX??? */ 114 "w" 115 #else 116 "" 117 #endif 118 ); 119 exit(1); 120 } 121 122 int 123 main(int argc, char *argv[]) 124 { 125 int ch; 126 char *p; 127 const char *file = NULL; 128 int namesize = UT_NAMESIZE; 129 int linesize = UT_LINESIZE; 130 int hostsize = UT_HOSTSIZE; 131 132 maxrec = -1; 133 134 while ((ch = getopt(argc, argv, "0123456789f:h:H:L:N:t:T")) != -1) 135 switch (ch) { 136 case '0': case '1': case '2': case '3': case '4': 137 case '5': case '6': case '7': case '8': case '9': 138 /* 139 * kludge: last was originally designed to take 140 * a number after a dash. 141 */ 142 if (maxrec == -1) { 143 p = argv[optind - 1]; 144 if (p[0] == '-' && p[1] == ch && !p[2]) 145 maxrec = atol(++p); 146 else 147 maxrec = atol(argv[optind] + 1); 148 if (!maxrec) 149 exit(0); 150 } 151 break; 152 case 'f': 153 file = optarg; 154 break; 155 case 'h': 156 hostconv(optarg); 157 addarg(HOST_TYPE, optarg); 158 break; 159 case 't': 160 addarg(TTY_TYPE, ttyconv(optarg)); 161 break; 162 case 'U': 163 namesize = atoi(optarg); 164 break; 165 case 'L': 166 linesize = atoi(optarg); 167 break; 168 case 'H': 169 hostsize = atoi(optarg); 170 break; 171 case 'T': 172 fulltime = 1; 173 break; 174 case '?': 175 default: 176 usage(); 177 } 178 179 if (argc) { 180 setlinebuf(stdout); 181 for (argv += optind; *argv; ++argv) { 182 #define COMPATIBILITY 183 #ifdef COMPATIBILITY 184 /* code to allow "last p5" to work */ 185 addarg(TTY_TYPE, ttyconv(*argv)); 186 #endif 187 addarg(USER_TYPE, *argv); 188 } 189 } 190 if (file == NULL) { 191 if (access(_PATH_WTMPX, R_OK) == 0) 192 file = _PATH_WTMPX; 193 if (file == NULL) 194 errx(1, "Cannot access `%s'", _PATH_WTMPX); 195 } 196 wtmpx(file, namesize, linesize, hostsize); 197 exit(0); 198 } 199 200 201 /* 202 * addarg -- 203 * add an entry to a linked list of arguments 204 */ 205 static void 206 addarg(int type, char *arg) 207 { 208 ARG *cur; 209 210 if (!(cur = (ARG *)malloc((u_int)sizeof(ARG)))) 211 err(1, "malloc failure"); 212 cur->next = arglist; 213 cur->type = type; 214 cur->name = arg; 215 arglist = cur; 216 } 217 218 /* 219 * addtty -- 220 * add an entry to a linked list of ttys 221 */ 222 static TTY * 223 addtty(const char *tty) 224 { 225 TTY *cur; 226 227 if (!(cur = (TTY *)malloc((u_int)sizeof(TTY)))) 228 err(1, "malloc failure"); 229 cur->next = ttylist; 230 cur->logout = currentout; 231 memmove(cur->tty, tty, sizeof(cur->tty)); 232 return (ttylist = cur); 233 } 234 235 /* 236 * hostconv -- 237 * convert the hostname to search pattern; if the supplied host name 238 * has a domain attached that is the same as the current domain, rip 239 * off the domain suffix since that's what login(1) does. 240 */ 241 static void 242 hostconv(char *arg) 243 { 244 static int first = 1; 245 static char *hostdot, name[MAXHOSTNAMELEN + 1]; 246 char *argdot; 247 248 if (!(argdot = strchr(arg, '.'))) 249 return; 250 if (first) { 251 first = 0; 252 if (gethostname(name, sizeof(name))) 253 err(1, "gethostname"); 254 name[sizeof(name) - 1] = '\0'; 255 hostdot = strchr(name, '.'); 256 } 257 if (hostdot && !strcasecmp(hostdot, argdot)) 258 *argdot = '\0'; 259 } 260 261 /* 262 * ttyconv -- 263 * convert tty to correct name. 264 */ 265 static char * 266 ttyconv(char *arg) 267 { 268 char *mval; 269 270 /* 271 * kludge -- we assume that all tty's end with 272 * a two character suffix. 273 */ 274 if (strlen(arg) == 2) { 275 /* either 6 for "ttyxx" or 8 for "console" */ 276 if (!(mval = malloc((u_int)8))) 277 err(1, "malloc failure"); 278 if (!strcmp(arg, "co")) 279 strcpy(mval, "console"); 280 else { 281 strcpy(mval, "tty"); 282 strcpy(mval + 3, arg); 283 } 284 return (mval); 285 } 286 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 287 return (arg + 5); 288 return (arg); 289 } 290 291 /* 292 * fmttime -- 293 * return pointer to (static) formatted time string. 294 */ 295 static char * 296 fmttime(time_t t, int flags) 297 { 298 struct tm *tm; 299 static char tbuf[TBUFLEN]; 300 301 tm = (flags & GMT) ? gmtime(&t) : localtime(&t); 302 if (tm == NULL) { 303 strcpy(tbuf, "????"); 304 return tbuf; 305 } 306 strftime(tbuf, sizeof(tbuf), 307 (flags & TIMEONLY) 308 ? (flags & FULLTIME ? LTFMTS : TFMTS) 309 : (flags & FULLTIME ? LTFMT : TFMT), 310 tm); 311 return (tbuf); 312 } 313 314 /* 315 * wtmpx -- 316 * read through the wtmpx file 317 */ 318 static void 319 wtmpx(const char *file, int namesz, int linesz, int hostsz) 320 { 321 struct utmpx *bp; /* current structure */ 322 TTY *T; /* tty list entry */ 323 struct stat stb; /* stat of file for sz */ 324 time_t delta; /* time difference */ 325 off_t bl; 326 int bytes, wfd; 327 char *ct; 328 const char *crmsg; 329 size_t len = sizeof(*bufx) * MAXUTMP; 330 331 if ((bufx = malloc(len)) == NULL) 332 err(1, "Cannot allocate utmpx buffer"); 333 334 crmsg = NULL; 335 336 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1) 337 err(1, "%s", file); 338 bl = (stb.st_size + len - 1) / len; 339 340 bufx[1].ut_xtime = time(NULL); 341 (void)signal(SIGINT, onintrx); 342 (void)signal(SIGQUIT, onintrx); 343 344 while (--bl >= 0) { 345 if (lseek(wfd, bl * len, SEEK_SET) == -1 || 346 (bytes = read(wfd, bufx, len)) == -1) 347 err(1, "%s", file); 348 for (bp = &bufx[bytes / sizeof(*bufx) - 1]; bp >= bufx; --bp) { 349 /* 350 * if the terminal line is '~', the machine stopped. 351 * see utmpx(5) for more info. 352 */ 353 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 354 /* everybody just logged out */ 355 for (T = ttylist; T; T = T->next) 356 T->logout = -bp->ut_xtime; 357 currentout = -bp->ut_xtime; 358 #ifdef __DragonFly__ /* XXX swildner: this should not be needed afaict */ 359 if (!strncmp(bp->ut_name, "shutdown", namesz)) 360 crmsg = "shutdown"; 361 else if (!strncmp(bp->ut_name, "reboot", namesz)) 362 crmsg = "reboot"; 363 else 364 crmsg = "crash"; 365 #else 366 crmsg = strncmp(bp->ut_name, "shutdown", 367 namesz) ? "crash" : "shutdown"; 368 #endif 369 if (wantx(bp, NO)) { 370 ct = fmttime(bp->ut_xtime, fulltime); 371 printf("%-*.*s %-*.*s %-*.*s %s\n", 372 namesz, namesz, bp->ut_name, 373 linesz, linesz, bp->ut_line, 374 hostsz, hostsz, bp->ut_host, ct); 375 if (maxrec != -1 && !--maxrec) 376 return; 377 } 378 continue; 379 } 380 /* 381 * if the line is '{' or '|', date got set; see 382 * utmpx(5) for more info. 383 */ 384 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') 385 && !bp->ut_line[1]) { 386 if (wantx(bp, NO)) { 387 ct = fmttime(bp->ut_xtime, fulltime); 388 printf("%-*.*s %-*.*s %-*.*s %s\n", 389 namesz, namesz, 390 bp->ut_name, 391 linesz, linesz, 392 bp->ut_line, 393 hostsz, hostsz, 394 bp->ut_host, 395 ct); 396 if (maxrec && !--maxrec) 397 return; 398 } 399 continue; 400 } 401 /* find associated tty */ 402 for (T = ttylist;; T = T->next) { 403 if (!T) { 404 /* add new one */ 405 T = addtty(bp->ut_line); 406 break; 407 } 408 if (!strncmp(T->tty, bp->ut_line, UTX_LINESIZE)) 409 break; 410 } 411 if (bp->ut_type == SIGNATURE) 412 continue; 413 if (bp->ut_name[0] && wantx(bp, YES)) { 414 ct = fmttime(bp->ut_xtime, fulltime); 415 printf("%-*.*s %-*.*s %-*.*s %s ", 416 namesz, namesz, bp->ut_name, 417 linesz, linesz, bp->ut_line, 418 hostsz, hostsz, bp->ut_host, 419 ct); 420 if (!T->logout) 421 puts(" still logged in"); 422 else { 423 if (T->logout < 0) { 424 T->logout = -T->logout; 425 printf("- %s", crmsg); 426 } 427 else 428 printf("- %s", 429 fmttime(T->logout, 430 fulltime | TIMEONLY)); 431 delta = T->logout - bp->ut_xtime; 432 if (delta < SECSPERDAY) 433 printf(" (%s)\n", 434 fmttime(delta, 435 fulltime | TIMEONLY | GMT)); 436 else 437 printf(" (%ld+%s)\n", 438 delta / SECSPERDAY, 439 fmttime(delta, 440 fulltime | TIMEONLY | GMT)); 441 } 442 if (maxrec != -1 && !--maxrec) 443 return; 444 } 445 T->logout = bp->ut_xtime; 446 } 447 } 448 fulltime = 1; /* show full time */ 449 crmsg = fmttime(bufx[1].ut_xtime, FULLTIME); 450 if ((ct = strrchr(file, '/')) != NULL) 451 ct++; 452 printf("\n%s begins %s\n", ct ? ct : file, crmsg); 453 } 454 455 /* 456 * wantx -- 457 * see if want this entry 458 */ 459 static int 460 wantx(struct utmpx *bp, int check) 461 { 462 ARG *step; 463 464 if (check) { 465 /* 466 * when uucp and ftp log in over a network, the entry in 467 * the utmpx file is the name plus their process id. See 468 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. 469 */ 470 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) 471 bp->ut_line[3] = '\0'; 472 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) 473 bp->ut_line[4] = '\0'; 474 } 475 if (!arglist) 476 return (YES); 477 478 for (step = arglist; step; step = step->next) 479 switch(step->type) { 480 case HOST_TYPE: 481 if (!strncasecmp(step->name, bp->ut_host, UTX_HOSTSIZE)) 482 return (YES); 483 break; 484 case TTY_TYPE: 485 if (!strncmp(step->name, bp->ut_line, UTX_LINESIZE)) 486 return (YES); 487 break; 488 case USER_TYPE: 489 if (!strncmp(step->name, bp->ut_name, UTX_USERSIZE)) 490 return (YES); 491 break; 492 } 493 return (NO); 494 } 495 496 /* 497 * onintrx -- 498 * on interrupt, we inform the user how far we've gotten 499 */ 500 static void 501 onintrx(int signo) 502 { 503 504 printf("\ninterrupted %s\n", fmttime(bufx[1].ut_xtime, 505 FULLTIME)); 506 if (signo == SIGINT) 507 exit(1); 508 (void)fflush(stdout); /* fix required for rsh */ 509 } 510