/*
 * $Id: users.c,v 1.5 2002/08/23 13:38:15 howardjp Exp $
 *
 * Copyright (c) 1990
 *      Jan Wolter.  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 Jan Wolter
 *      and his contributors.
 * 4. Neither the name of Jan Wolter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY JAN WOLTER 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 JAN WOLTER 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.
 */

/* PARTY PROGRAM -- USER LIST ROUTINES -- Jan Wolter */

#include "party.h"
#include "opt.h"
#include <sys/stat.h>

#define CMD_LEN 20
#define SUB_PARTY 8


/* PWHO FILE: this is a file basically similar to the utmp file, that keeps
 * track of who is on what party channel.  It is a binary file with entries
 * for each tty line.  To prevent strange contention problems, no party
 * process should ever write any line of the pwho file except the one
 * corresponding to it's own tty.
 *
 * Each pwho entry consists of one header, giving the ttyname and the
 * logname of the user on that line.  This is followed by up to eight
 * subparty lines for party invocations that have not exitted.  (Multiple
 * invocations can occur when users shell out or suspend their process.)
 * Each of these give that invocation's internal name, the time the process
 * started, the current channel, and any current shell command.
 *
 * One problem with this is that a hostile user can do a "kill -9" on his
 * party process, and leave without having his pwho entry erased, thus 
 * fooling the pwho program into thinking he is still there.  We try to
 * detect such chicanery by comparing the utmp file to the pwho file.  If
 * the start time of the process preceeds the login time of the user on that
 * line, we can safely assume that entry is a dud.
 *
 * We indicated users who have suspended with a "^" in the shelled line.
 */

struct pwho {			   /* HEADER: null name means no user on line */
	char line[UT_LINESIZE];	   /* user's tty name, as in utmp file */
	char logname[UT_NAMESIZE]; /* user's name, as in utmp file */
	};

struct sub_pwho {		 /* SUBFIELD: null alias invalidates entry */
	char alias[UT_NAMESIZE]; /* user's name inside party */
	long time;		 /* time the user entered party */
	char channel[CHN_LEN];	 /* user's current channel number */
	char shelled[CMD_LEN];	 /* name of command user shelled to */
	};
#define ENTRY_SIZE (sizeof(struct pwho) + SUB_PARTY * sizeof(struct sub_pwho))

/* ULIST STRUCTURE: The following structure used to store linked lists of users
 * to be output.  It includes only the info to be displayed.
 */

struct ulist {
	char alias[UT_NAMESIZE];
	long time;
	char channel[CHN_LEN];
	char shelled[CMD_LEN];
	struct ulist *next;
	};


FILE *pwfp= NULL;
struct pwho myhead;
struct sub_pwho mybody;
long myoff, mysuboff;
char *ttyname();

/* WHO_ENTER: Called when the user enters party.  If finds a slot for him in
 * the partywho file and writes in his current status.
 */

int who_enter()
{
long off;
int i;
struct pwho ahead;
struct sub_pwho abody;


	/* Setup our entry */
	strncpy(myhead.line,logtty+5,UT_LINESIZE);
	strncpy(myhead.logname,logname,UT_NAMESIZE);

	mybody.time= time(0L);
	if (name) strncpy(mybody.alias,name,UT_NAMESIZE);
	if (channel) strncpy(mybody.channel,channel,CHN_LEN);
	strncpy(mybody.shelled,"",CMD_LEN);

	/* Scan for our entry */
	for (off= 0L;;off += ENTRY_SIZE)
	{
	    fseek(pwfp,off,0);

	    if (fread(&ahead,sizeof(struct pwho),1,pwfp) == 0)
	    {
		/* Create slot for us at end of file */
		myoff = off;
		mysuboff = off + sizeof(struct pwho);
		break;
	    }

	    if (!strncmp(myhead.line,ahead.line,UT_LINESIZE))
	    {
		/* Found existing slot for us */
		myoff = off;

		if (strncmp(myhead.logname,ahead.logname,UT_NAMESIZE) ||
		    fread(&abody,sizeof(struct sub_pwho),1,pwfp) == 0 ||
		    abody.alias[0] == '\0' ||
		    abody.time < logtime)
		{
		    /* No valid user in it -- take first subslot */
		    who_clear(off,myhead.line);
		    mysuboff = off + sizeof(struct pwho);
		}
		else
		{
		    off += sizeof(struct pwho);
		    /* We're already there -- use first free slot */
		    for (i= 1; i<SUB_PARTY; i++)
		    {
			if (fread(&abody,sizeof(struct sub_pwho),1,pwfp) == 0 ||
			    abody.alias[0] == '\0')
				break;
		    }
		    if (i == SUB_PARTY)
			mysuboff= 0L;	/* No subrecords left */
		    else
			mysuboff= off+sizeof(struct sub_pwho)*i;
		}
		break;
	    }
	}

	/* Write my Entries in their proper place */
	if (mysuboff != 0L)
	{
	    fseek(pwfp,myoff,0);
	    fwrite(&myhead,sizeof(struct pwho),1,pwfp);
	    fseek(pwfp,mysuboff,0);
	    fwrite(&mybody,sizeof(struct sub_pwho),1,pwfp);
	    fflush(pwfp);
	}
	return(0);
}


/* WHO_OPEN:  Open the pwho file.
 */

int who_open()
{
    if ((pwfp= fopen(opt[OPT_WHOFILE].str,"r+"))==NULL)
    {
	err("cannot open whofile: %s\n", opt[OPT_WHOFILE].str);
	return(1);
    }
    return(0);
}


/* WHO_EXIT:  called when we exit the program.  Erases our subentry, and
 * maybe the header too if this was the last one.
 */

who_exit()
{
int i;
struct sub_pwho abody;

    if (!pwfp || !mysuboff) return;

    /* Erase our subentry */
    strncpy(mybody.alias,"",UT_NAMESIZE);
    fseek(pwfp,mysuboff,0);
    fwrite(&mybody,sizeof(struct sub_pwho),1,pwfp);
    fflush(pwfp);

    /* Scan subentries.  If any non-null ones are left, we are done */
    fseek(pwfp,myoff+sizeof(struct pwho),0);
    for (i= 0; i<SUB_PARTY; i++)
    {
	if (fread(&abody,sizeof(struct sub_pwho),1,pwfp) == 0)
	    break;
	if (abody.alias[0] == '\0')
	    return;
    }

    /* We erased last subentry.  Erase header too. */
    strncpy(myhead.logname,"",UT_NAMESIZE);
    fseek(pwfp,myoff,0);
    fwrite(&myhead,sizeof(struct pwho),1,pwfp);
    fflush(pwfp);
}

/* WHO_CHAN:  Called when a channel is changed.  Channel changes may involve
 * a name change as well.
 */

who_chan()
{
	strncpy(mybody.channel,channel,CHN_LEN);
	strncpy(mybody.alias,name,UT_NAMESIZE);

	if (!pwfp || !mysuboff) return;

	fseek(pwfp,mysuboff,0);
	fwrite(&mybody,sizeof(struct sub_pwho),1,pwfp);
	fflush(pwfp);
}

/* WHO_SHOUT:  Record that the user has shelled out.
 */

who_shout(cmd)
char *cmd;
{
char *ptr;
int len;

	if ((ptr= strchr(txbuf,' ')) == NULL)
		len= CMD_LEN;
	else if ((len= ptr - txbuf) > CMD_LEN)
		len= CMD_LEN;
	ncstrncpy(mybody.shelled,cmd,len);

	if (!pwfp || !mysuboff) return;

	fseek(pwfp,mysuboff,0);
	fwrite(&mybody,sizeof(struct sub_pwho),1,pwfp);
	fflush(pwfp);
}

/* WHO_SHIN: Record that the user has returned from a shell escape.
 */

who_shin()
{
	strncpy(mybody.shelled,"",CMD_LEN);

	if (!pwfp || !mysuboff) return;

	fseek(pwfp,mysuboff,0);
	fwrite(&mybody,sizeof(struct sub_pwho),1,pwfp);
	fflush(pwfp);
}

/* WHO_ISOUT: Check if the users is currently shelled out */
int who_isout()
{
	return (mybody.shelled[0] != '\0');
}


/* WHO_LIST:  Print a list of who is on.  The sortkey may be
 *    'n' - sort by user name
 *    'c' - sort by conference
 *    't' - sort by time
 */

who_list(fp,sortkey)
FILE *fp;
char sortkey;
{
struct pwho ahead;
struct sub_pwho abody;
int i,n;
struct ulist *head= NULL, *curr, *next;

    wscan_init();

    /* Scan file and load into ulist */
    while (wscan_next(&ahead,&abody,&n))
	add_ulist(&head, abody.alias, abody.channel, abody.time, abody.shelled,
		  sortkey);
    wscan_done();

    /* Print Headings */
    fprintf(fp,"User");
    for (i= 4; i < UT_NAMESIZE; i++) putc(' ',fp);
    fprintf(fp," Started          Channel\n");

    /* Print ulist */
    for (curr= head; curr != NULL; curr= curr->next)
    {
	fprintf(fp,"%-*.*s %15.15s  %.*s",
		UT_NAMESIZE,UT_NAMESIZE,curr->alias,
		ctime(&curr->time)+4,
		CHN_LEN,curr->channel);
	if (curr->shelled[0] == '\0')
	       fprintf(fp,"\n");
#ifdef BSD
	else if (curr->shelled[0] == '^' &&
		 curr->shelled[1] == '\0')
	       fprintf(fp," (suspended)\n");
#endif /*BSD*/
	else
	       fprintf(fp," (shelled to %s)\n",curr->shelled);
    }

    /* Deallocate ulist */
    for (curr= head, next= NULL; curr != NULL; curr= next)
    {
	next= curr->next;
	free(curr);
    }
}

/* ADD_ULIST:  Add a user entry to the ulist, inserting it in the list in the
 * proper place as selected by sortkey.
 */

add_ulist(head,al,ch,tm,sh,sortkey)
struct ulist **head;
char *al,*ch,*sh;
long tm;
char sortkey;
{
struct ulist *new= (struct ulist *)malloc(sizeof(struct ulist));
struct ulist *curr, *prev;
int c_ch, c_al, c_tm;

    new->time= tm;
    strncpy(new->alias,al,UT_NAMESIZE);
    strncpy(new->channel,ch,CHN_LEN);
    ncstrncpy(new->shelled,sh,CMD_LEN);

    for (curr= *head, prev= NULL; curr != NULL; prev= curr, curr=curr->next)
    {
	c_ch= strncmp(ch,curr->channel,CHN_LEN);
	c_al= strncmp(al,curr->alias,UT_NAMESIZE);
	c_tm= (tm < curr->time) ? -1 : 1;

	if ((sortkey == 'c' &&
	     (c_ch<0 || (c_ch==0 && (c_al < 0 || (c_al == 0 && c_tm < 0))))) ||
	    (sortkey == 'n' &&
	     (c_al<0 || (c_al==0 && c_tm < 0))) ||
	    (sortkey == 't' && c_tm < 0))
	    break;
    }

    /* Insert the new element before curr */
    if (prev == NULL)
	*head= new;
    else
	prev->next= new;
    new->next= curr;
}


#ifndef NOCLOSE

/* WHO_ISON:  Print a list of who is on the named channel.  This just prints
 * the real names of the users.  It is mainly used for initializing .usr
 * files for closed channels.
 */

who_ison(fp,chn)
FILE *fp;
char *chn;
{
struct pwho ahead;
struct sub_pwho abody;
int n;

    wscan_init();

    while (wscan_next(&ahead,&abody,&n))
    {
	if (!strncmp(chn,abody.channel,CHN_LEN))
	    fprintf(fp,"%-.*s\n",UT_NAMESIZE,ahead.logname);
    }
    wscan_done();
}
#endif /*NOCLOSE*/


/* WHO_CLEAR:  Erase an entry in the pwho file */

who_clear(off,line)
long off;
char *line;
{
struct pwho ahead;
struct sub_pwho abody;
int i;

	if (!pwfp) return;
	fseek(pwfp,off,0);
	strncpy(ahead.line,line,UT_LINESIZE);
	strncpy(ahead.logname,"",UT_NAMESIZE);
	fwrite(&ahead,sizeof(struct pwho),1,pwfp);

	strncpy(abody.alias,"",UT_NAMESIZE);
	for(i=0;i<SUB_PARTY;i++)
		fwrite(&abody,sizeof(struct sub_pwho),1,pwfp);
	fflush(pwfp);
}

/* WHO_EMPTY:  return true if the given channel is empty.  Actually, it returns
 * true if there is one person left, because we call it just before we leave.
 */

int who_empty(channel)
char *channel;
{
struct pwho ahead;
struct sub_pwho abody;
int count=0;
int n;

    wscan_init();

    while (wscan_next(&ahead,&abody,&n))
    {
	if (!strncmp(abody.channel,channel,CHN_LEN) && count++)
	{
	    wscan_done();
	    return(0);
	}
    }
    wscan_done();
    return(1);
}


/* WHO_COUNT:  count the total number of users currently running party.  */

int who_count()
{
struct pwho ahead;
struct sub_pwho abody;
int count=0;
int n;

    wscan_init();
    while (wscan_next(&ahead,&abody,&n))
	if (n==1) count++;
    wscan_done();
    return(count);
}

/* WHO_CLIST:  Build up a linked list of active channels, with user counts,
 * and return it.  If old is non-null, it should point to the first element
 * of a list of channels already known.
 */

struct chnname *who_clist(old)
struct chnname *old;
{
struct pwho ahead;
struct sub_pwho abody;
struct chnname *head=old,*ch;
int n;

    wscan_init();

    while (wscan_next(&ahead,&abody,&n))
    {
	/* Check if we have seen this channel before */
	for (ch= head; ch != NULL; ch= ch->next)
	{
	    if (!strncmp(ch->name,abody.channel,CHN_LEN))
		break;
	}

	if (ch)
	    ch->users++;
	else
	    head= addchn(head,abody.channel,1);
    }
    wscan_done();
    return(head);
}


/* WHO_UNIQALIAS:  Is the given alias used only by the given user in the
 * given channel?
 */

int who_uniqalias(alias,name,channel)
char *alias, *name, *channel;
{
struct pwho ahead;
struct sub_pwho abody;
int n, skipping= 0;

    wscan_init();
    while (wscan_next(&ahead,&abody,&n))
    {
    	/* If this belongs to me, skip the entire entry */
    	if (n == 1)
	    skipping= !strncmp(name, ahead.logname, UT_NAMESIZE);
	if (skipping)
	    continue;

    	/* Ignore users in other channels */
    	if (strncmp(channel, abody.channel, CHN_LEN))
    	    continue;

	if (!strncmp(alias, abody.alias, UT_NAMESIZE))
	{
	    wscan_done();
	    return(0);
	}
    }
    wscan_done();
    return(1);
}


/*********  P A R T Y T M P   S C A N N I N G   R O U T I N E S  *********/


struct utmp *utmp= NULL;  /* Pointer to internal copy of the utmp file */
int nutmp;	          /* Number of entries in utmp */
int subcnt;	          /* Number of subfields read so far */
long curtime;	          /* Time stamp from utmp file */

/* WSCAN_INIT -- This initializes for a new scan of the partytmp file. */

wscan_init()
{
FILE *utfp;
struct stat utst;

    if (!pwfp) return;

    /* Open utmp file */
    if ((utfp= fopen(UTMP,"r")) == NULL)
    {
	    err("cannot open utmp file %s\n",UTMP);
	    return;
    }

    if (utmp != NULL) free(utmp);

    /* Load a copy of utmp file into memory */
    fstat(fileno(utfp),&utst);
    utmp= (struct utmp *)malloc((mtype)(utst.st_size+2*sizeof(struct utmp)));
    for (nutmp=0;fread(utmp+nutmp,sizeof(struct utmp),1,utfp);nutmp++)
	    ;
    fclose(utfp);

    fseek(pwfp,0L,0);
    subcnt= 0;
}

/* WSCAN_NEXT -- Get the next valid head/body pair from the party file.  If
 * there isn't one, return 0.  'n' gives the subfield number of the current
 * entry, 1 for the first subfield, and so on.  wscan_init() must be called
 * shortly before the first call to this.  wscan_done() should be called after
 * it is complete.
 */

int wscan_next(phead,pbody,n)
struct pwho *phead;
struct sub_pwho *pbody;
int *n;
{
int j;
int ignore;

    if (!pwfp) return(0);

    for (;;)
    {
	if (subcnt == 0)
	{
	    /* Get the next header */
	    if (fread(phead,sizeof(struct pwho),1,pwfp) == 0)
		return(0);

	    /* Look for matching, non-obsolete utmp line */
	    for(j= 0; j<nutmp; j++)
	    {
		if (strncmp(utmp[j].ut_line,phead->line,UT_LINESIZE) == 0 && 
		    !strncmp(utmp[j].ut_name,phead->logname,UT_NAMESIZE))
		    break;
	    }

	    /* If no utmp entry exists for this line or the user on the line
	     * isn't the one in the partytmp file, then this is an obsolete
	     * entry.  We will ignore it.
	     */
	    ignore= (j == nutmp);

	    curtime= utmp[j].ut_time;
	}
	else
	    ignore= 0;

	/* Read in sub-records */
	for (subcnt++; subcnt<=SUB_PARTY; subcnt++)
	{
	    if (fread(pbody,sizeof(struct sub_pwho),1,pwfp) == 0)
		return(0);
	    else if (!ignore &&
		     pbody->alias[0] != '\0' &&
		     pbody->time >= curtime)
	    {
		*n= subcnt;
		return(1);
	    }
	}
	subcnt= 0;
    }
}

/* WSCAN_DONE -- Finish up a scan of the partytmp file */
int wscan_done()
{
	free(utmp);
	utmp= NULL;
}

/* NCSTRNCPY -- Do a string copy, replacing non-printable characters with ? */
ncstrncpy(s1,s2,n)
char *s1, *s2;
int n;
{
	for (;*s2 != '\0' && n > 0; s1++,s2++,n--)
	{
		if (isascii(*s2) && isprint(*s2))
			*s1= *s2;
		else
			*s1= '?';
	}

	for (;n > 0; s1++,n--)
		*s1= '\0';
}


/* FINDUSER - Figure out the user's control tty and /etc/utmp name in a fairly
 * robust manner.  This is generally tougher to fool than getlogin().  This
 * returns TRUE if the the user cannot be found for any reason.
 */

int finduser(logtty,logname,logtime)
char *logtty, *logname;
long *logtime;
{
FILE *fp;
char *tty;
struct utmp ut;
int i;

	/* See if we can find a useful ttyname for stderr, stdout, or stdin */
	for (i= 2; i >= 0; i--)
	{
		if ((tty= ttyname(i)) == NULL ||
                    strcmp(tty,"/dev/tty"))
			break;
	}
	if (i < 0)
	{
		err("cannot identify your tty\n"
                    "Try running %s from a different prompt\n",progname);
		return(1);
	}
	strcpy(logtty,tty);

	/* Open the utmp file */
	if ((fp= fopen(UTMP,"r")) == NULL)
	{
		err("Cannot open utmp file %s\n",UTMP);
		return(1);
	}

	/* Search the utmp file */
	while (fread(&ut,sizeof(struct utmp),1,fp) != 0)
	{
		if (!strncmp(ut.ut_line,logtty+5,UT_LINESIZE))
		{
			fclose(fp);
			*logtime= ut.ut_time;
			strncpy(logname,ut.ut_name,UT_NAMESIZE);
			logname[UT_NAMESIZE]= '\0';
			return(0);
		}
	}
	err("Cannot find your tty (%s) in utmp\n",logtty);
	fclose(fp);
	return(1);
}


syntax highlighted by Code2HTML, v. 0.9.1