/* $Header: /home/hyperion/mu/christos/src/sys/tcsh-6.00/RCS/tc.who.c,v 3.0 1991/07/04 21:49:28 christos Exp $ */
/*
 * tc.who.c: Watch logins and logouts...
 */
/*-
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "config.h"
#if !defined(lint) && !defined(pdp11)
static char *rcsid() 
    { return "$Id: tc.who.c,v 3.0 1991/07/04 21:49:28 christos Exp $"; }
#endif

#include "sh.h"

/*
 * kfk 26 Jan 1984 - for login watch functions.
 */
#include <ctype.h>
#include <utmp.h>

#ifndef BROKEN_CC
# define UTNAMLEN	sizeof(((struct utmp *) 0)->ut_name)
# define UTLINLEN	sizeof(((struct utmp *) 0)->ut_line)
# ifdef UTHOST
#  ifdef _SEQUENT_
#   define UTHOSTLEN	100
#  else
#   define UTHOSTLEN	sizeof(((struct utmp *) 0)->ut_host)
#  endif
# endif				/* UTHOST */
#else
/* give poor cc a little help if it needs it */
struct utmp __ut;

# define UTNAMLEN	sizeof(__ut.ut_name)
# define UTLINLEN	sizeof(__ut.ut_line)
# ifdef UTHOST
#  ifdef _SEQUENT_
#   define UTHOSTLEN	100
#  else
#   define UTHOSTLEN	sizeof(__ut.ut_host)
#  endif
# endif				/* UTHOST */
#endif				/* BROKEN_CC */

#ifndef _PATH_UTMP
# ifdef	UTMP_FILE
#  define _PATH_UTMP UTMP_FILE
# else
#  define _PATH_UTMP "/var/run/utmp"
# endif				/* UTMP_FILE */
#endif				/* _PATH_UTMP */


struct who {
    struct who *w_next;
    struct who *w_prev;
    char    w_name[UTNAMLEN + 1];
    char    w_new[UTNAMLEN + 1];
    char    w_tty[UTLINLEN + 1];
#ifdef UTHOST
    char    w_host[UTHOSTLEN + 1];
#endif /* UTHOST */
    long    w_time;
    int     w_status;
};

static struct who *wholist = NULL;
static int watch_period = 0;
static time_t stlast = 0;
extern char *month_list[];
#ifdef WHODEBUG
static	void	debugwholist	__P((struct who *, struct who *));
#endif
static	void	print_who	__P((struct who *));


#define ONLINE		01
#define OFFLINE		02
#define CHANGED		04
#define STMASK		07
#define ANNOUNCE	010

/*
 * Karl Kleinpaste, 26 Jan 1984.
 * Initialize the dummy tty list for login watch.
 * This dummy list eliminates boundary conditions
 * when doing pointer-chase searches.
 */
void
initwatch()
{
    register int i;

    wholist = (struct who *) xcalloc(1, sizeof *wholist);
    wholist->w_next = (struct who *) xcalloc(1, sizeof *wholist);
    wholist->w_next->w_prev = wholist;
    for (i = 0; i < UTLINLEN; i++) {
	wholist->w_tty[i] = '\01';
	wholist->w_next->w_tty[i] = '~';
    }
    wholist->w_tty[i] = '\0';
    wholist->w_next->w_tty[i] = '\0';

#ifdef WHODEBUG
    debugwholist(NULL, NULL);
#endif /* WHODEBUG */
}

void
resetwatch()
{
    watch_period = 0;
    stlast = 0;
}

/*
 * Karl Kleinpaste, 26 Jan 1984.
 * Watch /var/run/utmp for login/logout changes.
 */
void
watch_login()
{
    int     utmpfd, comp, alldone;
#ifdef BSDSIGS
    sigmask_t omask;
#endif				/* BSDSIGS */
    struct utmp utmp;
    struct who *wp, *wpnew;
    struct varent *v;
    Char  **vp;
    time_t  t, interval = MAILINTVL;
    struct stat sta;
#if defined(UTHOST) && defined(_SEQUENT_)
    char   *host, *ut_find_host();
#endif

    /* stop SIGINT, lest our login list get trashed. */
#ifdef BSDSIGS
    omask = sigblock(sigmask(SIGINT));
#else
    (void) sighold(SIGINT);
#endif

    v = adrof(STRwatch);
    if (v == NULL) {
#ifdef BSDSIGS
	(void) sigsetmask(omask);
#else
	(void) sigrelse(SIGINT);
#endif
	return;			/* no names to watch */
    }
    vp = v->vec;
    if (blklen(vp) % 2)		/* odd # args: 1st == # minutes. */
	interval = (number(*vp)) ? getn(*vp++) : MAILINTVL;
    (void) time(&t);
    if (t - watch_period < interval * 60) {
#ifdef BSDSIGS
	(void) sigsetmask(omask);
#else
	(void) sigrelse(SIGINT);
#endif
	return;			/* not long enough yet... */
    }
    watch_period = t;

    /*
     * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
     * Don't open utmp all the time, stat it first...
     */
    if (stat(_PATH_UTMP, &sta)) {
	xprintf("cannot stat %s.  Please \"unset watch\".\n", _PATH_UTMP);
#ifdef BSDSIGS
	(void) sigsetmask(omask);
#else
	(void) sigrelse(SIGINT);
#endif
	return;
    }
    if (stlast == sta.st_mtime) {
#ifdef BSDSIGS
	(void) sigsetmask(omask);
#else
	(void) sigrelse(SIGINT);
#endif
	return;
    }
    stlast = sta.st_mtime;
    if ((utmpfd = open(_PATH_UTMP, O_RDONLY)) < 0) {
	xprintf("%s cannot be opened.  Please \"unset watch\".\n", _PATH_UTMP);
#ifdef BSDSIGS
	(void) sigsetmask(omask);
#else
	(void) sigrelse(SIGINT);
#endif
	return;
    }

    /*
     * xterm clears the entire utmp entry - mark everyone on the status list
     * OFFLINE or we won't notice X "logouts"
     */
    for (wp = wholist; wp != NULL; wp = wp->w_next) {
	wp->w_status = OFFLINE;
	wp->w_time = 0;
    }

    /*
     * Read in the utmp file, sort the entries, and update existing entries or
     * add new entries to the status list.
     */
    while (read(utmpfd, (char *) &utmp, sizeof utmp) == sizeof utmp) {

#ifdef DEAD_PROCESS
# ifndef IRIS4D
	if (utmp.ut_type != USER_PROCESS)
	    continue;
# else
	/* Why is that? Cause the utmp file is always corrupted??? */
	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
	    continue;
# endif /* IRIS4D */
#endif /* DEAD_PROCESS */

	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
	    continue;	/* completely void entry */
#ifdef DEAD_PROCESS
	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
	    continue;
#endif /* DEAD_PROCESS */
	wp = wholist;
	while ((comp = strncmp(wp->w_tty, utmp.ut_line, UTLINLEN)) < 0)
	    wp = wp->w_next;/* find that tty! */

	if (comp == 0) {	/* found the tty... */
#ifdef DEAD_PROCESS
	    if (utmp.ut_type == DEAD_PROCESS) {
		wp->w_time = utmp.ut_time;
		wp->w_status = OFFLINE;
	    }
	    else
#endif /* DEAD_PROCESS */
	    if (utmp.ut_name[0] == '\0') {
		wp->w_time = utmp.ut_time;
		wp->w_status = OFFLINE;
	    }
	    else if (strncmp(utmp.ut_name, wp->w_name, UTNAMLEN) == 0) {
		/* someone is logged in */ 
		wp->w_time = utmp.ut_time;
		wp->w_status = 0;	/* same guy */
	    }
	    else {
		(void) strncpy(wp->w_new, utmp.ut_name, UTNAMLEN);
#ifdef UTHOST
# ifdef _SEQUENT_
		host = ut_find_host(wp->w_tty);
		if (host)
		    (void) strncpy(wp->w_host, host, UTHOSTLEN);
		else
		    wp->w_host[0] = 0;
# else
		(void) strncpy(wp->w_host, utmp.ut_host, UTHOSTLEN);
# endif
#endif /* UTHOST */
		wp->w_time = utmp.ut_time;
		if (wp->w_name[0] == '\0')
		    wp->w_status = ONLINE;
		else
		    wp->w_status = CHANGED;
	    }
	}
	else {		/* new tty in utmp */
	    wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
	    (void) strncpy(wpnew->w_tty, utmp.ut_line, UTLINLEN);
#ifdef UTHOST
# ifdef _SEQUENT_
	    host = ut_find_host(wpnew->w_tty);
	    if (host)
		(void) strncpy(wpnew->w_host, host, UTHOSTLEN);
	    else
		wpnew->w_host[0] = 0;
# else
	    (void) strncpy(wpnew->w_host, utmp.ut_host, UTHOSTLEN);
# endif
#endif /* UTHOST */
	    wpnew->w_time = utmp.ut_time;
#ifdef DEAD_PROCESS
	    if (utmp.ut_type == DEAD_PROCESS)
		wpnew->w_status = OFFLINE;
	    else
#endif /* DEAD_PROCESS */
	    if (utmp.ut_name[0] == '\0')
		wpnew->w_status = OFFLINE;
	    else {
		(void) strncpy(wpnew->w_new, utmp.ut_name, UTNAMLEN);
		wpnew->w_status = ONLINE;
	    }
#ifdef WHODEBUG
	    debugwholist(wpnew, wp);
#endif /* WHODEBUG */

	    wpnew->w_next = wp;	/* link in a new 'who' */
	    wpnew->w_prev = wp->w_prev;
	    wpnew->w_prev->w_next = wpnew;
	    wp->w_prev = wpnew;	/* linked in now */
	}
    }
    (void) close(utmpfd);
#if defined(UTHOST) && defined(_SEQUENT_)
    endutent();
#endif

    /*
     * The state of all logins is now known, so we can search the user's list
     * of watchables to print the interesting ones.
     */
    for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
	 *(vp + 1) != NULL && **(vp + 1) != '\0';
	 vp += 2) {		/* args used in pairs... */

	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
	    alldone = 1;

	for (wp = wholist; wp != NULL; wp = wp->w_next) {
	    if (wp->w_status & ANNOUNCE ||
		(!eq(*vp, STRany) &&
		 !eq(*vp, str2short(wp->w_name)) &&
		 !eq(*vp, str2short(wp->w_new))) ||
		(!eq(*(vp + 1), str2short(wp->w_tty)) &&
		 !eq(*(vp + 1), STRany)))
		continue;	/* entry doesn't qualify */
	    /* already printed or not right one to print */

	    if (wp->w_time == 0)/* utmp entry was cleared */
		wp->w_time = watch_period;

	    if ((wp->w_status & OFFLINE) &&
		(wp->w_name[0] != '\0')) {
		print_who(wp);
		wp->w_name[0] = '\0';
		wp->w_status |= ANNOUNCE;
		continue;
	    }
	    if (wp->w_status & ONLINE) {
		print_who(wp);
		(void) strcpy(wp->w_name, wp->w_new);
		wp->w_status |= ANNOUNCE;
		continue;
	    }
	    if (wp->w_status & CHANGED) {
		print_who(wp);
		(void) strcpy(wp->w_name, wp->w_new);
		wp->w_status |= ANNOUNCE;
		continue;
	    }
	}
    }
#ifdef BSDSIGS
    (void) sigsetmask(omask);
#else
    (void) sigrelse(SIGINT);
#endif
}

#ifdef WHODEBUG
static  oid
debugwholist(new, wp)
    register struct who *new, *wp;
{
    register struct who *a;

    a = wholist;
    while (a != NULL) {
	xprintf("%s/%s -> ", a->w_name, a->w_tty);
	a = a->w_next;
    }
    xprintf("NULL\n");
    a = wholist;
    xprintf("backward: ");
    while (a->w_next != NULL)
	a = a->w_next;
    while (a != NULL) {
	xprintf("%s/%s -> ", a->w_name, a->w_tty);
	a = a->w_prev;
    }
    xprintf("NULL\n");
    if (new)
	xprintf("new: %s/%s\n", new->w_name, new->w_tty);
    if (wp)
	xprintf("wp: %s/%s\n", wp->w_name, wp->w_tty);
}
#endif /* WHODEBUG */


static void
print_who(wp)
    struct who *wp;
{
#ifdef UTHOST
    char   *cp = "%n has %a %l from %m.";
    char   *ptr, flg;
#else
    char   *cp = "%n has %a %l.";
#endif /* UTHOST */

    struct varent *vp = adrof(STRwho);
    struct tm *t;
    char    ampm = 'a';
    int     attributes = 0;

    t = localtime(&wp->w_time);
    if (vp && vp->vec[0])
	cp = short2str(vp->vec[0]);

    for (; *cp; cp++)
	if (*cp != '%')
	    xputchar(*cp | attributes);
	else
	    switch (*++cp) {
	    case 'n':		/* user name */
		switch (wp->w_status & STMASK) {
		case ONLINE:
		case CHANGED:
		    xprintf("%a%s", attributes, wp->w_new);
		    break;
		case OFFLINE:
		    xprintf("%a%s", attributes, wp->w_name);
		    break;
		}
		break;
	    case 'a':
		switch (wp->w_status & STMASK) {
		case ONLINE:
		    xprintf("%a%s", attributes, "logged on");
		    break;
		case OFFLINE:
		    xprintf("%a%s", attributes, "logged off");
		    break;
		case CHANGED:
		    xprintf("%areplaced %s on", attributes, wp->w_name);
		    break;
		}
		break;
	    case 'S':		/* start standout */
		attributes |= STANDOUT;
		break;
	    case 'B':		/* start bold */
		attributes |= BOLD;
		break;
	    case 'U':		/* start underline */
		attributes |= UNDER;
		break;
	    case 's':		/* end standout */
		attributes &= ~STANDOUT;
		break;
	    case 'b':		/* end bold */
		attributes &= ~BOLD;
		break;
	    case 'u':		/* end underline */
		attributes &= ~UNDER;
		break;
	    case 't':
	    case 'T':
	    case '@':
		if (adrof(STRampm) || *cp != 'T') {
		    int     hr = t->tm_hour;

		    if (hr >= 12) {
			if (hr > 12)
			    hr -= 12;
			ampm = 'p';
		    }
		    else if (hr == 0)
			hr = 12;
		    xprintf("%a%d:%02d%cm", attributes, hr, t->tm_min, ampm);
		}
		else
		    xprintf("%a%d:%02d", attributes, t->tm_hour, t->tm_min);
		break;
	    case 'w':
		xprintf("%a%s %d", attributes, month_list[t->tm_mon],
			t->tm_mday);
		break;
	    case 'W':
		xprintf("%a%02d/%02d/%02d", attributes,
			t->tm_mon + 1, t->tm_mday, t->tm_year);
		break;
	    case 'D':
		xprintf("%a%02d-%02d-%02d", attributes,
			t->tm_year, t->tm_mon + 1, t->tm_mday);
		break;
	    case 'l':
		xprintf("%a%s", attributes, wp->w_tty);
		break;
#ifdef UTHOST
	    case 'm':
		if (wp->w_host[0] == '\0')
		    xprintf("%alocal", attributes);
		else
		    /* the ':' stuff is for <host>:<display>.<screen> */
		    for (ptr = wp->w_host, flg = Isdigit(*ptr) ? '\0' : '.';
			 *ptr != '\0' &&
			 (*ptr != flg || ((ptr = strchr(ptr, ':')) != 0));
			 ptr++) {
			if (*ptr == ':')
			    flg = '\0';
			xputchar((int)
				 ((Isupper(*ptr) ? Tolower(*ptr) : *ptr) |
				  attributes));
		    }
		break;
	    case 'M':
		if (wp->w_host[0] == '\0')
		    xprintf("%alocal", attributes);
		else
		    for (ptr = wp->w_host; *ptr != '\0'; ptr++)
			xputchar((int)
				 ((Isupper(*ptr) ? Tolower(*ptr) : *ptr) |
				  attributes));
		break;
#endif /* UTHOST */
	    default:
		xputchar('%' | attributes);
		xputchar(*cp | attributes);
		break;
	    }
    xputchar('\n');
} /* end print_who */

void
/*ARGSUSED*/
dolog(v)
Char **v;
{
    struct who *wp;
    struct varent *vp;

    if ((vp = adrof(STRwatch)) == NULL)
	stderror(ERR_NOWATCH);
    blkpr(vp->vec);
    xprintf("\n");
    watch_period = 0;
    stlast = 0;
    wp = wholist;
    while (wp != NULL) {
	wp->w_name[0] = '\0';
	wp = wp->w_next;
    }
}
