1/*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 1994 Christopher G. Demetriou 5 * Copyright (c) 1994 Simon J. Gerraty 6 * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31#include <sys/cdefs.h> 32#include <sys/queue.h> 33#include <sys/time.h> 34 35#include <err.h> 36#include <errno.h> 37#include <langinfo.h> 38#include <locale.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <timeconv.h> 43#include <unistd.h> 44#include <utmpx.h> 45 46/* 47 * this is for our list of currently logged in sessions 48 */ 49struct utmpx_entry { 50 SLIST_ENTRY(utmpx_entry) next; 51 char user[sizeof(((struct utmpx *)0)->ut_user)]; 52 char id[sizeof(((struct utmpx *)0)->ut_id)]; 53#ifdef CONSOLE_TTY 54 char line[sizeof(((struct utmpx *)0)->ut_line)]; 55#endif 56 struct timeval time; 57}; 58 59/* 60 * this is for our list of users that are accumulating time. 61 */ 62struct user_entry { 63 SLIST_ENTRY(user_entry) next; 64 char user[sizeof(((struct utmpx *)0)->ut_user)]; 65 struct timeval time; 66}; 67 68/* 69 * this is for choosing whether to ignore a login 70 */ 71struct tty_entry { 72 SLIST_ENTRY(tty_entry) next; 73 char line[sizeof(((struct utmpx *)0)->ut_line) + 2]; 74 size_t len; 75 int ret; 76}; 77 78/* 79 * globals - yes yuk 80 */ 81#ifdef CONSOLE_TTY 82static const char *Console = CONSOLE_TTY; 83#endif 84static struct timeval Total = { 0, 0 }; 85static struct timeval FirstTime = { 0, 0 }; 86static int Flags = 0; 87static SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx); 88static SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users); 89static SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys); 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 97static void ac(const char *); 98static void usage(void); 99 100static void 101add_tty(const char *line) 102{ 103 struct tty_entry *tp; 104 char *rcp; 105 106 Flags |= AC_T; 107 108 if ((tp = malloc(sizeof(*tp))) == NULL) 109 errx(1, "malloc failed"); 110 tp->len = 0; /* full match */ 111 tp->ret = 1; /* do if match */ 112 if (*line == '!') { /* don't do if match */ 113 tp->ret = 0; 114 line++; 115 } 116 strlcpy(tp->line, line, sizeof(tp->line)); 117 /* Wildcard. */ 118 if ((rcp = strchr(tp->line, '*')) != NULL) { 119 *rcp = '\0'; 120 /* Match len bytes only. */ 121 tp->len = strlen(tp->line); 122 } 123 SLIST_INSERT_HEAD(&Ttys, tp, next); 124} 125 126/* 127 * should we process the named tty? 128 */ 129static int 130do_tty(const char *line) 131{ 132 struct tty_entry *tp; 133 int def_ret = 0; 134 135 SLIST_FOREACH(tp, &Ttys, next) { 136 if (tp->ret == 0) /* specific don't */ 137 def_ret = 1; /* default do */ 138 if (tp->len != 0) { 139 if (strncmp(line, tp->line, tp->len) == 0) 140 return tp->ret; 141 } else { 142 if (strncmp(line, tp->line, sizeof(tp->line)) == 0) 143 return tp->ret; 144 } 145 } 146 return (def_ret); 147} 148 149#ifdef CONSOLE_TTY 150/* 151 * is someone logged in on Console? 152 */ 153static int 154on_console(void) 155{ 156 struct utmpx_entry *up; 157 158 SLIST_FOREACH(up, &CurUtmpx, next) 159 if (strcmp(up->line, Console) == 0) 160 return (1); 161 return (0); 162} 163#endif 164 165/* 166 * Update user's login time. 167 * If no entry for this user is found, a new entry is inserted into the 168 * list alphabetically. 169 */ 170static void 171update_user(const char *user, struct timeval secs) 172{ 173 struct user_entry *up, *aup; 174 int c; 175 176 aup = NULL; 177 SLIST_FOREACH(up, &Users, next) { 178 c = strcmp(up->user, user); 179 if (c == 0) { 180 timeradd(&up->time, &secs, &up->time); 181 timeradd(&Total, &secs, &Total); 182 return; 183 } else if (c > 0) 184 break; 185 aup = up; 186 } 187 /* 188 * not found so add new user unless specified users only 189 */ 190 if (Flags & AC_U) 191 return; 192 193 if ((up = malloc(sizeof(*up))) == NULL) 194 errx(1, "malloc failed"); 195 if (aup == NULL) 196 SLIST_INSERT_HEAD(&Users, up, next); 197 else 198 SLIST_INSERT_AFTER(aup, up, next); 199 strlcpy(up->user, user, sizeof(up->user)); 200 up->time = secs; 201 timeradd(&Total, &secs, &Total); 202} 203 204int 205main(int argc, char *argv[]) 206{ 207 const char *wtmpf = NULL; 208 int c; 209 210 (void) setlocale(LC_TIME, ""); 211 212 while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) { 213 switch (c) { 214 case 'c': 215#ifdef CONSOLE_TTY 216 Console = optarg; 217#else 218 usage(); /* XXX */ 219#endif 220 break; 221 case 'd': 222 Flags |= AC_D; 223 break; 224 case 'p': 225 Flags |= AC_P; 226 break; 227 case 't': /* only do specified ttys */ 228 add_tty(optarg); 229 break; 230 case 'w': 231 Flags |= AC_W; 232 wtmpf = optarg; 233 break; 234 case '?': 235 default: 236 usage(); 237 break; 238 } 239 } 240 if (optind < argc) { 241 /* 242 * initialize user list 243 */ 244 for (; optind < argc; optind++) { 245 update_user(argv[optind], (struct timeval){ 0, 0 }); 246 } 247 Flags |= AC_U; /* freeze user list */ 248 } 249 if (Flags & AC_D) 250 Flags &= ~AC_P; 251 ac(wtmpf); 252 253 return (0); 254} 255 256/* 257 * print login time in decimal hours 258 */ 259static void 260show(const char *user, struct timeval secs) 261{ 262 (void)printf("\t%-*s %8.2f\n", 263 (int)sizeof(((struct user_entry *)0)->user), user, 264 (double)secs.tv_sec / 3600); 265} 266 267static void 268show_users(void) 269{ 270 struct user_entry *lp; 271 272 SLIST_FOREACH(lp, &Users, next) 273 show(lp->user, lp->time); 274} 275 276/* 277 * print total login time for 24hr period in decimal hours 278 */ 279static void 280show_today(struct timeval today) 281{ 282 struct user_entry *up; 283 struct utmpx_entry *lp; 284 char date[64]; 285 struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday; 286 static int d_first = -1; 287 288 if (d_first < 0) 289 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 290 timersub(&today, &usec, &yesterday); 291 (void)strftime(date, sizeof(date), 292 d_first ? "%e %b total" : "%b %e total", 293 localtime(&yesterday.tv_sec)); 294 295 SLIST_FOREACH(lp, &CurUtmpx, next) { 296 timersub(&today, &lp->time, &diff); 297 update_user(lp->user, diff); 298 /* As if they just logged in. */ 299 lp->time = today; 300 } 301 SLIST_FOREACH(up, &Users, next) { 302 timeradd(&total, &up->time, &total); 303 /* For next day. */ 304 timerclear(&up->time); 305 } 306 if (timerisset(&total)) 307 (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600); 308} 309 310/* 311 * Log a user out and update their times. 312 * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the 313 * system has been shut down. 314 */ 315static void 316log_out(const struct utmpx *up) 317{ 318 struct utmpx_entry *lp, *lp2, *tlp; 319 struct timeval secs; 320 321 for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;) 322 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || 323 (up->ut_type == DEAD_PROCESS && 324 memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) { 325 timersub(&up->ut_tv, &lp->time, &secs); 326 update_user(lp->user, secs); 327 /* 328 * now lose it 329 */ 330 tlp = lp; 331 lp = SLIST_NEXT(lp, next); 332 if (lp2 == NULL) 333 SLIST_REMOVE_HEAD(&CurUtmpx, next); 334 else 335 SLIST_REMOVE_AFTER(lp2, next); 336 free(tlp); 337 } else { 338 lp2 = lp; 339 lp = SLIST_NEXT(lp, next); 340 } 341} 342 343/* 344 * if do_tty says ok, login a user 345 */ 346static void 347log_in(struct utmpx *up) 348{ 349 struct utmpx_entry *lp; 350 351 /* 352 * this could be a login. if we're not dealing with 353 * the console name, say it is. 354 * 355 * If we are, and if ut_host==":0.0" we know that it 356 * isn't a real login. _But_ if we have not yet recorded 357 * someone being logged in on Console - due to the wtmp 358 * file starting after they logged in, we'll pretend they 359 * logged in, at the start of the wtmp file. 360 */ 361 362#ifdef CONSOLE_TTY 363 if (up->ut_host[0] == ':') { 364 /* 365 * SunOS 4.0.2 does not treat ":0.0" as special but we 366 * do. 367 */ 368 if (on_console()) 369 return; 370 /* 371 * ok, no recorded login, so they were here when wtmp 372 * started! Adjust ut_time! 373 */ 374 up->ut_tv = FirstTime; 375 /* 376 * this allows us to pick the right logout 377 */ 378 strlcpy(up->ut_line, Console, sizeof(up->ut_line)); 379 } 380#endif 381 /* 382 * If we are doing specified ttys only, we ignore 383 * anything else. 384 */ 385 if (Flags & AC_T && !do_tty(up->ut_line)) 386 return; 387 388 /* 389 * go ahead and log them in 390 */ 391 if ((lp = malloc(sizeof(*lp))) == NULL) 392 errx(1, "malloc failed"); 393 SLIST_INSERT_HEAD(&CurUtmpx, lp, next); 394 strlcpy(lp->user, up->ut_user, sizeof(lp->user)); 395 memcpy(lp->id, up->ut_id, sizeof(lp->id)); 396#ifdef CONSOLE_TTY 397 memcpy(lp->line, up->ut_line, sizeof(lp->line)); 398#endif 399 lp->time = up->ut_tv; 400} 401 402static void 403ac(const char *file) 404{ 405 struct utmpx_entry *lp; 406 struct utmpx *usr, usht; 407 struct tm *ltm; 408 struct timeval prev_secs, ut_timecopy, secs, clock_shift, now; 409 int day; 410 411 day = -1; 412 timerclear(&prev_secs); /* Minimum acceptable date == 1970. */ 413 timerclear(&secs); 414 timerclear(&clock_shift); 415 if (setutxdb(UTXDB_LOG, file) != 0) 416 err(1, "%s", file); 417 while ((usr = getutxent()) != NULL) { 418 ut_timecopy = usr->ut_tv; 419 /* Don't let the time run backwards. */ 420 if (timercmp(&ut_timecopy, &prev_secs, <)) 421 ut_timecopy = prev_secs; 422 prev_secs = ut_timecopy; 423 424 if (!timerisset(&FirstTime)) 425 FirstTime = ut_timecopy; 426 if (Flags & AC_D) { 427 ltm = localtime(&ut_timecopy.tv_sec); 428 if (day >= 0 && day != ltm->tm_yday) { 429 day = ltm->tm_yday; 430 /* 431 * print yesterday's total 432 */ 433 secs = ut_timecopy; 434 secs.tv_sec -= ltm->tm_sec; 435 secs.tv_sec -= 60 * ltm->tm_min; 436 secs.tv_sec -= 3600 * ltm->tm_hour; 437 secs.tv_usec = 0; 438 show_today(secs); 439 } else 440 day = ltm->tm_yday; 441 } 442 switch(usr->ut_type) { 443 case OLD_TIME: 444 clock_shift = ut_timecopy; 445 break; 446 case NEW_TIME: 447 timersub(&clock_shift, &ut_timecopy, &clock_shift); 448 /* 449 * adjust time for those logged in 450 */ 451 SLIST_FOREACH(lp, &CurUtmpx, next) 452 timersub(&lp->time, &clock_shift, &lp->time); 453 break; 454 case BOOT_TIME: 455 case SHUTDOWN_TIME: 456 log_out(usr); 457 FirstTime = ut_timecopy; /* shouldn't be needed */ 458 break; 459 case USER_PROCESS: 460 /* 461 * If they came in on pts/..., then it is only 462 * a login session if the ut_host field is non-empty. 463 */ 464 if (strncmp(usr->ut_line, "pts/", 4) != 0 || 465 *usr->ut_host != '\0') 466 log_in(usr); 467 break; 468 case DEAD_PROCESS: 469 log_out(usr); 470 break; 471 } 472 } 473 endutxent(); 474 (void)gettimeofday(&now, NULL); 475 if (Flags & AC_W) 476 usht.ut_tv = ut_timecopy; 477 else 478 usht.ut_tv = now; 479 usht.ut_type = SHUTDOWN_TIME; 480 481 if (Flags & AC_D) { 482 ltm = localtime(&ut_timecopy.tv_sec); 483 if (day >= 0 && day != ltm->tm_yday) { 484 /* 485 * print yesterday's total 486 */ 487 secs = ut_timecopy; 488 secs.tv_sec -= ltm->tm_sec; 489 secs.tv_sec -= 60 * ltm->tm_min; 490 secs.tv_sec -= 3600 * ltm->tm_hour; 491 secs.tv_usec = 0; 492 show_today(secs); 493 } 494 } 495 /* 496 * anyone still logged in gets time up to now 497 */ 498 log_out(&usht); 499 500 if (Flags & AC_D) 501 show_today(now); 502 else { 503 if (Flags & AC_P) 504 show_users(); 505 show("total", Total); 506 } 507} 508 509static void 510usage(void) 511{ 512 (void)fprintf(stderr, 513#ifdef CONSOLE_TTY 514 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 515#else 516 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 517#endif 518 exit(1); 519} 520