1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 1994 Christopher G. Demetriou
5 * Copyright (c) 1994 Simon J. Gerraty
6 * Copyright (c) 2012 Ed Schouten <[email protected]>
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33
34 #include <sys/queue.h>
35 #include <sys/time.h>
36
37 #include <err.h>
38 #include <errno.h>
39 #include <langinfo.h>
40 #include <locale.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <timeconv.h>
45 #include <unistd.h>
46 #include <utmpx.h>
47
48 /*
49 * this is for our list of currently logged in sessions
50 */
51 struct utmpx_entry {
52 SLIST_ENTRY(utmpx_entry) next;
53 char user[sizeof(((struct utmpx *)0)->ut_user)];
54 char id[sizeof(((struct utmpx *)0)->ut_id)];
55 #ifdef CONSOLE_TTY
56 char line[sizeof(((struct utmpx *)0)->ut_line)];
57 #endif
58 struct timeval time;
59 };
60
61 /*
62 * this is for our list of users that are accumulating time.
63 */
64 struct user_entry {
65 SLIST_ENTRY(user_entry) next;
66 char user[sizeof(((struct utmpx *)0)->ut_user)];
67 struct timeval time;
68 };
69
70 /*
71 * this is for chosing whether to ignore a login
72 */
73 struct tty_entry {
74 SLIST_ENTRY(tty_entry) next;
75 char line[sizeof(((struct utmpx *)0)->ut_line) + 2];
76 size_t len;
77 int ret;
78 };
79
80 /*
81 * globals - yes yuk
82 */
83 #ifdef CONSOLE_TTY
84 static const char *Console = CONSOLE_TTY;
85 #endif
86 static struct timeval Total = { 0, 0 };
87 static struct timeval FirstTime = { 0, 0 };
88 static int Flags = 0;
89 static SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx);
90 static SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users);
91 static SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys);
92
93 #define AC_W 1 /* not _PATH_WTMP */
94 #define AC_D 2 /* daily totals (ignore -p) */
95 #define AC_P 4 /* per-user totals */
96 #define AC_U 8 /* specified users only */
97 #define AC_T 16 /* specified ttys only */
98
99 static void ac(const char *);
100 static void usage(void);
101
102 static void
add_tty(const char * line)103 add_tty(const char *line)
104 {
105 struct tty_entry *tp;
106 char *rcp;
107
108 Flags |= AC_T;
109
110 if ((tp = malloc(sizeof(*tp))) == NULL)
111 errx(1, "malloc failed");
112 tp->len = 0; /* full match */
113 tp->ret = 1; /* do if match */
114 if (*line == '!') { /* don't do if match */
115 tp->ret = 0;
116 line++;
117 }
118 strlcpy(tp->line, line, sizeof(tp->line));
119 /* Wildcard. */
120 if ((rcp = strchr(tp->line, '*')) != NULL) {
121 *rcp = '\0';
122 /* Match len bytes only. */
123 tp->len = strlen(tp->line);
124 }
125 SLIST_INSERT_HEAD(&Ttys, tp, next);
126 }
127
128 /*
129 * should we process the named tty?
130 */
131 static int
do_tty(const char * line)132 do_tty(const char *line)
133 {
134 struct tty_entry *tp;
135 int def_ret = 0;
136
137 SLIST_FOREACH(tp, &Ttys, next) {
138 if (tp->ret == 0) /* specific don't */
139 def_ret = 1; /* default do */
140 if (tp->len != 0) {
141 if (strncmp(line, tp->line, tp->len) == 0)
142 return tp->ret;
143 } else {
144 if (strncmp(line, tp->line, sizeof(tp->line)) == 0)
145 return tp->ret;
146 }
147 }
148 return (def_ret);
149 }
150
151 #ifdef CONSOLE_TTY
152 /*
153 * is someone logged in on Console?
154 */
155 static int
on_console(void)156 on_console(void)
157 {
158 struct utmpx_entry *up;
159
160 SLIST_FOREACH(up, &CurUtmpx, next)
161 if (strcmp(up->line, Console) == 0)
162 return (1);
163 return (0);
164 }
165 #endif
166
167 /*
168 * Update user's login time.
169 * If no entry for this user is found, a new entry is inserted into the
170 * list alphabetically.
171 */
172 static void
update_user(const char * user,struct timeval secs)173 update_user(const char *user, struct timeval secs)
174 {
175 struct user_entry *up, *aup;
176 int c;
177
178 aup = NULL;
179 SLIST_FOREACH(up, &Users, next) {
180 c = strcmp(up->user, user);
181 if (c == 0) {
182 timeradd(&up->time, &secs, &up->time);
183 timeradd(&Total, &secs, &Total);
184 return;
185 } else if (c > 0)
186 break;
187 aup = up;
188 }
189 /*
190 * not found so add new user unless specified users only
191 */
192 if (Flags & AC_U)
193 return;
194
195 if ((up = malloc(sizeof(*up))) == NULL)
196 errx(1, "malloc failed");
197 if (aup == NULL)
198 SLIST_INSERT_HEAD(&Users, up, next);
199 else
200 SLIST_INSERT_AFTER(aup, up, next);
201 strlcpy(up->user, user, sizeof(up->user));
202 up->time = secs;
203 timeradd(&Total, &secs, &Total);
204 }
205
206 int
main(int argc,char * argv[])207 main(int argc, char *argv[])
208 {
209 const char *wtmpf = NULL;
210 int c;
211
212 (void) setlocale(LC_TIME, "");
213
214 while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) {
215 switch (c) {
216 case 'c':
217 #ifdef CONSOLE_TTY
218 Console = optarg;
219 #else
220 usage(); /* XXX */
221 #endif
222 break;
223 case 'd':
224 Flags |= AC_D;
225 break;
226 case 'p':
227 Flags |= AC_P;
228 break;
229 case 't': /* only do specified ttys */
230 add_tty(optarg);
231 break;
232 case 'w':
233 Flags |= AC_W;
234 wtmpf = optarg;
235 break;
236 case '?':
237 default:
238 usage();
239 break;
240 }
241 }
242 if (optind < argc) {
243 /*
244 * initialize user list
245 */
246 for (; optind < argc; optind++) {
247 update_user(argv[optind], (struct timeval){ 0, 0 });
248 }
249 Flags |= AC_U; /* freeze user list */
250 }
251 if (Flags & AC_D)
252 Flags &= ~AC_P;
253 ac(wtmpf);
254
255 return (0);
256 }
257
258 /*
259 * print login time in decimal hours
260 */
261 static void
show(const char * user,struct timeval secs)262 show(const char *user, struct timeval secs)
263 {
264 (void)printf("\t%-*s %8.2f\n",
265 (int)sizeof(((struct user_entry *)0)->user), user,
266 (double)secs.tv_sec / 3600);
267 }
268
269 static void
show_users(void)270 show_users(void)
271 {
272 struct user_entry *lp;
273
274 SLIST_FOREACH(lp, &Users, next)
275 show(lp->user, lp->time);
276 }
277
278 /*
279 * print total login time for 24hr period in decimal hours
280 */
281 static void
show_today(struct timeval today)282 show_today(struct timeval today)
283 {
284 struct user_entry *up;
285 struct utmpx_entry *lp;
286 char date[64];
287 struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday;
288 static int d_first = -1;
289
290 if (d_first < 0)
291 d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
292 timersub(&today, &usec, &yesterday);
293 (void)strftime(date, sizeof(date),
294 d_first ? "%e %b total" : "%b %e total",
295 localtime(&yesterday.tv_sec));
296
297 SLIST_FOREACH(lp, &CurUtmpx, next) {
298 timersub(&today, &lp->time, &diff);
299 update_user(lp->user, diff);
300 /* As if they just logged in. */
301 lp->time = today;
302 }
303 SLIST_FOREACH(up, &Users, next) {
304 timeradd(&total, &up->time, &total);
305 /* For next day. */
306 timerclear(&up->time);
307 }
308 if (timerisset(&total))
309 (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600);
310 }
311
312 /*
313 * Log a user out and update their times.
314 * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the
315 * system has been shut down.
316 */
317 static void
log_out(const struct utmpx * up)318 log_out(const struct utmpx *up)
319 {
320 struct utmpx_entry *lp, *lp2, *tlp;
321 struct timeval secs;
322
323 for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;)
324 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME ||
325 (up->ut_type == DEAD_PROCESS &&
326 memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) {
327 timersub(&up->ut_tv, &lp->time, &secs);
328 update_user(lp->user, secs);
329 /*
330 * now lose it
331 */
332 tlp = lp;
333 lp = SLIST_NEXT(lp, next);
334 if (lp2 == NULL)
335 SLIST_REMOVE_HEAD(&CurUtmpx, next);
336 else
337 SLIST_REMOVE_AFTER(lp2, next);
338 free(tlp);
339 } else {
340 lp2 = lp;
341 lp = SLIST_NEXT(lp, next);
342 }
343 }
344
345 /*
346 * if do_tty says ok, login a user
347 */
348 static void
log_in(struct utmpx * up)349 log_in(struct utmpx *up)
350 {
351 struct utmpx_entry *lp;
352
353 /*
354 * this could be a login. if we're not dealing with
355 * the console name, say it is.
356 *
357 * If we are, and if ut_host==":0.0" we know that it
358 * isn't a real login. _But_ if we have not yet recorded
359 * someone being logged in on Console - due to the wtmp
360 * file starting after they logged in, we'll pretend they
361 * logged in, at the start of the wtmp file.
362 */
363
364 #ifdef CONSOLE_TTY
365 if (up->ut_host[0] == ':') {
366 /*
367 * SunOS 4.0.2 does not treat ":0.0" as special but we
368 * do.
369 */
370 if (on_console())
371 return;
372 /*
373 * ok, no recorded login, so they were here when wtmp
374 * started! Adjust ut_time!
375 */
376 up->ut_tv = FirstTime;
377 /*
378 * this allows us to pick the right logout
379 */
380 strlcpy(up->ut_line, Console, sizeof(up->ut_line));
381 }
382 #endif
383 /*
384 * If we are doing specified ttys only, we ignore
385 * anything else.
386 */
387 if (Flags & AC_T && !do_tty(up->ut_line))
388 return;
389
390 /*
391 * go ahead and log them in
392 */
393 if ((lp = malloc(sizeof(*lp))) == NULL)
394 errx(1, "malloc failed");
395 SLIST_INSERT_HEAD(&CurUtmpx, lp, next);
396 strlcpy(lp->user, up->ut_user, sizeof(lp->user));
397 memcpy(lp->id, up->ut_id, sizeof(lp->id));
398 #ifdef CONSOLE_TTY
399 memcpy(lp->line, up->ut_line, sizeof(lp->line));
400 #endif
401 lp->time = up->ut_tv;
402 }
403
404 static void
ac(const char * file)405 ac(const char *file)
406 {
407 struct utmpx_entry *lp;
408 struct utmpx *usr, usht;
409 struct tm *ltm;
410 struct timeval prev_secs, ut_timecopy, secs, clock_shift, now;
411 int day, rfound;
412
413 day = -1;
414 timerclear(&prev_secs); /* Minimum acceptable date == 1970. */
415 timerclear(&secs);
416 timerclear(&clock_shift);
417 rfound = 0;
418 if (setutxdb(UTXDB_LOG, file) != 0)
419 err(1, "%s", file);
420 while ((usr = getutxent()) != NULL) {
421 rfound++;
422 ut_timecopy = usr->ut_tv;
423 /* Don't let the time run backwards. */
424 if (timercmp(&ut_timecopy, &prev_secs, <))
425 ut_timecopy = prev_secs;
426 prev_secs = ut_timecopy;
427
428 if (!timerisset(&FirstTime))
429 FirstTime = ut_timecopy;
430 if (Flags & AC_D) {
431 ltm = localtime(&ut_timecopy.tv_sec);
432 if (day >= 0 && day != ltm->tm_yday) {
433 day = ltm->tm_yday;
434 /*
435 * print yesterday's total
436 */
437 secs = ut_timecopy;
438 secs.tv_sec -= ltm->tm_sec;
439 secs.tv_sec -= 60 * ltm->tm_min;
440 secs.tv_sec -= 3600 * ltm->tm_hour;
441 secs.tv_usec = 0;
442 show_today(secs);
443 } else
444 day = ltm->tm_yday;
445 }
446 switch(usr->ut_type) {
447 case OLD_TIME:
448 clock_shift = ut_timecopy;
449 break;
450 case NEW_TIME:
451 timersub(&clock_shift, &ut_timecopy, &clock_shift);
452 /*
453 * adjust time for those logged in
454 */
455 SLIST_FOREACH(lp, &CurUtmpx, next)
456 timersub(&lp->time, &clock_shift, &lp->time);
457 break;
458 case BOOT_TIME:
459 case SHUTDOWN_TIME:
460 log_out(usr);
461 FirstTime = ut_timecopy; /* shouldn't be needed */
462 break;
463 case USER_PROCESS:
464 /*
465 * If they came in on pts/..., then it is only
466 * a login session if the ut_host field is non-empty.
467 */
468 if (strncmp(usr->ut_line, "pts/", 4) != 0 ||
469 *usr->ut_host != '\0')
470 log_in(usr);
471 break;
472 case DEAD_PROCESS:
473 log_out(usr);
474 break;
475 }
476 }
477 endutxent();
478 (void)gettimeofday(&now, NULL);
479 if (Flags & AC_W)
480 usht.ut_tv = ut_timecopy;
481 else
482 usht.ut_tv = now;
483 usht.ut_type = SHUTDOWN_TIME;
484
485 if (Flags & AC_D) {
486 ltm = localtime(&ut_timecopy.tv_sec);
487 if (day >= 0 && day != ltm->tm_yday) {
488 /*
489 * print yesterday's total
490 */
491 secs = ut_timecopy;
492 secs.tv_sec -= ltm->tm_sec;
493 secs.tv_sec -= 60 * ltm->tm_min;
494 secs.tv_sec -= 3600 * ltm->tm_hour;
495 secs.tv_usec = 0;
496 show_today(secs);
497 }
498 }
499 /*
500 * anyone still logged in gets time up to now
501 */
502 log_out(&usht);
503
504 if (Flags & AC_D)
505 show_today(now);
506 else {
507 if (Flags & AC_P)
508 show_users();
509 show("total", Total);
510 }
511 }
512
513 static void
usage(void)514 usage(void)
515 {
516 (void)fprintf(stderr,
517 #ifdef CONSOLE_TTY
518 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
519 #else
520 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");
521 #endif
522 exit(1);
523 }
524