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 */
51struct 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 */
59struct 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 */
68struct 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 */
78static time_t	Total = 0;
79static time_t	FirstTime = 0;
80static int	Flags = 0;
81static struct user_list *Users = NULL;
82static struct tty_list *Ttys = NULL;
83static int Maxcon = 0, Ncon = 0;
84static 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
98static int Debug = 0;
99#endif
100
101static int		ac(FILE *);
102static struct tty_list	*add_tty(char *);
103static int		do_tty(char *);
104static FILE		*file(const char *);
105static struct utmp_list	*log_in(struct utmp_list *, struct utmp *);
106static struct utmp_list	*log_out(struct utmp_list *, struct utmp *);
107#ifdef notdef
108static int		on_console(struct utmp_list *);
109#endif
110static void		find_login_ttys(void);
111static void		show(const char *, time_t);
112static void		show_today(struct user_list *, struct utmp_list *,
113    time_t);
114static void		show_users(struct user_list *);
115static struct user_list	*update_user(struct user_list *, char *, time_t);
116static int		compare(const void *, const void *);
117__dead static void	usage(void);
118
119/*
120 * open wtmp or die
121 */
122static FILE *
123file(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
137static struct tty_list *
138add_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 */
166static int
167do_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
186static int
187compare(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 */
197static void
198find_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 */
227static int
228on_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 */
243static struct user_list *
244update_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
270int
271main(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 */
332static void
333show(const char *name, time_t secs)
334{
335	(void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
336	    ((double)secs / 3600));
337}
338
339static void
340show_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 */
351static void
352show_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 */
384static struct utmp_list *
385log_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 */
424struct utmp_list *
425log_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
458static int
459ac(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
558static void
559usage(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