1 /* $NetBSD: ac.c,v 1.23 2006/05/26 02:16:17 jnemeth Exp $ */ 2 3 /* 4 * Copyright (c) 1994 Christopher G. Demetriou 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed for the 18 * NetBSD Project. See http://www.NetBSD.org/ for 19 * information about NetBSD. 20 * 4. The name of the author may not be used to endorse or promote products 21 * derived from this software without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 * 34 * <<Id: LICENSE,v 1.2 2000/06/14 15:57:33 cgd Exp>> 35 * 36 * 37 * @(#)Copyright (c) 1994, Simon J. Gerraty. 38 * 39 * This is free software. It comes with NO WARRANTY. 40 * Permission to use, modify and distribute this source code 41 * is granted subject to the following conditions. 42 * 1/ that the above copyright notice and this notice 43 * are preserved in all copies and that due credit be given 44 * to the author. 45 * 2/ that any changes to this code are clearly commented 46 * as such so that the author does not get blamed for bugs 47 * other than his own. 48 */ 49 50 #include <sys/cdefs.h> 51 #ifndef lint 52 __RCSID("$NetBSD: ac.c,v 1.23 2006/05/26 02:16:17 jnemeth Exp $"); 53 #endif 54 55 #include <sys/types.h> 56 57 #include <err.h> 58 #include <errno.h> 59 #include <pwd.h> 60 #include <stdio.h> 61 #include <string.h> 62 #include <stdlib.h> 63 #include <unistd.h> 64 #include <time.h> 65 #include <utmp.h> 66 #include <ttyent.h> 67 68 /* 69 * this is for our list of currently logged in sessions 70 */ 71 struct utmp_list { 72 struct utmp_list *next; 73 struct utmp usr; 74 }; 75 76 /* 77 * this is for our list of users that are accumulating time. 78 */ 79 struct user_list { 80 struct user_list *next; 81 char name[UT_NAMESIZE+1]; 82 time_t secs; 83 }; 84 85 /* 86 * this is for chosing whether to ignore a login 87 */ 88 struct tty_list { 89 struct tty_list *next; 90 char name[UT_LINESIZE+3]; 91 int len; 92 int ret; 93 }; 94 95 /* 96 * globals - yes yuk 97 */ 98 static time_t Total = 0; 99 static time_t FirstTime = 0; 100 static int Flags = 0; 101 static struct user_list *Users = NULL; 102 static struct tty_list *Ttys = NULL; 103 static int Maxcon = 0, Ncon = 0; 104 static char (*Con)[UT_LINESIZE] = NULL; 105 106 #define NEW(type) (type *)malloc(sizeof (type)) 107 108 #define is_login_tty(line) \ 109 (bsearch(line, Con, Ncon, sizeof(Con[0]), compare) != NULL) 110 111 #define AC_W 1 /* not _PATH_WTMP */ 112 #define AC_D 2 /* daily totals (ignore -p) */ 113 #define AC_P 4 /* per-user totals */ 114 #define AC_U 8 /* specified users only */ 115 #define AC_T 16 /* specified ttys only */ 116 117 #ifdef DEBUG 118 static int Debug = 0; 119 #endif 120 121 static int ac(FILE *); 122 static struct tty_list *add_tty(char *); 123 static int do_tty(char *); 124 static FILE *file(const char *); 125 static struct utmp_list *log_in(struct utmp_list *, struct utmp *); 126 static struct utmp_list *log_out(struct utmp_list *, struct utmp *); 127 #ifdef notdef 128 static int on_console(struct utmp_list *); 129 #endif 130 static void find_login_ttys(void); 131 static void show(const char *, time_t); 132 static void show_today(struct user_list *, struct utmp_list *, 133 time_t); 134 static void show_users(struct user_list *); 135 static struct user_list *update_user(struct user_list *, char *, time_t); 136 static int compare(const void *, const void *); 137 static void usage(void); 138 139 /* 140 * open wtmp or die 141 */ 142 static FILE * 143 file(const char *name) 144 { 145 FILE *fp; 146 147 if (strcmp(name, "-") == 0) 148 fp = stdin; 149 else if ((fp = fopen(name, "r")) == NULL) 150 err(1, "%s", name); 151 /* in case we want to discriminate */ 152 if (strcmp(_PATH_WTMP, name)) 153 Flags |= AC_W; 154 return fp; 155 } 156 157 static struct tty_list * 158 add_tty(char *name) 159 { 160 struct tty_list *tp; 161 char *rcp; 162 163 Flags |= AC_T; 164 165 if ((tp = NEW(struct tty_list)) == NULL) 166 err(1, "malloc"); 167 tp->len = 0; /* full match */ 168 tp->ret = 1; /* do if match */ 169 if (*name == '!') { /* don't do if match */ 170 tp->ret = 0; 171 name++; 172 } 173 (void)strlcpy(tp->name, name, sizeof (tp->name)); 174 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 175 *rcp = '\0'; 176 tp->len = strlen(tp->name); /* match len bytes only */ 177 } 178 tp->next = Ttys; 179 Ttys = tp; 180 return Ttys; 181 } 182 183 /* 184 * should we process the named tty? 185 */ 186 static int 187 do_tty(char *name) 188 { 189 struct tty_list *tp; 190 int def_ret = 0; 191 192 for (tp = Ttys; tp != NULL; tp = tp->next) { 193 if (tp->ret == 0) /* specific don't */ 194 def_ret = 1; /* default do */ 195 if (tp->len != 0) { 196 if (strncmp(name, tp->name, tp->len) == 0) 197 return tp->ret; 198 } else { 199 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 200 return tp->ret; 201 } 202 } 203 return def_ret; 204 } 205 206 static int 207 compare(const void *a, const void *b) 208 { 209 return strncmp(a, b, UT_LINESIZE); 210 } 211 212 /* 213 * Deal correctly with multiple virtual consoles/login ttys. 214 * We read the ttyent's from /etc/ttys and classify as login 215 * ttys ones that are running getty and they are turned on. 216 */ 217 static void 218 find_login_ttys(void) 219 { 220 struct ttyent *tty; 221 char (*nCon)[UT_LINESIZE]; 222 223 if ((Con = malloc((Maxcon = 10) * sizeof(Con[0]))) == NULL) 224 err(1, "malloc"); 225 226 setttyent(); 227 while ((tty = getttyent()) != NULL) 228 if ((tty->ty_status & TTY_ON) != 0 && 229 strstr(tty->ty_getty, "getty") != NULL) { 230 if (Ncon == Maxcon) { 231 if ((nCon = realloc(Con, (Maxcon + 10) * 232 sizeof(Con[0]))) == NULL) 233 err(1, "malloc"); 234 Con = nCon; 235 Maxcon += 10; 236 } 237 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE); 238 } 239 endttyent(); 240 qsort(Con, Ncon, sizeof(Con[0]), compare); 241 } 242 243 #ifdef notdef 244 /* 245 * is someone logged in on Console/login tty? 246 */ 247 static int 248 on_console(struct utmp_list *head) 249 { 250 struct utmp_list *up; 251 252 for (up = head; up; up = up->next) 253 if (is_login_tty(up->usr.ut_line)) 254 return 1; 255 256 return 0; 257 } 258 #endif 259 260 /* 261 * update user's login time 262 */ 263 static struct user_list * 264 update_user(struct user_list *head, char *name, time_t secs) 265 { 266 struct user_list *up; 267 268 for (up = head; up != NULL; up = up->next) { 269 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 270 up->secs += secs; 271 Total += secs; 272 return head; 273 } 274 } 275 /* 276 * not found so add new user unless specified users only 277 */ 278 if (Flags & AC_U) 279 return head; 280 281 if ((up = NEW(struct user_list)) == NULL) 282 err(1, "malloc"); 283 up->next = head; 284 (void)strlcpy(up->name, name, sizeof (up->name)); 285 up->secs = secs; 286 Total += secs; 287 return up; 288 } 289 290 int 291 main(int argc, char **argv) 292 { 293 FILE *fp; 294 int c; 295 296 fp = NULL; 297 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) { 298 switch (c) { 299 #ifdef DEBUG 300 case 'D': 301 Debug++; 302 break; 303 #endif 304 case 'd': 305 Flags |= AC_D; 306 break; 307 case 'p': 308 Flags |= AC_P; 309 break; 310 case 't': /* only do specified ttys */ 311 add_tty(optarg); 312 break; 313 case 'w': 314 fp = file(optarg); 315 break; 316 case '?': 317 default: 318 usage(); 319 } 320 } 321 322 find_login_ttys(); 323 324 if (optind < argc) { 325 /* 326 * initialize user list 327 */ 328 for (; optind < argc; optind++) { 329 Users = update_user(Users, argv[optind], 0L); 330 } 331 Flags |= AC_U; /* freeze user list */ 332 } 333 if (Flags & AC_D) 334 Flags &= ~AC_P; 335 if (fp == NULL) { 336 /* 337 * if _PATH_WTMP does not exist, exit quietly 338 */ 339 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) 340 return 0; 341 342 fp = file(_PATH_WTMP); 343 } 344 ac(fp); 345 346 return 0; 347 } 348 349 /* 350 * print login time in decimal hours 351 */ 352 static void 353 show(const char *name, time_t secs) 354 { 355 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 356 ((double)secs / 3600)); 357 } 358 359 static void 360 show_users(struct user_list *list) 361 { 362 struct user_list *lp; 363 364 for (lp = list; lp; lp = lp->next) 365 show(lp->name, lp->secs); 366 } 367 368 /* 369 * print total login time for 24hr period in decimal hours 370 */ 371 static void 372 show_today(struct user_list *users, struct utmp_list *logins, time_t secs) 373 { 374 struct user_list *up; 375 struct utmp_list *lp; 376 char date[64]; 377 time_t yesterday = secs - 1; 378 379 (void)strftime(date, sizeof (date), "%b %e total", 380 localtime(&yesterday)); 381 382 /* restore the missing second */ 383 yesterday++; 384 385 for (lp = logins; lp != NULL; lp = lp->next) { 386 secs = yesterday - lp->usr.ut_time; 387 Users = update_user(Users, lp->usr.ut_name, secs); 388 lp->usr.ut_time = yesterday; /* as if they just logged in */ 389 } 390 secs = 0; 391 for (up = users; up != NULL; up = up->next) { 392 secs += up->secs; 393 up->secs = 0; /* for next day */ 394 } 395 if (secs) 396 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 397 } 398 399 /* 400 * log a user out and update their times. 401 * if ut_line is "~", we log all users out as the system has 402 * been shut down. 403 */ 404 static struct utmp_list * 405 log_out(struct utmp_list *head, struct utmp *up) 406 { 407 struct utmp_list *lp, *lp2, *tlp; 408 time_t secs; 409 410 for (lp = head, lp2 = NULL; lp != NULL; ) 411 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line, 412 sizeof (up->ut_line)) == 0) { 413 secs = up->ut_time - lp->usr.ut_time; 414 Users = update_user(Users, lp->usr.ut_name, secs); 415 #ifdef DEBUG 416 if (Debug) 417 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 418 19, ctime(&up->ut_time), 419 sizeof (lp->usr.ut_line), lp->usr.ut_line, 420 sizeof (lp->usr.ut_name), lp->usr.ut_name, 421 secs / 3600, (secs % 3600) / 60, secs % 60); 422 #endif 423 /* 424 * now lose it 425 */ 426 tlp = lp; 427 lp = lp->next; 428 if (tlp == head) { 429 head = lp; 430 } else if (lp2 != NULL) 431 lp2->next = lp; 432 free(tlp); 433 } else { 434 lp2 = lp; 435 lp = lp->next; 436 } 437 return head; 438 } 439 440 441 /* 442 * if do_tty says ok, login a user 443 */ 444 struct utmp_list * 445 log_in(struct utmp_list *head, struct utmp *up) 446 { 447 struct utmp_list *lp; 448 449 /* 450 * If we are doing specified ttys only, we ignore 451 * anything else. 452 */ 453 if (Flags & AC_T) 454 if (!do_tty(up->ut_line)) 455 return head; 456 457 /* 458 * go ahead and log them in 459 */ 460 if ((lp = NEW(struct utmp_list)) == NULL) 461 err(1, "malloc"); 462 lp->next = head; 463 head = lp; 464 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); 465 #ifdef DEBUG 466 if (Debug) { 467 printf("%-.*s %-.*s: %-.*s logged in", 19, 468 ctime(&lp->usr.ut_time), sizeof (up->ut_line), 469 up->ut_line, sizeof (up->ut_name), up->ut_name); 470 if (*up->ut_host) 471 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host); 472 putchar('\n'); 473 } 474 #endif 475 return head; 476 } 477 478 static int 479 ac(FILE *fp) 480 { 481 struct utmp_list *lp, *head = NULL; 482 struct utmp usr; 483 struct tm *ltm; 484 time_t secs = 0; 485 int day = -1; 486 487 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { 488 if (!FirstTime) 489 FirstTime = usr.ut_time; 490 if (Flags & AC_D) { 491 ltm = localtime(&usr.ut_time); 492 if (day >= 0 && day != ltm->tm_yday) { 493 day = ltm->tm_yday; 494 /* 495 * print yesterday's total 496 */ 497 secs = usr.ut_time; 498 secs -= ltm->tm_sec; 499 secs -= 60 * ltm->tm_min; 500 secs -= 3600 * ltm->tm_hour; 501 show_today(Users, head, secs); 502 } else 503 day = ltm->tm_yday; 504 } 505 switch(*usr.ut_line) { 506 case '|': 507 secs = usr.ut_time; 508 break; 509 case '{': 510 secs -= usr.ut_time; 511 /* 512 * adjust time for those logged in 513 */ 514 for (lp = head; lp != NULL; lp = lp->next) 515 lp->usr.ut_time -= secs; 516 break; 517 case '~': /* reboot or shutdown */ 518 head = log_out(head, &usr); 519 FirstTime = usr.ut_time; /* shouldn't be needed */ 520 break; 521 default: 522 /* 523 * if they came in on tty[p-y]*, then it is only 524 * a login session if the ut_host field is non-empty, 525 * or this tty is a login tty [eg. a console] 526 */ 527 if (*usr.ut_name) { 528 if ((strncmp(usr.ut_line, "tty", 3) != 0 && 529 strncmp(usr.ut_line, "dty", 3) != 0) || 530 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 || 531 *usr.ut_host != '\0' || 532 is_login_tty(usr.ut_line)) 533 head = log_in(head, &usr); 534 #ifdef DEBUG 535 else if (Debug) 536 printf("%-.*s %-.*s: %-.*s ignored\n", 537 19, ctime(&usr.ut_time), 538 sizeof (usr.ut_line), usr.ut_line, 539 sizeof (usr.ut_name), usr.ut_name); 540 #endif 541 } else 542 head = log_out(head, &usr); 543 break; 544 } 545 } 546 (void)fclose(fp); 547 usr.ut_time = time((time_t *)0); 548 (void)strcpy(usr.ut_line, "~"); 549 550 if (Flags & AC_D) { 551 ltm = localtime(&usr.ut_time); 552 if (day >= 0 && day != ltm->tm_yday) { 553 /* 554 * print yesterday's total 555 */ 556 secs = usr.ut_time; 557 secs -= ltm->tm_sec; 558 secs -= 60 * ltm->tm_min; 559 secs -= 3600 * ltm->tm_hour; 560 show_today(Users, head, secs); 561 } 562 } 563 /* 564 * anyone still logged in gets time up to now 565 */ 566 head = log_out(head, &usr); 567 568 if (Flags & AC_D) 569 show_today(Users, head, time((time_t *)0)); 570 else { 571 if (Flags & AC_P) 572 show_users(Users); 573 show("total", Total); 574 } 575 return 0; 576 } 577 578 static void 579 usage(void) 580 { 581 582 (void)fprintf(stderr, 583 "usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n", 584 getprogname()); 585 exit(1); 586 } 587