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