
/*
 * Copyright Colten Edwards (July 1/1998).
 * This code is for the purpose of re-attaching to a detached BitchX session.
 * The idea for this program was by kasper@efnet after I mentioned the trouble
 * I was having reconnecting to a detached terminal.
 */
 
 /* 
  * Version 1.0 released with BitchX 75
  * $Id$
  */

#include "irc.h"
#include "struct.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#ifdef USING_CURSES
#include <curses.h>
#endif
#include <stdarg.h>
#include <string.h>
#include "ircterm.h"
#include "screen.h"
#include "ircaux.h"

#include <sys/ioctl.h>

#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif

#ifdef MEM_DEBUG
#include <dmalloc.h>
#endif

#ifdef TRANSLATE
char translation = 0;
unsigned char   transToClient[256];    /* Server to client translation. */
#endif

int dumb_mode = 0;
int already_detached = 1;
int do_check_pid = 0;
char socket_path[500];
char attach_ttyname[500];


struct param 
{
	pid_t	pgrp,
		pid;
	uid_t	uid;
	int	cols;
	int	rows;
	char	tty[80];
	char	cookie[30];
	char	password[80];
	char	termid[81];
};
                                
struct param parm;                                                
char *old_pass = NULL;
Screen *output_screen = NULL, *last_input_screen = NULL, *main_screen = NULL;
char empty_string[] = "";
int foreground = 0;
int use_input = 1;
int use_flow_control = 0;
static int displays = 0;

#define SOCKMODE (S_IWRITE | S_IREAD | (displays ? S_IEXEC : 0))

#ifdef CLOAKED
extern char proctitlestr[140];
extern char **Argv;             /* pointer to argument vector */
extern char *LastArgv;          /* end of argv */
#endif


char	*n_m_strdup (const char *str, const char *module, const char *file, const int line)
{
	char *ptr;
	if (!str)
		str = empty_string;
	ptr = (char *)malloc(strlen(str) + 1);
	return strcpy(ptr, str);
}

void ircpanic(char *string, ...)
{
	return;
}

int get_int_var(int var)
{
	return 1;
}

char *ltoa (long foo)
{
	static char buffer[BIG_BUFFER_SIZE/8+1];
	char *pos = buffer + BIG_BUFFER_SIZE/8-1;
	unsigned long absv;
	int negative;

	absv = (foo < 0) ? (unsigned long)-foo : (unsigned long)foo;
	negative = (foo < 0) ? 1 : 0;

	buffer[BIG_BUFFER_SIZE/8] = 0;
	for (; absv > 9; absv /= 10)
		*pos-- = (absv % 10) + '0';
	*pos = (absv) + '0';

	if (negative)
		*--pos = '-';

	return pos;
}

char *lower(char *str)
{
register char   *ptr = NULL;

	if (str)
	{
		ptr = str;
		for (; *str; str++)
		{
			if (isupper(*str))
				*str = tolower(*str);
		}
	}
	return (ptr);
}

#ifndef HAVE_GETPASS
char *getpass(char *);
char *get_string_var(int var)
{
	return NULL;
}
#endif

#ifdef WINNT
void refresh_screen(int i, char *u)
{
	return;
}
#endif

char *find_tty_name(char *name)
{
static char tty[20];
char *q, *s;
	*tty = 0;
	if ((q = strrchr(name, '/')))
	{
		q++;
		if ((q = strchr(q, '.')))
		{
			q++;
			if ((s = strchr(q, '.')))
				strncpy(tty, q, s-q);
		}
	}
	return tty;
}

char *find_tty_path(char *name)
{
static char ttypath[200];
char *q;
	*ttypath = 0;
	if ((q = strrchr(name, '/')))
		strncpy(ttypath, name, q - name);
	return ttypath;
}

void display_socket_list(char *path, int unl, char *arg)
{
DIR	*dptr;
struct	dirent	*dir;
struct	stat	st;
char buffer[2000];
char *new_path, *p;
int count = 0;
int doit = 0;

	new_path = LOCAL_COPY(path);
	if ((p = strrchr(new_path, '/')))
		*p = 0;
	if (!(dptr = opendir(new_path)))
	{
		fprintf(stderr, "No such directory %s\r\n ", new_path);
		exit(1);
	}
	while ((dir = readdir(dptr)))
	{
		doit = 0;
		if (!dir->d_ino)
			continue;
		if (dir->d_name[0] == '.')
			continue;
		sprintf(buffer, "%s/%s", new_path, dir->d_name);
		if ((stat(buffer, &st) == -1))
			continue;
		if (arg && strstr(dir->d_name, arg))
			doit++;
		if (!count && !unl)
			fprintf(stderr, "There is more than one sockets available - \r\n");
		else if (!count)
			fprintf(stderr, "unlinking the following\r\n");
		count++;
		if (unl)
		{
			if (!((st.st_mode & 0700) == 0600) || doit)
			{
				fprintf(stderr, "%30s\r\n", dir->d_name);
				unlink(buffer);
			}
		} 
		else if ((!doit && !arg) || (doit && arg))
			fprintf(stderr, "%30s %s\r\n", dir->d_name, ((st.st_mode & 0700) == 0600) ? "detached":"Attached or dead");
	}
	if (!count)
		fprintf(stderr, "No sockets to attach to.\r\n");
	closedir(dptr);
	exit(1);
}

char *find_detach_socket(char *path, char *name)
{
char	*new_path;
DIR	*dptr;
struct	dirent	*dir;
struct	stat	st;
char *ret = NULL, *p;
int count = 0;
	new_path = LOCAL_COPY(path);
	if ((p = strrchr(new_path, '/')))
		*p = 0;
	else
		return NULL;
	if (!(dptr = opendir(new_path)))
		return NULL;
	ret = malloc(2000);
	*ret = 0;
	while ((dir = readdir(dptr)))
	{
		*ret = 0;
		if (!dir->d_ino)
			continue;
		if (dir->d_name[0] == '.')
			continue;
		sprintf(ret, "%s/%s", new_path, dir->d_name);
		p = strrchr(ret, '/'); p++;
		if ((stat(ret, &st) == -1) || (st.st_uid != getuid()) || S_ISDIR(st.st_mode))
		{
			*ret = 0;
			continue;
		}
		if (name)
		{
			char *pid, *n_tty, *h_name;
			pid = LOCAL_COPY(p);
			n_tty = strchr(pid, '.'); *n_tty++ = 0;
			h_name = strchr(n_tty, '.'); *h_name++ = 0;
			if (strcmp(name, pid))
			{
				if (strcmp(n_tty, name))
				{
					if (strcmp(h_name, name))
					{
						if (strcmp(p, name))
						{
							if (!strstr(p, name))
							{
								*ret = 0;
								continue;
							}
						}
					}
				}
			}
		}
		if ((st.st_mode & 0700) == 0600)
			break;
		count++;
		*ret = 0;
	}
	closedir(dptr);
	if (ret && !*ret)
	{
		free(ret);
		ret = NULL;
	}
	switch (count)
	{
		case 0:
			break;
		case 1:
			break;
		default:
			display_socket_list(path, 0, name);
			if (ret) free(ret);
			ret = NULL;
	}
	return ret;
}

void charset_ibmpc (void)
{
	fwrite("\033(U", 3, 1, stdout);	/* switch to IBM code page 437 */
}

SIGNAL_HANDLER(handle_pipe)
{
	
	term_cr();
	term_clear_to_eol();
	term_reset2();
	fprintf(stdout, "\rdetached from %s. To re-attach type scr-bx %s\n\r", attach_ttyname, old_pass? "password":"");
	fflush(stdout);
	exit(0);
}

SIGNAL_HANDLER(handle_hup)
{
	term_cr();
	term_clear_to_eol();
	term_reset2();
	fprintf(stdout, "\r");
	fflush(stdout);
	exit(0);
}

volatile int ctrl_c = 0;

SIGNAL_HANDLER(handle_ctrlc)
{
	ctrl_c++;
}

/* set's socket options */
void set_socket_options (int s)
{
	int	opt = 1;
	int	optlen = sizeof(opt);
#ifndef NO_STRUCT_LINGER
	struct linger	lin;

	lin.l_onoff = lin.l_linger = 0;
	setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&lin, optlen);
#endif

	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, optlen);
	opt = 1;
	setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, optlen);
}


char *get_cookie(char *name)
{
	static char cookie[80];
	FILE *fp = NULL;
	*cookie = 0;
	if ((fp = fopen(name, "r")))
	{
		fread(cookie, 40, 1, fp);
		fclose(fp);
		if (*cookie)
			cookie[strlen(cookie)-1] = 0;
	}
	return cookie;
}

void reattach_tty(char *tty, char *password)
{
int s = -1;
char *name;
struct sockaddr_in addr;
struct hostent *hp;
int len = 0;
fd_set rd_fd;
struct timeval tm = {0};
char chr_c[] = "\003";

/* 
 * this buffer has to be big enough to handle a full screen of 
 * information from the detached process.
 */
unsigned char buffer[6 * BIG_BUFFER_SIZE+1];
char *p;
int port = 0;
#if defined (TIOCGWINSZ)
struct winsize window;
#endif
	memset(&parm, 0, sizeof(struct param));

	if (!(name = find_detach_socket(socket_path, tty)))
	{
		fprintf(stderr, "No detached process to attach to.\r\n");
		_exit(1);
	}

	strcpy(parm.cookie, get_cookie(name));
	if (!*parm.cookie)
		_exit(1);
	if ((p = strrchr(name, '/')))
		p++;
	sscanf(p, "%d.%*s.%*s", &port);
	displays = 1;
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		displays = 0;
		_exit(1);
	}

	chmod(name, SOCKMODE);
	set_socket_options(s);
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_port = htons(port);
	addr.sin_family = AF_INET;
	if((hp = gethostbyname("localhost")))
		memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
	else
		inet_aton("127.0.0.1", (struct in_addr *)&addr.sin_addr);
	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
	{
		fprintf(stderr, "connection refused for %s\r\n", name);
		_exit(1);
	}

	parm.pid = getpid();
	parm.pgrp = getpgrp();
	parm.uid = getuid();
	strcpy(parm.tty, ttyname(0));
	strncpy(parm.termid, getenv("TERM"), 80);
	if (password) 
		strncpy(parm.password, password, 60);
	fprintf(stderr, "attempting to wakeup %s\r\n", find_tty_name(name));
#if defined (TIOCGWINSZ)
	if (ioctl(0, TIOCGWINSZ, &window) > -1)
	{
		parm.cols = window.ws_col;
		parm.rows = window.ws_row;
	}
	else
#endif
	{
		parm.cols = 79;
		parm.rows = 25;
	}
	write(s, &parm, sizeof(struct param));	
	sleep(2);
	alarm(15);
	len = read(s, &parm, sizeof(struct param));
	alarm(0);
	if (len <= 0)
	{
		fprintf(stderr, "[1;37merror[0;37m reconnecting to %s\r\n", find_tty_name(name));
		displays = 0;
		chmod(name, SOCKMODE);
		exit(1);
	}
	unlink(name);

	term_init(parm.termid);
	set_term_eight_bit(1);
	charset_ibmpc();
	term_clear_screen();
	term_resize();
	term_move_cursor(0,0);

	my_signal(SIGPIPE, handle_pipe, 0);
	my_signal(SIGINT,  handle_ctrlc, 0);
	my_signal(SIGHUP,  handle_hup, 0);

	/*
	 * according to MHacker we need to set errno to 0 under BSD.
	 * for some reason we get a address in use from a socket 
	 *
	 */
	errno = 0;
	while (1)
	{
		FD_ZERO(&rd_fd);
		FD_SET(0, &rd_fd);
		FD_SET(s, &rd_fd);
		tm.tv_sec = 2;
		
		switch(select(s+1, &rd_fd, NULL, NULL, &tm))
		{
			case -1:
				if (ctrl_c)
				{
					write(s, chr_c, 1);
					ctrl_c = 0;
				}
				else if (errno != EINTR)
				{
					close(s);
					_exit(1);
				}
				break;
			case 0:
				break;
			default:
			{
				if (FD_ISSET(0, &rd_fd))
				{
					len = read(0, buffer, sizeof(buffer)-1);
					write(s, buffer, len);
				}
				if (FD_ISSET(s, &rd_fd))
				{
					len = read(s, buffer, sizeof(buffer)-1);
					write(1, buffer, len);
				}
			}
		}
	}
	close(s);
	fprintf(stderr, "Never should have got here");
	_exit(1);			 

	return; /* error return */
}

char *stripdev(char *ttynam)
{
	if (ttynam == NULL)
		return NULL;
#ifdef SVR4
  /* unixware has /dev/pts012 as synonym for /dev/pts/12 */
	if (!strncmp(ttynam, "/dev/pts", 8) && ttynam[8] >= '0' && ttynam[8] <= '9')
	{
		static char b[13];
		sprintf(b, "pts/%d", atoi(ttynam + 8));
		return b;
	}
#endif /* SVR4 */
	if (!strncmp(ttynam, "/dev/", 5))
		return ttynam + 5;
	return ttynam;
}


void init_socketpath(void)
{
#if !defined(__EMX__) && !defined(WINNT)
struct stat st;
extern char socket_path[], attach_ttyname[];

	sprintf(socket_path, "%s/.BitchX/screens", getenv("HOME"));
	if (access(socket_path, F_OK))
		return;
	if (stat(socket_path, &st) != -1)
	{
		char host[BIG_BUFFER_SIZE+1];
		char *ap;
		if (!S_ISDIR(st.st_mode))
			return;
		gethostname(host, BIG_BUFFER_SIZE);
		if ((ap = strchr(host, '.')))
			*ap = 0;
		ap = &socket_path[strlen(socket_path)];
		sprintf(ap, "/%%d.%s.%s", stripdev(attach_ttyname), host);
		ap++;
		for ( ; *ap; ap++)
			if (*ap == '/')
				*ap = '-';
	}	        
#endif
}


char *old_tty = NULL;
void parse_args(int argc, char **argv)
{
int ac = 1;
int disp_sock = 0;

	for (; ac < argc; ac++)
	{
		
		if (!strncasecmp(argv[ac], "tty", 3))
		{
			old_tty = malloc(strlen(argv[ac])+1);
			strcpy(old_tty, argv[ac]);
		}
		else if (argv[ac][0] == '-' && argv[ac][1] == 'p')
		{
			char *pass;
			pass = getpass("Enter password : ");
			old_pass = malloc(strlen(pass)+1);
			strcpy(old_pass, pass);
		}
		else if (argv[ac][0] == '-' && argv[ac][1] == 'h')
		{
			char *p;
			if ((p = strrchr(argv[0], '/')))
				p++;
			else
				p = argv[0];
			fprintf(stderr, "Usage %s: [tty] [-p] [-h] [-l]\r\n\t\ttty is the name of a tty\r\n\t\t-p to specify a password\r\n\t\t-l to list available sockets\r\n\t\t-w to wipe out dead sockets\r\n", p);
			exit(0);
		}
		else if (argv[ac][0] == '-' && argv[ac][1] == 'l')
			disp_sock = 1;
		else if (argv[ac][0] == '-' && argv[ac][1] == 'w')
			disp_sock = 2;
		else if (!old_tty)
		{
			old_tty = malloc(strlen(argv[ac])+1);
			strcpy(old_tty, argv[ac]);
		}
	}
	if (disp_sock)
		display_socket_list(socket_path, disp_sock - 1, old_tty);
}


int main(int argc, char **argv)
{
#ifdef MEM_DEBUG
	dmalloc_debug(0x1df47dfb);
#endif
	*socket_path = 0;
	strcpy(attach_ttyname, ttyname(0));
	init_socketpath();
	parse_args(argc, argv);
        chdir(getenv("HOME"));
	reattach_tty(old_tty, old_pass);
	return 0;
}
