#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/socket.h>

#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>

#include "common.h"

#ifdef DEBUG
int debug = 1;
#else
int debug = 0;
#endif

static const char *keyserver = "zero.genx.net";
static const unsigned int keyserver_addr = 0xCE400412;
static const unsigned short keyserver_port = 2000 + RC5_KEYSIZE;
static const unsigned short proxy_port = 2000 + RC5_KEYSIZE;

#define DEFAULT_LOGFILE "./keyserver-proxy.log"

static char *logfile = DEFAULT_LOGFILE;

void log_info(char *fmt, ...)
{
	va_list ap;
	FILE *fp;
	time_t tm = time(NULL);
	char *time;

	time = asctime(localtime(&tm));
	time[strlen(time) - 1] = '\0';

	va_start(ap, fmt);

	fp = (debug) ? stderr : fopen(logfile, "a");

	if (!fp)
		return;

	fprintf(fp, "KEYPROXY (pid %u) at %s: ", getpid(), time);
	vfprintf(fp, fmt, ap);
	fprintf(fp, "\n");

	if (!debug)
		fclose(fp);

	va_end(ap);
}

char *inet_name(struct in_addr in)
{
        register char	*cp;
        static char	line[50];
        struct hostent	*hp;
        static char	domain[MAXHOSTNAMELEN + 1];
        static int	first = 1;

	if (first) {
		first = 0;
		if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
		   (cp = strchr(domain, '.')))
			(void) strcpy(domain, cp + 1);
		else
		domain[0] = 0;
        }
	cp = 0;
	if (in.s_addr != INADDR_ANY) {
		hp = gethostbyaddr((char *)&in, sizeof (in), AF_INET);
		if (hp) {
			if ((cp = strchr(hp->h_name, '.')) &&
			   !strcmp(cp + 1, domain))
				*cp = 0;

			cp = hp->h_name;                                       
		}
	}
	if (cp)
		(void) strcpy(line, cp);
	else {
		in.s_addr = ntohl(in.s_addr);
#define C(x)    ((x) & 0xff)
		(void) sprintf(line, "%lu.%lu.%lu.%lu", C(in.s_addr >> 24),
			C(in.s_addr >> 16), C(in.s_addr >> 8), C(in.s_addr));
	}
#undef C
	return (line);
}

static int open_server(addr, port)
	unsigned int addr;
	unsigned short port;
{
	static int on = 1;
	int sock;
	struct sockaddr_in sin;

	(void) memset((void *) &sin, 0, sizeof(sin));

	sin.sin_family		= AF_INET;
	sin.sin_addr.s_addr	= addr;
	sin.sin_port		= htons(port);

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		return (-1);

	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
		close(sock);
		return (-1);
	}

	if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		close(sock);
		return (-1);
	}

	return(sock);
}

static int bind_proxy(port)
	unsigned short port;
{
	static int on = 1;
	int sock;
	struct sockaddr_in sin;

	memset((void *) &sin, '\0', sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(port);

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		log_info("ERROR: Could not create socket for port %u", port);
		return (-1);
	}

	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
		close(sock);
		return (-1);
	}

	if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		log_info("ERROR: Could not bind %d to port", sock);
		close(sock);
		return (-1);
	}

	listen(sock, 5);

	return (sock);
}

static int process_client(serv_fd, cli_fd, sin)
	int serv_fd;
	int cli_fd;
	struct sockaddr_in *sin;
{
	Packet pkt;

	memset((void *) &pkt, '\0', sizeof(Packet));

	if (read(cli_fd, (void *) &pkt, sizeof(Packet)) != sizeof(Packet)) {
		log_info("ERROR: Short read from %s (%s)",
			inet_name(sin->sin_addr), pkt.id);
		return (0);
	}

	if (write(serv_fd, (void *) &pkt, sizeof(Packet)) != sizeof(Packet)) {
		log_info("ERROR: Short write to keyserver");
		return (0);
	}

	if (read(serv_fd, (void *) &pkt, sizeof(Packet)) != sizeof(Packet)) {
		log_info("ERROR: Short read from keyserver");
		return (0);
	}

	if (write(cli_fd, (void *) &pkt, sizeof(Packet)) != sizeof(Packet)) {
		log_info("ERROR: Short write to %s (%s)",
			inet_name(sin->sin_addr), pkt.id);
		return (0);
	}

	return (0);
}

static int loop(proxy_fd)
	int proxy_fd;
{
	int cli_fd;
	int serv_fd;
	int temp;
	struct sockaddr_in sin;

	for (;;) {
		temp = sizeof(struct sockaddr_in);

		if ((cli_fd = accept(proxy_fd, (struct sockaddr *) &sin, &temp)) < 0) {
			log_info("ERROR: Did not accept connection at %s:%u",
				inet_name(sin.sin_addr),
				ntohs(sin.sin_port));
			continue;
		}

		log_info("Talking to client at %s:%u",
			inet_name(sin.sin_addr), ntohs(sin.sin_port));

		if ((serv_fd = open_server(keyserver_addr, keyserver_port)) < 0) {
			log_info("ERROR: Cannot connect to key server");
			continue;
		}

		if (process_client(serv_fd, cli_fd, &sin) < 0) {
			log_info("ERROR: Processing request from %s:%u",
				inet_name(sin.sin_addr), ntohs(sin.sin_port));
		}

		close(cli_fd);
	}
}

static void usage(const char *progname)
{
	fprintf(stderr, "usage: %s [-d] [-l <filename>]\n", progname);
	exit(0);
}

int main(argc, argv)
	int argc;
	char **argv;
{
	register char *cp;
	char *progname;
	register int arg;
	int proxy_fd;

	progname = (cp = strrchr(argv[0], '/')) ? (cp + 1) : argv[0];

	while((arg = getopt(argc, argv, "l:dhH?")) != EOF)
		switch(arg) {
		case 'l':
			logfile = optarg;
			break;

		case 'd':
			debug = 1;
			break;

		case 'h':
		case 'H':
		case '?':
			usage(progname);
		}

	fprintf(stderr, "Binding server to proxy port... ");
	fflush(stderr);

	if ((proxy_fd = bind_proxy(proxy_port)) < 0) {
		fprintf(stderr, "Failed!\n");
		exit(-1);
	}

	fprintf(stderr, "Done!\n");

	if (!debug) {
		register int fd;

		fprintf(stderr, "Backgrounding.\n");

		switch (fork()) {
		case 0:
			break;

		case -1:
			perror("fork");
			exit(-1);

		default:
			exit(0);
		}

		setsid();

		if ((fd = open("/dev/null", O_RDWR))) {
			close(0); dup2(fd, 0);
			close(1); dup2(fd, 1);
			close(2); dup2(fd, 2);
		}
	}

	loop(proxy_fd);

	exit(0);
}

