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 void show(char *, time_t); 93 void show_today(struct user_list *, struct utmp_list *, 94 time_t); 95 void show_users(struct user_list *); 96 struct user_list *update_user(struct user_list *, char *, time_t); 97 void usage(void); 98 99 /* 100 * open wtmp or die 101 */ 102 FILE * 103 file(char *name) 104 { 105 FILE *fp; 106 107 if (strcmp(name, "-") == 0) 108 fp = stdin; 109 else if ((fp = fopen(name, "r")) == NULL) 110 err(1, "%s", name); 111 /* in case we want to discriminate */ 112 if (strcmp(_PATH_WTMP, name)) 113 Flags |= AC_W; 114 return fp; 115 } 116 117 void 118 add_tty(char *name) 119 { 120 struct tty_list *tp; 121 char *rcp; 122 123 Flags |= AC_T; 124 125 if ((tp = malloc(sizeof(struct tty_list))) == NULL) 126 err(1, "malloc"); 127 tp->len = 0; /* full match */ 128 tp->ret = 1; /* do if match */ 129 if (*name == '!') { /* don't do if match */ 130 tp->ret = 0; 131 name++; 132 } 133 strlcpy(tp->name, name, sizeof (tp->name)); 134 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 135 *rcp = '\0'; 136 tp->len = strlen(tp->name); /* match len bytes only */ 137 } 138 tp->next = Ttys; 139 Ttys = tp; 140 } 141 142 /* 143 * should we process the named tty? 144 */ 145 int 146 do_tty(char *name) 147 { 148 struct tty_list *tp; 149 int def_ret = 0; 150 151 for (tp = Ttys; tp != NULL; tp = tp->next) { 152 if (tp->ret == 0) /* specific don't */ 153 def_ret = 1; /* default do */ 154 if (tp->len != 0) { 155 if (strncmp(name, tp->name, tp->len) == 0) 156 return tp->ret; 157 } else { 158 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 159 return tp->ret; 160 } 161 } 162 return def_ret; 163 } 164 165 /* 166 * update user's login time 167 */ 168 struct user_list * 169 update_user(struct user_list *head, char *name, time_t secs) 170 { 171 struct user_list *up; 172 173 for (up = head; up != NULL; up = up->next) { 174 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 175 up->secs += secs; 176 Total += secs; 177 return head; 178 } 179 } 180 /* 181 * not found so add new user unless specified users only 182 */ 183 if (Flags & AC_U) 184 return head; 185 186 if ((up = malloc(sizeof(struct user_list))) == NULL) 187 err(1, "malloc"); 188 up->next = head; 189 strncpy(up->name, name, sizeof(up->name) - 1); 190 up->name[sizeof(up->name) - 1] = '\0'; 191 up->secs = secs; 192 Total += secs; 193 return up; 194 } 195 196 int 197 main(int argc, char *argv[]) 198 { 199 FILE *fp; 200 int c; 201 202 if (pledge("stdio rpath", NULL) == -1) 203 err(1, "pledge"); 204 205 fp = NULL; 206 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) { 207 switch (c) { 208 #ifdef DEBUG 209 case 'D': 210 Debug = 1; 211 break; 212 #endif 213 case 'd': 214 Flags |= AC_D; 215 break; 216 case 'p': 217 Flags |= AC_P; 218 break; 219 case 't': /* only do specified ttys */ 220 add_tty(optarg); 221 break; 222 case 'w': 223 fp = file(optarg); 224 break; 225 default: 226 usage(); 227 break; 228 } 229 } 230 if (optind < argc) { 231 /* 232 * initialize user list 233 */ 234 for (; optind < argc; optind++) { 235 Users = update_user(Users, argv[optind], 0L); 236 } 237 Flags |= AC_U; /* freeze user list */ 238 } 239 if (Flags & AC_D) 240 Flags &= ~AC_P; 241 if (fp == NULL) { 242 /* 243 * if _PATH_WTMP does not exist, exit quietly 244 */ 245 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) 246 return 0; 247 248 fp = file(_PATH_WTMP); 249 } 250 ac(fp); 251 252 return 0; 253 } 254 255 /* 256 * print login time in decimal hours 257 */ 258 void 259 show(char *name, time_t secs) 260 { 261 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 262 ((double)secs / 3600)); 263 } 264 265 void 266 show_users(struct user_list *list) 267 { 268 struct user_list *lp; 269 270 for (lp = list; lp; lp = lp->next) 271 show(lp->name, lp->secs); 272 } 273 274 /* 275 * print total login time for 24hr period in decimal hours 276 */ 277 void 278 show_today(struct user_list *users, struct utmp_list *logins, time_t secs) 279 { 280 struct user_list *up; 281 struct utmp_list *lp; 282 char date[64]; 283 time_t yesterday = secs - 1; 284 285 (void)strftime(date, sizeof (date), "%b %e total", 286 localtime(&yesterday)); 287 288 /* restore the missing second */ 289 yesterday++; 290 291 for (lp = logins; lp != NULL; lp = lp->next) { 292 secs = yesterday - lp->usr.ut_time; 293 Users = update_user(Users, lp->usr.ut_name, secs); 294 lp->usr.ut_time = yesterday; /* as if they just logged in */ 295 } 296 secs = 0; 297 for (up = users; up != NULL; up = up->next) { 298 secs += up->secs; 299 up->secs = 0; /* for next day */ 300 } 301 if (secs) 302 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 303 } 304 305 /* 306 * log a user out and update their times. 307 * if ut_line is "~", we log all users out as the system has 308 * been shut down. 309 */ 310 struct utmp_list * 311 log_out(struct utmp_list *head, struct utmp *up) 312 { 313 struct utmp_list *lp, *lp2, *tlp; 314 time_t secs; 315 316 for (lp = head, lp2 = NULL; lp != NULL; ) 317 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line, 318 sizeof (up->ut_line)) == 0) { 319 secs = up->ut_time - lp->usr.ut_time; 320 Users = update_user(Users, lp->usr.ut_name, secs); 321 #ifdef DEBUG 322 if (Debug) 323 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 324 19, ctime(&up->ut_time), 325 sizeof (lp->usr.ut_line), lp->usr.ut_line, 326 sizeof (lp->usr.ut_name), lp->usr.ut_name, 327 secs / 3600, (secs % 3600) / 60, secs % 60); 328 #endif 329 /* 330 * now lose it 331 */ 332 tlp = lp; 333 lp = lp->next; 334 if (tlp == head) 335 head = lp; 336 else if (lp2 != NULL) 337 lp2->next = lp; 338 free(tlp); 339 } else { 340 lp2 = lp; 341 lp = lp->next; 342 } 343 return head; 344 } 345 346 347 /* 348 * if do_tty says ok, login a user 349 */ 350 struct utmp_list * 351 log_in(struct utmp_list *head, struct utmp *up) 352 { 353 struct utmp_list *lp; 354 355 /* 356 * this could be a login. if we're not dealing with 357 * the console name, say it is. 358 * 359 * If we are, and if ut_host==":0.0" we know that it 360 * isn't a real login. _But_ if we have not yet recorded 361 * someone being logged in on Console - due to the wtmp 362 * file starting after they logged in, we'll pretend they 363 * logged in, at the start of the wtmp file. 364 */ 365 366 /* 367 * If we are doing specified ttys only, we ignore 368 * anything else. 369 */ 370 if (Flags & AC_T) 371 if (!do_tty(up->ut_line)) 372 return head; 373 374 /* 375 * go ahead and log them in 376 */ 377 if ((lp = malloc(sizeof(struct utmp_list))) == NULL) 378 err(1, "malloc"); 379 lp->next = head; 380 head = lp; 381 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); 382 #ifdef DEBUG 383 if (Debug) { 384 printf("%-.*s %-.*s: %-.*s logged in", 19, 385 ctime(&lp->usr.ut_time), sizeof (up->ut_line), 386 up->ut_line, sizeof (up->ut_name), up->ut_name); 387 if (*up->ut_host) 388 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host); 389 putchar('\n'); 390 } 391 #endif 392 return head; 393 } 394 395 int 396 ac(FILE *fp) 397 { 398 struct utmp_list *lp, *head = NULL; 399 struct utmp usr; 400 struct tm *ltm; 401 time_t secs = 0, prev = 0; 402 int day = -1; 403 404 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { 405 if (!FirstTime) 406 FirstTime = usr.ut_time; 407 if (usr.ut_time < prev) 408 continue; /* broken record */ 409 prev = usr.ut_time; 410 if (Flags & AC_D) { 411 ltm = localtime(&usr.ut_time); 412 if (ltm == NULL) 413 err(1, "localtime"); 414 if (day >= 0 && day != ltm->tm_yday) { 415 day = ltm->tm_yday; 416 /* 417 * print yesterday's total 418 */ 419 secs = usr.ut_time; 420 secs -= ltm->tm_sec; 421 secs -= 60 * ltm->tm_min; 422 secs -= 3600 * ltm->tm_hour; 423 show_today(Users, head, secs); 424 } else 425 day = ltm->tm_yday; 426 } 427 switch(*usr.ut_line) { 428 case '|': 429 secs = usr.ut_time; 430 break; 431 case '{': 432 secs -= usr.ut_time; 433 /* 434 * adjust time for those logged in 435 */ 436 for (lp = head; lp != NULL; lp = lp->next) 437 lp->usr.ut_time -= secs; 438 break; 439 case '~': /* reboot or shutdown */ 440 head = log_out(head, &usr); 441 FirstTime = usr.ut_time; /* shouldn't be needed */ 442 break; 443 default: 444 /* 445 * if they came in on a pseudo-tty, then it is only 446 * a login session if the ut_host field is non-empty 447 */ 448 if (*usr.ut_name) { 449 if (strncmp(usr.ut_line, "tty", 3) != 0 || 450 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) != NULL || 451 *usr.ut_host != '\0') 452 head = log_in(head, &usr); 453 } else 454 head = log_out(head, &usr); 455 break; 456 } 457 } 458 (void)fclose(fp); 459 if (!(Flags & AC_W)) 460 usr.ut_time = time(NULL); 461 (void)strlcpy(usr.ut_line, "~", sizeof usr.ut_line); 462 463 if (Flags & AC_D) { 464 ltm = localtime(&usr.ut_time); 465 if (ltm == NULL) 466 err(1, "localtime"); 467 if (day >= 0 && day != ltm->tm_yday) { 468 /* 469 * print yesterday's total 470 */ 471 secs = usr.ut_time; 472 secs -= ltm->tm_sec; 473 secs -= 60 * ltm->tm_min; 474 secs -= 3600 * ltm->tm_hour; 475 show_today(Users, head, secs); 476 } 477 } 478 /* 479 * anyone still logged in gets time up to now 480 */ 481 head = log_out(head, &usr); 482 483 if (Flags & AC_D) 484 show_today(Users, head, time(NULL)); 485 else { 486 if (Flags & AC_P) 487 show_users(Users); 488 show("total", Total); 489 } 490 return 0; 491 } 492 493 void 494 usage(void) 495 { 496 extern char *__progname; 497 (void)fprintf(stderr, "usage: " 498 "%s [-dp] [-t tty] [-w wtmp] [user ...]\n", __progname); 499 exit(1); 500 } 501