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