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/time.h>
29
30 #include <err.h>
31 #include <errno.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <utmp.h>
37 #include <unistd.h>
38
39 /*
40 * this is for our list of currently logged in sessions
41 */
42 struct utmp_list {
43 struct utmp_list *next;
44 struct utmp usr;
45 };
46
47 /*
48 * this is for our list of users that are accumulating time.
49 */
50 struct user_list {
51 struct user_list *next;
52 char name[UT_NAMESIZE+1];
53 time_t secs;
54 };
55
56 /*
57 * this is for choosing whether to ignore a login
58 */
59 struct tty_list {
60 struct tty_list *next;
61 char name[UT_LINESIZE+3];
62 size_t len;
63 int ret;
64 };
65
66 /*
67 * globals - yes yuk
68 */
69 static time_t Total = 0;
70 static time_t FirstTime = 0;
71 static int Flags = 0;
72 static struct user_list *Users = NULL;
73 static struct tty_list *Ttys = NULL;
74
75 #define AC_W 1 /* not _PATH_WTMP */
76 #define AC_D 2 /* daily totals (ignore -p) */
77 #define AC_P 4 /* per-user totals */
78 #define AC_U 8 /* specified users only */
79 #define AC_T 16 /* specified ttys only */
80
81 #ifdef DEBUG
82 static int Debug = 0;
83 #endif
84
85 int main(int, char **);
86 int ac(FILE *);
87 void add_tty(char *);
88 int do_tty(char *);
89 FILE *file(char *);
90 struct utmp_list *log_in(struct utmp_list *, struct utmp *);
91 struct utmp_list *log_out(struct utmp_list *, struct utmp *);
92 void show(char *, time_t);
93 void show_today(struct user_list *, struct utmp_list *,
94 time_t);
95 void show_users(struct user_list *);
96 struct user_list *update_user(struct user_list *, char *, time_t);
97 void usage(void);
98
99 /*
100 * open wtmp or die
101 */
102 FILE *
file(char * name)103 file(char *name)
104 {
105 FILE *fp;
106
107 if (strcmp(name, "-") == 0)
108 fp = stdin;
109 else if ((fp = fopen(name, "r")) == NULL)
110 err(1, "%s", name);
111 /* in case we want to discriminate */
112 if (strcmp(_PATH_WTMP, name))
113 Flags |= AC_W;
114 return fp;
115 }
116
117 void
add_tty(char * name)118 add_tty(char *name)
119 {
120 struct tty_list *tp;
121 char *rcp;
122
123 Flags |= AC_T;
124
125 if ((tp = malloc(sizeof(struct tty_list))) == NULL)
126 err(1, "malloc");
127 tp->len = 0; /* full match */
128 tp->ret = 1; /* do if match */
129 if (*name == '!') { /* don't do if match */
130 tp->ret = 0;
131 name++;
132 }
133 strlcpy(tp->name, name, sizeof (tp->name));
134 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */
135 *rcp = '\0';
136 tp->len = strlen(tp->name); /* match len bytes only */
137 }
138 tp->next = Ttys;
139 Ttys = tp;
140 }
141
142 /*
143 * should we process the named tty?
144 */
145 int
do_tty(char * name)146 do_tty(char *name)
147 {
148 struct tty_list *tp;
149 int def_ret = 0;
150
151 for (tp = Ttys; tp != NULL; tp = tp->next) {
152 if (tp->ret == 0) /* specific don't */
153 def_ret = 1; /* default do */
154 if (tp->len != 0) {
155 if (strncmp(name, tp->name, tp->len) == 0)
156 return tp->ret;
157 } else {
158 if (strncmp(name, tp->name, sizeof (tp->name)) == 0)
159 return tp->ret;
160 }
161 }
162 return def_ret;
163 }
164
165 /*
166 * update user's login time
167 */
168 struct user_list *
update_user(struct user_list * head,char * name,time_t secs)169 update_user(struct user_list *head, char *name, time_t secs)
170 {
171 struct user_list *up;
172
173 for (up = head; up != NULL; up = up->next) {
174 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) {
175 up->secs += secs;
176 Total += secs;
177 return head;
178 }
179 }
180 /*
181 * not found so add new user unless specified users only
182 */
183 if (Flags & AC_U)
184 return head;
185
186 if ((up = malloc(sizeof(struct user_list))) == NULL)
187 err(1, "malloc");
188 up->next = head;
189 strncpy(up->name, name, sizeof(up->name) - 1);
190 up->name[sizeof(up->name) - 1] = '\0';
191 up->secs = secs;
192 Total += secs;
193 return up;
194 }
195
196 int
main(int argc,char * argv[])197 main(int argc, char *argv[])
198 {
199 FILE *fp;
200 int c;
201
202 if (pledge("stdio rpath", NULL) == -1)
203 err(1, "pledge");
204
205 fp = NULL;
206 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) {
207 switch (c) {
208 #ifdef DEBUG
209 case 'D':
210 Debug = 1;
211 break;
212 #endif
213 case 'd':
214 Flags |= AC_D;
215 break;
216 case 'p':
217 Flags |= AC_P;
218 break;
219 case 't': /* only do specified ttys */
220 add_tty(optarg);
221 break;
222 case 'w':
223 fp = file(optarg);
224 break;
225 default:
226 usage();
227 break;
228 }
229 }
230 if (optind < argc) {
231 /*
232 * initialize user list
233 */
234 for (; optind < argc; optind++) {
235 Users = update_user(Users, argv[optind], 0L);
236 }
237 Flags |= AC_U; /* freeze user list */
238 }
239 if (Flags & AC_D)
240 Flags &= ~AC_P;
241 if (fp == NULL) {
242 /*
243 * if _PATH_WTMP does not exist, exit quietly
244 */
245 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT)
246 return 0;
247
248 fp = file(_PATH_WTMP);
249 }
250 ac(fp);
251
252 return 0;
253 }
254
255 /*
256 * print login time in decimal hours
257 */
258 void
show(char * name,time_t secs)259 show(char *name, time_t secs)
260 {
261 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
262 ((double)secs / 3600));
263 }
264
265 void
show_users(struct user_list * list)266 show_users(struct user_list *list)
267 {
268 struct user_list *lp;
269
270 for (lp = list; lp; lp = lp->next)
271 show(lp->name, lp->secs);
272 }
273
274 /*
275 * print total login time for 24hr period in decimal hours
276 */
277 void
show_today(struct user_list * users,struct utmp_list * logins,time_t secs)278 show_today(struct user_list *users, struct utmp_list *logins, time_t secs)
279 {
280 struct user_list *up;
281 struct utmp_list *lp;
282 char date[64];
283 time_t yesterday = secs - 1;
284
285 (void)strftime(date, sizeof (date), "%b %e total",
286 localtime(&yesterday));
287
288 /* restore the missing second */
289 yesterday++;
290
291 for (lp = logins; lp != NULL; lp = lp->next) {
292 secs = yesterday - lp->usr.ut_time;
293 Users = update_user(Users, lp->usr.ut_name, secs);
294 lp->usr.ut_time = yesterday; /* as if they just logged in */
295 }
296 secs = 0;
297 for (up = users; up != NULL; up = up->next) {
298 secs += up->secs;
299 up->secs = 0; /* for next day */
300 }
301 if (secs)
302 (void)printf("%s %11.2f\n", date, ((double)secs / 3600));
303 }
304
305 /*
306 * log a user out and update their times.
307 * if ut_line is "~", we log all users out as the system has
308 * been shut down.
309 */
310 struct utmp_list *
log_out(struct utmp_list * head,struct utmp * up)311 log_out(struct utmp_list *head, struct utmp *up)
312 {
313 struct utmp_list *lp, *lp2, *tlp;
314 time_t secs;
315
316 for (lp = head, lp2 = NULL; lp != NULL; )
317 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line,
318 sizeof (up->ut_line)) == 0) {
319 secs = up->ut_time - lp->usr.ut_time;
320 Users = update_user(Users, lp->usr.ut_name, secs);
321 #ifdef DEBUG
322 if (Debug)
323 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
324 19, ctime(&up->ut_time),
325 sizeof (lp->usr.ut_line), lp->usr.ut_line,
326 sizeof (lp->usr.ut_name), lp->usr.ut_name,
327 secs / 3600, (secs % 3600) / 60, secs % 60);
328 #endif
329 /*
330 * now lose it
331 */
332 tlp = lp;
333 lp = lp->next;
334 if (tlp == head)
335 head = lp;
336 else if (lp2 != NULL)
337 lp2->next = lp;
338 free(tlp);
339 } else {
340 lp2 = lp;
341 lp = lp->next;
342 }
343 return head;
344 }
345
346
347 /*
348 * if do_tty says ok, login a user
349 */
350 struct utmp_list *
log_in(struct utmp_list * head,struct utmp * up)351 log_in(struct utmp_list *head, struct utmp *up)
352 {
353 struct utmp_list *lp;
354
355 /*
356 * this could be a login. if we're not dealing with
357 * the console name, say it is.
358 *
359 * If we are, and if ut_host==":0.0" we know that it
360 * isn't a real login. _But_ if we have not yet recorded
361 * someone being logged in on Console - due to the wtmp
362 * file starting after they logged in, we'll pretend they
363 * logged in, at the start of the wtmp file.
364 */
365
366 /*
367 * If we are doing specified ttys only, we ignore
368 * anything else.
369 */
370 if (Flags & AC_T)
371 if (!do_tty(up->ut_line))
372 return head;
373
374 /*
375 * go ahead and log them in
376 */
377 if ((lp = malloc(sizeof(struct utmp_list))) == NULL)
378 err(1, "malloc");
379 lp->next = head;
380 head = lp;
381 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp));
382 #ifdef DEBUG
383 if (Debug) {
384 printf("%-.*s %-.*s: %-.*s logged in", 19,
385 ctime(&lp->usr.ut_time), sizeof (up->ut_line),
386 up->ut_line, sizeof (up->ut_name), up->ut_name);
387 if (*up->ut_host)
388 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host);
389 putchar('\n');
390 }
391 #endif
392 return head;
393 }
394
395 int
ac(FILE * fp)396 ac(FILE *fp)
397 {
398 struct utmp_list *lp, *head = NULL;
399 struct utmp usr;
400 struct tm *ltm;
401 time_t secs = 0, prev = 0;
402 int day = -1;
403
404 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) {
405 if (!FirstTime)
406 FirstTime = usr.ut_time;
407 if (usr.ut_time < prev)
408 continue; /* broken record */
409 prev = usr.ut_time;
410 if (Flags & AC_D) {
411 ltm = localtime(&usr.ut_time);
412 if (ltm == NULL)
413 err(1, "localtime");
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 (ltm == NULL)
466 err(1, "localtime");
467 if (day >= 0 && day != ltm->tm_yday) {
468 /*
469 * print yesterday's total
470 */
471 secs = usr.ut_time;
472 secs -= ltm->tm_sec;
473 secs -= 60 * ltm->tm_min;
474 secs -= 3600 * ltm->tm_hour;
475 show_today(Users, head, secs);
476 }
477 }
478 /*
479 * anyone still logged in gets time up to now
480 */
481 head = log_out(head, &usr);
482
483 if (Flags & AC_D)
484 show_today(Users, head, time(NULL));
485 else {
486 if (Flags & AC_P)
487 show_users(Users);
488 show("total", Total);
489 }
490 return 0;
491 }
492
493 void
usage(void)494 usage(void)
495 {
496 extern char *__progname;
497 (void)fprintf(stderr, "usage: "
498 "%s [-dp] [-t tty] [-w wtmp] [user ...]\n", __progname);
499 exit(1);
500 }
501