/*
 * cgi2scgi: A CGI to SCGI translator
 *
 */

/* configuration settings */
#ifndef HOST
#define HOST "127.0.0.1"
#endif

#ifndef PORT
#define PORT 4000
#endif


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h> /* for TCP_NODELAY */

#define SCGI_PROTOCOL_VERSION "1"


struct scgi_header {
	struct scgi_header *next;
	char *name;
	char *value;
};

static void
die(void)
{
	_exit(2);
}

static void
die_perror(char *msg)
{
	char buf[500];
	snprintf(buf, sizeof buf, "error: %s", msg);
	perror(buf);
	die();
}

static void
die_msg(char *msg)
{
	fprintf(stderr, "error: %s\n", msg);
	die();
}

static int
open_socket(void)
{
	int sock, set;
	int tries = 5, retrytime = 1;
	struct in_addr host;
	struct sockaddr_in addr;

	/* create socket */
	if (!inet_aton(HOST, &(host))) {
		die_perror("parsing host IP");
	}

	addr.sin_addr = host;
	addr.sin_port = htons(PORT);
	addr.sin_family = AF_INET;

	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1) {
		die_perror("creating socket");
	}

	/* connect */
retry:
	if (connect(sock, (struct sockaddr *)&addr, sizeof addr) == -1) {
		if (errno == ECONNREFUSED && tries > 0) {
			sleep(retrytime);
			tries--;
			retrytime *= 2;
			goto retry;
		}
		die_perror("connecting to server");
	}

#ifdef TCP_NODELAY
	/* disable Nagle */
	set = 1;
	setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set));
#endif

	return sock;
}

static void
add_header(struct scgi_header *list_head, char *name, char *value) 
{
	if (name != NULL && value != NULL) {
		struct scgi_header *head = malloc(sizeof(struct scgi_header));
		if (!head)
			die_msg("out of memory");
		head->next = list_head->next;
		list_head->next = head;
		head->name = name;
		head->value = value;
		/*fprintf(stderr, "%s = %s\n", name, value);*/
	}
}

static void
add_env_var(struct scgi_header *list_head, char *name)
{
	add_header(list_head, name, getenv(name));
}

static int
send_headers(FILE *fp)
{
	char *v;
	int n;
	struct scgi_header *node;
	struct scgi_header *t = malloc(sizeof(struct scgi_header));
	if (!t)
		die_msg("out of memory");
	t->next = NULL;
	t->name = NULL;
	t->value = NULL;

	/* add headers in reverse order */
	add_env_var(t, "SERVER_SOFTWARE");
	add_env_var(t, "SERVER_PROTOCOL");
	add_env_var(t, "SERVER_NAME");
	add_env_var(t, "SERVER_ADMIN");
	add_env_var(t, "SERVER_ADDR");
	add_env_var(t, "SERVER_PORT");
	add_env_var(t, "REMOTE_ADDR");
	add_env_var(t, "REMOTE_PORT");
	add_env_var(t, "REMOTE_USER");
	add_env_var(t, "REQUEST_METHOD");
	add_env_var(t, "REQUEST_URI");
	add_env_var(t, "QUERY_STRING");
	add_env_var(t, "SCRIPT_NAME");
	add_env_var(t, "PATH_INFO");
	add_env_var(t, "HTTPS");
	add_env_var(t, "CONTENT_TYPE");
	add_env_var(t, "DOCUMENT_ROOT");
	add_env_var(t, "HTTP_ACCEPT");
	add_env_var(t, "HTTP_ACCEPT_CHARSET");
	add_env_var(t, "HTTP_ACCEPT_ENCODING");
	add_env_var(t, "HTTP_ACCEPT_LANGUAGE");
	add_env_var(t, "HTTP_AUTHORIZATION");
	add_env_var(t, "HTTP_COOKIE");
	add_env_var(t, "HTTP_EXPECT");
	add_env_var(t, "HTTP_FROM");
	add_env_var(t, "HTTP_HOST");
	add_env_var(t, "HTTP_IF_MATCH");
	add_env_var(t, "HTTP_IF_MODIFIED_SINCE");
	add_env_var(t, "HTTP_IF_NONE_MATCH");
	add_env_var(t, "HTTP_IF_RANGE");
	add_env_var(t, "HTTP_IF_UNMODIFIED_SINCE");
	add_env_var(t, "HTTP_REFERER");
	add_env_var(t, "HTTP_TE");
	add_env_var(t, "HTTP_USER_AGENT");
	add_header(t, "SCGI", SCGI_PROTOCOL_VERSION);
	/* CONTENT_LENGTH must come first and always be present */
	v = getenv("CONTENT_LENGTH");
	add_header(t, "CONTENT_LENGTH", v ? v : "0");

	/* calculate length of header data (including nulls) */
	n = 0;
	for (node = t->next; node != NULL; node = node->next) {
		n += strlen(node->name) + 1;
		n += strlen(node->value) + 1;
	}

	/* send header data as netstring */
	if (fprintf(fp, "%u:", n) < 0) return 0;
	for (node = t->next; node != NULL; node = node->next) {
		if (fputs(node->name, fp) < 0) return 0;
		if (fputc('\0', fp) == EOF) return 0;
		if (fputs(node->value, fp) < 0) return 0;
		if (fputc('\0', fp) == EOF) return 0;
	}
	if (fputc(',', fp) == EOF) return 0;
	return 1;
}

static int
copyfp(FILE *in, FILE *out)
{
	size_t n, n2;
	char buf[8000];
	for (;;) {
		n = fread(buf, 1, sizeof buf, in);
		if (n != sizeof buf && ferror(in))
			return 0;
		if (n == 0)
			break; /* EOF */
		n2 = fwrite(buf, 1, n, out);
		if (n2 != n)
			return 0;
	}
	return 1;
}

int main(int argc, char **argv)
{
	int sock, fd;
	FILE *fp;

	sock = open_socket();

	/* send request */
	if ((fd = dup(sock)) < 0)
		die_perror("duplicating fd");
	if ((fp = fdopen(fd, "w")) == NULL)
		die_perror("creating buffered file");
	if (!send_headers(fp)) {
		die_msg("sending request headers");
	}
	if (!copyfp(stdin, fp)) {
		die_msg("sending request body");
	}
	if (fclose(fp) != 0)
		die_perror("sending request body");

	/* send reponse */
	if ((fd = dup(sock)) < 0 || (fp = fdopen(fd, "r")) == NULL)
		die_perror("creating buffered file from socket");
	if (!copyfp(fp, stdout)) {
		die_msg("sending response");
	}
	if (fclose(fp) != 0)
	    die_perror("closing socket");

	return 0;
}
