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 strftime(tbuf, sizeof(tbuf), 303 (flags & TIMEONLY) 304 ? (flags & FULLTIME ? LTFMTS : TFMTS) 305 : (flags & FULLTIME ? LTFMT : TFMT), 306 tm); 307 return (tbuf); 308 } 309 310 /* 311 * wtmpx -- 312 * read through the wtmpx file 313 */ 314 static void 315 wtmpx(const char *file, int namesz, int linesz, int hostsz) 316 { 317 struct utmpx *bp; /* current structure */ 318 TTY *T; /* tty list entry */ 319 struct stat stb; /* stat of file for sz */ 320 time_t delta; /* time difference */ 321 off_t bl; 322 int bytes, wfd; 323 char *ct; 324 const char *crmsg; 325 size_t len = sizeof(*bufx) * MAXUTMP; 326 327 if ((bufx = malloc(len)) == NULL) 328 err(1, "Cannot allocate utmpx buffer"); 329 330 crmsg = NULL; 331 332 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1) 333 err(1, "%s", file); 334 bl = (stb.st_size + len - 1) / len; 335 336 bufx[1].ut_xtime = time(NULL); 337 (void)signal(SIGINT, onintrx); 338 (void)signal(SIGQUIT, onintrx); 339 340 while (--bl >= 0) { 341 if (lseek(wfd, bl * len, SEEK_SET) == -1 || 342 (bytes = read(wfd, bufx, len)) == -1) 343 err(1, "%s", file); 344 for (bp = &bufx[bytes / sizeof(*bufx) - 1]; bp >= bufx; --bp) { 345 /* 346 * if the terminal line is '~', the machine stopped. 347 * see utmpx(5) for more info. 348 */ 349 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 350 /* everybody just logged out */ 351 for (T = ttylist; T; T = T->next) 352 T->logout = -bp->ut_xtime; 353 currentout = -bp->ut_xtime; 354 #ifdef __DragonFly__ /* XXX swildner: this should not be needed afaict */ 355 if (!strncmp(bp->ut_name, "shutdown", namesz)) 356 crmsg = "shutdown"; 357 else if (!strncmp(bp->ut_name, "reboot", namesz)) 358 crmsg = "reboot"; 359 else 360 crmsg = "crash"; 361 #else 362 crmsg = strncmp(bp->ut_name, "shutdown", 363 namesz) ? "crash" : "shutdown"; 364 #endif 365 if (wantx(bp, NO)) { 366 ct = fmttime(bp->ut_xtime, fulltime); 367 printf("%-*.*s %-*.*s %-*.*s %s\n", 368 namesz, namesz, bp->ut_name, 369 linesz, linesz, bp->ut_line, 370 hostsz, hostsz, bp->ut_host, ct); 371 if (maxrec != -1 && !--maxrec) 372 return; 373 } 374 continue; 375 } 376 /* 377 * if the line is '{' or '|', date got set; see 378 * utmpx(5) for more info. 379 */ 380 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') 381 && !bp->ut_line[1]) { 382 if (wantx(bp, NO)) { 383 ct = fmttime(bp->ut_xtime, fulltime); 384 printf("%-*.*s %-*.*s %-*.*s %s\n", 385 namesz, namesz, 386 bp->ut_name, 387 linesz, linesz, 388 bp->ut_line, 389 hostsz, hostsz, 390 bp->ut_host, 391 ct); 392 if (maxrec && !--maxrec) 393 return; 394 } 395 continue; 396 } 397 /* find associated tty */ 398 for (T = ttylist;; T = T->next) { 399 if (!T) { 400 /* add new one */ 401 T = addtty(bp->ut_line); 402 break; 403 } 404 if (!strncmp(T->tty, bp->ut_line, UTX_LINESIZE)) 405 break; 406 } 407 if (bp->ut_type == SIGNATURE) 408 continue; 409 if (bp->ut_name[0] && wantx(bp, YES)) { 410 ct = fmttime(bp->ut_xtime, fulltime); 411 printf("%-*.*s %-*.*s %-*.*s %s ", 412 namesz, namesz, bp->ut_name, 413 linesz, linesz, bp->ut_line, 414 hostsz, hostsz, bp->ut_host, 415 ct); 416 if (!T->logout) 417 puts(" still logged in"); 418 else { 419 if (T->logout < 0) { 420 T->logout = -T->logout; 421 printf("- %s", crmsg); 422 } 423 else 424 printf("- %s", 425 fmttime(T->logout, 426 fulltime | TIMEONLY)); 427 delta = T->logout - bp->ut_xtime; 428 if (delta < SECSPERDAY) 429 printf(" (%s)\n", 430 fmttime(delta, 431 fulltime | TIMEONLY | GMT)); 432 else 433 printf(" (%ld+%s)\n", 434 delta / SECSPERDAY, 435 fmttime(delta, 436 fulltime | TIMEONLY | GMT)); 437 } 438 if (maxrec != -1 && !--maxrec) 439 return; 440 } 441 T->logout = bp->ut_xtime; 442 } 443 } 444 fulltime = 1; /* show full time */ 445 crmsg = fmttime(bufx[1].ut_xtime, FULLTIME); 446 if ((ct = strrchr(file, '/')) != NULL) 447 ct++; 448 printf("\n%s begins %s\n", ct ? ct : file, crmsg); 449 } 450 451 /* 452 * wantx -- 453 * see if want this entry 454 */ 455 static int 456 wantx(struct utmpx *bp, int check) 457 { 458 ARG *step; 459 460 if (check) { 461 /* 462 * when uucp and ftp log in over a network, the entry in 463 * the utmpx file is the name plus their process id. See 464 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. 465 */ 466 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) 467 bp->ut_line[3] = '\0'; 468 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) 469 bp->ut_line[4] = '\0'; 470 } 471 if (!arglist) 472 return (YES); 473 474 for (step = arglist; step; step = step->next) 475 switch(step->type) { 476 case HOST_TYPE: 477 if (!strncasecmp(step->name, bp->ut_host, UTX_HOSTSIZE)) 478 return (YES); 479 break; 480 case TTY_TYPE: 481 if (!strncmp(step->name, bp->ut_line, UTX_LINESIZE)) 482 return (YES); 483 break; 484 case USER_TYPE: 485 if (!strncmp(step->name, bp->ut_name, UTX_USERSIZE)) 486 return (YES); 487 break; 488 } 489 return (NO); 490 } 491 492 /* 493 * onintrx -- 494 * on interrupt, we inform the user how far we've gotten 495 */ 496 static void 497 onintrx(int signo) 498 { 499 500 printf("\ninterrupted %s\n", fmttime(bufx[1].ut_xtime, 501 FULLTIME)); 502 if (signo == SIGINT) 503 exit(1); 504 (void)fflush(stdout); /* fix required for rsh */ 505 } 506