/*
* $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