
/* (C) 1995 by T. Zwettler */
/* spiff@calvin.priv.at    */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <utmp.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#define USAGE "usage idle_watch [-h] [-i seconds] [-w seconds] [-l logfile] [-m]"
/* -i sets the idle time in seconds                                   */
/*    the dafault idle time is set to 10 minutes                      */
/* -w sets the time to wait after a message is sent to the user       */
/* -m if set no message will be sent to the user and the process will */
/*    be terminated without waiting for input                         */
/* -l set the path to the logfile and the name                        */

#define IDLE_TIME 600
#define WAIT_TIME 60
#define LOGFILE "/var/adm/idle_logout"
#define USERFILE "/etc/nologout"
#define MAX_CHARS 255
/* time to wait after kill */
#define WAIT_KILL 10

char progname[MAX_CHARS];
int  idle_time,
     wait_time;
int  message;

int check(FILE *, struct utmp *);
void log(FILE *, char *);
int logout(FILE *file, char *device, struct utmp *putmp);

void runerror(char *cause)
{
    fprintf(stderr,"%s: error %s\n", progname, cause);
    exit(1);
}


int main( int argc, char *argv[] )
{
    char c;
    int pid;
    struct utmp *putmp;
    FILE *logfile;
    char logfilename[MAX_CHARS];

    strcpy( progname, argv[0] );
    /* define the maximum allowed idle time */
    idle_time = IDLE_TIME;
    /* time to wait after message is sent */
    wait_time = WAIT_TIME;
    /* send a message to the user before killing the process */
    message = 1;
    /* set the logfilename to the default location */
    strcpy(logfilename, LOGFILE);

    while ((c = getopt( argc, argv, "i:w:l:mh" )) != EOF )
	switch( c )
	    {
	    case 'i':
		idle_time = atoi(optarg);
		break;
	    case 'w':
		wait_time = atoi(optarg);
		break;
	    case 'm':
		message = 0;
		break;
	    case 'l':
		strcpy(logfilename, optarg);
		break;
	    case 'h':
		fprintf(stdout, "%s: %s\n", progname, USAGE);
		fprintf(stdout, "   -h to get this help\n");
		fprintf(stdout, "   -i idle time in seconds (default 600)\n");
		fprintf(stdout, "   -w time to wait for user input (default 60)\n");
		fprintf(stdout, "   -l path and name of logfile to use\n");
		fprintf(stdout, "   -m if set no message is printed to the user\n");
		exit(0);
	    default:
		fprintf(stderr, "%s: %s\n",progname,USAGE);
		exit(1);
	    }

    /* terminate the father */
    if( (pid=fork()) )
	exit(0);
    if( pid != 0 )
	runerror("forking child");
    /* child is active now */

    /* open the logfile */
    if ((logfile = fopen(logfilename, "a+")) == NULL)
	runerror("opening logfile");
    log(logfile, "running");
    while( (putmp=getutent()) != NULL )
	check(logfile, putmp);
	
    log(logfile, "main program terminated normally");
    fclose(logfile);
    return(0);
}

int check(FILE *file, struct utmp *putmp)
{
    struct stat status;
    char device[MAX_CHARS];
    long idle;
    char mess[MAX_CHARS], username[UT_NAMESIZE];
    FILE *dev, *user;

    /* only try to find user processes */
    if(putmp->ut_type != USER_PROCESS)
	return(0);
    /* lets look if the user is in our file so he can stay idle */

    if((user=fopen(USERFILE, "r")) == NULL)
       {
	   sprintf(mess, "could not open %s\n", USERFILE);
	   log(file, mess);
       }
    else
	{
	   while(fscanf(user, "%s", username)!=EOF)
	       if( !strcmp(username, putmp->ut_user))
		   return(0);
	   fclose(user);
       }
       
    strcpy(device, "/dev/");
    strncat(device, putmp->ut_line, MAX_CHARS-4);
    if(stat(device, &status) < 0)
	{
	    sprintf(mess, "can't get status of device %s - exiting", device);
	    log(file, mess);
	    fclose(file);
	    exit(1);
	}
    /* get the idle time in seconds */
    idle = time(NULL) - status.st_atime;
    if( idle > idle_time )
	{
	    sprintf(mess, " user %s on %s exceeded idle time", 
		    putmp->ut_user, 
		    device);
	    log(file, mess);

	    /* print the message on the device */
	    if(message)
		{
		    if((dev = fopen(device, "w")) != NULL)
			{
			    fprintf(dev, "\a\n\nYou have been idle for %d minutes\n", idle_time/60);
			    fprintf(dev, "You will be logged off in %d seconds unless some input is done\n", wait_time);
			    fflush(dev);
			    fclose(dev);
			    logout(file, device, putmp);
			    
			}else
			    {
				sprintf(mess, "failed to open %s", device);
				log(file, mess);
			    }
		}
	}


    return(0);
}

int logout(FILE *file, char *device, struct utmp *putmp)
{
    int pid;
    struct stat status;
    char tmp[MAX_CHARS];

    pid = fork();
    if(pid == -1)
	{
	    log(file,"unable to fork child");
	    return(-1);
	}
    /* father returns and hunts for other idle users */
    if(pid)
	return(0);
    /* the child waits for user input */
    sleep(wait_time);
    if(stat(device, &status) < 0)
	{
	    sprintf(tmp, "child: can't get status of device %s - exiting", device);
	    log(file, tmp);
	    fclose(file);
	    exit(1);
	}
    /* kill the user process if no input is done */
    if((time(NULL) - status.st_atime) > idle_time)
	{
	    pid = putmp->ut_pid;
	    if(kill(pid, SIGHUP) < 0) /* freundlich */
		if(kill(pid, SIGKILL) < 0) /* eindringlich :-) */
		{
		    sprintf(tmp, "child: process %d refuses to die - can't logoff", pid);
		    log(file, tmp);
		    exit(1);
		}
	    sprintf(tmp, "child: user %s has been logged out from %s", putmp->ut_user,
		    device);
	    log(file, tmp);
	}
    /* und weg.... */
    exit(0);
}

void log(FILE *log, char *string)
{
    time_t t;
    char tmp[255];

    time(&t);
    strcpy(tmp, ctime(&t));
    tmp[strlen(tmp)-1] = '\0';
    fprintf(log, "%s %s %s\n", tmp, progname, string);
    fflush(log);
}
















