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/types.h> 29 #include <sys/file.h> 30 #include <sys/time.h> 31 #include <err.h> 32 #include <errno.h> 33 #include <pwd.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <utmp.h> 38 #include <unistd.h> 39 40 /* 41 * this is for our list of currently logged in sessions 42 */ 43 struct utmp_list { 44 struct utmp_list *next; 45 struct utmp usr; 46 }; 47 48 /* 49 * this is for our list of users that are accumulating time. 50 */ 51 struct user_list { 52 struct user_list *next; 53 char name[UT_NAMESIZE+1]; 54 time_t secs; 55 }; 56 57 /* 58 * this is for chosing whether to ignore a login 59 */ 60 struct tty_list { 61 struct tty_list *next; 62 char name[UT_LINESIZE+3]; 63 size_t len; 64 int ret; 65 }; 66 67 /* 68 * globals - yes yuk 69 */ 70 static time_t Total = 0; 71 static time_t FirstTime = 0; 72 static int Flags = 0; 73 static struct user_list *Users = NULL; 74 static struct tty_list *Ttys = NULL; 75 76 #define AC_W 1 /* not _PATH_WTMP */ 77 #define AC_D 2 /* daily totals (ignore -p) */ 78 #define AC_P 4 /* per-user totals */ 79 #define AC_U 8 /* specified users only */ 80 #define AC_T 16 /* specified ttys only */ 81 82 #ifdef DEBUG 83 static int Debug = 0; 84 #endif 85 86 int main(int, char **); 87 int ac(FILE *); 88 void add_tty(char *); 89 int do_tty(char *); 90 FILE *file(char *); 91 struct utmp_list *log_in(struct utmp_list *, struct utmp *); 92 struct utmp_list *log_out(struct utmp_list *, struct utmp *); 93 int on_console(struct utmp_list *); 94 void show(char *, time_t); 95 void show_today(struct user_list *, struct utmp_list *, 96 time_t); 97 void show_users(struct user_list *); 98 struct user_list *update_user(struct user_list *, char *, time_t); 99 void usage(void); 100 101 /* 102 * open wtmp or die 103 */ 104 FILE * 105 file(char *name) 106 { 107 FILE *fp; 108 109 if (strcmp(name, "-") == 0) 110 fp = stdin; 111 else if ((fp = fopen(name, "r")) == NULL) 112 err(1, "%s", name); 113 /* in case we want to discriminate */ 114 if (strcmp(_PATH_WTMP, name)) 115 Flags |= AC_W; 116 return fp; 117 } 118 119 void 120 add_tty(char *name) 121 { 122 struct tty_list *tp; 123 char *rcp; 124 125 Flags |= AC_T; 126 127 if ((tp = malloc(sizeof(struct tty_list))) == NULL) 128 err(1, "malloc"); 129 tp->len = 0; /* full match */ 130 tp->ret = 1; /* do if match */ 131 if (*name == '!') { /* don't do if match */ 132 tp->ret = 0; 133 name++; 134 } 135 strlcpy(tp->name, name, sizeof (tp->name)); 136 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 137 *rcp = '\0'; 138 tp->len = strlen(tp->name); /* match len bytes only */ 139 } 140 tp->next = Ttys; 141 Ttys = tp; 142 } 143 144 /* 145 * should we process the named tty? 146 */ 147 int 148 do_tty(char *name) 149 { 150 struct tty_list *tp; 151 int def_ret = 0; 152 153 for (tp = Ttys; tp != NULL; tp = tp->next) { 154 if (tp->ret == 0) /* specific don't */ 155 def_ret = 1; /* default do */ 156 if (tp->len != 0) { 157 if (strncmp(name, tp->name, tp->len) == 0) 158 return tp->ret; 159 } else { 160 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 161 return tp->ret; 162 } 163 } 164 return def_ret; 165 } 166 167 /* 168 * update user's login time 169 */ 170 struct user_list * 171 update_user(struct user_list *head, char *name, time_t secs) 172 { 173 struct user_list *up; 174 175 for (up = head; up != NULL; up = up->next) { 176 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 177 up->secs += secs; 178 Total += secs; 179 return head; 180 } 181 } 182 /* 183 * not found so add new user unless specified users only 184 */ 185 if (Flags & AC_U) 186 return head; 187 188 if ((up = malloc(sizeof(struct user_list))) == NULL) 189 err(1, "malloc"); 190 up->next = head; 191 strlcpy(up->name, name, sizeof (up->name)); 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 (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 (day >= 0 && day != ltm->tm_yday) { 466 /* 467 * print yesterday's total 468 */ 469 secs = usr.ut_time; 470 secs -= ltm->tm_sec; 471 secs -= 60 * ltm->tm_min; 472 secs -= 3600 * ltm->tm_hour; 473 show_today(Users, head, secs); 474 } 475 } 476 /* 477 * anyone still logged in gets time up to now 478 */ 479 head = log_out(head, &usr); 480 481 if (Flags & AC_D) 482 show_today(Users, head, time(NULL)); 483 else { 484 if (Flags & AC_P) 485 show_users(Users); 486 show("total", Total); 487 } 488 return 0; 489 } 490 491 void 492 usage(void) 493 { 494 extern char *__progname; 495 (void)fprintf(stderr, "usage: " 496 "%s [-dp] [-t tty] [-w wtmp] [user ...]\n", __progname); 497 exit(1); 498 } 499