#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <string.h>
#include <dirent.h>
#include <limits.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <getopt.h>
#ifdef USE_SSL
#include <openssl/rsa.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
#include "lhs.h"
#if defined (__sun)
#include <sys/filio.h>
#endif

#define debug(x...) { if (verbose) printf(x); }

char *content_types[][2] = { CONTENT_TYPE_ARRAY };
char icon_dir[] = ICON_DIR;
char icon_dir_up[] = ICON_DIR_UP;
char icon_file[] = ICON_FILE;
char icon_empty[] = ICON_EMPTY;
char hostname[128];

int http_port = DEFAULT_HTTP_PORT, https_port = DEFAULT_HTTPS_PORT;
int max_socks = DEFAULT_MAX_SOCKS, verbose = 0;
char *root_dir = DEFAULT_ROOT_DIR;
#ifdef USE_SSL
char *cert_file = DEFAULT_CERT_FILE;
int ssl_protocol = DEFAULT_SSL_PROTOCOL;
#endif
 
#ifndef USE_IPV6

int create_sock(int port, struct in_addr *addr)
{
	int sock, one = 1;
	struct sockaddr_in sin;

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	sin.sin_addr.s_addr = (addr) ? addr->s_addr : INADDR_ANY;
	sin.sin_port = htons(port);
	sin.sin_family = AF_INET;

	if (bind(sock, (struct sockaddr*) &sin, sizeof(sin)))
		return -1;
	if (listen(sock, BACKLOG))
		return -1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
		return -1;

	return sock;
}	

#else /* USE_IPV6 */

int create_sock(int port, struct in6_addr *addr)
{
	int sock, one = 1;
	struct sockaddr_in6 sin6;
	
	sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);

	if (addr)
		memcpy(&sin6.sin6_addr, addr, sizeof(sin6.sin6_addr));
	else
		sin6.sin6_addr = in6addr_any;
	sin6.sin6_port = htons(port);
	sin6.sin6_family = AF_INET6;
	sin6.sin6_flowinfo = 0;

	if (bind(sock, (struct sockaddr*) &sin6, sizeof(sin6)))
		return -1;
	if (listen(sock, BACKLOG))
		return -1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
		return -1;

	return sock;
}

#endif /* USE_IPV6 */

int first_free(struct client *c, int count)
{
	int i;
	
	for (i = 0; i < count; i++)
		if (!c[i].fd)
			return i;

	return -1;
}

void cleanup(struct client *c)
{
	shutdown(c->fd, 2);
	close(c->fd);
	if (c->buffer)
		free(c->buffer);
	if (c->filename)
		free(c->filename);
	if (c->file_fd)
		close(c->file_fd);
	memset(c, 0, sizeof(struct client));
}

char *sprintf_alloc(char *format, ...)
{
	va_list ap;
	char *buf;
	int size;
	
	va_start(ap, format);
	size = vsnprintf(NULL, 0, format, ap);
	if (!(buf = malloc(size+1)))
		return NULL;
	vsprintf(buf, format, ap);

	return buf;
}

char *strcat_alloc(char *dest, char *src)
{
	if (!(dest = realloc(dest, strlen(dest) + strlen(src) + 1)))
		return NULL;
	strcpy(dest + strlen(dest), src);

	return dest;
}

#define append(x,y,z...) \
{ \
	char *append_tmp = sprintf_alloc(y, z); \
	if (append_tmp) { \
		x = strcat_alloc(x, append_tmp); \
		free(append_tmp); \
	} \
}

char *create_index(char *request, char *pathname, int port)
{
	struct dirent **dir;
	struct stat st;
	char *idx, *fmt, *parent;
	int i, count;
	char tmp[PATH_MAX];
	
	if (!(parent = strdup(request)))
		return NULL;
	for (i = strlen(parent)-1; i && parent[i] == '/'; i--);
	for (; i && parent[i] != '/'; i--);
	parent[i] = 0;
	
	idx = sprintf_alloc(
		"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
		"<html><head><title>Index of %s</title></head><body>\n"
		"<h1>Index of %s</h1>\n"
		"<pre><img src=\"/lhs-icons/empty.gif\" width=16 height=16 alt=\"     \"> Name                                 Last modified       Size"
		"<hr>",
		request, request);

	if (!idx) {
		free(parent);
		return NULL;
	}

	count = scandir(pathname, &dir, NULL, alphasort);
	for (i = 0; i < count; i++) {
		char *alt, *src, mod[30], size_str[10], *fname, *slash;
		struct tm *tm;
		
		if (!strcmp(dir[i]->d_name, "."))
			continue;
		snprintf(tmp, PATH_MAX-1, "%s/%s", pathname, dir[i]->d_name);
		if (stat(tmp, &st) || !S_ISDIR(st.st_mode)) {
			alt = "[FIL]";
			src = "file.gif";
		} else {
			alt = "[DIR]";
			src = "dir.gif";
		}
		if (!strcmp(dir[i]->d_name, "..")) {
			src = "dir-up.gif";
			fname = parent;
		} else
			fname = dir[i]->d_name;
		if (strlen(dir[i]->d_name) > (S_ISDIR(st.st_mode) ? 34 : 35)) {
			strcpy(tmp, "");
			fmt = "<img src=\"/lhs-icons/%s\" width=16 height=16 alt=\"%s\"> <a href=\"%s%s\">%.32s..&gt;%s</a>%s  %s %s\n";
			slash = "";
		} else {
			strcpy(tmp, "                                                 ");
			tmp[(S_ISDIR(st.st_mode) ? 34 : 35) - strlen(dir[i]->d_name)] = 0;
			fmt = "<img src=\"/lhs-icons/%s\" width=16 height=16 alt=\"%s\"> <a href=\"%s%s\">%s%s</a>%s  %s %s\n";
			slash = (S_ISDIR(st.st_mode) ? "/" : "");
		}
		tm = localtime(&st.st_mtime);
		if (S_ISREG(st.st_mode)) {
			if (st.st_size >= 10 * 1048576)
				snprintf(size_str, sizeof(size_str), "%5dM", (int) st.st_size / 1048576);
			else
				snprintf(size_str, sizeof(size_str), "%5dk", (st.st_size / 1024) ? ((int) st.st_size / 1024) : 1);
		} else
			strcpy(size_str, "     -");
		strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", tm);
		append(idx, fmt, src, alt, fname, slash, dir[i]->d_name, slash, tmp, mod, size_str);
	}
	
	free(parent);
	append(idx, "</pre><hr>\n", "");
	append(idx, "<address>" VERSION " Server at %s Port %d</address>\n", hostname, port);
	append(idx, "</body></html>", "");

	return idx;
}

char *lookup_content_type(char *filename)
{
	char *tmp, *result;
	int i;
	
	for (tmp = filename + strlen(filename) - 1; tmp > filename; tmp--)
		if (*tmp == '.') {
			tmp++;
			break;
		}
	
	for (i = 0;; i++) {
		if (!content_types[i][0] || !strcmp(tmp, content_types[i][0])) {
			result = content_types[i][1];
			break;
		}
	}

	return result;
}

int hexdec(char ch)
{
	if (ch >= 'a' && ch <= 'z')
		ch -= 32;
        if (ch >= '0' && ch <= '9')
		return (ch - '0');
	if (ch >= 'A' && ch <= 'Z')
		return (ch - 'A' + 10);
	return 0;
}
	  
int parse_header(struct client *c)
{
	char *tmp, *foo, *request;
	struct stat st;

#define __port ((!c->ssl) ? http_port : https_port)
	
	if (!c->buffer || !(strstr(c->buffer, "\r\n\r\n") || strstr(c->buffer, "\n\n")))
		return 0;

	if (strncasecmp(c->buffer, "GET ", 4)) {
		for (tmp = c->buffer; *tmp; tmp++) {
			if (*tmp == '\r' || *tmp == '\n') {
				*tmp = 0;
				break;
			}
		}
		if (!(tmp = strdup(c->buffer)))
			return -1;
		free(c->buffer);
		c->buffer = sprintf_alloc(REPLY_501, tmp, hostname, __port);
		free(tmp);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);
		return 1;
	}
	
	if (!(request = malloc(strlen(c->buffer + 4) + 1)))
		return -1;
	for (tmp = c->buffer + 4, foo = request; *tmp && *tmp != ' ' && *tmp != '\r' && *tmp != '\n' && *tmp != '?'; tmp++, foo++) {
		if (*tmp == '+')
			*foo = ' ';
		else if (*tmp == '%') {
			if (*(tmp+1) && *(tmp+2)) {
				*foo = hexdec(*(tmp+1)) * 16 + hexdec(*(tmp+2));
				tmp += 2;
			} else
				break;
		} else
			*foo = *tmp;
	}
	*foo = 0;
	free(c->buffer);

	if (strstr(request, "/..") || !strncmp(request, "..", 2)) {
		c->buffer = sprintf_alloc(REPLY_400, request, hostname, __port);
		free(request);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);

		return 1;
	}		

	if (!strcmp(request, "/lhs-about") || !strcmp(request, "/lhs-about/")) {
		c->buffer = sprintf_alloc(REPLY_ABOUT, hostname, __port);
		free(request);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);
		
		return 1;
	}

	if (!strncmp(request, "/lhs-icons/", 11)) {
		if (!strcmp(request + 11, "dir.gif")) {
			c->internal = icon_dir;
			c->size = ICON_DIR_LEN;
		} else if (!strcmp(request + 11, "dir-up.gif")) {
			c->internal = icon_dir_up;
			c->size = ICON_DIR_UP_LEN;
		} else if (!strcmp(request + 11, "file.gif")) {
			c->internal = icon_file;
			c->size = ICON_FILE_LEN;
		} else {
			c->internal = icon_empty;
			c->size = ICON_EMPTY_LEN;
		}
		free(request);
		c->buffer = sprintf_alloc(
			"HTTP/1.0 200 OK\r\n"
			"Content-Type: image/gif\r\n"
			"Content-Length: %d\r\n"
			"Server: " VERSION "\r\n"
			"\r\n",
			c->size);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);
		
		return 1;
	}

	c->filename = sprintf_alloc("%s/%s", root_dir, request);
	if (!c->filename) {
		free(request);	
		return -1;
	}

	if (stat(c->filename, &st)) {
		c->buffer = sprintf_alloc((errno == EACCES) ? REPLY_403 : REPLY_404, request, hostname, __port);
		free(request);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);

		return 1;
	}

	if (S_ISDIR(st.st_mode) && request[strlen(request)-1] != '/') {
		c->buffer = sprintf_alloc(REPLY_301, request, request, hostname, __port);
		free(request);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);
		
		return 1;
	}
	
	if (S_ISDIR(st.st_mode)) {
		tmp = sprintf_alloc("%s/index.html", c->filename);
		if (!tmp) {
			free(request);
			return -1;
		}
		if (!stat(tmp, &st)) {
			free(c->filename);
			c->filename = tmp;
		} else {
			c->directory = 1;
			free(tmp);
		}
	}

	if (c->directory) {
		char *idx;
		
		idx = create_index(request, c->filename, __port);
		if (!idx) {
			free(request);
			return -1;
		}
		c->buffer = sprintf_alloc(
			"HTTP/1.0 200 OK\r\n"
			"Content-Type: text/html\r\n"
			"Content-Length: %u\r\n"
			"Server: " VERSION "\r\n"
			"\r\n"
			"%s",
			strlen(idx), idx);
		free(request);
		free(idx);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);

		return 1;
	} else {
		if ((c->file_fd = open(c->filename, O_RDONLY)) == -1) {
			c->buffer = sprintf_alloc(REPLY_403, request, hostname, __port);
			free(request);
			if (!c->buffer)
				return -1;
			c->buffer_size = strlen(c->buffer);
			c->file_fd = 0;

			return 1;
		}

		c->size = (S_ISREG(st.st_mode)) ? st.st_size : -1;

		c->buffer = sprintf_alloc(
			"HTTP/1.0 200 OK\r\n"
			"Content-Type: %s\r\n"
			"Content-Length: %u\r\n"
			"Server: " VERSION "\r\n"
			"\r\n",
			lookup_content_type(c->filename),
			st.st_size);
		if (!c->buffer)
			return -1;
		c->buffer_size = strlen(c->buffer);
	}
	
	free(request);
	
	return 1;

#undef __port

}

#ifdef USE_SSL

SSL_CTX *ctx;

RSA *rsa_callback(SSL *ssl, int export, int keylen)
{
	return RSA_generate_key(keylen, RSA_F4, NULL, NULL);
}

int init_ssl()
{
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();
	
	ctx = SSL_CTX_new(SSLv23_method());
	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
	if (ssl_protocol == TLS1_VERSION)
		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
	if (!ctx) {
		fprintf(stderr, "SSL_CTX_new failed.\n");
		return 0;
	}
	SSL_CTX_set_options(ctx, SSL_OP_ALL);
	SSL_CTX_set_cipher_list(ctx, "!ADH:RC4+RSA:HIGH:MEDIUM:LOW:EXP:+SSLv2:+EXP");
	SSL_CTX_set_timeout(ctx, 0);

	if (SSL_CTX_use_RSAPrivateKey_file(ctx, cert_file, SSL_FILETYPE_PEM) == -1) {
		fprintf(stderr, "SSL_CTX_use_RSAPrivateKey_file failed.\n");
	        return 0;
	}
	if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) == -1) {
		fprintf(stderr, "SSL_CTX_use_certificate_file failed.\n");
	        return 0;
	}
	SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);

	return 1;
}

SSL *init_ssl_socket(int sock)
{
	SSL *ssl;
	SSL_CIPHER *cipher;
	int bits = 0;

	if (!(ssl = SSL_new(ctx)))
		return NULL;
	
	SSL_set_fd(ssl, sock);
	SSL_set_accept_state(ssl);
	if (SSL_accept(ssl) == -1) {
		SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
		SSL_free(ssl);
		return NULL;
	}
//	SSL_set_session_id_context(ssl,AppContext,sizeof(AppContext));
	switch(SSL_SESSION_get_protocol_version(SSL_get_session(ssl))) {
		case SSL3_VERSION:
			debug("ssl3\n");
			break;
		case TLS1_VERSION:
			debug("tls3\n");
			break;
		default:
			debug("nieznany standard szyfrowania\n");
	}
	cipher = SSL_get_current_cipher(ssl);
	SSL_CIPHER_get_bits(cipher, &bits);
	
	if (!bits) {
		debug("nie można otworzyć bezpiecznego kanału.\n");
		SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
		SSL_free(ssl);
		return NULL;
	}
	debug("otworzono bezpieczny kanał. szyfr: %s, bitów: %d.\n",
		SSL_CIPHER_get_name(cipher), bits);

	return ssl;
}

inline int my_read(int fd, char *buf, int size, void *ssl)
{
	return (!ssl) ?
		read(fd, buf, size) :
		SSL_read((SSL*) ssl, buf, size);
}

inline int my_write(int fd, char *buf, int size, void *ssl)
{
	int res, one;
	
	one = 1;
	ioctl(fd, FIONBIO, &one);
	
	res = (!ssl) ?
		write(fd, buf, size) :
		SSL_write((SSL*) ssl, buf, size);

	one = 0;
	ioctl(fd, FIONBIO, &one);
	
	return res;
}

#else /* USE_SSL */

#define my_read(a,b,c,d) read(a,b,c)

inline int my_write(int fd, char *buf, int size, void *ssl)
{
	int res, one;
	
	one = 1;
	ioctl(fd, FIONBIO, &one);
	
	res = write(fd, buf, size);

	one = 0;
	ioctl(fd, FIONBIO, &one);
	
	return res;
}

#endif /* USE_SSL */

void usage(char *a0)
{
	fprintf(stderr,
"Usage: %s [OPTIONS]\n"
"\n"
"  -p port      set default HTTP port (default: 80)\n"
#ifdef USE_SSL
"  -P port      set default HTTPS port (default: 443)\n"
"  -s version   set SSL protocol version (default: SSLv2)\n"
"  -c filename  load certificate file (default: lhs.pem)\n"
#endif
"  -r path      set server's root directory (default: " DEFAULT_ROOT_DIR ")\n"
"  -u user      switch to other user after startup\n"
"  -g group     change gid to other than user's\n"
"  -m socks     set number of sockets (default: 50, min: 3)\n"
"  -v           be verbose, don't fork\n"
"\n", a0);
}

int main(int argc, char **argv)
{
	int ch, sock = 0;
#ifdef USE_SSL
	int sock_s = 0;
#endif
	struct pollfd *fds;
	struct client *c;
	char *user = NULL, *group = NULL;
	int gid = -1;

	while ((ch = getopt(argc, argv, "p:P:r:u:g:s:m:c:vh")) != -1) {
		switch (ch) {
			case 'p':
				http_port = atoi(optarg);
				break;
			case 'r':
				root_dir = optarg;
				break;
			case 'u':
				user = optarg;
				break;
			case 'g':
				group = optarg;
				break;
#ifdef USE_SSL
			case 'P':
				https_port = atoi(optarg);
				break;
			case 'c':
				cert_file = optarg;
				break;
			case 's':
				if (!strcasecmp(optarg, "tls1") || !strcasecmp(optarg, "tls1"))
					ssl_protocol = TLS1_VERSION;
				else if (!strcasecmp(optarg, "ssl3") || !strcasecmp(optarg, "sslv3"))
					ssl_protocol = SSL3_VERSION;
				else {
					fprintf(stderr, "%s: Unknown SSL version.\n", argv[0]);
					return 1;
				}
				break;
#endif
			case 'm':
				max_socks = atoi(optarg);
				if (max_socks < 3) {
					fprintf(stderr, "%s: Invalid sockets count.\n", argv[0]);
					return 1;
				}
				break;
			case 'v':
				verbose = 1;
				break;
			case 'h':
				usage(argv[0]);
				return 1;
			default:
				fprintf(stderr, "%s: Invalid option -- %c.\n", argv[0], ch);
				return 1;
		}
	}

#ifdef USE_SSL
	if (!init_ssl()) {
		fprintf(stderr, "init_ssl: Unable to create SSL context.\n");
		return 1;
	}
#endif

	if (!http_port && !https_port) {
		fprintf(stderr, "%s: No ports specified.\n", argv[0]);
		return 1;
	}
	
	if (!(fds = (void*) malloc(sizeof(struct pollfd) * (max_socks + 2))) || !(c = (void*) malloc(sizeof(struct client) * max_socks))) {
		perror("malloc");
		return 1;
	}
	memset(fds, 0, sizeof(struct pollfd) * (max_socks + 2));
	memset(c, 0, sizeof(struct client) * max_socks);

	if (gethostname(hostname, sizeof(hostname)-1))
		strcpy(hostname, "Unknown");
	
	if (http_port && (sock = create_sock(http_port, NULL)) == -1) {
		perror("create_sock");
		return 1;
	}
#ifdef USE_SSL
	if (https_port && (sock_s = create_sock(https_port, NULL)) == -1) {
		perror("create_sock");
		return 1;
	}
#endif

	if (group) {
		struct group *gr;
		
		if (!(gr = getgrnam(group))) {
			fprintf(stderr, "%s: Group %s not found.\n", argv[0], group);
			return 1;
		}
		gid = gr->gr_gid;
		if (!user && setgid(gid)) {
			perror("setgid");
			return 1;
		}
	}
	
	if (user) {
		struct passwd *pw;
		
		if (!(pw = getpwnam(user))) {
			fprintf(stderr, "%s: User %s not found.\n", argv[0], user);
			return 1;
		}
		if (setgid((group) ? gid : pw->pw_gid)) {
			perror("setgid");
			return 1;
		}
		if (setuid(pw->pw_uid)) {
			perror("setuid");
			return 1;
		}
	}
		
	if (!verbose) {
		int fd, res;
		
		if ((res = fork()) == -1) {
			perror("fork");
			return 1;
		}
		
		if (res)
			return 0;

		chdir(root_dir);
		setsid();
		fd = open("/dev/null", O_RDWR | O_NOCTTY);
		dup2(0, fd);
		dup2(1, fd);
		dup2(2, fd);
	}

	while (1) {
#ifdef USE_IPV6
		struct sockaddr_in caddr;
#else
		struct sockaddr_in6 caddr;
#endif
		int caddr_len, i, j, curr_socks;

		fds[0].fd = sock;
		fds[0].events = POLLIN;
#ifdef USE_SSL
		fds[1].fd = sock_s;
		fds[1].events = POLLIN;
#endif
		for (i = 0, j = 2; i < max_socks; i++) {
			if (c[i].fd) {
				fds[j].fd = c[i].fd;
				fds[j].events = 0;
				if (c[i].state == STATE_HEADER)
					fds[j].events = POLLIN | POLLERR | POLLHUP;
				if (c[i].state == STATE_DATA || c[i].state == STATE_REPLY)
					fds[j].events = POLLOUT | POLLERR | POLLHUP;
				j++;
			}
		}
		
		curr_socks = j;

		for (i = 0; i < curr_socks; i++)
			debug("fds[%d].fd = %d, fds[%d].events = %d\n", i, fds[i].fd, i, fds[i].events);
		debug("--\n");
		
		if (poll(fds, curr_socks, -1) < 0) {
			perror("poll");
			return 1;
		}

		if ((fds[0].revents & POLLIN) || (fds[1].revents & POLLIN)) {
			int i, j, sock;
			
			j = (fds[0].revents & POLLIN) ? 0 : 1;
			debug("before accept, j=%d\n", j);
			if ((sock = accept(fds[j].fd, (struct sockaddr*) &caddr, &caddr_len)) != -1) {
				
				if ((i = first_free(c, max_socks)) == -1) {
					shutdown(sock, 2);
					close(sock);
				} else {
					c[i].fd = sock;
					c[i].state = STATE_HEADER;
				}
				debug("connection on sock, client=%d\n", i);
#ifdef USE_SSL
				if (j && !(c[i].ssl = init_ssl_socket(c[i].fd)))
					cleanup(&c[i]);
#endif
			} else
				debug("accept failed.\n");
		}
		
		for (j = 2; j < curr_socks; j++) {
			debug("czeking j=%d (of %d)\n", j, curr_socks - 1);
			for (i = 0; i < max_socks; i++) {
				debug("  fds[j=%d].fd = %d\n", j, fds[j].fd);
				debug("  c[i=%d].fd = %d\n", i, c[i].fd);
				if (fds[j].fd == c[i].fd) {
					debug("  we've got a match!\n");
					break;
				}
			}
			if (i == max_socks) {
				debug("not found :(\n");
				continue;
			}
			
			if ((fds[j].revents & POLLERR) || (fds[j].revents & POLLHUP)) {
				debug("connection reset by peer, client=%d\n", i);
				cleanup(&c[i]);
				continue;
			}
			if (c[i].state == STATE_HEADER && (fds[j].revents & POLLIN)) {
				int size = 0;
				
				ioctl(c[i].fd, FIONREAD, &size);
				debug("data arrived, client=%d, count=%d\n", i, size);

				if (!size) {
					debug("connection reset by peer, FIONREAD=0\n");
					cleanup(&c[i]);
					continue;
				}
				
				if (c[i].buffer_size + size > MAX_HEADER_SIZE) {
					debug("oversized header\n");
					cleanup(&c[i]);
					continue;
				}
				
				if (!c[i].buffer)
					c[i].buffer = malloc(size + 1);
				else
					c[i].buffer = realloc(c[i].buffer, c[i].buffer_size + size + 1);
				
				size = my_read(c[i].fd, c[i].buffer + c[i].buffer_size, size, c[i].ssl);
				if (size > 0) {
					int res;
					
					c[i].buffer_size += size;
					c[i].buffer[c[i].buffer_size] = 0;
				
					res = parse_header(&c[i]);
					if (res == -1) {
						cleanup(&c[i]);
						debug("parse_header() failed for some reason.\n");
						continue;
					}
					if (res == 1) {
						c[i].state = STATE_REPLY;
						res = my_write(c[i].fd, c[i].buffer, c[i].buffer_size, c[i].ssl);
						if (res < c[i].buffer_size)
							cleanup(&c[i]);
					}
				}
			}
			if (c[i].state == STATE_REPLY && (fds[j].revents & POLLOUT)) {
				if (!c[i].file_fd && !c[i].internal) {
					cleanup(&c[i]);
					debug("transfer done.\n");
					continue;
				}
				c[i].state = STATE_DATA;
			}
			if (c[i].state == STATE_DATA && (fds[j].revents & POLLOUT)) {
				char buf[2048];
				int res, res2;

				if (!c[i].size) {
					cleanup(&c[i]);
					continue;
				}

				if (c[i].internal) {
					if (my_write(c[i].fd, c[i].internal, c[i].size, c[i].ssl) < c[i].size) {
						cleanup(&c[i]);
						continue;
					}
					c[i].size = 0;
				} else {			
					if ((res = read(c[i].file_fd, buf, sizeof(buf))) < 1) {
						debug("read() < 1\n");
						cleanup(&c[i]);
						continue;
					}
					debug("read() = %d\n", res);
					
					if ((res2 = my_write(c[i].fd, buf, res, c[i].ssl)) < res) {
						debug("my_write(%d) = %d < %d\n", res, res2, res);
						cleanup(&c[i]);
						continue;
					}
					debug("my_write(%d) = %d\n", res, res2);

					if (c[i].size != -1)
						c[i].size -= res2;
					debug("size = %d\n", c[i].size);
				}
			}
		}
	}
	
	return 0;
}
