/*
 * console viewer fingerserver cfingerd
 * Written by Nathan Laredo (laredo@kernel.org)
 *
 * Distributed under the terms of the GNU Public Licence
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>

char *cfile = "/tmp/vcsa";
mode_t cmode = 0600 | S_IFCHR;
dev_t cdev = 0x0780;
#define MAXCONB		131072
struct scrn {
	char lines, cols, x, y;
	unsigned char buf[MAXCONB];
};

struct scrn console;

/* maximum possible length of output file */
#define MAXOUTB		4096
char outfile[MAXOUTB];
int outfile_len = 0;

#define LISTEN_PORT	1079

int sockfd;
fd_set readfs;

/* accept a new connection, indicated by positive select on bound socket */
int accept_connect(void)
{
	struct sockaddr_in sa;
	int s, u = sizeof(sa);

	bzero(&sa, u);
	s = accept(sockfd, (struct sockaddr *) &sa, &u);
	fprintf(stderr,"Connect from %s\n", inet_ntoa(sa.sin_addr.s_addr));
	fcntl(s, F_SETFL, O_NDELAY);
	return s;

}

/* make a connection to a remote host or bind a socket to accept connects */
int bind_socket(void)
{
	struct sockaddr_in sa;
	struct hostent *hp;
	int s, t;

	bzero(&sa, sizeof(sa));
	sa.sin_port = htons(LISTEN_PORT);
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
		return -1;
	if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
		return -1;
	if (listen(s, 0) < 0)
		return -1;
	fcntl(s, F_SETFL, O_NDELAY);
	return s;
}

/* for converting IBM extended characters to latin-1 */
unsigned char cp437[128] = /* characters 0x80 - 0xff */
	"\xc7\xfc\xe9\xe2\xe4\xe0\xe5\xe7\xea\xeb\xe8\xef\xee\xec\xc4\xc5"
	"\xc9\xe6\xc6\xf4\xf6\xf2\xfb\xf9\xff\xd6\xdc\xa2\xa3\xa5\x50\x66"
	"\xe1\xed\xf3\xfa\xf1\xaa\xba\xbf\x2d\xac\xbc\xbd\xa1\xab\xbb\x23"
	"\x23\x23\x7c\x7c\x2b\x2b\x2b\x2b\x2b\x7c\x7c\x2b\x2b\x2b\x2b\x2b"
	"\x2b\x2b\x2b\x2b\x2d\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x3d\x2b\x2b"
	"\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x2b\x23\x23\x23\x23\x23"
	"\x61\xdf\x47\xb6\x45\x6f\xb5\x76\xfe\x30\x6f\x64\xb7\xf8\x65\x69"
	"\x3d\xb1\xbb\xab\x66\x6a\xf7\xba\xb7\xb7\x73\x6e\xb2\xb7\xb7\x20";

unsigned char ctbl[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };

void read_outfile(void)
{
	int f, len, i, j, k, c, col, pcol;

	if ((f = open(cfile, O_RDONLY)) < 0) {
		perror(cfile);
		exit(117);
	}
	if ((len = read(f, &console, MAXCONB)) < 0) {
		perror(cfile);
		exit(117);
	}
	close(f);
	strcpy(outfile, "textmode dump\n");
	for (i = col = pcol = 0; i < len - 4; i += console.cols * 2) {
		for (j = (console.cols - 1) * 2; j > 0; j -= 2) {
			if (console.buf[i + j] == 0 ||
			    console.buf[i + j] == 0xff) {
				j = 0;
				break;
			}
			if (console.buf[i + j] != ' ' ||
			    console.buf[i + j + 1] != 7)
				break;
		}
		if (j < 1)	/* skip blank lines */
			continue;
		for (k = 0; k <= j; k += 2) {
			c = console.buf[i + k];
			col = console.buf[i + k + 1];
			if (col != pcol) {
				sprintf(&outfile[strlen(outfile)],
					"\033[%d;%d;%dm",
					(col >> 3) & 1,
					30 + ctbl[col & 7],
					40 + ctbl[(col >> 4) & 7]);
				pcol = col;
			}
			if (c < 0x20)
				c = '.';
			if (c > 0x7f)
				c = cp437[c - 128];
			sprintf(&outfile[strlen(outfile)], "%c", c);
		}
		sprintf(&outfile[strlen(outfile)], "\n", c);
		if (strlen(outfile) > MAXOUTB - 64)
			break;
	}
	sprintf(&outfile[strlen(outfile)], "\033[mend of textmode dump\n");
	outfile_len = strlen(outfile);
}


void graceful_exit(int n)
{
	shutdown(sockfd, 2);	/* get rid of socket */
	close(sockfd);
	exit(n);
}

int main(char argc, char **argv)
{
	int i, ok = 1;

	fprintf(stderr, "%s starting\n", argv[0]);
	if (fork() > 0) {
		exit(0);
	}
	mknod(cfile, cmode, cdev);
	read_outfile();
	if ((sockfd = bind_socket()) < 0) {
		perror("bind");
		exit(1);
	}
	close(0); close(1); close(2);

	/* the "big" input loop */
	while (ok) {
		FD_ZERO(&readfs);
		FD_SET(sockfd, &readfs);
		if (select(FD_SETSIZE, &readfs, NULL, NULL, NULL) > 0) {
			if (FD_ISSET(sockfd, &readfs)) {
				int s = accept_connect();
				read_outfile();
				write(s, outfile, outfile_len);
				shutdown(s, 2);	/* get rid of socket */
				close(s);
			}
		}
	}
	graceful_exit(0);
}
