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