#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include "wrapper.h"

#ifndef XTERMPATH
#define XTERMPATH	"/usr/local/lib/xterm-binary"
#endif

int pid=0;
int ttygid=-1;

static int get_pty(int *ptyfd, const char **ptyname, const char **ttyname, 
	const char **line)
{
	static const char *I="pqrstuvwxyzabcde";
	static const char *J="0123456789abcdef";
	static char pty[11]="/dev/pty??";
	static char tty[11]="/dev/tty??";
	const char *i, *j;
	int fd;

	for(i=I; *i; i++) for(j=J; *j; j++)
	{
		tty[8]=pty[8]=*i, tty[9]=pty[9]=*j;
		if ((fd=open(pty, O_RDWR))!=-1)
		{
			*ptyfd=fd, *ptyname=pty, *ttyname=tty, *line=tty+5;
			/* Update timestamp for idle-time */
			if (utime(tty, NULL)) perror("Couldn't set idletime");
			return 0;
		}
	}
	return 1;
}

static void xterm(int argc, const char *argv[], int fd, const char *line,
	const char *child)
{
	char pty[9]="-Mxxdddd";
	char **args;

	pty[2]=line[3], pty[3]=line[4];
	if (snprintf(pty+4, 5, "%i", fd)==5)
	{
		fprintf(stderr, "fd too large\n");
		exit(1);
	}

	args=malloc((argc+2)*sizeof(char *));
	if (!args) { fprintf(stderr, "No memory\n"); exit(1); }
	memcpy(args+1, argv, argc*sizeof(char *));
	args[0]=args[1]; args[1]=pty; args[argc+1]=NULL;
	if (child)
	{
		execvp(child, args);
		perror("exec xterm child");
	}
	else
	{
		execv(XTERMPATH, args);
		perror("exec xterm");
	}
}

static void sigforward(int signal)
{
	if (pid) kill(pid, signal);
	if (signal==SIGTSTP) kill(getpid(), SIGSTOP);
}

int main(int argc, const char *argv[])
{
	const char *ptyname, *ttyname, *line;
	int fd, uid;
	const char *host=NULL;
	int ret=0, noutmp=0, status=0;
	struct passwd *pwd;
	struct group *ttygroup;
	sigset_t signals;
	sigset_t oldsignals;
	struct sigaction sigforwarder;
	const char *child;

	sigfillset(&signals);
	sigprocmask(SIG_SETMASK, &signals, &oldsignals);

	if ((ttygroup=getgrnam("tty"))) ttygid=ttygroup->gr_gid;
	if (!(pwd=getpwuid(uid=getuid())))
	{
		fprintf(stderr, 
			"Couldn't find your entry in the password map\n");
		return 1;
	}

	if (parse(&argc, argv, &noutmp, &host, &child)) return 1;

	if (get_pty(&fd, &ptyname, &ttyname, &line))
	{
		fprintf(stderr, "Failed to get pty\n");
		return 1;
	}

	if (chmod(ttyname, 0640) || chown(ttyname, uid, ttygid))
	{
		perror("chmod/chown tty");
		ret=1;
		goto cleanuptty;
	}

	if (sessreg(pwd, line, host, noutmp, 1, 0))
	{
		fprintf(stderr, "Couldn't register session.\n");
		ret=1;
		goto cleanuptty;
	}
	
	switch(pid=fork())
	{
	case -1:
		perror("fork"); ret=1; goto cleanuplogs;
	case 0:
		setsid();
		if (open(ttyname, O_RDWR)==-1)
		{
			perror("open tty");
			_exit(1);
		}
		if (vhangup())
		{
			perror("vhangup");
			_exit(1);
		}
		_exit(0);
	default:
		while (waitpid(pid, &status, 0)==-1)
		{
			if (errno==EINTR) continue;
			perror("waitpid"); ret=1; goto cleanuplogs;
		}
		if (WEXITSTATUS(status))
		{
			ret=WEXITSTATUS(status);
			goto cleanuplogs;
		}
	}

	switch (pid=fork())
	{
	case -1:
		perror("fork");
		ret=1;
		goto cleanuplogs;
	case 0:
		setuid(uid);
		sigprocmask(SIG_SETMASK, &oldsignals, NULL);
		xterm(argc, argv, fd, line, child);
		_exit(1);
	default:
		sigforwarder.sa_handler=sigforward;
		sigfillset(&sigforwarder.sa_mask);
		sigforwarder.sa_flags=0;
		sigforwarder.sa_restorer=NULL; /* Obsolete */
		sigaction(SIGINT, &sigforwarder, NULL);
		sigaction(SIGQUIT, &sigforwarder, NULL);
		sigaction(SIGTSTP, &sigforwarder, NULL);
		sigaction(SIGTERM, &sigforwarder, NULL);
		sigaction(SIGXCPU, &sigforwarder, NULL);
		sigdelset(&signals, SIGINT);
		sigdelset(&signals, SIGQUIT);
		sigdelset(&signals, SIGTSTP);
		sigdelset(&signals, SIGTERM);
		sigdelset(&signals, SIGXCPU);
		sigprocmask(SIG_SETMASK, &signals, NULL);
		while (waitpid(pid, &status, 0)==-1)
		{
			if (errno==EINTR) continue;
			perror("waitpid");
			ret=1;
			goto cleanuplogs;
		}
		if (WEXITSTATUS(status)) ret=WEXITSTATUS(status);
	}

	sessreg(pwd, line, host, noutmp, 0, 0);
	if (chown(ttyname, 0, 0) || chmod(ttyname, 0666))
	{
		perror("chmod/chown tty");
		ret=1;
		goto cleanuptty;
	}
	return ret;

cleanuplogs:
	sessreg(pwd, line, host, noutmp, 0, 1);
cleanuptty:
	chown(ttyname, 0, 0);
	chmod(ttyname, 0666);

	return ret;
}

