/********************************** BOUNCER ***********************************
*	BOUNCER is yet another idle terminal killer.  The difference between
* BOUNCER and other idle terminal killers is that the killing is optional.  On
* our system, we allow users to be idle unless we run out of lines (we have a
* port selector).  The default mode only monitors and reports idle users to the
* operator, and the operator manually kills them when necessary.  The exception
* to this rule is that privileged users are always killed, without warning,
* after exceeding the idle time limit.  The console (OPA0:) and the user
* defined in exempt_user (OPS on our system) are never timed out.
*
*	BOUNCER has a variable idle time limit (in minute increments), and any
* user exceeding that limit is logged in [SYSMGR]BOUNCER.LOG and optionally
* killed.  BOUNCER also writes a file once a minute which is called 
* [SYSMGR]DEADBEATS.LOG and contains an entry for each user which has been idle
* for more than a minute.  The operator or system manager can type this file
* to see who has been idle how long (we have defined a symbol which types it).
*
*	A process is idle if hasn't used any BIO (buffered I/O's), DIO (direct
* I/O's) and has used less than 10 milleseconds of cpu time.  Because issuing
* GETJPI calls causes a kernel mode AST to run in the context of the target
* process, CPU time is occasionally incremented in the target process even
* though it is truly idle.  Since GETJPI reports CPU time in 10 millesecond
* increments and the GETJPI call seems to increment their CPU time by less
* than a millesecond, we only occasionally see the change in CPU time.  By
* allowing a process to use 10 milleseconds of CPU time and still be considered
* idle, we can eliminate the possibility of mis-reporting a process as being
* active.  Some will argue that an active process could use 10 milleseconds
* (or less) of CPU in one minute and still be doing usefull work... but I won't
* beleive it unless I see it.  If you disagree, you may change IDLE_CPU to 0,
* which will treat any increment in CPU time as "active" CPU time.
*
*	BOUNCER properly handles sub-processes and will not consider a process
* idle if any subprocess is active.  All subprocess BIO's, DIO's and CPU times
* are added to those of the root process. 
*
* Modification History
*
*   Date         Initials        Description
*   ----         --------        -----------
*  3-25-86         RBM           Add SYS$FORCEX for normal exit before
*                                the killing starts !
*  3-26-86         RBM           Insert for/next loop between SYS$FORCEX
*                                and SYS$DELPRC so database routines exit
*                                smoothly
******************************************************************************/
# include	<jpidef.h>
# include	<ssdef.h>
# include	<stdio.h>
# include	<prvdef.h>

# define	SLEEPTIME	1	/* minutes to sleep between cycles   */
# define	MAXSYSGRP	1	/* set same as sysgen parameter */
# define	FUNCTION
# define	TRUE		1	/* logical true */
# define	FALSE		0	/* logical false */
# define	DEFIDLE		20	/* default idle time */
# define	MAXPROCS	100	/* max number of procs handled */
# define	IDLE_CPU	1	/* number of 10 millesecond ticks of */
					/* CPU that a process may use and */
					/* still be "idle" (see text) */
static char	log[] = "BOUNCER.LOG";	/* name of logfile */
static char	dead[] = "DEADBEATS.LOG"; /* name of current deadbeat file */
static char	exempt_user[] = "OPS";	/* name of exempt user */
static char	message_text[] = 
	    "\n**** Your idle process has been deleted! ****\n";

struct DESC
{
	unsigned short length;
	unsigned char type;
	unsigned char class;
	char *addr;
};

#define CLASS_ARRAY 4
#define TYPE_STRING 14


struct	DESC message;
struct	DESC term;

struct	PROCINFO
{
	int	bio;			/* buffered IO count */
	int	sts;			/* status word */
	int	cputime;		/* accumulated cpu time */
	int	dio;			/* direct IO count */
	int	idlecount;		/* number of idle units incurred */
	int	pid;			/* process identification */
	int	process_count;		/* sub-process count */
	int	owner;			/* owner process if subprocess */
	int	auth_priv[2];		/* authorized privileges */
	int	grp;			/* uic group */
	int	root;			/* root owner of process */
	char	username[13];		/* user name */
	char	img[128];		/* image name */
	char	terminal[7];		/* terminal of process */
	int	valid;			/* valid record indicator */
};

struct	ITEM
{
	short	len;			/* buffer length */
	short	item_code;		/* information item requested */
	char	*buf;			/* return buffer */
	int	*rlen;			/* return length */
};

struct	PROCINFO jpi;			/* temporary stash for jpi call */
struct	PROCINFO table_a[MAXPROCS];	/* Actual table A */
struct	PROCINFO table_b[MAXPROCS];	/* Actual table B */
struct	PROCINFO sub_table[MAXPROCS];	/* Sub-process table */

struct	PROCINFO *curr;			/* These two pointers are toggled */
struct	PROCINFO *prev;			     /* to alternate tables A & B */
struct	PROCINFO *sub1;			/* These two pointers are used to */
struct	PROCINFO *sub2;			     /* index into the subproc table */
struct	PROCINFO *curr_begin;		/* Temporary pointer storage */
struct	PROCINFO *prev_begin;		/* Temporary pointer storage */
struct	PROCINFO *sub_begin;		/* Temporary pointer storage */
struct	PROCINFO *temp;			/* Temporary storage during toggle */

struct
{
	struct	ITEM items[12];
	int	endoflist;
} itemlist				=
					{
		/* bio		*/	 4 ,JPI$_BUFIO	,&jpi.bio	,0
		/* sts		*/	,4 ,JPI$_STS	,&jpi.sts	,0
		/* cputime	*/	,4 ,JPI$_CPUTIM	,&jpi.cputime	,0
		/* dio		*/	,4 ,JPI$_DIRIO	,&jpi.dio	,0
		/* pid		*/	,4 ,JPI$_PID	,&jpi.pid	,0
		/* process	*/	,4 ,JPI$_PRCCNT	,&jpi.process_count,0
		/* owner	*/	,4 ,JPI$_OWNER	,&jpi.owner 	,0
		/* auth privs	*/	,8 ,JPI$_AUTHPRIV ,&jpi.auth_priv,0
		/* uic group	*/	,4 ,JPI$_GRP	,&jpi.grp	,0
		/* user name	*/	,12,JPI$_USERNAME,jpi.username	,0
		/* imagename	*/	,128,JPI$_IMAGNAME,jpi.img	,0
		/* terminal	*/	,7 ,JPI$_TERMINAL,jpi.terminal	,0
					};

int	monitor_only;			/* flag to indicate report only */
int	idlelimit;			/* idle minutes allowed before kill */
FILE	*logfile;			/* file pointer for log file */
FILE	*curfile;			/* file pointer for deadbeats log */
short	status;				/* status returned by calls */
char	name_only[128];			/* temporary image name buffer */
int	bad_priv_mask[2];		/* masks of dangerous privs */

int	index_sub1;			/* sub_process table index 1 */
int	index_sub2;			/* sub_process table index 2 */
int	index_curr;			/* current table index */
int	index_prev;			/* previous table index */
int	found;				/* temporary flag */
int	counter;			/* general purpose counter */
int	counter1;			/* another general purpose counter */
int	curr_entries;
int	prev_entries;
int	sub_entries;

main(argc,argv)
int argc;
char *argv[];
{
	argv++;					/* skip first parm (bounce) */
	monitor_only = TRUE;			/* default is passive */
	if (argc == 1)				/* parse optional parameter */
		{
		idlelimit = DEFIDLE;
		}
	  else
		{
		sscanf(*argv,"%d",&idlelimit);
		}
	if(idlelimit < 0)
		{
		idlelimit = idlelimit * -1;
		monitor_only = FALSE;
		}

/* Following are the privileges deemed dangerous.  Any user having one or more
   of these privileges and exceeding the idlelimit is going to be sorry....
   When converted to V4, these privileges should include READALL, GRPPRV and
   SECURITY */

	bad_priv_mask[0] = (1 << PRV$V_CMKRNL) + (1 << PRV$V_CMEXEC) +
			   (1 << PRV$V_DETACH) + (1 << PRV$V_SETPRV) +
			   (1 << PRV$V_WORLD)  + (1 << PRV$V_OPER)   +
			   (1 << PRV$V_SYSPRV) + (1 << PRV$V_BYPASS) +
			   (1 << PRV$V_LOG_IO) + (1 << PRV$V_PFNMAP) +
			   (1 << PRV$V_PHY_IO) + (1 << PRV$V_SYSNAM);
	bad_priv_mask[1] = 0;


	message.addr = message_text;		/* build message structure */
	message.length = strlen(message.addr);
	term.length = 5;

	curr_begin = table_a;			/* initialize pointers */
	prev_begin = table_b;
	sub_begin = sub_table;
	curr = table_a;
	prev = table_b;
	sub1 = sub_begin;
	sub2 = sub_begin;

/* Welcome back my friends, to the show that never ends......  EL&P */
	for(;;)
		{
		getjpi_info();			/* load GETJPI data */
		if (curr_entries != 0)
			{
			if (sub_entries != 0)
				{
				combine_subs(); /* combine sub procs to root */
				}
			update_idlecount();	/* increment idlecounts */
			log_and_bounce();	/* write log and maybe kill */
			temp = curr_begin;	 /* toggle   */
			curr_begin = prev_begin; /* the      */
			prev_begin = temp;	 /* pointers */
			prev_entries = curr_entries;
			}
		sleep(SLEEPTIME * 60);		/* count the black sheep */
		}

}
FUNCTION int getjpi_info()
{
int	pidcontext;

	itemlist.endoflist = status = curr_entries = sub_entries = 0;
	curr = curr_begin;
	sub1 = sub_begin;
	for(pidcontext = -1;status != SS$_NOMOREPROC;)
		{
		status = sys$getjpi(1,&pidcontext,0,&itemlist,0,0,0);
		sys$waitfr(1);
		if (status == SS$_NORMAL)
		    {
		    if (jpi.terminal[0] != 0)
			{
/* Top level proc */
			curr->bio = jpi.bio;
			curr->cputime = jpi.cputime;
			curr->dio = jpi.dio;
			curr->pid = jpi.pid;
			curr->process_count = jpi.process_count;
			curr->auth_priv[0] = jpi.auth_priv[0];
			curr->auth_priv[1] = jpi.auth_priv[1];
			/* If user has SYSPRV implicitly, make it explicit */
			if (jpi.grp <= MAXSYSGRP)
			   jpi.auth_priv[0] = 
				(jpi.auth_priv[0] | (1 << PRV$V_SYSPRV));
			strncpy(curr->username,jpi.username,12);
			strncpy(curr->img,jpi.img,128);
			strncpy(curr->terminal,jpi.terminal,7);
			curr->idlecount = 0;
			curr_entries++;
			curr++;
			if (curr_entries == MAXPROCS)
				break;
			}
		    else if ((jpi.owner != 0) &&
			    ((long)(16384 & jpi.sts) != 16384))
			{
/* Sub proc */
			sub1->bio = jpi.bio;
			sub1->cputime = jpi.cputime;
			sub1->dio = jpi.dio;
			sub1->pid = jpi.pid;
			sub1->owner = jpi.owner;
			sub1->root = jpi.owner;
			sub_entries++;
			sub1++;
			if (sub_entries == MAXPROCS)
				break;
			}
		    }
		}
}

FUNCTION int combine_subs()
{

/* THIS LOOP SETS THE ROOT FIELD TO THE PID OF THE ROOT OWNER PID */
	sub1 = sub_begin;
	sub2 = sub_begin;
	for (index_sub1 = 0;index_sub1 < sub_entries;index_sub1++,sub1++)
	    {
	    for (found = 1;found == 1;)
		{
		found = 0;
		for (index_sub2 = 0,sub2 = sub_begin;
			index_sub2 < sub_entries;index_sub2++,sub2++)
		    {
		    if (sub2->pid == sub1->root)
			{
			sub1->root = sub2->root;
			found = 1;
			break;
			}
		    }
		}
	    }

/*	THIS LOOP COMBINES THE TOP LEVEL SUBPROCESS WITH THE INTERACTIVE PROC*/
	curr = curr_begin;
	sub1 = sub_begin;
	for(index_curr = 0;index_curr < curr_entries;index_curr++,curr++)
	    {
	    if (curr->process_count != 0)
		{
		for(index_sub1 = 0,sub1 = sub_begin;index_sub1 
			< sub_entries;index_sub1++,sub1++)
		    {
		    if (sub1->root == curr->pid)
			{
			curr->bio =+ sub1->bio;
			curr->cputime =+ sub1->cputime;
			curr->dio =+ sub1->dio;
			}
		    }
		}
	    }
}

FUNCTION int update_idlecount()
{
/* This routine increments the idle counter if the proc was idle */

	curr = curr_begin;
	prev = prev_begin;
	for(index_curr = 0;index_curr < curr_entries;index_curr++,curr++)
	    {
	    for(index_prev = 0,prev = prev_begin;index_prev 
				< prev_entries;index_prev++,prev++)
		{
		if (curr->pid == prev->pid)
		    {
		    if ((curr->bio == prev->bio) && 
			((curr->cputime - prev->cputime) <= IDLE_CPU)
			&&(curr->dio == prev->dio))
			{
			curr->idlecount = prev->idlecount + 1;
			}
		    break;
		    }
		}
	    }
}

FUNCTION int log_and_bounce()
{
int	temp,opn_flag = 0;
char	*date_time;
int	clock;

	curr = curr_begin;
	status = time(&clock);
	date_time = ctime(&clock);
	date_time[19] = '\0';
	delete(dead);			/* delete old deadbeats log */
	curfile = fopen(dead,"w");	/* and create a new one */
	fprintf(curfile,"%s \n \n",date_time);
	fprintf(curfile,"Min  Term  User           PID          Image \n");
/* Scan the table */
	for (index_curr=0;index_curr < curr_entries;index_curr++,curr++)
		{
/* Check to see if idle.  If so, write to DEADBEATS.LOG */
		if (curr->idlecount >= 1)
		    {
		    name_only[0] = '\0';
/* If they're running an image, reduce the size by stripping dev and version */
		    if (curr->img[0] != '\0')
			{
			strcpy(name_only,strchr(curr->img,'['));
			for(counter=0;name_only[counter] != '\0';counter++)
			    {
			    if (name_only[counter] == ';')
				{
				name_only[counter - 4] = '\0';
				break;
				}
			    }
			}
		    fprintf(curfile,"%3d %s %s %08X %s \n",
		        curr->idlecount,
		        curr->terminal,
		        curr->username,
		        curr->pid,
		        name_only);

/* If they've reached a multiple of the idlelimit, log and maybe clobber */
		    temp = (curr->idlecount/idlelimit) * idlelimit;
		    if ((temp > 0) && (temp == curr->idlecount) &&
			(strncmp(curr->username,exempt_user,
			strlen(exempt_user)) != 0))
			{
/* Open BOUNCER.LOG if not previously opened */
			if (opn_flag == 0)
			    {
			    logfile = fopen(log,"a");
			    opn_flag = 1;
			    }
/* Never clobber the console */
			if (strncmp(curr->terminal,"OPA0:",5) != 0)
			    {
/* If they are privileged, we always clobber them for security reasons */
			    if ((curr->auth_priv[0] & bad_priv_mask[0]) ||
			     (curr->auth_priv[1] & bad_priv_mask[1]))
			       {
			       term.addr = curr->terminal;
			       status = sys$brdcst(&message,&term,0,0);
                               status = sys$forcex(&curr->pid,0);
			       for (counter1=0;counter1<500000;counter1++);
			       status = sys$delprc(&curr->pid,0);
			       fprintf(logfile,"*Privileged* %s on %s killed\n"
				  ,curr->username,curr->terminal);
			       }
			      else
/* Otherwise, only clobber underprivileged users if we were told to */
			    if (monitor_only == FALSE)
			       {
			       term.addr = curr->terminal;
			       status = sys$brdcst(&message,&term,0,0);
                               status = sys$forcex(&curr->pid,0);
			       for (counter1=0;counter1<500000;counter1++);
			       status = sys$delprc(&curr->pid,0);
			       fprintf(logfile,"**** %s on %s killed\n"
				  ,curr->username,curr->terminal);
			       }
			    }
/* Log it */
			fprintf(logfile,"%s %s %s %3d %s \n",
				date_time,
				curr->terminal,
				curr->username,
				curr->idlecount,
				name_only);
			}
	    	    }
		}
	if (opn_flag == 1)
		{
		fclose(logfile);
		opn_flag = 0;
		}
	fclose(curfile);
}
