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