#include <err.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "whowatch.h"
#include "config.h"

#ifndef UTMP_FILE
#define UTMP_FILE 	"/var/run/utmp"
#endif

#ifndef WTMP_FILE
#define WTMP_FILE 	"/var/log/wtmp"
#endif

#define TIMEOUT 	3
#define LOGIN		0
#define LOGOUT		1		

#ifdef HAVE_UT_NAME
#define ut_user ut_name
#endif

enum key {  ENTER=0x100, UP, DOWN, LEFT, RIGHT, DELETE, ESC, CTRL_K, CTRL_I,
	   PG_DOWN, PG_UP, HOME, END };
	   
enum State{ USERS_LIST, PROC_TREE, INIT_TREE } state;

struct window users_list, proc_win;
struct window *windows[] = { &users_list, &proc_win, &proc_win, 0 };
void (*rfrsh[])() = { users_list_refresh, refresh_tree, refresh_tree };

int screen_size_changed;	/* not yet implemented */

struct list_head users = { &users, &users };

#ifdef DEBUG
FILE *debug_file;
#endif

int toggle;		/* if 0 show cmd line else show idle time 	*/
int full_cmd = 1;	/* if 1 then show full cmd line in tree		*/
int signal_sent;

int how_many, telnet_users, ssh_users, local_users;

int wtmp_fd;		/* wtmp file wd 			*/
int screen_rows;	/* screen rows returned by ioctl		*/
int screen_cols;	/* screen cols returned by ioctl		*/

char *line_buf;		/* global buffer for line printing		*/
int buf_size;		/* allocated buffer size			*/

#ifdef HAVE_PROCESS_SYSCTL
char * const ssh    = 	"sshd";   
char * const local  =	"init";
char * const telnet = 	"telnetd";
#else
char * const ssh    = 	"(sshd";    /* we don't know version of sshd 	*/
char * const local  =	"(init)";
char * const telnet = 	"(in.telnetd)";
#endif

int ssh_port = 22;
int telnet_port = 23;
int local_port = -1;

int tree_pid = 1;	
int show_owner;		/* if 1 display processes owners		*/

void show_cmd_or_idle();
void dump_users();
void cleanup();

#ifdef HAVE_PROCESS_SYSCTL
int get_login_pid(char *);
#endif

#ifdef HAVE_LIBKVM
int kvm_init();
int can_use_kvm = 0;
#endif

void allocate_error(){
	curses_end();
	fprintf(stderr,"Cannot allocate memory.\n");
	exit (1);
}

/*  
 *  update number of users (ssh users, telnet users...) and change 
 *  prot in the appropriate user structure.
 */
void update_nr_users(char *login_type, int *pprot, int x)
{
	int i;
	void *tab[] = {	
			ssh, 	&ssh_port, 	&ssh_users,
			telnet, &telnet_port, 	&telnet_users,
			local, 	&local_port, 	&local_users,
	      	       };
	*pprot = 0;		       
	for (i = 0; i < 9 ; i += 3){
		if (!strncmp( (char *) tab[i], login_type, strlen(tab[i]))){
			*pprot = * (int *) tab[i+1];
			if (x == LOGIN) (*(int *) tab[i+2])++;
			else (*(int *) tab[i+2])--;
			break;
		}
	}
}

void update_line_nr(int line)
{
	struct user_t *u;
	for_each(u, users) 
		if(u->line > line) u->line--;
}
			
/* 
 * Create new user structure and fill it
 */
struct user_t *allocate_user(struct utmp *entry)
{
	struct user_t *u;
	int ppid;
	u = calloc(1, sizeof *u);
	if(!u) errx(1, "Cannot allocate memory.");
	strncpy(u->name, entry->ut_user, UT_NAMESIZE);
	strncpy(u->tty, entry->ut_line, UT_LINESIZE);
	strncpy(u->host, entry->ut_host, UT_HOSTSIZE);
	
#ifdef HAVE_UTPID		
	u->pid = entry->ut_pid;
#else
	u->pid = get_login_pid(u->tty);
#endif

 	if((ppid = get_ppid(u->pid)) == -1)
		strncpy(u->parent, "can't access", sizeof u->parent);
	else 	strncpy(u->parent, get_name(ppid), sizeof u->parent - 1);
	
	u->line = how_many;
	return u;
}
	
void print_user(struct user_t *u)
{
	wattrset(users_list.wd, A_BOLD);
	snprintf(line_buf, buf_size, 
		"%-14.14s %-9.9s %-6.6s %-19.19s %s", 
		u->parent, 
		u->name, 
		u->tty, 
		u->host, 
		toggle?count_idle(u->tty):get_w(u->pid));
	line_buf[buf_size - 1] = '\0';
	print_line(&users_list, line_buf ,u->line, state);
	wrefresh(users_list.wd);
}

void cleanup()
{
	struct user_t *u;
	dellist(u, users);
	/* clear list of processes */
	clear_list();			
	close(wtmp_fd);
}

void windows_init()
{
	struct window *p;
	int i = 0;
	for(p = windows[i]; p; p = windows[i++]){
		p->first_line = p->last_line = p->cursor_line = 0;
		p->has_cursor = 1;
	}
}

void users_list_refresh()
{
	struct user_t *u;
	for_each(u, users) print_user(u);
	/* hide cursor */
	wmove(users_list.wd, users_list.cursor_line, users_list.cols+1);
	wrefresh(users_list.wd);
}
	
/*
 * Gather informations about users currently on the machine
 * Needed only at start or restart
 */
void read_utmp()		
{
	int fd, i;
	static struct utmp entry;
	struct user_t *u;
	
	if ((fd = open(UTMP_FILE ,O_RDONLY)) == -1){
		curses_end();
		errx(1, "Cannot open " UTMP_FILE);
	}
	while((i = read(fd, &entry,sizeof entry)) > 0) {
		if(i != sizeof entry) errx(1, "Error reading " UTMP_FILE );
#ifdef HAVE_USER_PROCESS
		if(entry.ut_type != USER_PROCESS) continue;
#else
		if(!entry.ut_name[0]) continue;
#endif
		u = allocate_user(&entry);
		print_user(u);
		update_nr_users(u->parent, &u->prot, LOGIN);
		how_many ++;
		users_list.d_lines = how_many;		
		addto_list(u, users);
	}
	close(fd);
	wnoutrefresh(users_list.wd);
	return;
}

struct user_t* new_user(struct utmp *newone)
{
	struct user_t *u;
	u = allocate_user(newone);
	users_list.d_lines = how_many;
	how_many++;
	addto_list(u, users);
	update_nr_users(u->parent, &u->prot, LOGIN);
	return u;
}

/*
 * get user entry from specific line (cursor position)
 */
struct user_t *cursor_user(int line)	
{
	struct user_t *u;
	for_each(u, users) if(u->line == line) return u;
	return 0;
}

/*
 * Check wtmp for logouts or new logins
 */
void check_wtmp()
{
	struct user_t *u;
	struct utmp entry;
	int i;

	while((i = read(wtmp_fd, &entry, sizeof entry)) > 0){ 
		if (i < sizeof entry){
			curses_end();
			cleanup();
			errx(1, "Error reading " WTMP_FILE );
		}
		/* user just logged in */
#ifdef HAVE_USER_PROCESS
		if(entry.ut_type == USER_PROCESS) {
#else
		if(entry.ut_user[0]) {
#endif
			u = new_user(&entry);
			print_user(u);
			wrefresh(users_list.wd);
			print_info();
			continue;
		}
#ifdef HAVE_DEAD_PROCESS
		if(entry.ut_type != DEAD_PROCESS) continue;
#else
//		if(entry.ut_line[0]) continue;
#endif
	/* user just logged out */
		for_each(u, users) {
			if(strncmp(u->tty, entry.ut_line, UT_LINESIZE)) 
				continue;
			if (state == USERS_LIST) 
				delete_line(&users_list, u->line);
			else virtual_delete_line(&users_list, u->line);
			update_line_nr(u->line);
			how_many--;
			users_list.d_lines = how_many;
			update_nr_users(u->parent, &u->prot, LOGOUT);
			print_info();
			delfrom_list(u, users);
			break;
		}
	}
}

char *users_list_giveline(int line)
{
	struct user_t *u;
	for_each(u, users) {
		if (line == u->line){
			snprintf(line_buf, buf_size, 
				"%-14.14s %-9.9s %-6.6s %-19.19s %s", 
				u->parent, 
				u->name, 
				u->tty, 
				u->host, 
				toggle?count_idle(u->tty):get_w(u->pid));
			return line_buf;
		}
	}
	return 0;
}

void update_load();
void periodic()
{
	/* always check wtmp for logins and logouts */
	check_wtmp();
	update_load();		
	switch(state){
		case INIT_TREE:
			tree_periodic();
			tree_title(0);
			updatescr(&proc_win);
			break;
		case PROC_TREE:
			tree_periodic();
			updatescr(&proc_win);
			break;
		case USERS_LIST:
			show_cmd_or_idle();
			updatescr(&users_list);
			break;
	}
}

int read_key()
{
	int c;
	c = getc(stdin);
	switch (c){
		case 0xD:
		case 0xA: return ENTER;
		case 0xB: return CTRL_K;
		case 0x9: return CTRL_I;
		case 0x1B:
			getc(stdin);
			c = getc(stdin);
			switch(c) {
				case 0x41: return UP;
				case 0x42: return DOWN;
				case 0x34:
				case 0x38:
				case 0x46: return END;
				case 0x36:
				case 0x47: return PG_DOWN;
				case 0x31:
				case 0x37:
				case 0x48: return HOME;
				case 0x35:
				case 0x49: return PG_UP;
			}
			break;
		default:
			break;
	}
	return c;
}

void proc_tree_init()
{
	proc_win.first_line = proc_win.last_line = proc_win.cursor_line = 0;
}


void send_signal(int sig, pid_t pid)
{
	int p;
	char buf[64];
	p = kill(pid, sig);
	signal_sent = 1;
	if (p == -1)
		sprintf(buf,"Can't send signal %d to process %d",
			sig, pid); 
	else sprintf(buf,"Signal %d was sent to process %d",sig, pid);
	werase(help_win.wd);
	echo_line(&help_win, buf, 0);
	wrefresh(help_win.wd);
}

void main_init()
{
	if((wtmp_fd = open(WTMP_FILE ,O_RDONLY)) == -1) 
		errx(1, "Cannot open " WTMP_FILE);
	if(lseek(wtmp_fd, 0, SEEK_END) == -1) 
		errx(1, "Cannot seek in " WTMP_FILE);
}

void restart()
{
	how_many = ssh_users = telnet_users = local_users = 0;
	toggle = show_owner = 0;
	cleanup();

	windows_init();
	clear_tree_title();
	state = USERS_LIST;
	werase(users_list.wd);
	wrefresh(users_list.wd);
	read_utmp();
	main_init();
	
	print_help(state);
	print_info();
}

void key_action(int key)
{
	int pid;
	struct user_t *p;
	if (signal_sent) {
	    print_help(state);
	    signal_sent = 0;
	}
	switch(key){
	case ENTER:
		werase(windows[state]->wd);
		switch(state){
		case USERS_LIST:
			state = PROC_TREE;
			print_help(state);
			proc_tree_init();
			p = cursor_user(
				users_list.cursor_line +
				users_list.first_line
				);
			if (!p) tree_pid = 1;
			else tree_pid = p->pid; 
			tree_periodic();
			tree_title(p);
			updatescr(&proc_win);
			break;
		case INIT_TREE:
		case PROC_TREE:
			state = USERS_LIST;
			print_help(state);
			clear_tree_title();
			clear_list();
			users_list_refresh();
			break;
		}
		break;
	case CTRL_I:
		if (state < PROC_TREE) break;
		pid = pid_from_tree(proc_win.cursor_line 
				+ proc_win.first_line);
		send_signal(2, pid);
		tree_periodic();
		
		break;
	case CTRL_K:
		if (state < PROC_TREE) break;
		pid = pid_from_tree(proc_win.cursor_line 
				+ proc_win.first_line);
		send_signal(9, pid);
		tree_periodic();
		break;
	case PG_DOWN:
		page_down(windows[state], rfrsh[state]);
		break;
	case PG_UP:
		page_up(windows[state], rfrsh[state]);
		break;
	case HOME:
		key_home(windows[state], rfrsh[state]);
		break;
	case END:
		key_end(windows[state], rfrsh[state]);
		break;
	case UP:
		cursor_up(windows[state]);
		wrefresh(windows[state]->wd);
		break;
	case DOWN:
		cursor_down(windows[state]);
		wrefresh(windows[state]->wd);
		break;
	case 'q': 
		curses_end(); 
		cleanup();	
		free(line_buf);
		exit(0);
	case 't':
		if (state < INIT_TREE){
			werase(windows[state]->wd);
			proc_tree_init();
			clear_list();
			state = INIT_TREE;
			print_help(state);
			tree_pid = 1; /* all processes */
			tree_periodic();
			tree_title(0);
			updatescr(&proc_win);
		}
		break;
	case 'i': 
		if (state != USERS_LIST) break;
		toggle = toggle?0:1;
		show_cmd_or_idle();
		updatescr(&users_list);
		break;
	case 'o':
		if (state == USERS_LIST) break;
		show_owner ^= 1;
		refresh_tree();
		wrefresh(proc_win.wd);
/* ugly hack to force proper cursor display on older curses versions */
//cursor_off(&proc_win, proc_win.cursor_line);
//cursor_on(&proc_win, proc_win.cursor_line);
		break;
	case 'c':
		full_cmd ^= 1;
		(*rfrsh[state])(); 
		updatescr(&proc_win);
//		wrefresh(windows[state]->wd);
		break;
#ifdef DEBUG
	case 'd':
		dump_list();
		dump_users();
		break;
#endif
	case 'x':
		restart();
		break;
	}
}

void get_rows_cols(int *y, int *x)
{
	struct winsize win;
	if (ioctl(1,TIOCGWINSZ,&win) != -1){
                *y = win.ws_row;
		*x = win.ws_col;
		return;
	}
	fprintf(stderr,"ioctl error: cannot read screen size\n");
	exit(0);
}								

void resize()	/* not yet implemented */
{
//	get_rows_cols(&users_list.rows, &users_list.cols);
	users_list.cursor_line = 0;
	curses_init();
	maintree(1);
}

void winch_handler()
{
	screen_size_changed++;
}

void dump_users()
{
#ifdef DEBUG
	struct user_t *u;
	fprintf(debug_file, "%p %p\n", users.next, users.prev);
	for_each(u, users) 
		fprintf(debug_file,"%p, prev %p, next %p, %s, %s\n",
			u, 
			u->prev, 
			u->next, 
			u->name, 
			u->tty
			);
	fflush(debug_file);
#endif

}
void segv_handler()
{
	signal(SIGSEGV,SIG_IGN);
	curses_end();
	printf("\nProgram has received a segmentation fault signal.\n");
	printf("It means that there is an error in the code.\n");
	printf("Please send a bug report to mike@wizard.ae.krakow.pl\n");
	printf("Your support will help to eliminate bugs once and for all.\n\n");
	fflush(stdout);
#ifdef DEBUG
	dump_list();
	dump_users();
#endif
	exit(1);
}

void int_handler()
{
	curses_end();
	exit(0);
}		

int main()
{
	struct timeval tv;
#ifndef RETURN_TV_IN_SELECT
  	struct timeval before;
  	struct timeval after;
#endif
	fd_set rfds;
	int retval;
	main_init();
#ifdef HAVE_LIBKVM
	if (kvm_init()) can_use_kvm = 1;
#endif
	
#ifdef DEBUG
	if (!(debug_file = fopen("debug", "w"))){
		printf("file debug open error\n");
		exit(0);
	}
#endif
	get_rows_cols(&screen_rows, &screen_cols);
	buf_size = screen_cols + screen_cols/2;
	line_buf = malloc(buf_size);
	if (!line_buf)
		errx(1, "Cannot allocate memory for buffer.");

	curses_init();
	windows_init();
	
	proc_win.giveme_line = proc_give_line;
	users_list.giveme_line = users_list_giveline;
	
        signal(SIGINT, int_handler);
/*	signal(SIGWINCH, winch_handler);   not yet implemented */
//	signal(SIGSEGV, segv_handler);

	wrefresh(users_list.wd);
	read_utmp();
	print_help(state);
	print_info();
	update_load();
	updatescr(&users_list);
	
	tv.tv_sec = TIMEOUT;
	tv.tv_usec = 0;
	for(;;) {				/* main loop */
		FD_ZERO(&rfds);
		FD_SET(0,&rfds);
#ifdef RETURN_TV_IN_SELECT
		retval = select(1,&rfds,0,0,&tv);
		if(retval) {
			int key = read_key();
			key_action(key);
		}
		if (!tv.tv_sec && !tv.tv_usec){
			periodic();
			tv.tv_sec = TIMEOUT;
		}
#else
		gettimeofday(&before, 0);
		retval = select(1, &rfds, 0, 0, &tv);
		gettimeofday(&after, 0);
		tv.tv_sec -= (after.tv_sec - before.tv_sec);
		if(retval) {
			int key = read_key();
			key_action(key);
		}
		if(tv.tv_sec <= 0) {
			periodic();
			tv.tv_sec = TIMEOUT;
		}
#endif
	}
}

void show_cmd_or_idle()
{
	struct window *q = &users_list;
	struct user_t *u;
	int y, x;
	for_each(u, users) {
		if (u->line < q->first_line || 
			u->line > q->first_line + q->rows - 1) 
			continue;
	        wmove(q->wd, u->line - q->first_line, CMD_COLUMN);
		cursor_off(q, q->cursor_line);
	        wattrset(q->wd, A_BOLD);
		wmove(q->wd, u->line - q->first_line, CMD_COLUMN);
	        waddnstr(q->wd, toggle?count_idle(u->tty):get_w(u->pid),
	        	 COLS - CMD_COLUMN - 1);
	        getyx(q->wd, y, x);
		while(x++ < q->cols + 1)
			waddch(q->wd, ' ');
		cursor_on(q, q->cursor_line);
		wmove(q->wd, q->cursor_line, q->cols + 1);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1