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 */
108struct 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
137struct 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
150static struct who whohead, whotail;
151static time_t watch_period = 0;
152static time_t stlast = 0;
153#ifdef WHODEBUG
154static	void	debugwholist	(struct who *, struct who *);
155#endif
156static	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 */
172void
173initwatch(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
183void
184resetwatch(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 */
194void
195watch_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
456static void
457debugwholist(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
490static void
491print_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
513char *
514who_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
599void
600/*ARGSUSED*/
601dolog(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
620size_t
621utmphostsize(void)
622{
623    return UTHOSTLEN;
624}
625
626char *
627utmphost(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