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