1 /*
2 * tc.who.c: Watch logins and logouts...
3 */
4 /*-
5 * Copyright (c) 1980, 1991 The Regents of the University of California.
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 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32 #include "sh.h"
33 #include "tc.h"
34
35 #ifndef HAVENOUTMP
36 /*
37 * kfk 26 Jan 1984 - for login watch functions.
38 */
39 #include <ctype.h>
40
41 #ifdef HAVE_UTMPX_H
42 # include <utmpx.h>
43 # define UTNAMLEN sizeof(((struct utmpx *) 0)->ut_name)
44 # define UTLINLEN sizeof(((struct utmpx *) 0)->ut_line)
45 # ifdef HAVE_STRUCT_UTMPX_UT_HOST
46 # define UTHOSTLEN sizeof(((struct utmpx *) 0)->ut_host)
47 # endif
48 /* I just redefine a few words here. Changing every occurrence below
49 * seems like too much of work. All UTMP functions have equivalent
50 * UTMPX counterparts, so they can be added all here when needed.
51 * Kimmo Suominen, Oct 14 1991
52 */
53 # if defined(__UTMPX_FILE) && !defined(UTMPX_FILE)
54 # define TCSH_PATH_UTMP __UTMPX_FILE
55 # elif defined(_PATH_UTMPX)
56 # define TCSH_PATH_UTMP _PATH_UTMPX
57 # elif defined(UTMPX_FILE)
58 # define TCSH_PATH_UTMP UTMPX_FILE
59 # elif __FreeBSD_version >= 900000
60 # /* Why isn't this defined somewhere? */
61 # define TCSH_PATH_UTMP "/var/run/utx.active"
62 # elif defined(__hpux)
63 # define TCSH_PATH_UTMP "/etc/utmpx"
64 # elif defined(IBMAIX) && defined(UTMP_FILE)
65 # define TCSH_PATH_UTMP UTMP_FILE
66 # endif
67 # if defined(TCSH_PATH_UTMP) || !defined(HAVE_UTMP_H)
68 # define utmp utmpx
69 # define TCSH_USE_UTMPX
70 # if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
71 # define getutent getutxent
72 # define setutent setutxent
73 # define endutent endutxent
74 # endif /* HAVE_GETUTENT || HAVE_GETUTXENT */
75 # if defined(HAVE_STRUCT_UTMPX_UT_TV)
76 # define ut_time ut_tv.tv_sec
77 # elif defined(HAVE_STRUCT_UTMPX_UT_XTIME)
78 # define ut_time ut_xtime
79 # endif
80 # if defined(HAVE_STRUCT_UTMPX_UT_USER)
81 # define ut_name ut_user
82 # endif
83 # endif /* TCSH_PATH_UTMP || !HAVE_UTMP_H */
84 #endif /* HAVE_UTMPX_H */
85
86 #if !defined(TCSH_USE_UTMPX) && defined(HAVE_UTMP_H)
87 # include <utmp.h>
88 # if defined(HAVE_STRUCT_UTMP_UT_TV)
89 # define ut_time ut_tv.tv_sec
90 # elif defined(HAVE_STRUCT_UTMP_UT_XTIME)
91 # define ut_time ut_xtime
92 # endif
93 # if defined(HAVE_STRUCT_UTMP_UT_USER)
94 # define ut_name ut_user
95 # endif
96 # ifndef BROKEN_CC
97 # define UTNAMLEN sizeof(((struct utmp *) 0)->ut_name)
98 # define UTLINLEN sizeof(((struct utmp *) 0)->ut_line)
99 # ifdef HAVE_STRUCT_UTMP_UT_HOST
100 # ifdef _SEQUENT_
101 # define UTHOSTLEN 100
102 # else
103 # define UTHOSTLEN sizeof(((struct utmp *) 0)->ut_host)
104 # endif
105 # endif /* HAVE_STRUCT_UTMP_UT_HOST */
106 # else
107 /* give poor cc a little help if it needs it */
108 struct utmp __ut;
109 # define UTNAMLEN sizeof(__ut.ut_name)
110 # define UTLINLEN sizeof(__ut.ut_line)
111 # ifdef HAVE_STRUCT_UTMP_UT_HOST
112 # ifdef _SEQUENT_
113 # define UTHOSTLEN 100
114 # else
115 # define UTHOSTLEN sizeof(__ut.ut_host)
116 # endif
117 # endif /* HAVE_STRUCT_UTMP_UT_HOST */
118 # endif /* BROKEN_CC */
119 # ifndef TCSH_PATH_UTMP
120 # ifdef UTMP_FILE
121 # define TCSH_PATH_UTMP UTMP_FILE
122 # elif defined(_PATH_UTMP)
123 # define TCSH_PATH_UTMP _PATH_UTMP
124 # else
125 # define TCSH_PATH_UTMP "/etc/utmp"
126 # endif /* UTMP_FILE */
127 # endif /* TCSH_PATH_UTMP */
128 #endif /* !TCSH_USE_UTMPX && HAVE_UTMP_H */
129
130 #ifndef UTNAMLEN
131 #define UTNAMLEN 64
132 #endif
133 #ifndef UTLINLEN
134 #define UTLINLEN 64
135 #endif
136
137 struct who {
138 struct who *who_next;
139 struct who *who_prev;
140 char who_name[UTNAMLEN + 1];
141 char who_new[UTNAMLEN + 1];
142 char who_tty[UTLINLEN + 1];
143 #ifdef UTHOSTLEN
144 char who_host[UTHOSTLEN + 1];
145 #endif /* UTHOSTLEN */
146 time_t who_time;
147 int who_status;
148 };
149
150 static struct who whohead, whotail;
151 static time_t watch_period = 0;
152 static time_t stlast = 0;
153 #ifdef WHODEBUG
154 static void debugwholist (struct who *, struct who *);
155 #endif
156 static void print_who (struct who *);
157
158
159 #define ONLINE 01
160 #define OFFLINE 02
161 #define CHANGED 04
162 #define STMASK 07
163 #define ANNOUNCE 010
164 #define CLEARED 020
165
166 /*
167 * Karl Kleinpaste, 26 Jan 1984.
168 * Initialize the dummy tty list for login watch.
169 * This dummy list eliminates boundary conditions
170 * when doing pointer-chase searches.
171 */
172 void
initwatch(void)173 initwatch(void)
174 {
175 whohead.who_next = &whotail;
176 whotail.who_prev = &whohead;
177 stlast = 1;
178 #ifdef WHODEBUG
179 debugwholist(NULL, NULL);
180 #endif /* WHODEBUG */
181 }
182
183 void
resetwatch(void)184 resetwatch(void)
185 {
186 watch_period = 0;
187 stlast = 0;
188 }
189
190 /*
191 * Karl Kleinpaste, 26 Jan 1984.
192 * Watch /etc/utmp for login/logout changes.
193 */
194 void
watch_login(int force)195 watch_login(int force)
196 {
197 int comp = -1, alldone;
198 int firsttime = stlast == 1;
199 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
200 struct utmp *uptr;
201 #else
202 int utmpfd;
203 #endif
204 struct utmp utmp;
205 struct who *wp, *wpnew;
206 struct varent *v;
207 Char **vp = NULL;
208 time_t t, interval = MAILINTVL;
209 struct stat sta;
210 #if defined(HAVE_STRUCT_UTMP_UT_HOST) && defined(_SEQUENT_)
211 char *host, *ut_find_host();
212 #endif
213 #ifdef WINNT_NATIVE
214 USE(utmp);
215 USE(utmpfd);
216 USE(sta);
217 USE(wpnew);
218 #endif /* WINNT_NATIVE */
219
220 /* stop SIGINT, lest our login list get trashed. */
221 pintr_disabled++;
222 cleanup_push(&pintr_disabled, disabled_cleanup);
223
224 v = adrof(STRwatch);
225 if ((v == NULL || v->vec == NULL) && !force) {
226 cleanup_until(&pintr_disabled);
227 return; /* no names to watch */
228 }
229 if (!force) {
230 trim(vp = v->vec);
231 if (blklen(vp) % 2) /* odd # args: 1st == # minutes. */
232 interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL;
233 }
234 else
235 interval = 0;
236
237 (void) time(&t);
238 if (t - watch_period < interval) {
239 cleanup_until(&pintr_disabled);
240 return; /* not long enough yet... */
241 }
242 watch_period = t;
243 #ifndef WINNT_NATIVE
244 /*
245 * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
246 * Don't open utmp all the time, stat it first...
247 */
248 if (stat(TCSH_PATH_UTMP, &sta)) {
249 if (!force)
250 xprintf(CGETS(26, 1,
251 "cannot stat %s. Please \"unset watch\".\n"),
252 TCSH_PATH_UTMP);
253 cleanup_until(&pintr_disabled);
254 return;
255 }
256 if (stlast == sta.st_mtime) {
257 cleanup_until(&pintr_disabled);
258 return;
259 }
260 stlast = sta.st_mtime;
261 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
262 setutent();
263 #else
264 if ((utmpfd = xopen(TCSH_PATH_UTMP, O_RDONLY|O_LARGEFILE)) < 0) {
265 if (!force)
266 xprintf(CGETS(26, 2,
267 "%s cannot be opened. Please \"unset watch\".\n"),
268 TCSH_PATH_UTMP);
269 cleanup_until(&pintr_disabled);
270 return;
271 }
272 cleanup_push(&utmpfd, open_cleanup);
273 #endif
274
275 /*
276 * xterm clears the entire utmp entry - mark everyone on the status list
277 * OFFLINE or we won't notice X "logouts"
278 */
279 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next)
280 wp->who_status = OFFLINE | CLEARED;
281
282 /*
283 * Read in the utmp file, sort the entries, and update existing entries or
284 * add new entries to the status list.
285 */
286 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
287 while ((uptr = getutent()) != NULL) {
288 memcpy(&utmp, uptr, sizeof (utmp));
289 #else
290 while (xread(utmpfd, &utmp, sizeof utmp) == sizeof utmp) {
291 #endif
292
293 # ifdef DEAD_PROCESS
294 # ifndef IRIS4D
295 if (utmp.ut_type != USER_PROCESS)
296 continue;
297 # else
298 /* Why is that? Cause the utmp file is always corrupted??? */
299 if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
300 continue;
301 # endif /* IRIS4D */
302 # endif /* DEAD_PROCESS */
303
304 if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
305 continue; /* completely void entry */
306 # ifdef DEAD_PROCESS
307 if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
308 continue;
309 # endif /* DEAD_PROCESS */
310 wp = whohead.who_next;
311 while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
312 wp = wp->who_next;/* find that tty! */
313
314 if (wp->who_next && comp == 0) { /* found the tty... */
315 if (utmp.ut_time < wp->who_time)
316 continue;
317 # ifdef DEAD_PROCESS
318 if (utmp.ut_type == DEAD_PROCESS) {
319 wp->who_time = utmp.ut_time;
320 wp->who_status = OFFLINE;
321 }
322 else
323 # endif /* DEAD_PROCESS */
324 if (utmp.ut_name[0] == '\0') {
325 wp->who_time = utmp.ut_time;
326 wp->who_status = OFFLINE;
327 }
328 else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
329 /* someone is logged in */
330 wp->who_time = utmp.ut_time;
331 wp->who_status = ONLINE | ANNOUNCE; /* same guy */
332 }
333 else {
334 (void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
335 # ifdef UTHOSTLEN
336 # ifdef _SEQUENT_
337 host = ut_find_host(wp->who_tty);
338 if (host)
339 (void) strncpy(wp->who_host, host, UTHOSTLEN);
340 else
341 wp->who_host[0] = 0;
342 # else
343 (void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
344 # endif
345 # endif /* UTHOSTLEN */
346 wp->who_time = utmp.ut_time;
347 if (wp->who_name[0] == '\0')
348 wp->who_status = ONLINE;
349 else
350 wp->who_status = CHANGED;
351 }
352 }
353 else { /* new tty in utmp */
354 wpnew = xcalloc(1, sizeof *wpnew);
355 (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
356 # ifdef UTHOSTLEN
357 # ifdef _SEQUENT_
358 host = ut_find_host(wpnew->who_tty);
359 if (host)
360 (void) strncpy(wpnew->who_host, host, UTHOSTLEN);
361 else
362 wpnew->who_host[0] = 0;
363 # else
364 (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
365 # endif
366 # endif /* UTHOSTLEN */
367 wpnew->who_time = utmp.ut_time;
368 # ifdef DEAD_PROCESS
369 if (utmp.ut_type == DEAD_PROCESS)
370 wpnew->who_status = OFFLINE;
371 else
372 # endif /* DEAD_PROCESS */
373 if (utmp.ut_name[0] == '\0')
374 wpnew->who_status = OFFLINE;
375 else {
376 (void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
377 wpnew->who_status = ONLINE;
378 }
379 # ifdef WHODEBUG
380 debugwholist(wpnew, wp);
381 # endif /* WHODEBUG */
382
383 wpnew->who_next = wp; /* link in a new 'who' */
384 wpnew->who_prev = wp->who_prev;
385 wpnew->who_prev->who_next = wpnew;
386 wp->who_prev = wpnew; /* linked in now */
387 }
388 }
389 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
390 endutent();
391 #else
392 cleanup_until(&utmpfd);
393 #endif
394 #endif /* !WINNT_NATIVE */
395
396 if (force || vp == NULL) {
397 cleanup_until(&pintr_disabled);
398 return;
399 }
400
401 /*
402 * The state of all logins is now known, so we can search the user's list
403 * of watchables to print the interesting ones.
404 */
405 for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
406 *(vp + 1) != NULL && **(vp + 1) != '\0';
407 vp += 2) { /* args used in pairs... */
408
409 if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
410 alldone = 1;
411
412 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
413 if (wp->who_status & ANNOUNCE ||
414 (!eq(STRany, vp[0]) &&
415 !Gmatch(str2short(wp->who_name), vp[0]) &&
416 !Gmatch(str2short(wp->who_new), vp[0])) ||
417 (!Gmatch(str2short(wp->who_tty), vp[1]) &&
418 !eq(STRany, vp[1])))
419 continue; /* entry doesn't qualify */
420 /* already printed or not right one to print */
421
422
423 if (wp->who_status & CLEARED) {/* utmp entry was cleared */
424 wp->who_time = watch_period;
425 wp->who_status &= ~CLEARED;
426 }
427
428 if ((wp->who_status & OFFLINE) &&
429 (wp->who_name[0] != '\0')) {
430 if (!firsttime)
431 print_who(wp);
432 wp->who_name[0] = '\0';
433 wp->who_status |= ANNOUNCE;
434 continue;
435 }
436 if (wp->who_status & ONLINE) {
437 if (!firsttime)
438 print_who(wp);
439 (void) strcpy(wp->who_name, wp->who_new);
440 wp->who_status |= ANNOUNCE;
441 continue;
442 }
443 if (wp->who_status & CHANGED) {
444 if (!firsttime)
445 print_who(wp);
446 (void) strcpy(wp->who_name, wp->who_new);
447 wp->who_status |= ANNOUNCE;
448 continue;
449 }
450 }
451 }
452 cleanup_until(&pintr_disabled);
453 }
454
455 #ifdef WHODEBUG
456 static void
457 debugwholist(struct who *new, struct who *wp)
458 {
459 struct who *a;
460
461 a = whohead.who_next;
462 while (a->who_next != NULL) {
463 xprintf("%s/%s -> ", a->who_name, a->who_tty);
464 a = a->who_next;
465 }
466 xprintf("TAIL\n");
467 if (a != &whotail) {
468 xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
469 abort();
470 }
471 a = whotail.who_prev;
472 xprintf(CGETS(26, 4, "backward: "));
473 while (a->who_prev != NULL) {
474 xprintf("%s/%s -> ", a->who_name, a->who_tty);
475 a = a->who_prev;
476 }
477 xprintf("HEAD\n");
478 if (a != &whohead) {
479 xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
480 abort();
481 }
482 if (new)
483 xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
484 if (wp)
485 xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
486 }
487 #endif /* WHODEBUG */
488
489
490 static void
491 print_who(struct who *wp)
492 {
493 #ifdef UTHOSTLEN
494 Char *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
495 #else
496 Char *cp = str2short(CGETS(26, 8, "%n has %a %l."));
497 #endif /* UTHOSTLEN */
498 struct varent *vp = adrof(STRwho);
499 Char *str;
500
501 if (vp && vp->vec && vp->vec[0])
502 cp = vp->vec[0];
503
504 str = tprintf(FMT_WHO, cp, NULL, wp->who_time, wp);
505 cleanup_push(str, xfree);
506 for (cp = str; *cp;)
507 xputwchar(*cp++);
508 cleanup_until(str);
509 xputchar('\n');
510 } /* end print_who */
511
512
513 char *
514 who_info(ptr_t ptr, int c)
515 {
516 struct who *wp = ptr;
517 char *wbuf;
518 #ifdef UTHOSTLEN
519 char *wb;
520 int flg;
521 char *pb;
522 #endif /* UTHOSTLEN */
523
524 switch (c) {
525 case 'n': /* user name */
526 switch (wp->who_status & STMASK) {
527 case ONLINE:
528 case CHANGED:
529 return strsave(wp->who_new);
530 case OFFLINE:
531 return strsave(wp->who_name);
532 default:
533 break;
534 }
535 break;
536
537 case 'a':
538 switch (wp->who_status & STMASK) {
539 case ONLINE:
540 return strsave(CGETS(26, 9, "logged on"));
541 case OFFLINE:
542 return strsave(CGETS(26, 10, "logged off"));
543 case CHANGED:
544 return xasprintf(CGETS(26, 11, "replaced %s on"), wp->who_name);
545 default:
546 break;
547 }
548 break;
549
550 #ifdef UTHOSTLEN
551 case 'm':
552 if (wp->who_host[0] == '\0')
553 return strsave(CGETS(26, 12, "local"));
554 else {
555 pb = wp->who_host;
556 wbuf = xmalloc(strlen(pb) + 1);
557 wb = wbuf;
558 /* the ':' stuff is for <host>:<display>.<screen> */
559 for (flg = isdigit((unsigned char)*pb) ? '\0' : '.';
560 *pb != '\0' && (*pb != flg || ((pb = strchr(pb, ':')) != 0));
561 pb++) {
562 if (*pb == ':')
563 flg = '\0';
564 *wb++ = isupper((unsigned char)*pb) ?
565 tolower((unsigned char)*pb) : *pb;
566 }
567 *wb = '\0';
568 return wbuf;
569 }
570
571 case 'M':
572 if (wp->who_host[0] == '\0')
573 return strsave(CGETS(26, 12, "local"));
574 else {
575 pb = wp->who_host;
576 wbuf = xmalloc(strlen(pb) + 1);
577 wb = wbuf;
578 for (; *pb != '\0'; pb++)
579 *wb++ = isupper((unsigned char)*pb) ?
580 tolower((unsigned char)*pb) : *pb;
581 *wb = '\0';
582 return wbuf;
583 }
584 #endif /* UTHOSTLEN */
585
586 case 'l':
587 return strsave(wp->who_tty);
588
589 default:
590 wbuf = xmalloc(3);
591 wbuf[0] = '%';
592 wbuf[1] = (char) c;
593 wbuf[2] = '\0';
594 return wbuf;
595 }
596 return NULL;
597 }
598
599 void
600 /*ARGSUSED*/
601 dolog(Char **v, struct command *c)
602 {
603 struct who *wp;
604 struct varent *vp;
605
606 USE(v);
607 USE(c);
608 vp = adrof(STRwatch); /* lint insists vp isn't used unless we */
609 if (vp == NULL) /* unless we assign it outside the if */
610 stderror(ERR_NOWATCH);
611 resetwatch();
612 wp = whohead.who_next;
613 while (wp->who_next != NULL) {
614 wp->who_name[0] = '\0';
615 wp = wp->who_next;
616 }
617 }
618
619 # ifdef UTHOSTLEN
620 size_t
621 utmphostsize(void)
622 {
623 return UTHOSTLEN;
624 }
625
626 char *
627 utmphost(void)
628 {
629 char *tty = short2str(varval(STRtty));
630 struct who *wp;
631 char *host = NULL;
632
633 watch_login(1);
634
635 for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
636 if (strcmp(tty, wp->who_tty) == 0)
637 host = wp->who_host;
638 wp->who_name[0] = '\0';
639 }
640 resetwatch();
641 return host;
642 }
643 # endif /* UTHOSTLEN */
644
645 #endif /* HAVENOUTMP */
646