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