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 chosing 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 case '?': 227 default: 228 usage(); 229 break; 230 } 231 } 232 if (optind < argc) { 233 /* 234 * initialize user list 235 */ 236 for (; optind < argc; optind++) { 237 Users = update_user(Users, argv[optind], 0L); 238 } 239 Flags |= AC_U; /* freeze user list */ 240 } 241 if (Flags & AC_D) 242 Flags &= ~AC_P; 243 if (fp == NULL) { 244 /* 245 * if _PATH_WTMP does not exist, exit quietly 246 */ 247 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) 248 return 0; 249 250 fp = file(_PATH_WTMP); 251 } 252 ac(fp); 253 254 return 0; 255 } 256 257 /* 258 * print login time in decimal hours 259 */ 260 void 261 show(char *name, time_t secs) 262 { 263 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 264 ((double)secs / 3600)); 265 } 266 267 void 268 show_users(struct user_list *list) 269 { 270 struct user_list *lp; 271 272 for (lp = list; lp; lp = lp->next) 273 show(lp->name, lp->secs); 274 } 275 276 /* 277 * print total login time for 24hr period in decimal hours 278 */ 279 void 280 show_today(struct user_list *users, struct utmp_list *logins, time_t secs) 281 { 282 struct user_list *up; 283 struct utmp_list *lp; 284 char date[64]; 285 time_t yesterday = secs - 1; 286 287 (void)strftime(date, sizeof (date), "%b %e total", 288 localtime(&yesterday)); 289 290 /* restore the missing second */ 291 yesterday++; 292 293 for (lp = logins; lp != NULL; lp = lp->next) { 294 secs = yesterday - lp->usr.ut_time; 295 Users = update_user(Users, lp->usr.ut_name, secs); 296 lp->usr.ut_time = yesterday; /* as if they just logged in */ 297 } 298 secs = 0; 299 for (up = users; up != NULL; up = up->next) { 300 secs += up->secs; 301 up->secs = 0; /* for next day */ 302 } 303 if (secs) 304 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 305 } 306 307 /* 308 * log a user out and update their times. 309 * if ut_line is "~", we log all users out as the system has 310 * been shut down. 311 */ 312 struct utmp_list * 313 log_out(struct utmp_list *head, struct utmp *up) 314 { 315 struct utmp_list *lp, *lp2, *tlp; 316 time_t secs; 317 318 for (lp = head, lp2 = NULL; lp != NULL; ) 319 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line, 320 sizeof (up->ut_line)) == 0) { 321 secs = up->ut_time - lp->usr.ut_time; 322 Users = update_user(Users, lp->usr.ut_name, secs); 323 #ifdef DEBUG 324 if (Debug) 325 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 326 19, ctime(&up->ut_time), 327 sizeof (lp->usr.ut_line), lp->usr.ut_line, 328 sizeof (lp->usr.ut_name), lp->usr.ut_name, 329 secs / 3600, (secs % 3600) / 60, secs % 60); 330 #endif 331 /* 332 * now lose it 333 */ 334 tlp = lp; 335 lp = lp->next; 336 if (tlp == head) 337 head = lp; 338 else if (lp2 != NULL) 339 lp2->next = lp; 340 free(tlp); 341 } else { 342 lp2 = lp; 343 lp = lp->next; 344 } 345 return head; 346 } 347 348 349 /* 350 * if do_tty says ok, login a user 351 */ 352 struct utmp_list * 353 log_in(struct utmp_list *head, struct utmp *up) 354 { 355 struct utmp_list *lp; 356 357 /* 358 * this could be a login. if we're not dealing with 359 * the console name, say it is. 360 * 361 * If we are, and if ut_host==":0.0" we know that it 362 * isn't a real login. _But_ if we have not yet recorded 363 * someone being logged in on Console - due to the wtmp 364 * file starting after they logged in, we'll pretend they 365 * logged in, at the start of the wtmp file. 366 */ 367 368 /* 369 * If we are doing specified ttys only, we ignore 370 * anything else. 371 */ 372 if (Flags & AC_T) 373 if (!do_tty(up->ut_line)) 374 return head; 375 376 /* 377 * go ahead and log them in 378 */ 379 if ((lp = malloc(sizeof(struct utmp_list))) == NULL) 380 err(1, "malloc"); 381 lp->next = head; 382 head = lp; 383 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); 384 #ifdef DEBUG 385 if (Debug) { 386 printf("%-.*s %-.*s: %-.*s logged in", 19, 387 ctime(&lp->usr.ut_time), sizeof (up->ut_line), 388 up->ut_line, sizeof (up->ut_name), up->ut_name); 389 if (*up->ut_host) 390 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host); 391 putchar('\n'); 392 } 393 #endif 394 return head; 395 } 396 397 int 398 ac(FILE *fp) 399 { 400 struct utmp_list *lp, *head = NULL; 401 struct utmp usr; 402 struct tm *ltm; 403 time_t secs = 0, prev = 0; 404 int day = -1; 405 406 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { 407 if (!FirstTime) 408 FirstTime = usr.ut_time; 409 if (usr.ut_time < prev) 410 continue; /* broken record */ 411 prev = usr.ut_time; 412 if (Flags & AC_D) { 413 ltm = localtime(&usr.ut_time); 414 if (ltm == NULL) 415 err(1, "localtime"); 416 if (day >= 0 && day != ltm->tm_yday) { 417 day = ltm->tm_yday; 418 /* 419 * print yesterday's total 420 */ 421 secs = usr.ut_time; 422 secs -= ltm->tm_sec; 423 secs -= 60 * ltm->tm_min; 424 secs -= 3600 * ltm->tm_hour; 425 show_today(Users, head, secs); 426 } else 427 day = ltm->tm_yday; 428 } 429 switch(*usr.ut_line) { 430 case '|': 431 secs = usr.ut_time; 432 break; 433 case '{': 434 secs -= usr.ut_time; 435 /* 436 * adjust time for those logged in 437 */ 438 for (lp = head; lp != NULL; lp = lp->next) 439 lp->usr.ut_time -= secs; 440 break; 441 case '~': /* reboot or shutdown */ 442 head = log_out(head, &usr); 443 FirstTime = usr.ut_time; /* shouldn't be needed */ 444 break; 445 default: 446 /* 447 * if they came in on a pseudo-tty, then it is only 448 * a login session if the ut_host field is non-empty 449 */ 450 if (*usr.ut_name) { 451 if (strncmp(usr.ut_line, "tty", 3) != 0 || 452 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) != NULL || 453 *usr.ut_host != '\0') 454 head = log_in(head, &usr); 455 } else 456 head = log_out(head, &usr); 457 break; 458 } 459 } 460 (void)fclose(fp); 461 if (!(Flags & AC_W)) 462 usr.ut_time = time(NULL); 463 (void)strlcpy(usr.ut_line, "~", sizeof usr.ut_line); 464 465 if (Flags & AC_D) { 466 ltm = localtime(&usr.ut_time); 467 if (ltm == NULL) 468 err(1, "localtime"); 469 if (day >= 0 && day != ltm->tm_yday) { 470 /* 471 * print yesterday's total 472 */ 473 secs = usr.ut_time; 474 secs -= ltm->tm_sec; 475 secs -= 60 * ltm->tm_min; 476 secs -= 3600 * ltm->tm_hour; 477 show_today(Users, head, secs); 478 } 479 } 480 /* 481 * anyone still logged in gets time up to now 482 */ 483 head = log_out(head, &usr); 484 485 if (Flags & AC_D) 486 show_today(Users, head, time(NULL)); 487 else { 488 if (Flags & AC_P) 489 show_users(Users); 490 show("total", Total); 491 } 492 return 0; 493 } 494 495 void 496 usage(void) 497 { 498 extern char *__progname; 499 (void)fprintf(stderr, "usage: " 500 "%s [-dp] [-t tty] [-w wtmp] [user ...]\n", __progname); 501 exit(1); 502 } 503