/******************************************************************************

	afk v1.1
	Copyright (C) 2000 by Daniel Lowe.

	Displays amusing and useful information while away from the keyboard.

	Compilation:
		gcc afk.c -o afk -ltermcap


	Changes:
		1.1 - Now checks email using the same method as bash.  Added idle
			  kickoff for truly inactive users.  Clears screen using termcap.

		1.0 - First version.

******************************************************************************/


#include <fcntl.h>
#include <malloc.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <term.h>
#include <termios.h>
#include <unistd.h>
#include <utmp.h>
#include <sys/stat.h>
#include <sys/time.h>

#define MAX_IDLE_SEX	14400

struct user_entry
	{
	struct user_entry *next;
	struct user_entry *prev;
	char name[UT_NAMESIZE + 1];
	char marked;
	};

struct user_entry *g_users = NULL;
char my_name[UT_NAMESIZE + 1] = "";
struct termios orig_term_settings;
off_t last_email_size = 0;
time_t last_email_time = 0;
char*	email_file = NULL;

void init_terminal( void )
	{
	struct termios changes;
	int flags;
	char str[127];
	char *c = str;
	char *term_type;

	tcgetattr( STDIN_FILENO,&orig_term_settings );
	changes = orig_term_settings;
	changes.c_lflag &= ~(ICANON|ECHO);
	changes.c_oflag |= OFILL;
	tcsetattr(STDIN_FILENO,TCSADRAIN,&changes );

	flags = fcntl( STDIN_FILENO,F_GETFL,0 );
	flags |= O_NONBLOCK;
	fcntl( STDIN_FILENO,F_SETFL,flags );

	term_type = getenv( "TERM" );
	if ( term_type && tgetent( NULL,term_type ) > 0 )
		printf( "%s",tgetstr( "cl",&c ) );
	}

int touch_user( char *name )
	{
	struct user_entry *cur_entry = g_users;
	struct user_entry *the_entry = NULL;

	if ( !strcmp( my_name,name ) )
		return 0;

	while ( cur_entry && strcmp( cur_entry->name,name ) )
		cur_entry = cur_entry->next;

	if ( cur_entry )
		{
		cur_entry->marked = 1;
		return 0;
		}
	  else
		{
		the_entry = (struct user_entry *)malloc( sizeof(struct user_entry) );
		strncpy( the_entry->name,name,14 );
		the_entry->marked = 1;
		the_entry->next = g_users;
		the_entry->prev = NULL;
		if ( g_users )
			g_users->prev = the_entry;
		g_users = the_entry;
		return 1;
		}
	}

void clear_marks( void )
	{
	struct user_entry *cur_entry = g_users;

	while ( cur_entry )
		{
		cur_entry->marked = 0;
		cur_entry = cur_entry->next;
		}
	}

void find_unmarked( void )
	{
	struct user_entry *cur_entry = g_users;
	struct user_entry *next_entry;
	char datestr[25];
	time_t cur_time;

	while ( cur_entry )
		{
		next_entry = cur_entry->next;
		if ( !cur_entry->marked )
			{
			cur_time = time( NULL );
			strftime( datestr,25,"%H:%M:%S",localtime( &cur_time ) );
			printf( "[%s] %s logged out.\n",datestr,cur_entry->name );
			fflush( NULL );
			if ( cur_entry->next )
				cur_entry->next->prev = cur_entry->prev;
			if ( cur_entry->prev )
				cur_entry->prev->next = cur_entry->next;
			  else
				g_users = cur_entry->next;
			free( cur_entry );
			}
		cur_entry = next_entry;
		}
	}

void check_mail( void )
	{
	struct stat f_stat;
	time_t now = time( NULL );
	char datestr[25];

	if ( email_file == NULL )
		return;

	if ( stat( email_file,&f_stat ) < 0 )
		return;

	strftime( datestr,25,"%H:%M:%S",localtime( &now ) );

	if ( last_email_time && f_stat.st_mtime > last_email_time )
		{
		if ( ( last_email_size && f_stat.st_size > last_email_size ) &&
			 ( f_stat.st_atime < f_stat.st_mtime ) )
			printf( "\a[%s] new mail received.\n",datestr );
		}
	last_email_size = f_stat.st_size;
	last_email_time = f_stat.st_mtime;
	}

int main( int argc,char **argv )
	{
	struct utmp *u;
	struct passwd *pw_entry;
	char datestr[9];
	char username[UT_NAMESIZE + 1];
	long start_time = time( NULL );

	if ( argc > 1 )
		{
		fprintf( stderr,"Usage: afk [-v][-h]\nafk v1.1\n" );
		exit( 0 );
		}

	// Initialize the terminal so we can do the "quit by pressing any key" bit
	init_terminal();

	// Get our own username - we're not displaying ourselves
	pw_entry = getpwuid( getuid() );
	strncpy( my_name,pw_entry->pw_name,UT_NAMESIZE );

	// Initialize the user table
	utmpname(UTMP_FILE);

	setutent();
	clear_marks();
	while ( ( u = getutent() ) != NULL )
		{
		// Make sure we have null termination on the user name
		strncpy( username,u->ut_user,UT_NAMESIZE );
		if ( u->ut_type == USER_PROCESS && touch_user( u->ut_user ) )
			{
			strftime( datestr,25,"%H:%M:%S",localtime( &u->ut_time ) );
			printf( "[%s] %s is online.\n",datestr,u->ut_user );
			fflush( NULL );
			}
		}
	endutent();

	// Initialize the mail checking
	email_file = getenv( "MAIL" );
	check_mail();

	// Loop until user presses a key
	while ( getchar() == -1 && ( time(NULL) - start_time < MAX_IDLE_SEX ) )
		{
		fd_set in_set;
		struct timeval sleep_time = { 1,0 };

		FD_ZERO( &in_set );
		FD_SET( 0,&in_set );
		select( 1,&in_set,NULL,NULL,&sleep_time );
		if ( FD_ISSET( 0,&in_set ) )
			continue;
		setutent();
		clear_marks();
		while ( ( u = getutent() ) )
			{
			if ( u->ut_type == USER_PROCESS && touch_user( u->ut_user ) )
				{
				strftime( datestr,25,"%H:%M:%S",localtime( &u->ut_time ) );
				printf( "\a[%s] %s logged in.\n",datestr,u->ut_user );
				fflush( NULL );
				}
			}
		endutent();
		find_unmarked();
		check_mail();
		}

	// Inform the guy when he's being kicked off for idling
	if ( time(NULL) - start_time >= MAX_IDLE_SEX )
		printf( "You have been idle, and are pulled into the shell.\n" );

	// Restore the terminal settings
	tcsetattr( STDIN_FILENO,TCSADRAIN,&orig_term_settings );
	return 0;
	}
