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