#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include <stdio.h>

#include "network.h"
#include "fdevent.h"
#include "log.h"
#include "connections.h"
#include "plugin.h"
#include "joblist.h"

#include "network_backends.h"
#include "sys-mmap.h"
#include "sys-socket.h"
#include "sys-files.h"

#ifdef USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
# include <openssl/rand.h>
#endif

#define BACKEND_HANDLERS(read, write) network_read_chunkqueue_##read, network_write_chunkqueue_##write
static network_backend_info_t network_backends[] = {
	/* lowest id wins */
	{
		NETWORK_BACKEND_LINUX_SENDFILE,
		"linux-sendfile",
		NULL,
#if defined USE_WRITE && defined USE_LINUX_SENDFILE
		BACKEND_HANDLERS(read, linuxsendfile)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_LINUX_AIO_SENDFILE,
		"linux-aio-sendfile",
		NULL,
#if defined USE_WRITE && defined USE_LINUX_AIO_SENDFILE
		BACKEND_HANDLERS(read, linuxaiosendfile)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_FREEBSD_SENDFILE,
		"freebsd-sendfile",
		NULL,
#if defined USE_WRITE && defined USE_FREEBSD_SENDFILE
		BACKEND_HANDLERS(read, freebsdsendfile)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_SOLARIS_SENDFILEV,
		"solaris-sendfilev",
		NULL,
#if defined USE_WRITE && defined USE_SOLARIS_SENDFILEV
		BACKEND_HANDLERS(read, solarissendfilev)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_POSIX_AIO,
		"posix-aio",
		NULL,
#if defined USE_WRITE && defined USE_POSIX_AIO
		BACKEND_HANDLERS(read, posixaio)
#else
		NULL, NULL
#endif
	},

	{
		NETWORK_BACKEND_GTHREAD_AIO,
		"gthread-aio",
		NULL,
#if defined USE_WRITE && defined USE_GTHREAD_AIO
		BACKEND_HANDLERS(read, gthreadaio)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_GTHREAD_SENDFILE,
		"gthread-sendfile",
		NULL,
#if defined USE_WRITE && defined USE_GTHREAD_AIO && defined USE_GTHREAD_SENDFILE
		BACKEND_HANDLERS(read, gthreadsendfile)
#else
		NULL, NULL
#endif
	},

	{
		NETWORK_BACKEND_WRITEV,
		"writev",
		NULL,
#if defined USE_WRITE && defined USE_WRITEV
		BACKEND_HANDLERS(read, writev)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_WRITE,
		"write",
		NULL,
#if defined USE_WRITE
		BACKEND_HANDLERS(read, write)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_WIN32_TRANSMITFILE,
		"win32-transmitfile",
		NULL,
#if defined USE_WIN32_TRANSMITFILE
		BACKEND_HANDLERS(win32recv, win32transmitfile)
#else
		NULL, NULL
#endif
	},
	{
		NETWORK_BACKEND_WIN32_SEND,
		"win32-send",
		NULL,
#if defined USE_WIN32_SEND && defined USE_WIN32_SEND
		BACKEND_HANDLERS(win32recv, win32send)
#else
		NULL, NULL
#endif
	},

	{
		NETWORK_BACKEND_UNSET,
		NULL,
		NULL,
		NULL, NULL
	}
};

const network_backend_info_t *network_get_backends() {
	return network_backends;
}

const network_backend_info_t *network_get_defaultbackend() {
	const network_backend_info_t *backend = network_get_backends();

	while (backend->name) {
		if (backend->write_handler) {
			return backend;
		}
		backend ++;
	}

	return NULL;
}

const network_backend_info_t *network_get_backend_info_by_type(network_backend_t type) {
	const network_backend_info_t *backend = network_get_backends();

	while (backend->name) {
		if (type == backend->type) {
			return backend;
		}
		backend ++;
	}

	return NULL;
}

const network_backend_info_t *network_get_backend_info_by_name(const char *name) {
	const network_backend_info_t *backend = network_get_backends();

	while (backend->name) {
		if (strcmp(name, backend->name) == 0) {
			return backend;
		}
		backend ++;
	}

	return NULL;
}

handler_t network_server_handle_fdevent(void *s, void *context, int revents) {
	server     *srv = (server *)s;
	server_socket *srv_socket = (server_socket *)context;
	connection *con;
	int loops = 0;

	UNUSED(context);

	if (revents != FDEVENT_IN) {
		log_error_write(srv, __FILE__, __LINE__, "sdd",
				"strange event for server socket",
				srv_socket->sock->fd,
				revents);
		return HANDLER_ERROR;
	}

	/* accept()s at most 100 connections directly
	 *
	 * we jump out after 100 to give the waiting connections a chance */
	for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
		joblist_append(srv, con);
	}
	return HANDLER_GO_ON;
}

int network_server_init(server *srv, buffer *host_token, specific_config *s) {
	int val;
	socklen_t addr_len;
	server_socket *srv_socket;
	char *sp;
	unsigned int port = 0;
	const char *host;
	buffer *b;
	int is_unix_domain_socket = 0;
	int fd;

#ifdef SO_ACCEPTFILTER
	struct accept_filter_arg afa;
#endif

#ifdef _WIN32
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		    /* Tell the user that we could not find a usable */
		    /* WinSock DLL.                                  */
		    return -1;
	}
#endif

	srv_socket = calloc(1, sizeof(*srv_socket));
	srv_socket->sock = iosocket_init();

	srv_socket->srv_token = buffer_init();
	buffer_copy_string_buffer(srv_socket->srv_token, host_token);

	b = buffer_init();
	buffer_copy_string_buffer(b, host_token);

	/* ipv4:port
	 * [ipv6]:port
	 */
	if (NULL == (sp = strrchr(b->ptr, ':'))) {
		log_error_write(srv, __FILE__, __LINE__, "sb", "value of $SERVER[\"socket\"] has to be \"ip:port\".", b);

		return -1;
	}

	host = b->ptr;

	/* check for [ and ] */
	if (b->ptr[0] == '[' && *(sp-1) == ']') {
		*(sp-1) = '\0';
		host++;

		s->use_ipv6 = 1;
	}

	*(sp++) = '\0';

	port = strtol(sp, NULL, 10);

	if (host[0] == '/') {
		/* host is a unix-domain-socket */
		is_unix_domain_socket = 1;
	} else if (port == 0 || port > 65535) {
		log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port);

		return -1;
	}

	if (*host == '\0') host = NULL;

	if (is_unix_domain_socket) {
#ifdef HAVE_SYS_UN_H

		srv_socket->addr.plain.sa_family = AF_UNIX;

		if (-1 == (srv_socket->sock->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, 0))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
			return -1;
		}
#else
		log_error_write(srv, __FILE__, __LINE__, "s",
				"ERROR: Unix Domain sockets are not supported.");
		return -1;
#endif
	}

#ifdef HAVE_IPV6
	if (s->use_ipv6) {
		srv_socket->addr.plain.sa_family = AF_INET6;

		if (-1 == (srv_socket->sock->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
			return -1;
		}
		srv_socket->use_ipv6 = 1;
	}
#endif

	if (srv_socket->sock->fd == -1) {
		srv_socket->addr.plain.sa_family = AF_INET;
		if (-1 == (srv_socket->sock->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
			return -1;
		}
	}

	val = 1;
	if (setsockopt(srv_socket->sock->fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "socketsockopt failed:", strerror(errno));
		return -1;
	}

	switch(srv_socket->addr.plain.sa_family) {
#ifdef HAVE_IPV6
	case AF_INET6:
		memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in6));
		srv_socket->addr.ipv6.sin6_family = AF_INET6;
		if (host == NULL) {
			srv_socket->addr.ipv6.sin6_addr = in6addr_any;
		} else {
			struct addrinfo hints, *res;
			int r;

			memset(&hints, 0, sizeof(hints));

			hints.ai_family   = AF_INET6;
			hints.ai_socktype = SOCK_STREAM;
			hints.ai_protocol = IPPROTO_TCP;

			if (0 != (r = getaddrinfo(host, NULL, &hints, &res))) {
				log_error_write(srv, __FILE__, __LINE__,
						"sssss", "getaddrinfo failed: ",
						gai_strerror(r), "'", host, "'");

				return -1;
			}

			memcpy(&(srv_socket->addr), res->ai_addr, res->ai_addrlen);

			freeaddrinfo(res);
		}
		srv_socket->addr.ipv6.sin6_port = htons(port);
		addr_len = sizeof(struct sockaddr_in6);
		break;
#endif
	case AF_INET:
		memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in));
		srv_socket->addr.ipv4.sin_family = AF_INET;
		if (host == NULL) {
			srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			struct hostent *he;
			if (NULL == (he = gethostbyname(host))) {
				log_error_write(srv, __FILE__, __LINE__,
						"sds", "gethostbyname failed: ",
						h_errno, host);
				return -1;
			}

			if (he->h_addrtype != AF_INET) {
				log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
				return -1;
			}

			if (he->h_length != sizeof(struct in_addr)) {
				log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
				return -1;
			}

			memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
		}
		srv_socket->addr.ipv4.sin_port = htons(port);

		addr_len = sizeof(struct sockaddr_in);

		break;
#ifndef _WIN32
	case AF_UNIX:
		srv_socket->addr.un.sun_family = AF_UNIX;
		strcpy(srv_socket->addr.un.sun_path, host);

#ifdef SUN_LEN
		addr_len = SUN_LEN(&srv_socket->addr.un);
#else
		/* stevens says: */
		addr_len = strlen(host) + 1 + sizeof(srv_socket->addr.un.sun_family);
#endif

		/* check if the socket exists and try to connect to it. */
		if (-1 != (fd = connect(srv_socket->sock->fd, (struct sockaddr *) &(srv_socket->addr), addr_len))) {
			close(fd);

			log_error_write(srv, __FILE__, __LINE__, "ss",
				"server socket is still in use:",
				host);


			return -1;
		}

		/* connect failed */
		switch(errno) {
		case ECONNREFUSED:
			unlink(host);
			break;
		case ENOENT:
			break;
		default:
			log_error_write(srv, __FILE__, __LINE__, "sds",
				"testing socket failed:",
				host, strerror(errno));

			return -1;
		}

		break;
#endif
	default:
		addr_len = 0;

		return -1;
	}

	if (0 != bind(srv_socket->sock->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {
		switch(srv_socket->addr.plain.sa_family) {
		case AF_UNIX:
			log_error_write(srv, __FILE__, __LINE__, "sds",
					"can't bind to socket:",
					host, strerror(errno));
			break;
		default:
			log_error_write(srv, __FILE__, __LINE__, "ssds",
					"can't bind to port:",
					host, port, strerror(errno));
			break;
		}
		return -1;
	}

	if (-1 == listen(srv_socket->sock->fd, 128 * 8)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno));
		return -1;
	}

	if (s->is_ssl) {
#ifdef USE_OPENSSL
		if (srv->ssl_is_init == 0) {
			SSL_load_error_strings();
			SSL_library_init();
			srv->ssl_is_init = 1;

			if (0 == RAND_status()) {
				log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
						"not enough entropy in the pool");
				return -1;
			}
		}

		if (NULL == (s->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
					ERR_error_string(ERR_get_error(), NULL));
			return -1;
		}

		if (buffer_is_empty(s->ssl_pemfile)) {
			log_error_write(srv, __FILE__, __LINE__, "s", "ssl.pemfile has to be set");
			return -1;
		}

		if (!buffer_is_empty(s->ssl_ca_file)) {
			if (1 != SSL_CTX_load_verify_locations(s->ssl_ctx, s->ssl_ca_file->ptr, NULL)) {
				log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
						ERR_error_string(ERR_get_error(), NULL), s->ssl_ca_file);
				return -1;
			}
		}

		if (SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) {
			log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
					ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
			return -1;
		}

		if (SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) {
			log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
					ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
			return -1;
		}

		if (SSL_CTX_check_private_key(s->ssl_ctx) != 1) {
			log_error_write(srv, __FILE__, __LINE__, "sssb", "SSL:",
					"Private key does not match the certificate public key, reason:",
					ERR_error_string(ERR_get_error(), NULL),
					s->ssl_pemfile);
			return -1;
		}
		srv_socket->ssl_ctx = s->ssl_ctx;
#else

		buffer_free(srv_socket->srv_token);
		free(srv_socket);

		buffer_free(b);

		log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
				"ssl requested but openssl support is not compiled in");

		return -1;
#endif
	} else {
#ifdef SO_ACCEPTFILTER
		/*
		 * FreeBSD accf_http filter
		 *
		 */
		memset(&afa, 0, sizeof(afa));
		strcpy(afa.af_name, "httpready");
		if (setsockopt(srv_socket->sock->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
			if (errno != ENOENT) {
				log_error_write(srv, __FILE__, __LINE__, "ss", "can't set accept-filter 'httpready': ", strerror(errno));
			}
		}
#endif
	}

	srv_socket->is_ssl = s->is_ssl;

	if (srv->srv_sockets.size == 0) {
		srv->srv_sockets.size = 4;
		srv->srv_sockets.used = 0;
		srv->srv_sockets.ptr = malloc(srv->srv_sockets.size * sizeof(server_socket));
	} else if (srv->srv_sockets.used == srv->srv_sockets.size) {
		srv->srv_sockets.size += 4;
		srv->srv_sockets.ptr = realloc(srv->srv_sockets.ptr, srv->srv_sockets.size * sizeof(server_socket));
	}

	srv->srv_sockets.ptr[srv->srv_sockets.used++] = srv_socket;
	buffer_free(b);

	return 0;
}

int network_close(server *srv) {
	size_t i;
	for (i = 0; i < srv->srv_sockets.used; i++) {
		server_socket *srv_socket = srv->srv_sockets.ptr[i];

		if (srv_socket->sock->fd != -1) {
			/* check if server fd are already registered */
			if (srv_socket->sock->fde_ndx != -1) {
				fdevent_event_del(srv->ev, srv_socket->sock);
				fdevent_unregister(srv->ev, srv_socket->sock);
			}
		}

		if (srv_socket->is_ssl) {
#ifdef USE_OPENSSL
			SSL_CTX_free(srv_socket->ssl_ctx);
#endif
		}

		iosocket_free(srv_socket->sock);

		buffer_free(srv_socket->srv_token);

		free(srv_socket);
	}

#ifdef USE_OPENSSL
	ERR_free_strings();
#endif
	free(srv->srv_sockets.ptr);

	return 0;
}

int network_init(server *srv) {
	buffer *b;
	size_t i;
	const network_backend_info_t *backend;

	b = buffer_init();

	buffer_copy_string_buffer(b, srv->srvconf.bindhost);
	buffer_append_string(b, ":");
	buffer_append_long(b, srv->srvconf.port);

	if (0 != network_server_init(srv, b, srv->config_storage[0])) {
		return -1;
	}
	buffer_free(b);

#ifdef USE_OPENSSL
	srv->network_ssl_backend_write = network_write_chunkqueue_openssl;
#endif

	backend = network_get_backend_info_by_type(srv->network_backend);
	assert(backend && backend->read_handler);
	srv->network_backend_read  = backend->read_handler;
	srv->network_backend_write = backend->write_handler;

#ifdef USE_OPENSSL
	srv->network_ssl_backend_write = network_write_chunkqueue_openssl;
	srv->network_ssl_backend_read  = network_read_chunkqueue_openssl;
#endif

	/* check for $SERVER["socket"] */
	for (i = 1; i < srv->config_context->used; i++) {
		data_config *dc = (data_config *)srv->config_context->data[i];
		specific_config *s = srv->config_storage[i];
		size_t j;

		/* not our stage */
		if (COMP_SERVER_SOCKET != dc->comp) continue;

		if (dc->cond != CONFIG_COND_EQ) {
			log_error_write(srv, __FILE__, __LINE__, "s", "only == is allowed for $SERVER[\"socket\"].");

			return -1;
		}

		/* check if we already know this socket,
		 * if yes, don't init it */
		for (j = 0; j < srv->srv_sockets.used; j++) {
			if (buffer_is_equal(srv->srv_sockets.ptr[j]->srv_token, dc->string)) {
				break;
			}
		}

		if (j == srv->srv_sockets.used) {
			if (0 != network_server_init(srv, dc->string, s)) return -1;
		}
	}

	return 0;
}

int network_register_fdevents(server *srv) {
	size_t i;
	if (-1 == fdevent_reset(srv->ev)) {
		return -1;
	}
	/* register fdevents after reset */
	for (i = 0; i < srv->srv_sockets.used; i++) {
		server_socket *srv_socket = srv->srv_sockets.ptr[i];
		fdevent_register(srv->ev, srv_socket->sock, network_server_handle_fdevent, srv_socket);
		fdevent_event_add(srv->ev, srv_socket->sock, FDEVENT_IN);
	}
	return 0;
}

network_status_t network_read(server *srv, connection *con, iosocket *sock, chunkqueue *cq) {
	server_socket *srv_socket = con->srv_socket;

   	if (srv_socket->is_ssl) {
#ifdef USE_OPENSSL
		return srv->network_ssl_backend_read(srv, con, sock, cq);
#else
		return NETWORK_STATUS_FATAL_ERROR;
#endif
	} else {
		return srv->network_backend_read(srv, con, sock, cq);
	}
}

network_status_t network_write_chunkqueue(server *srv, connection *con, chunkqueue *cq) {
	network_status_t ret = NETWORK_STATUS_UNSET;
	off_t written = 0;
#ifdef TCP_CORK
	int corked = 0;
#endif
	server_socket *srv_socket = con->srv_socket;

	if (con->conf.global_kbytes_per_second &&
	    *(con->conf.global_bytes_per_second_cnt_ptr) > con->conf.global_kbytes_per_second * 1024) {
		/* we reached the global traffic limit */

		con->traffic_limit_reached = 1;
		joblist_append(srv, con);

		return 1;
	}

	written = cq->bytes_out;

#ifdef TCP_CORK
	/* Linux: put a cork into the socket as we want to combine the write() calls
	 * but only if we really have multiple chunks
	 */
	if (cq->first && cq->first->next) {
		corked = 1;
		setsockopt(con->sock->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
	}
#endif

	if (srv_socket->is_ssl) {
#ifdef USE_OPENSSL
		ret = srv->network_ssl_backend_write(srv, con, con->sock, cq);
#endif
	} else {
		ret = srv->network_backend_write(srv, con, con->sock, cq);
	}

	switch (ret) {
	case NETWORK_STATUS_WAIT_FOR_FD:
	case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
	case NETWORK_STATUS_WAIT_FOR_EVENT:
	case NETWORK_STATUS_SUCCESS:
		chunkqueue_remove_finished_chunks(cq);

		break;
	default:
		break;
	}

#ifdef TCP_CORK
	if (corked) {
		corked = 0;
		setsockopt(con->sock->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
	}
#endif

	written = cq->bytes_out - written;
	con->bytes_written += written;
	con->bytes_written_cur_second += written;

	*(con->conf.global_bytes_per_second_cnt_ptr) += written;

	if (con->conf.kbytes_per_second &&
	    (con->bytes_written_cur_second > con->conf.kbytes_per_second * 1024)) {
		/* we reached the traffic limit */

		con->traffic_limit_reached = 1;
		joblist_append(srv, con);
	}
	return ret;
}
