1 /* 2 * Copyright (c) 1987, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * $FreeBSD: src/usr.bin/last/last.c,v 1.10.6.11 2002/11/02 23:00:26 mux Exp $ 34 * $DragonFly: src/usr.bin/last/last.c,v 1.5 2004/09/14 00:13:36 drhodus Exp $ 35 * 36 * @(#) Copyright (c) 1987, 1993, 1994 The Regents of the University of California. All rights reserved. 37 * @(#)last.c 8.2 (Berkeley) 4/2/94 38 */ 39 40 #include <sys/param.h> 41 #include <sys/stat.h> 42 43 #include <err.h> 44 #include <fcntl.h> 45 #include <langinfo.h> 46 #include <locale.h> 47 #include <paths.h> 48 #include <signal.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <time.h> 53 #include <unistd.h> 54 #include <utmp.h> 55 #include <sys/queue.h> 56 57 #define NO 0 /* false/no */ 58 #define YES 1 /* true/yes */ 59 #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 60 61 static struct utmp buf[1024]; /* utmp read buffer */ 62 63 typedef struct arg { 64 char *name; /* argument */ 65 #define HOST_TYPE -2 66 #define TTY_TYPE -3 67 #define USER_TYPE -4 68 int type; /* type of arg */ 69 struct arg *next; /* linked list pointer */ 70 } ARG; 71 ARG *arglist; /* head of linked list */ 72 73 LIST_HEAD(ttylisthead, ttytab) ttylist; 74 75 struct ttytab { 76 time_t logout; /* log out time */ 77 char tty[UT_LINESIZE + 1]; /* terminal name */ 78 LIST_ENTRY(ttytab) list; 79 }; 80 81 static const char *crmsg; /* cause of last reboot */ 82 static long currentout, /* current logout value */ 83 maxrec; /* records to display */ 84 static const char *file = _PATH_WTMP; /* wtmp file */ 85 static int sflag = 0; /* show delta in seconds */ 86 static int width = 5; /* show seconds in delta */ 87 static int yflag; /* show year */ 88 static int d_first; 89 static int snapfound = 0; /* found snapshot entry? */ 90 static time_t snaptime; /* if != 0, we will only 91 * report users logged in 92 * at this snapshot time 93 */ 94 95 int main(int, char *[]); 96 void addarg(int, char *); 97 time_t dateconv(char *); 98 void doentry(struct utmp *); 99 void hostconv(char *); 100 void onintr(int); 101 void printentry(struct utmp *, struct ttytab *); 102 char *ttyconv(char *); 103 char *ttyconv(char *); 104 int want(struct utmp *); 105 void usage(void); 106 void wtmp(void); 107 108 void 109 usage(void) 110 { 111 (void)fprintf(stderr, 112 "usage: last [-#] [-y] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n" 113 "\t[-t tty] [-s|w] [user ...]\n"); 114 exit(1); 115 } 116 117 int 118 main(int argc, char **argv) 119 { 120 int ch; 121 char *p; 122 123 (void) setlocale(LC_TIME, ""); 124 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 125 126 maxrec = -1; 127 snaptime = 0; 128 while ((ch = getopt(argc, argv, "0123456789d:f:h:st:wy")) != -1) 129 switch (ch) { 130 case '0': case '1': case '2': case '3': case '4': 131 case '5': case '6': case '7': case '8': case '9': 132 /* 133 * kludge: last was originally designed to take 134 * a number after a dash. 135 */ 136 if (maxrec == -1) { 137 p = strchr(argv[optind - 1], ch); 138 if (p == NULL) 139 p = strchr(argv[optind], ch); 140 maxrec = atol(p); 141 if (!maxrec) 142 exit(0); 143 } 144 break; 145 case 'd': 146 snaptime = dateconv(optarg); 147 break; 148 case 'f': 149 file = optarg; 150 break; 151 case 'h': 152 hostconv(optarg); 153 addarg(HOST_TYPE, optarg); 154 break; 155 case 's': 156 sflag++; /* Show delta as seconds */ 157 break; 158 case 't': 159 addarg(TTY_TYPE, ttyconv(optarg)); 160 break; 161 case 'w': 162 width = 8; 163 break; 164 case 'y': 165 yflag++; 166 break; 167 case '?': 168 default: 169 usage(); 170 } 171 172 if (sflag && width == 8) usage(); 173 174 if (argc) { 175 setlinebuf(stdout); 176 for (argv += optind; *argv; ++argv) { 177 #define COMPATIBILITY 178 #ifdef COMPATIBILITY 179 /* code to allow "last p5" to work */ 180 addarg(TTY_TYPE, ttyconv(*argv)); 181 #endif 182 addarg(USER_TYPE, *argv); 183 } 184 } 185 wtmp(); 186 exit(0); 187 } 188 189 /* 190 * wtmp -- 191 * read through the wtmp file 192 */ 193 void 194 wtmp(void) 195 { 196 struct utmp *bp; /* current structure */ 197 struct stat stb; /* stat of file for size */ 198 long bl; 199 int bytes, wfd; 200 char ct[80]; 201 struct tm *tm; 202 203 LIST_INIT(&ttylist); 204 205 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1) 206 err(1, "%s", file); 207 bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf); 208 209 (void)time(&buf[0].ut_time); 210 (void)signal(SIGINT, onintr); 211 (void)signal(SIGQUIT, onintr); 212 213 while (--bl >= 0) { 214 if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 || 215 (bytes = read(wfd, buf, sizeof(buf))) == -1) 216 err(1, "%s", file); 217 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) 218 doentry(bp); 219 } 220 tm = localtime(&buf[0].ut_time); 221 (void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm); 222 printf("%s", ct); 223 } 224 225 /* 226 * doentry -- 227 * process a single wtmp entry 228 */ 229 void 230 doentry(struct utmp *bp) 231 { 232 struct ttytab *tt, *ttx; /* ttylist entry */ 233 234 /* 235 * if the terminal line is '~', the machine stopped. 236 * see utmp(5) for more info. 237 */ 238 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 239 /* everybody just logged out */ 240 for (tt = LIST_FIRST(&ttylist); tt;) { 241 LIST_REMOVE(tt, list); 242 ttx = tt; 243 tt = LIST_NEXT(tt, list); 244 free(ttx); 245 } 246 currentout = -bp->ut_time; 247 crmsg = strncmp(bp->ut_name, "shutdown", UT_NAMESIZE) ? 248 "crash" : "shutdown"; 249 /* 250 * if we're in snapshot mode, we want to exit if this 251 * shutdown/reboot appears while we we are tracking the 252 * active range 253 */ 254 if (snaptime && snapfound) 255 exit(0); 256 /* 257 * don't print shutdown/reboot entries unless flagged for 258 */ 259 if (!snaptime && want(bp)) 260 printentry(bp, NULL); 261 return; 262 } 263 /* 264 * if the line is '{' or '|', date got set; see 265 * utmp(5) for more info. 266 */ 267 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') && 268 !bp->ut_line[1]) { 269 if (want(bp) && !snaptime) 270 printentry(bp, NULL); 271 return; 272 } 273 /* find associated tty */ 274 LIST_FOREACH(tt, &ttylist, list) 275 if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE)) 276 break; 277 278 if (tt == NULL) { 279 /* add new one */ 280 tt = malloc(sizeof(struct ttytab)); 281 if (tt == NULL) 282 err(1, "malloc failure"); 283 tt->logout = currentout; 284 strncpy(tt->tty, bp->ut_line, UT_LINESIZE); 285 LIST_INSERT_HEAD(&ttylist, tt, list); 286 } 287 288 /* 289 * print record if not in snapshot mode and wanted 290 * or in snapshot mode and in snapshot range 291 */ 292 if (bp->ut_name[0] && (want(bp) || (bp->ut_time < snaptime && 293 (tt->logout > snaptime || tt->logout < 1)))) { 294 snapfound = 1; 295 /* 296 * when uucp and ftp log in over a network, the entry in 297 * the utmp file is the name plus their process id. See 298 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. 299 */ 300 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) 301 bp->ut_line[3] = '\0'; 302 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) 303 bp->ut_line[4] = '\0'; 304 printentry(bp, tt); 305 } 306 tt->logout = bp->ut_time; 307 } 308 309 /* 310 * printentry -- 311 * output an entry 312 * 313 * If `tt' is non-NULL, use it and `crmsg' to print the logout time or 314 * logout type (crash/shutdown) as appropriate. 315 */ 316 void 317 printentry(struct utmp *bp, struct ttytab *tt) 318 { 319 char ct[80]; 320 struct tm *tm; 321 time_t delta; /* time difference */ 322 323 if (maxrec != -1 && !maxrec--) 324 exit(0); 325 tm = localtime(&bp->ut_time); 326 (void) strftime(ct, sizeof(ct), d_first ? 327 (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") : 328 (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm); 329 printf("%-*.*s %-*.*s %-*.*s %s%c", 330 UT_NAMESIZE, UT_NAMESIZE, bp->ut_name, 331 UT_LINESIZE, UT_LINESIZE, bp->ut_line, 332 UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host, 333 ct, tt == NULL ? '\n' : ' '); 334 if (tt == NULL) 335 return; 336 if (!tt->logout) { 337 puts(" still logged in"); 338 return; 339 } 340 if (tt->logout < 0) { 341 tt->logout = -tt->logout; 342 printf("- %s", crmsg); 343 } else { 344 tm = localtime(&tt->logout); 345 (void) strftime(ct, sizeof(ct), "%R", tm); 346 printf("- %s", ct); 347 } 348 delta = tt->logout - bp->ut_time; 349 if (sflag) { 350 printf(" (%8ld)\n", (long)delta); 351 } else { 352 tm = gmtime(&delta); 353 (void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm); 354 if (delta < 86400) 355 printf(" (%s)\n", ct); 356 else 357 printf(" (%ld+%s)\n", (long)delta / 86400, ct); 358 } 359 } 360 361 /* 362 * want -- 363 * see if want this entry 364 */ 365 int 366 want(struct utmp *bp) 367 { 368 ARG *step; 369 370 if (snaptime) 371 return (NO); 372 373 if (!arglist) 374 return (YES); 375 376 for (step = arglist; step; step = step->next) 377 switch(step->type) { 378 case HOST_TYPE: 379 if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE)) 380 return (YES); 381 break; 382 case TTY_TYPE: 383 if (!strncmp(step->name, bp->ut_line, UT_LINESIZE)) 384 return (YES); 385 break; 386 case USER_TYPE: 387 if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE)) 388 return (YES); 389 break; 390 } 391 return (NO); 392 } 393 394 /* 395 * addarg -- 396 * add an entry to a linked list of arguments 397 */ 398 void 399 addarg(int type, char *arg) 400 { 401 ARG *cur; 402 403 if (!(cur = (ARG *)malloc((u_int)sizeof(ARG)))) 404 err(1, "malloc failure"); 405 cur->next = arglist; 406 cur->type = type; 407 cur->name = arg; 408 arglist = cur; 409 } 410 411 /* 412 * hostconv -- 413 * convert the hostname to search pattern; if the supplied host name 414 * has a domain attached that is the same as the current domain, rip 415 * off the domain suffix since that's what login(1) does. 416 */ 417 void 418 hostconv(char *arg) 419 { 420 static int first = 1; 421 static char *hostdot, name[MAXHOSTNAMELEN]; 422 char *argdot; 423 424 if (!(argdot = strchr(arg, '.'))) 425 return; 426 if (first) { 427 first = 0; 428 if (gethostname(name, sizeof(name))) 429 err(1, "gethostname"); 430 hostdot = strchr(name, '.'); 431 } 432 if (hostdot && !strcasecmp(hostdot, argdot)) 433 *argdot = '\0'; 434 } 435 436 /* 437 * ttyconv -- 438 * convert tty to correct name. 439 */ 440 char * 441 ttyconv(char *arg) 442 { 443 char *mval; 444 445 /* 446 * kludge -- we assume that all tty's end with 447 * a two character suffix. 448 */ 449 if (strlen(arg) == 2) { 450 /* either 6 for "ttyxx" or 8 for "console" */ 451 if (!(mval = malloc((u_int)8))) 452 err(1, "malloc failure"); 453 if (!strcmp(arg, "co")) 454 (void)strcpy(mval, "console"); 455 else { 456 (void)strcpy(mval, "tty"); 457 (void)strcpy(mval + 3, arg); 458 } 459 return (mval); 460 } 461 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 462 return (arg + 5); 463 return (arg); 464 } 465 466 /* 467 * dateconv -- 468 * Convert the snapshot time in command line given in the format 469 * [[CC]YY]MMDDhhmm[.SS]] to a time_t. 470 * Derived from atime_arg1() in usr.bin/touch/touch.c 471 */ 472 time_t 473 dateconv(char *arg) 474 { 475 time_t timet; 476 struct tm *t; 477 int yearset; 478 char *p; 479 480 /* Start with the current time. */ 481 if (time(&timet) < 0) 482 err(1, "time"); 483 if ((t = localtime(&timet)) == NULL) 484 err(1, "localtime"); 485 486 /* [[CC]YY]MMDDhhmm[.SS] */ 487 if ((p = strchr(arg, '.')) == NULL) 488 t->tm_sec = 0; /* Seconds defaults to 0. */ 489 else { 490 if (strlen(p + 1) != 2) 491 goto terr; 492 *p++ = '\0'; 493 t->tm_sec = ATOI2(p); 494 } 495 496 yearset = 0; 497 switch (strlen(arg)) { 498 case 12: /* CCYYMMDDhhmm */ 499 t->tm_year = ATOI2(arg); 500 t->tm_year *= 100; 501 yearset = 1; 502 /* FALLTHROUGH */ 503 case 10: /* YYMMDDhhmm */ 504 if (yearset) { 505 yearset = ATOI2(arg); 506 t->tm_year += yearset; 507 } else { 508 yearset = ATOI2(arg); 509 if (yearset < 69) 510 t->tm_year = yearset + 2000; 511 else 512 t->tm_year = yearset + 1900; 513 } 514 t->tm_year -= 1900; /* Convert to UNIX time. */ 515 /* FALLTHROUGH */ 516 case 8: /* MMDDhhmm */ 517 t->tm_mon = ATOI2(arg); 518 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 519 t->tm_mday = ATOI2(arg); 520 t->tm_hour = ATOI2(arg); 521 t->tm_min = ATOI2(arg); 522 break; 523 case 4: /* hhmm */ 524 t->tm_hour = ATOI2(arg); 525 t->tm_min = ATOI2(arg); 526 break; 527 default: 528 goto terr; 529 } 530 t->tm_isdst = -1; /* Figure out DST. */ 531 timet = mktime(t); 532 if (timet == -1) 533 terr: errx(1, 534 "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 535 return timet; 536 } 537 538 539 /* 540 * onintr -- 541 * on interrupt, we inform the user how far we've gotten 542 */ 543 void 544 onintr(int signo) 545 { 546 char ct[80]; 547 struct tm *tm; 548 549 tm = localtime(&buf[0].ut_time); 550 (void) strftime(ct, sizeof(ct), 551 d_first ? "%a %e %b %R" : "%a %b %e %R", 552 tm); 553 printf("\ninterrupted %s\n", ct); 554 if (signo == SIGINT) 555 exit(1); 556 (void)fflush(stdout); /* fix required for rsh */ 557 } 558