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