/*
 * Copyright (C) 2004-2012 Free Software Foundation, Inc.
 * Copyright (C) 2001,2002 Paul Sheer
 * Copyright (C) 2016-2018 Red Hat, Inc.
 * Portions Copyright (C) 2002,2003 Nikos Mavrogiannopoulos
 *
 * This file is part of GnuTLS.
 *
 * GnuTLS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GnuTLS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/* This server is heavily modified for GnuTLS by Nikos Mavrogiannopoulos
 * (which means it is quite unreadable)
 */

#include "config.h"

#include "common.h"
#include "gnutls-serv-options.h"
#include "udp-serv.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <string.h>
#include <gnutls/gnutls.h>
#include <gnutls/dtls.h>
#include <sys/time.h>
#include <sys/select.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include "socket.h"

/* Gnulib portability files. */
#include "gl_linked_list.h"
#include "gl_xlist.h"
#include "minmax.h"
#include "parse-datetime.h"
#include "read-file.h"
#include "sockets.h"
#include "xalloc.h"
#include "xsize.h"

/* konqueror cannot handle sending the page in multiple
 * pieces.
 */
/* global stuff */
static int generate = 0;
static int http = 0;
static int strip_crlf = 1;
static int x509ctype;
static int debug = 0;

unsigned int verbose = 1;
static int nodb;
static int noticket;
static int earlydata;
int require_cert;
int disable_client_cert;

const char *psk_passwd = NULL;
const char *srp_passwd = NULL;
const char *srp_passwd_conf = NULL;
const char **x509_keyfile = NULL;
const char **x509_certfile = NULL;
unsigned x509_certfile_size = 0;
unsigned x509_keyfile_size = 0;
const char *x509_cafile = NULL;
const char *dh_params_file = NULL;
const char *x509_crlfile = NULL;
const char *priorities = NULL;
const char **rawpk_keyfile = NULL;
const char **rawpk_file = NULL;
unsigned rawpk_keyfile_size = 0;
unsigned rawpk_file_size = 0;

const char **ocsp_responses = NULL;
unsigned ocsp_responses_size = 0;

const char *sni_hostname = NULL;
int sni_hostname_fatal = 0;

const char **alpn_protos = NULL;
unsigned alpn_protos_size = 0;

gnutls_datum_t session_ticket_key;
gnutls_anti_replay_t anti_replay;
int record_max_size;
const char *http_data_file = NULL;
static void tcp_server(const char *name, int port, int timeout);

/* end of globals */

/* This is a sample TCP echo server.
 * This will behave as an http server if any argument in the
 * command line is present
 */

#define SMALL_READ_TEST (2147483647)

#define GERR(ret) fprintf(stderr, "Error: %s\n", safe_strerror(ret))

#define HTTP_END "</BODY></HTML>\n\n"

#define HTTP_UNIMPLEMENTED \
	"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<HTML><HEAD>\r\n<TITLE>501 Method Not Implemented</TITLE>\r\n</HEAD><BODY>\r\n<H1>Method Not Implemented</H1>\r\n<HR>\r\n</BODY></HTML>\r\n"
#define HTTP_OK "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n"

#define HTTP_BEGIN                                                             \
	HTTP_OK                                                                \
	"\n"                                                                   \
	"<HTML><BODY>\n"                                                       \
	"<CENTER><H1>This is <a href=\"https://www.gnu.org/software/gnutls\">" \
	"GnuTLS</a></H1></CENTER>\n\n"

/* These are global */
gnutls_srp_server_credentials_t srp_cred = NULL;
gnutls_psk_server_credentials_t psk_cred = NULL;
#ifdef ENABLE_ANON
gnutls_anon_server_credentials_t dh_cred = NULL;
#endif
gnutls_certificate_credentials_t cert_cred = NULL;

const int ssl_session_cache = 2048;

static void wrap_db_init(void);
static void wrap_db_deinit(void);
static int wrap_db_store(void *dbf, gnutls_datum_t key, gnutls_datum_t data);
static gnutls_datum_t wrap_db_fetch(void *dbf, gnutls_datum_t key);
static int wrap_db_delete(void *dbf, gnutls_datum_t key);
static int anti_replay_db_add(void *dbf, time_t exp, const gnutls_datum_t *key,
			      const gnutls_datum_t *data);

static void cmd_parser(int argc, char **argv);

#define HTTP_STATE_REQUEST 1
#define HTTP_STATE_RESPONSE 2
#define HTTP_STATE_CLOSING 3

typedef struct {
	char *http_request;
	char *http_response;
	int request_length;
	int response_length;
	int response_written;
	int http_state;
	int listen_socket;
	int fd;
	gnutls_session_t tls_session;
	int handshake_ok;
	int close_ok;
	time_t start;
	int earlydata_eof;
} listener_item;

static const char *safe_strerror(int value)
{
	const char *ret = gnutls_strerror(value);
	if (ret == NULL)
		ret = str_unknown;
	return ret;
}

static void listener_free(const void *elt)
{
	listener_item *j = (listener_item *)elt;

	free(j->http_request);
	free(j->http_response);
	if (j->fd >= 0) {
		if (j->close_ok)
			gnutls_bye(j->tls_session, GNUTLS_SHUT_WR);
		shutdown(j->fd, 2);
		close(j->fd);
		gnutls_deinit(j->tls_session);
	}
}

/* we use primes up to 1024 in this server.
 * otherwise we should add them here.
 */

gnutls_dh_params_t dh_params = NULL;
gnutls_rsa_params_t rsa_params = NULL;

static int generate_dh_primes(void)
{
	int prime_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
						     GNUTLS_SEC_PARAM_MEDIUM);

	if (gnutls_dh_params_init(&dh_params) < 0) {
		fprintf(stderr, "Error in dh parameter initialization\n");
		exit(1);
	}

	/* Generate Diffie-Hellman parameters - for use with DHE
	 * kx algorithms. These should be discarded and regenerated
	 * once a week or once a month. Depends on the
	 * security requirements.
	 */
	printf("Generating Diffie-Hellman parameters [%d]. Please wait...\n",
	       prime_bits);
	fflush(stdout);

	if (gnutls_dh_params_generate2(dh_params, prime_bits) < 0) {
		fprintf(stderr, "Error in prime generation\n");
		exit(1);
	}

	return 0;
}

static void read_dh_params(void)
{
	char tmpdata[2048];
	int size;
	gnutls_datum_t params;
	FILE *fp;

	if (gnutls_dh_params_init(&dh_params) < 0) {
		fprintf(stderr, "Error in dh parameter initialization\n");
		exit(1);
	}

	/* read the params file
	 */
	fp = fopen(dh_params_file, "r");
	if (fp == NULL) {
		fprintf(stderr, "Could not open %s\n", dh_params_file);
		exit(1);
	}

	size = fread(tmpdata, 1, sizeof(tmpdata) - 1, fp);
	tmpdata[size] = 0;
	fclose(fp);

	params.data = (unsigned char *)tmpdata;
	params.size = size;

	size = gnutls_dh_params_import_pkcs3(dh_params, &params,
					     GNUTLS_X509_FMT_PEM);

	if (size < 0) {
		fprintf(stderr, "Error parsing dh params: %s\n",
			safe_strerror(size));
		exit(1);
	}

	printf("Read Diffie-Hellman parameters.\n");
	fflush(stdout);
}

static int get_params(gnutls_session_t session, gnutls_params_type_t type,
		      gnutls_params_st *st)
{
	if (type == GNUTLS_PARAMS_DH) {
		if (dh_params == NULL)
			return -1;
		st->params.dh = dh_params;
	} else
		return -1;

	st->type = type;
	st->deinit = 0;

	return 0;
}

static gl_list_t listener_list;

static int cert_verify_callback(gnutls_session_t session)
{
	listener_item *j = gnutls_session_get_ptr(session);
	unsigned int size;
	int ret;

	if (gnutls_auth_get_type(session) == GNUTLS_CRD_CERTIFICATE) {
		if (!require_cert &&
		    gnutls_certificate_get_peers(session, &size) == NULL)
			return 0;

		if (ENABLED_OPT(VERIFY_CLIENT_CERT)) {
			if (cert_verify(session, NULL, NULL) == 0) {
				do {
					ret = gnutls_alert_send(
						session, GNUTLS_AL_FATAL,
						GNUTLS_A_ACCESS_DENIED);
				} while (ret == GNUTLS_E_INTERRUPTED ||
					 ret == GNUTLS_E_AGAIN);

				j->http_state = HTTP_STATE_CLOSING;
				return -1;
			}
		} else {
			printf("- Peer's certificate was NOT verified.\n");
		}
	}
	return 0;
}

/* callback used to verify if the host name advertised in client hello matches
 * the one configured in server
 */
static int post_client_hello(gnutls_session_t session)
{
	int ret;
	/* DNS names (only type supported) may be at most 256 byte long */
	char *name;
	size_t len = 256;
	unsigned int type;
	int i;

	name = malloc(len);
	if (name == NULL)
		return GNUTLS_E_MEMORY_ERROR;

	for (i = 0;;) {
		ret = gnutls_server_name_get(session, name, &len, &type, i);
		if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
			char *new_name;
			new_name = realloc(name, len);
			if (new_name == NULL) {
				ret = GNUTLS_E_MEMORY_ERROR;
				goto end;
			}
			name = new_name;
			continue; /* retry call with same index */
		}

		/* check if it is the last entry in list */
		if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
			break;
		i++;
		if (ret != GNUTLS_E_SUCCESS)
			goto end;
		/* unknown types need to be ignored */
		if (type != GNUTLS_NAME_DNS)
			continue;

		if (strlen(sni_hostname) != len)
			continue;
		/* API guarantees that the name of type DNS will be null terminated */
		if (!strncmp(name, sni_hostname, len)) {
			ret = GNUTLS_E_SUCCESS;
			goto end;
		}
	};
	/* when there is no extension, we can't send the extension specific alert */
	if (i == 0) {
		fprintf(stderr,
			"Warning: client did not include SNI extension, using default host\n");
		ret = GNUTLS_E_SUCCESS;
		goto end;
	}

	if (sni_hostname_fatal == 1) {
		/* abort the connection, propagate error up the stack */
		ret = GNUTLS_E_UNRECOGNIZED_NAME;
		goto end;
	}

	fprintf(stderr, "Warning: client provided unrecognized host name\n");
	/* since we just want to send an alert, not abort the connection, we
	 * need to send it ourselves
	 */
	do {
		ret = gnutls_alert_send(session, GNUTLS_AL_WARNING,
					GNUTLS_A_UNRECOGNIZED_NAME);
	} while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);

	/* continue handshake, fall through */
end:
	free(name);
	return ret;
}

#define MAX_ALPN_PROTOCOLS 16
gnutls_session_t initialize_session(int dtls)
{
	gnutls_session_t session;
	int ret;
	unsigned i;
	const char *err;
#ifdef ENABLE_ALPN
	gnutls_datum_t alpn[MAX_ALPN_PROTOCOLS];
#endif
	unsigned alpn_size;
	unsigned flags = GNUTLS_SERVER | GNUTLS_POST_HANDSHAKE_AUTH |
			 GNUTLS_ENABLE_RAWPK;

	if (dtls)
		flags |= GNUTLS_DATAGRAM;

	if (earlydata)
		flags |= GNUTLS_ENABLE_EARLY_DATA;

	gnutls_init(&session, flags);

	/* allow the use of private ciphersuites.
	 */
	gnutls_handshake_set_private_extensions(session, 1);

	gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);

	if (nodb == 0) {
		gnutls_db_set_retrieve_function(session, wrap_db_fetch);
		gnutls_db_set_remove_function(session, wrap_db_delete);
		gnutls_db_set_store_function(session, wrap_db_store);
		gnutls_db_set_ptr(session, NULL);
	}

	if (noticket == 0)
		gnutls_session_ticket_enable_server(session,
						    &session_ticket_key);

	if (earlydata) {
		gnutls_anti_replay_enable(session, anti_replay);
		if (HAVE_OPT(MAXEARLYDATA)) {
			ret = gnutls_record_set_max_early_data_size(
				session, OPT_VALUE_MAXEARLYDATA);
			if (ret < 0) {
				fprintf(stderr,
					"Could not set max early data size: %s\n",
					gnutls_strerror(ret));
				exit(1);
			}
		}
	}

	if (sni_hostname != NULL)
		gnutls_handshake_set_post_client_hello_function(
			session, &post_client_hello);

	if (priorities == NULL) {
		ret = gnutls_set_default_priority(session);
		if (ret < 0) {
			fprintf(stderr, "Could not set default policy: %s\n",
				gnutls_strerror(ret));
			exit(1);
		}
	} else {
		ret = gnutls_priority_set_direct(session, priorities, &err);
		if (ret < 0) {
			fprintf(stderr, "Syntax error at: %s\n", err);
			exit(1);
		}
	}

#ifndef ENABLE_ALPN
	if (alpn_protos_size != 0) {
		fprintf(stderr, "ALPN is not supported\n");
		exit(1);
	}
#else
	alpn_size = MIN(MAX_ALPN_PROTOCOLS, alpn_protos_size);
	for (i = 0; i < alpn_size; i++) {
		alpn[i].data = (void *)alpn_protos[i];
		alpn[i].size = strlen(alpn_protos[i]);
	}

	ret = gnutls_alpn_set_protocols(
		session, alpn, alpn_size,
		HAVE_OPT(ALPN_FATAL) ? GNUTLS_ALPN_MANDATORY : 0);
	if (ret < 0) {
		fprintf(stderr, "Error setting ALPN protocols: %s\n",
			gnutls_strerror(ret));
		exit(1);
	}
#endif

#ifdef ENABLE_ANON
	gnutls_credentials_set(session, GNUTLS_CRD_ANON, dh_cred);
#endif

	if (srp_cred != NULL)
		gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred);

	if (psk_cred != NULL)
		gnutls_credentials_set(session, GNUTLS_CRD_PSK, psk_cred);

	if (cert_cred != NULL) {
		gnutls_certificate_set_verify_function(cert_cred,
						       cert_verify_callback);

		gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
				       cert_cred);
	}

	if (disable_client_cert)
		gnutls_certificate_server_set_request(session,
						      GNUTLS_CERT_IGNORE);
	else {
		if (require_cert)
			gnutls_certificate_server_set_request(
				session, GNUTLS_CERT_REQUIRE);
		else
			gnutls_certificate_server_set_request(
				session, GNUTLS_CERT_REQUEST);
	}

	/* use the record size limit extension */
	if (record_max_size > 0) {
		if (gnutls_record_set_max_recv_size(session, record_max_size) <
		    0) {
			fprintf(stderr,
				"Cannot set the maximum record receive size to %d.\n",
				record_max_size);
			exit(1);
		}
	}

	if (HAVE_OPT(COMPRESS_CERT)) {
		ret = compress_cert_set_methods(session,
						OPTS_ARRAY(COMPRESS_CERT),
						OPTS_COUNT(COMPRESS_CERT));
		if (ret < 0)
			exit(1);
	}

	if (HAVE_OPT(HEARTBEAT))
		gnutls_heartbeat_enable(session,
					GNUTLS_HB_PEER_ALLOWED_TO_SEND);

#ifdef ENABLE_DTLS_SRTP
	if (HAVE_OPT(SRTP_PROFILES)) {
		ret = gnutls_srtp_set_profile_direct(
			session, OPT_ARG(SRTP_PROFILES), &err);
		if (ret == GNUTLS_E_INVALID_REQUEST)
			fprintf(stderr, "Syntax error at: %s\n", err);
		else if (ret != 0)
			fprintf(stderr, "Error in profiles: %s\n",
				gnutls_strerror(ret));
		else
			fprintf(stderr, "DTLS profile set to %s\n",
				OPT_ARG(SRTP_PROFILES));

		if (ret != 0)
			exit(1);
	}
#endif

	return session;
}

#include <gnutls/x509.h>

static const char DEFAULT_DATA[] =
	"This is the default message reported by the GnuTLS implementation. "
	"For more information please visit "
	"<a href=\"https://www.gnutls.org/\">https://www.gnutls.org/</a>.";

/* Creates html with the current session information.
 */
#define tmp_buffer &http_buffer[strlen(http_buffer)]
#define tmp_buffer_size len - strlen(http_buffer)
static char *peer_print_info(gnutls_session_t session, int *ret_length,
			     const char *header)
{
	const char *tmp;
	unsigned char sesid[32];
	size_t i, sesid_size;
	char *http_buffer, *desc;
	gnutls_kx_algorithm_t kx_alg;
	size_t len = 20 * 1024 + strlen(header);
	char *crtinfo = NULL;
	gnutls_protocol_t version;
	size_t ncrtinfo = 0;

	if (verbose == 0) {
		http_buffer = malloc(len);
		if (http_buffer == NULL)
			return NULL;

		strcpy(http_buffer, HTTP_BEGIN);
		strcpy(&http_buffer[sizeof(HTTP_BEGIN) - 1], DEFAULT_DATA);
		strcpy(&http_buffer[sizeof(HTTP_BEGIN) + sizeof(DEFAULT_DATA) -
				    2],
		       HTTP_END);
		*ret_length = sizeof(DEFAULT_DATA) + sizeof(HTTP_BEGIN) +
			      sizeof(HTTP_END) - 3;
		return http_buffer;
	}

	if (gnutls_certificate_type_get2(session, GNUTLS_CTYPE_CLIENT) ==
	    GNUTLS_CRT_X509) {
		const gnutls_datum_t *cert_list;
		unsigned int cert_list_size = 0;

		cert_list =
			gnutls_certificate_get_peers(session, &cert_list_size);

		for (i = 0; i < cert_list_size; i++) {
			gnutls_x509_crt_t cert = NULL;
			gnutls_datum_t info;

			if (gnutls_x509_crt_init(&cert) == 0 &&
			    gnutls_x509_crt_import(cert, &cert_list[i],
						   GNUTLS_X509_FMT_DER) == 0 &&
			    gnutls_x509_crt_print(cert, GNUTLS_CRT_PRINT_FULL,
						  &info) == 0) {
				const char post[] = "</PRE><P><PRE>";
				char *crtinfo_new;
				size_t ncrtinfo_new;

				ncrtinfo_new = xsum3(ncrtinfo, info.size,
						     sizeof(post));
				if (size_overflow_p(ncrtinfo_new)) {
					free(crtinfo);
					return NULL;
				}
				crtinfo_new = realloc(crtinfo, ncrtinfo_new);
				if (crtinfo_new == NULL) {
					free(crtinfo);
					return NULL;
				}
				crtinfo = crtinfo_new;
				memcpy(crtinfo + ncrtinfo, info.data,
				       info.size);
				ncrtinfo += info.size;
				memcpy(crtinfo + ncrtinfo, post, strlen(post));
				ncrtinfo += strlen(post);
				crtinfo[ncrtinfo] = '\0';
				gnutls_free(info.data);
			}
			gnutls_x509_crt_deinit(cert);
		}
	}

	http_buffer = malloc(len);
	if (http_buffer == NULL) {
		free(crtinfo);
		return NULL;
	}

	strcpy(http_buffer, HTTP_BEGIN);

	version = gnutls_protocol_get_version(session);

	/* print session_id */
	sesid_size = sizeof(sesid);
	gnutls_session_get_id(session, sesid, &sesid_size);
	snprintf(tmp_buffer, tmp_buffer_size, "\n<p>Session ID: <i>");
	for (i = 0; i < sesid_size; i++)
		snprintf(tmp_buffer, tmp_buffer_size, "%.2X", sesid[i]);
	snprintf(tmp_buffer, tmp_buffer_size, "</i></p>\n");
	snprintf(
		tmp_buffer, tmp_buffer_size,
		"<h5>If your browser supports session resumption, then you should see the "
		"same session ID, when you press the <b>reload</b> button.</h5>\n");

	/* Here unlike print_info() we use the kx algorithm to distinguish
	 * the functions to call.
	 */
	{
		char dns[256];
		size_t dns_size = sizeof(dns);
		unsigned int type;

		if (gnutls_server_name_get(session, dns, &dns_size, &type, 0) ==
		    0) {
			snprintf(tmp_buffer, tmp_buffer_size,
				 "\n<p>Server Name: %s</p>\n", dns);
		}
	}

	kx_alg = gnutls_kx_get(session);

	/* print srp specific data */
#ifdef ENABLE_SRP
	if (kx_alg == GNUTLS_KX_SRP) {
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<p>Connected as user '%s'.</p>\n",
			 gnutls_srp_server_get_username(session));
	}
#endif

#ifdef ENABLE_PSK
	if (kx_alg == GNUTLS_KX_PSK &&
	    gnutls_psk_server_get_username(session)) {
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<p>Connected as user '%s'.</p>\n",
			 gnutls_psk_server_get_username(session));
	}
#endif

	/* print session information */
	strcat(http_buffer, "<P>\n");

	tmp = gnutls_protocol_get_name(version);
	if (tmp == NULL)
		tmp = str_unknown;
	snprintf(
		tmp_buffer, tmp_buffer_size,
		"<TABLE border=1><TR><TD>Protocol version:</TD><TD>%s</TD></TR>\n",
		tmp);

	desc = gnutls_session_get_desc(session);
	if (desc) {
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<TR><TD>Description:</TD><TD>%s</TD></TR>\n", desc);
		gnutls_free(desc);
	}

	if (gnutls_auth_get_type(session) == GNUTLS_CRD_CERTIFICATE &&
	    gnutls_certificate_type_get2(session, GNUTLS_CTYPE_CLIENT) !=
		    GNUTLS_CRT_X509) {
		tmp = gnutls_certificate_type_get_name(
			gnutls_certificate_type_get2(session,
						     GNUTLS_CTYPE_CLIENT));
		if (tmp == NULL)
			tmp = str_unknown;
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<TR><TD>Certificate Type:</TD><TD>%s</TD></TR>\n",
			 tmp);
	}

	if (version < GNUTLS_TLS1_3) {
		tmp = gnutls_kx_get_name(kx_alg);
		if (tmp == NULL)
			tmp = str_unknown;
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<TR><TD>Key Exchange:</TD><TD>%s</TD></TR>\n", tmp);

#ifdef ENABLE_ANON
		if (kx_alg == GNUTLS_KX_ANON_DH) {
			snprintf(
				tmp_buffer, tmp_buffer_size,
				"<p> Connect using anonymous DH (prime of %d bits)</p>\n",
				gnutls_dh_get_prime_bits(session));
		}
#endif

#if defined(ENABLE_DHE) || defined(ENABLE_ANON)
		if (kx_alg == GNUTLS_KX_DHE_RSA ||
		    kx_alg == GNUTLS_KX_DHE_DSS) {
			snprintf(
				tmp_buffer, tmp_buffer_size,
				"Ephemeral DH using prime of <b>%d</b> bits.<br>\n",
				gnutls_dh_get_prime_bits(session));
		}
#endif

		tmp = gnutls_compression_get_name(
			gnutls_compression_get(session));
		if (tmp == NULL)
			tmp = str_unknown;
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<TR><TD>Compression</TD><TD>%s</TD></TR>\n", tmp);

		tmp = gnutls_cipher_suite_get_name(kx_alg,
						   gnutls_cipher_get(session),
						   gnutls_mac_get(session));
		if (tmp == NULL)
			tmp = str_unknown;
		snprintf(tmp_buffer, tmp_buffer_size,
			 "<TR><TD>Ciphersuite</TD><TD>%s</TD></TR>\n", tmp);
	}

	tmp = gnutls_cipher_get_name(gnutls_cipher_get(session));
	if (tmp == NULL)
		tmp = str_unknown;
	snprintf(tmp_buffer, tmp_buffer_size,
		 "<TR><TD>Cipher</TD><TD>%s</TD></TR>\n", tmp);

	tmp = gnutls_mac_get_name(gnutls_mac_get(session));
	if (tmp == NULL)
		tmp = str_unknown;
	snprintf(tmp_buffer, tmp_buffer_size,
		 "<TR><TD>MAC</TD><TD>%s</TD></TR>\n", tmp);

	snprintf(tmp_buffer, tmp_buffer_size, "</TABLE></P>\n");

	if (crtinfo) {
		snprintf(tmp_buffer, tmp_buffer_size, "<hr><PRE>%s\n</PRE>\n",
			 crtinfo);
		free(crtinfo);
	}

	snprintf(tmp_buffer, tmp_buffer_size,
		 "<hr><P>Your HTTP header was:<PRE>%s</PRE></P>\n" HTTP_END,
		 header);

	*ret_length = strlen(http_buffer);

	return http_buffer;
}

static char *peer_print_data(gnutls_session_t session, int *ret_length)
{
	gnutls_datum_t data;
	char *http_buffer;
	size_t len;
	int ret;

	ret = gnutls_load_file(http_data_file, &data);
	if (ret < 0) {
		ret = asprintf(
			&http_buffer,
			"HTTP/1.0 404 Not Found\r\n"
			"Content-type: text/html\r\n"
			"\r\n"
			"<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>\n"
			"<BODY><H1>Couldn't read %s</H1></BODY></HTML>\n\n",
			http_data_file);
		if (ret < 0)
			return NULL;

		*ret_length = strlen(http_buffer);
		return http_buffer;
	}

	ret = asprintf(&http_buffer,
		       "HTTP/1.0 200 OK\r\n"
		       "Content-Type: application/octet-stream\r\n"
		       "Content-Length: %u\r\n"
		       "\r\n",
		       data.size);
	if (ret < 0)
		return NULL;
	len = ret;
	http_buffer = xrealloc(http_buffer, len + data.size);
	memcpy(&http_buffer[len], data.data, data.size);
	gnutls_free(data.data);
	*ret_length = len + data.size;
	return http_buffer;
}

const char *human_addr(const struct sockaddr *sa, socklen_t salen, char *buf,
		       size_t buflen)
{
	const char *save_buf = buf;
	size_t l;

	if (!buf || !buflen)
		return "(error)";

	*buf = 0;

	switch (sa->sa_family) {
#if HAVE_IPV6
	case AF_INET6:
		snprintf(buf, buflen, "IPv6 ");
		break;
#endif

	case AF_INET:
		snprintf(buf, buflen, "IPv4 ");
		break;
	}

	l = 5;
	buf += l;
	buflen -= l;

	if (getnameinfo(sa, salen, buf, buflen, NULL, 0, NI_NUMERICHOST) != 0) {
		return "(error)";
	}

	l = strlen(buf);
	buf += l;
	buflen -= l;

	if (buflen < 8)
		return save_buf;

	strcat(buf, " port ");
	buf += 6;
	buflen -= 6;

	if (getnameinfo(sa, salen, NULL, 0, buf, buflen, NI_NUMERICSERV) != 0) {
		snprintf(buf, buflen, "%s", " unknown");
	}

	return save_buf;
}

int wait_for_connection(void)
{
	fd_set rd, wr;
	int n, sock = -1;
	gl_list_iterator_t iter;
	const void *elt;

	FD_ZERO(&rd);
	FD_ZERO(&wr);
	n = 0;

	iter = gl_list_iterator(listener_list);
	while (gl_list_iterator_next(&iter, &elt, NULL)) {
		const listener_item *j = elt;

		if (j->listen_socket) {
			FD_SET(j->fd, &rd);
			n = MAX(n, j->fd);
		}
	}
	gl_list_iterator_free(&iter);

	/* waiting part */
	n = select(n + 1, &rd, &wr, NULL, NULL);
	if (n == -1 && errno == EINTR)
		return -1;
	if (n < 0) {
		perror("select()");
		exit(1);
	}

	/* find which one is ready */
	iter = gl_list_iterator(listener_list);
	while (gl_list_iterator_next(&iter, &elt, NULL)) {
		const listener_item *j = elt;

		/* a new connection has arrived */
		if (FD_ISSET(j->fd, &rd) && j->listen_socket) {
			sock = j->fd;
			break;
		}
	}
	gl_list_iterator_free(&iter);
	return sock;
}

int listen_socket(const char *name, int listen_port, int socktype)
{
	struct addrinfo hints, *res, *ptr;
	char portname[6];
	int s = -1;
	int yes;
	listener_item *j = NULL;

	snprintf(portname, sizeof(portname), "%d", listen_port);
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = socktype;
	hints.ai_flags = AI_PASSIVE;

	if ((s = getaddrinfo(NULL, portname, &hints, &res)) != 0) {
		fprintf(stderr, "getaddrinfo() failed: %s\n", gai_strerror(s));
		return -1;
	}

	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
		int news;
#ifndef HAVE_IPV6
		if (ptr->ai_family != AF_INET)
			continue;
#endif

		/* Print what we are doing. */
		{
			char topbuf[512];

			fprintf(stderr, "%s listening on %s...", name,
				human_addr(ptr->ai_addr, ptr->ai_addrlen,
					   topbuf, sizeof(topbuf)));
		}

		if ((news = socket(ptr->ai_family, ptr->ai_socktype,
				   ptr->ai_protocol)) < 0) {
			perror("socket() failed");
			continue;
		}
		s = news; /* to not overwrite existing s from previous loops */
#if defined(HAVE_IPV6) && !defined(_WIN32)
		if (ptr->ai_family == AF_INET6) {
			yes = 1;
			/* avoid listen on ipv6 addresses failing
			 * because already listening on ipv4 addresses: */
			(void)setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
					 (const void *)&yes, sizeof(yes));
		}
#endif

		if (socktype == SOCK_STREAM) {
			yes = 1;
			if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
				       (const void *)&yes, sizeof(yes)) < 0) {
				perror("setsockopt() failed");
				close(s);
				continue;
			}
		} else {
#if defined(IP_DONTFRAG)
			yes = 1;
			if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG,
				       (const void *)&yes, sizeof(yes)) < 0)
				perror("setsockopt(IP_DF) failed");
#elif defined(IP_MTU_DISCOVER)
			yes = IP_PMTUDISC_DO;
			if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER,
				       (const void *)&yes, sizeof(yes)) < 0)
				perror("setsockopt(IP_DF) failed");
#endif
		}

		if (bind(s, ptr->ai_addr, ptr->ai_addrlen) < 0) {
			perror("bind() failed");
			close(s);
			continue;
		}

		if (socktype == SOCK_STREAM) {
			if (listen(s, 10) < 0) {
				perror("listen() failed");
				exit(1);
			}
		}

		/* new list entry for the connection */
		j = xzalloc(sizeof(*j));
		gl_list_add_last(listener_list, j);
		j->listen_socket = 1;
		j->fd = s;

		/* Complete earlier message. */
		fprintf(stderr, "done\n");
	}

	fflush(stderr);

	freeaddrinfo(res);

	return s;
}

/* strips \r\n from the end of the string 
 */
static void strip(char *data)
{
	int i;
	int len = strlen(data);

	for (i = 0; i < len; i++) {
		if (data[i] == '\r' && data[i + 1] == '\n' &&
		    data[i + 2] == 0) {
			data[i] = '\n';
			data[i + 1] = 0;
			break;
		}
	}
}

static unsigned get_response(gnutls_session_t session, char *request,
			     char **response, int *response_length)
{
	char *p, *h;

	if (http != 0) {
		if (strncmp(request, "GET ", 4))
			goto unimplemented;

		if (!(h = strchr(request, '\n')))
			goto unimplemented;

		*h++ = '\0';
		while (*h == '\r' || *h == '\n')
			h++;

		if (!(p = strchr(request + 4, ' ')))
			goto unimplemented;
		*p = '\0';
	}

	if (http != 0) {
		if (http_data_file == NULL)
			*response =
				peer_print_info(session, response_length, h);
		else
			*response = peer_print_data(session, response_length);
	} else {
		int ret;
		if (strip_crlf != 0)
			strip(request);
		fprintf(stderr, "received cmd: %s\n", request);

		ret = check_command(session, request, disable_client_cert);
		if (ret > 0) {
			*response = strdup("Successfully executed command\n");
			if (*response == NULL) {
				fprintf(stderr, "Memory error\n");
				return 0;
			}
			*response_length = strlen(*response);
			return 1;
		} else if (ret == 0) {
			*response = strdup(request);
			if (*response == NULL) {
				fprintf(stderr, "Memory error\n");
				return 0;
			}
			*response_length = strlen(*response);
		} else {
			*response = NULL;
			do {
				ret = gnutls_alert_send_appropriate(session,
								    ret);
			} while (ret == GNUTLS_E_AGAIN ||
				 ret == GNUTLS_E_INTERRUPTED);
			return 0;
		}
	}

	return 1;

unimplemented:
	*response = strdup(HTTP_UNIMPLEMENTED);
	if (*response == NULL)
		return 0;
	*response_length = ((*response) ? strlen(*response) : 0);
	return 1;
}

static void terminate(int sig) __attribute__((__noreturn__));

static void terminate(int sig)
{
	char buf[64] = { 0 };
	char *p;

	/* This code must be async-signal-safe. */
	p = stpcpy(buf, "Exiting via signal ");

	if (sig > 10)
		*p++ = '0' + sig / 10;
	*p++ = '0' + sig % 10;
	*p++ = '\n';

	write(STDERR_FILENO, buf, p - buf);
	_exit(1);
}

static void check_alert(gnutls_session_t session, int ret)
{
	if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED ||
	    ret == GNUTLS_E_FATAL_ALERT_RECEIVED) {
		int last_alert = gnutls_alert_get(session);
		if (last_alert == GNUTLS_A_NO_RENEGOTIATION &&
		    ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
			printf("* Received NO_RENEGOTIATION alert. Client does not support renegotiation.\n");
		else
			printf("* Received alert '%d': %s.\n", last_alert,
			       gnutls_alert_get_name(last_alert));
	}
}

static void tls_log_func(int level, const char *str)
{
	fprintf(stderr, "|<%d>| %s", level, str);
}

static void tls_audit_log_func(gnutls_session_t session, const char *str)
{
	fprintf(stderr, "|<%p>| %s", session, str);
}

int main(int argc, char **argv)
{
	int ret, mtu, port, timeout;
	char name[256];
	int cert_set = 0;
	unsigned use_static_dh_params = 0;
	unsigned i;

	cmd_parser(argc, argv);

#ifndef _WIN32
	signal(SIGHUP, SIG_IGN);
	signal(SIGTERM, terminate);
	if (signal(SIGINT, terminate) == SIG_IGN)
		signal(SIGINT, SIG_IGN); /* e.g. background process */
#endif

	sockets_init();

	listener_list = gl_list_create_empty(GL_LINKED_LIST, NULL, NULL,
					     listener_free, true);

	if (nodb == 0)
		wrap_db_init();

	if (HAVE_OPT(UDP))
		strcpy(name, "UDP ");
	else
		name[0] = 0;

	if (http == 1) {
		strcat(name, "HTTP Server");
	} else {
		strcat(name, "Echo Server");
	}

	gnutls_global_set_log_function(tls_log_func);
	gnutls_global_set_audit_log_function(tls_audit_log_func);
	gnutls_global_set_log_level(debug);

	if ((ret = gnutls_global_init()) < 0) {
		fprintf(stderr, "global_init: %s\n", gnutls_strerror(ret));
		exit(1);
	}

	if (ENABLED_OPT(ATTIME)) {
		struct timespec r;

		if (!parse_datetime(&r, OPT_ARG(ATTIME), NULL)) {
			fprintf(stderr,
				"%s option value %s is not a valid time\n",
				"attime", OPT_ARG(ATTIME));
			exit(1);
		}
		set_system_time(&r);
	}

#ifdef ENABLE_PKCS11
	if (HAVE_OPT(PROVIDER)) {
		ret = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
		if (ret < 0)
			fprintf(stderr, "pkcs11_init: %s",
				gnutls_strerror(ret));
		else {
			ret = gnutls_pkcs11_add_provider(OPT_ARG(PROVIDER),
							 NULL);
			if (ret < 0) {
				fprintf(stderr, "pkcs11_add_provider: %s",
					gnutls_strerror(ret));
				exit(1);
			}
		}
	}
	pkcs11_common(NULL);
#endif

	/* Note that servers must generate parameters for
	 * Diffie-Hellman. See gnutls_dh_params_generate(), and
	 * gnutls_dh_params_set().
	 */
	if (generate != 0) {
		generate_dh_primes();
	} else if (dh_params_file) {
		read_dh_params();
	} else {
		use_static_dh_params = 1;
	}

	if (gnutls_certificate_allocate_credentials(&cert_cred) < 0) {
		fprintf(stderr, "memory error\n");
		exit(1);
	}

	/* X509 credentials */
	if (x509_cafile != NULL) {
		if ((ret = gnutls_certificate_set_x509_trust_file(
			     cert_cred, x509_cafile, x509ctype)) < 0) {
			fprintf(stderr, "Error reading '%s'\n", x509_cafile);
			GERR(ret);
			exit(1);
		} else {
			printf("Processed %d CA certificate(s).\n", ret);
		}
	}
	if (x509_crlfile != NULL) {
		if ((ret = gnutls_certificate_set_x509_crl_file(
			     cert_cred, x509_crlfile, x509ctype)) < 0) {
			fprintf(stderr, "Error reading '%s'\n", x509_crlfile);
			GERR(ret);
			exit(1);
		} else {
			printf("Processed %d CRL(s).\n", ret);
		}
	}

	if (x509_certfile_size > 0 && x509_keyfile_size > 0) {
		for (i = 0; i < x509_certfile_size; i++) {
			ret = gnutls_certificate_set_x509_key_file(
				cert_cred, x509_certfile[i], x509_keyfile[i],
				x509ctype);
			if (ret < 0) {
				fprintf(stderr, "Error reading '%s' or '%s'\n",
					x509_certfile[i], x509_keyfile[i]);
				GERR(ret);
				exit(1);
			} else
				cert_set = 1;
		}
	}

	/* Raw public-key credentials */
	if (rawpk_file_size > 0 && rawpk_keyfile_size > 0) {
		for (i = 0; i < rawpk_keyfile_size; i++) {
			ret = gnutls_certificate_set_rawpk_key_file(
				cert_cred, rawpk_file[i], rawpk_keyfile[i],
				x509ctype, NULL, 0, NULL, 0, 0, 0);
			if (ret < 0) {
				fprintf(stderr, "Error reading '%s' or '%s'\n",
					rawpk_file[i], rawpk_keyfile[i]);
				GERR(ret);
				exit(1);
			} else {
				cert_set = 1;
			}
		}
	}

	if (cert_set == 0) {
		fprintf(stderr,
			"Warning: no private key and certificate pairs were set.\n");
	}
#ifndef ENABLE_OCSP
	if (HAVE_OPT(IGNORE_OCSP_RESPONSE_ERRORS) || ocsp_responses_size != 0) {
		fprintf(stderr, "OCSP is not supported!\n");
		exit(1);
	}
#else
	/* OCSP status-request TLS extension */
	if (HAVE_OPT(IGNORE_OCSP_RESPONSE_ERRORS))
		gnutls_certificate_set_flags(
			cert_cred, GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK);

	for (i = 0; i < ocsp_responses_size; i++) {
		ret = gnutls_certificate_set_ocsp_status_request_file(
			cert_cred, ocsp_responses[i], 0);
		if (ret < 0) {
			fprintf(stderr,
				"Cannot set OCSP status request file: %s: %s\n",
				ocsp_responses[i], gnutls_strerror(ret));
			exit(1);
		}
	}
#endif

	if (use_static_dh_params) {
#if defined(ENABLE_DHE) || defined(ENABLE_ANON)
		ret = gnutls_certificate_set_known_dh_params(
			cert_cred, GNUTLS_SEC_PARAM_MEDIUM);
		if (ret < 0) {
			fprintf(stderr,
				"Error while setting DH parameters: %s\n",
				gnutls_strerror(ret));
			exit(1);
		}
#else
		fprintf(stderr, "Setting DH parameters is not supported\n");
		exit(1);
#endif
	} else {
		gnutls_certificate_set_params_function(cert_cred, get_params);
	}

	/* this is a password file (created with the included srpcrypt utility) 
	 * Read README.crypt prior to using SRP.
	 */
#ifdef ENABLE_SRP
	if (srp_passwd != NULL) {
		gnutls_srp_allocate_server_credentials(&srp_cred);

		if ((ret = gnutls_srp_set_server_credentials_file(
			     srp_cred, srp_passwd, srp_passwd_conf)) < 0) {
			/* only exit is this function is not disabled 
			 */
			fprintf(stderr, "Error while setting SRP parameters\n");
			GERR(ret);
		}
	}
#endif

	/* this is a password file 
	 */
#ifdef ENABLE_PSK
	if (psk_passwd != NULL) {
		gnutls_psk_allocate_server_credentials(&psk_cred);

		if ((ret = gnutls_psk_set_server_credentials_file(
			     psk_cred, psk_passwd)) < 0) {
			/* only exit is this function is not disabled 
			 */
			fprintf(stderr, "Error while setting PSK parameters\n");
			GERR(ret);
		}

		if (HAVE_OPT(PSKHINT)) {
			ret = gnutls_psk_set_server_credentials_hint(
				psk_cred, OPT_ARG(PSKHINT));
			if (ret) {
				fprintf(stderr,
					"Error setting PSK identity hint.\n");
				GERR(ret);
			}
		}

		if (use_static_dh_params) {
			ret = gnutls_psk_set_server_known_dh_params(
				psk_cred, GNUTLS_SEC_PARAM_MEDIUM);
			if (ret < 0) {
				fprintf(stderr,
					"Error while setting DH parameters: %s\n",
					gnutls_strerror(ret));
				exit(1);
			}
		} else {
			gnutls_psk_set_server_params_function(psk_cred,
							      get_params);
		}
	}
#endif

#ifdef ENABLE_ANON
	gnutls_anon_allocate_server_credentials(&dh_cred);

	if (use_static_dh_params) {
		ret = gnutls_anon_set_server_known_dh_params(
			dh_cred, GNUTLS_SEC_PARAM_MEDIUM);
		if (ret < 0) {
			fprintf(stderr,
				"Error while setting DH parameters: %s\n",
				gnutls_strerror(ret));
			exit(1);
		}
	} else {
		gnutls_anon_set_server_params_function(dh_cred, get_params);
	}
#endif

	if (noticket == 0)
		gnutls_session_ticket_key_generate(&session_ticket_key);

	if (earlydata) {
		ret = gnutls_anti_replay_init(&anti_replay);
		if (ret < 0) {
			fprintf(stderr,
				"Error while initializing anti-replay: %s\n",
				gnutls_strerror(ret));
			exit(1);
		}
		gnutls_anti_replay_set_add_function(anti_replay,
						    anti_replay_db_add);
		gnutls_anti_replay_set_ptr(anti_replay, NULL);
	}

	if (HAVE_OPT(MTU))
		mtu = OPT_VALUE_MTU;
	else
		mtu = 1300;

	if (HAVE_OPT(PORT))
		port = OPT_VALUE_PORT;
	else
		port = 5556;

	if (HAVE_OPT(TIMEOUT))
		timeout = OPT_VALUE_TIMEOUT;
	else
		timeout = 30;

	if (HAVE_OPT(UDP))
		udp_server(name, port, mtu);
	else
		tcp_server(name, port, timeout);

	return 0;
}

static void retry_handshake(listener_item *j)
{
	int r, ret;

	r = gnutls_handshake(j->tls_session);
	if (r < 0 && gnutls_error_is_fatal(r) == 0) {
		check_alert(j->tls_session, r);
		/* nothing */
	} else if (r < 0) {
		j->http_state = HTTP_STATE_CLOSING;
		check_alert(j->tls_session, r);
		fprintf(stderr, "Error in handshake: %s\n", gnutls_strerror(r));

		do {
			ret = gnutls_alert_send_appropriate(j->tls_session, r);
		} while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
		j->close_ok = 0;
	} else if (r == 0) {
		if (gnutls_session_is_resumed(j->tls_session) != 0 &&
		    verbose != 0)
			printf("*** This is a resumed session\n");

		if (verbose != 0) {
#if 0
			printf("- connection from %s\n",
			       human_addr((struct sockaddr *)
					  &client_address,
					  calen, topbuf, sizeof(topbuf)));
#endif

			print_info(j->tls_session, verbose, verbose);

			if (HAVE_OPT(KEYMATEXPORT))
				print_key_material(
					j->tls_session, OPT_ARG(KEYMATEXPORT),
					HAVE_OPT(KEYMATEXPORTSIZE) ?
						OPT_VALUE_KEYMATEXPORTSIZE :
						20);
		}

		j->close_ok = 1;
		j->handshake_ok = 1;
	}
}

static void try_rehandshake(listener_item *j)
{
	int r, ret;
	fprintf(stderr, "*** Received hello message\n");

	do {
		r = gnutls_handshake(j->tls_session);
	} while (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN);

	if (r < 0) {
		do {
			ret = gnutls_alert_send_appropriate(j->tls_session, r);
		} while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
		fprintf(stderr, "Error in rehandshake: %s\n",
			gnutls_strerror(r));
		j->http_state = HTTP_STATE_CLOSING;
	} else {
		j->close_ok = 1;
		j->http_state = HTTP_STATE_REQUEST;
	}
}

static void tcp_server(const char *name, int port, int timeout)
{
	int n, s;
	char topbuf[512];
	int accept_fd;
	struct sockaddr_storage client_address;
	socklen_t calen;
	struct timeval tv;
	void *ptr = NULL;

	s = listen_socket(name, port, SOCK_STREAM);
	if (s < 0)
		exit(1);

	for (;;) {
		gl_list_iterator_t iter;
		gl_list_node_t node;
		const void *elt;
		gl_list_t accepted_list = gl_list_create_empty(
			GL_LINKED_LIST, NULL, NULL, NULL, true);
		fd_set rd, wr;
		time_t now = time(0);
#ifndef _WIN32
		int val;
#endif

		FD_ZERO(&rd);
		FD_ZERO(&wr);
		n = 0;

		/* flag which connections we are reading or writing to within the fd sets */
		iter = gl_list_iterator(listener_list);
		while (gl_list_iterator_next(&iter, &elt, &node)) {
			listener_item *j = (listener_item *)elt;

#ifndef _WIN32
			val = fcntl(j->fd, F_GETFL, 0);
			if ((val == -1) ||
			    (fcntl(j->fd, F_SETFL, val | O_NONBLOCK) < 0)) {
				perror("fcntl()");
				exit(1);
			}
#endif
			if (j->start != 0 && now - j->start > timeout) {
				if (verbose != 0) {
					fprintf(stderr,
						"Scheduling inactive connection for close\n");
				}
				j->http_state = HTTP_STATE_CLOSING;
			}

			if (j->listen_socket) {
				FD_SET(j->fd, &rd);
				n = MAX(n, j->fd);
			}
			if (j->http_state == HTTP_STATE_REQUEST) {
				FD_SET(j->fd, &rd);
				n = MAX(n, j->fd);
			}
			if (j->http_state == HTTP_STATE_RESPONSE) {
				FD_SET(j->fd, &wr);
				n = MAX(n, j->fd);
			}
			gl_list_node_set_value(listener_list, node, j);
		}
		gl_list_iterator_free(&iter);

		/* core operation */
		tv.tv_sec = 10;
		tv.tv_usec = 0;
		n = select(n + 1, &rd, &wr, NULL, &tv);
		if (n == -1 && errno == EINTR)
			continue;
		if (n < 0) {
			perror("select()");
			exit(1);
		}

		/* read or write to each connection as indicated by select()'s return argument */
		iter = gl_list_iterator(listener_list);
		while (gl_list_iterator_next(&iter, &elt, &node)) {
			listener_item *j = (listener_item *)elt;

			/* a new connection has arrived */
			if (FD_ISSET(j->fd, &rd) && j->listen_socket) {
				calen = sizeof(client_address);
				memset(&client_address, 0, calen);
				accept_fd = accept(
					j->fd,
					(struct sockaddr *)&client_address,
					&calen);

				if (accept_fd < 0) {
					perror("accept()");
				} else {
					char timebuf[SIMPLE_CTIME_BUF_SIZE];
					time_t tt = time(0);
					char *ctt;
					listener_item *jj;

					/* new list entry for the connection */
					jj = xzalloc(sizeof(*jj));
					gl_list_add_last(accepted_list, jj);
					jj->http_request = (char *)strdup("");
					jj->http_state = HTTP_STATE_REQUEST;
					jj->fd = accept_fd;
					jj->start = tt;

					jj->tls_session = initialize_session(0);
					gnutls_session_set_ptr(jj->tls_session,
							       jj);
					gnutls_transport_set_int(
						jj->tls_session, accept_fd);
					set_read_funcs(jj->tls_session);
					jj->handshake_ok = 0;
					jj->close_ok = 0;

					if (verbose != 0) {
						ctt = simple_ctime(&tt,
								   timebuf);
						ctt[strlen(ctt) - 1] = 0;

						printf("\n* Accepted connection from %s on %s\n",
						       human_addr(
							       (struct sockaddr
									*)&client_address,
							       calen, topbuf,
							       sizeof(topbuf)),
						       ctt);
					}
				}
			}

			if (FD_ISSET(j->fd, &rd) && !j->listen_socket) {
				/* read partial GET request */
				char buf[16 * 1024];
				int r;

				if (j->handshake_ok == 0) {
					retry_handshake(j);
				}

				if (j->handshake_ok == 1) {
					int earlydata_read = 0;
					if (earlydata && !j->earlydata_eof) {
						r = gnutls_record_recv_early_data(
							j->tls_session, buf,
							MIN(sizeof(buf),
							    SMALL_READ_TEST));
						if (r ==
						    GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
							j->earlydata_eof = 1;
						}
						if (r == 0) {
							earlydata_read = 1;
						}
					}
					if (!earlydata_read) {
						r = gnutls_record_recv(
							j->tls_session, buf,
							MIN(sizeof(buf),
							    SMALL_READ_TEST));
					}
					if (r == GNUTLS_E_INTERRUPTED ||
					    r == GNUTLS_E_AGAIN) {
						/* do nothing */
					} else if (r <= 0) {
						if (r ==
						    GNUTLS_E_HEARTBEAT_PING_RECEIVED) {
							gnutls_heartbeat_pong(
								j->tls_session,
								0);
						} else if (r ==
							   GNUTLS_E_REHANDSHAKE) {
							try_rehandshake(j);
						} else {
							j->http_state =
								HTTP_STATE_CLOSING;
							if (r < 0) {
								int ret;
								check_alert(
									j->tls_session,
									r);
								fprintf(stderr,
									"Error while receiving data\n");
								do {
									ret = gnutls_alert_send_appropriate(
										j->tls_session,
										r);
								} while (
									ret == GNUTLS_E_AGAIN ||
									ret == GNUTLS_E_INTERRUPTED);
								GERR(r);
								j->close_ok = 0;
							}
						}
					} else {
						ptr = realloc(
							j->http_request,
							j->request_length + r +
								1);
						if (ptr != NULL) {
							j->http_request = ptr;
							memcpy(j->http_request +
								       j->request_length,
							       buf, r);
							j->request_length += r;
							j->http_request
								[j->request_length] =
								'\0';
						} else {
							j->http_state =
								HTTP_STATE_CLOSING;
						}
					}
					/* check if we have a full HTTP header */

					j->http_response = NULL;
					if (j->http_state ==
						    HTTP_STATE_REQUEST &&
					    j->http_request != NULL) {
						if ((http == 0 &&
						     strchr(j->http_request,
							    '\n')) ||
						    strstr(j->http_request,
							   "\r\n\r\n") ||
						    strstr(j->http_request,
							   "\n\n")) {
							if (get_response(
								    j->tls_session,
								    j->http_request,
								    &j->http_response,
								    &j->response_length)) {
								j->http_state =
									HTTP_STATE_RESPONSE;
								j->response_written =
									0;
							} else {
								j->http_state =
									HTTP_STATE_CLOSING;
							}
						}
					}
				}
			}

			if (FD_ISSET(j->fd, &wr)) {
				/* write partial response request */
				int r;

				if (j->handshake_ok == 0) {
					retry_handshake(j);
				}

				if (j->handshake_ok == 1 &&
				    j->http_response == NULL) {
					j->http_state = HTTP_STATE_CLOSING;
				} else if (j->handshake_ok == 1 &&
					   j->http_response != NULL) {
					r = gnutls_record_send(
						j->tls_session,
						j->http_response +
							j->response_written,
						MIN(j->response_length -
							    j->response_written,
						    SMALL_READ_TEST));
					if (r == GNUTLS_E_INTERRUPTED ||
					    r == GNUTLS_E_AGAIN) {
						/* do nothing */
					} else if (r <= 0) {
						j->http_state =
							HTTP_STATE_CLOSING;
						if (r < 0) {
							fprintf(stderr,
								"Error while sending data\n");
							GERR(r);
						}
						check_alert(j->tls_session, r);
					} else {
						j->response_written += r;
						/* check if we have written a complete response */
						if (j->response_written ==
						    j->response_length) {
							if (http != 0)
								j->http_state =
									HTTP_STATE_CLOSING;
							else {
								j->http_state =
									HTTP_STATE_REQUEST;
								free(j->http_response);
								j->http_response =
									NULL;
								j->response_length =
									0;
								j->request_length =
									0;
								j->http_request
									[0] = 0;
							}
						}
					}
				} else {
					j->request_length = 0;
					j->http_request[0] = 0;
					j->http_state = HTTP_STATE_REQUEST;
				}
			}
			gl_list_node_set_value(listener_list, node, j);
		}
		gl_list_iterator_free(&iter);

		/* loop through all connections, closing those that are in error */
		iter = gl_list_iterator(listener_list);
		while (gl_list_iterator_next(&iter, &elt, &node)) {
			const listener_item *j = elt;

			if (j->http_state == HTTP_STATE_CLOSING) {
				gl_list_remove_node(listener_list, node);
			}
		}
		gl_list_iterator_free(&iter);

		iter = gl_list_iterator(accepted_list);
		while (gl_list_iterator_next(&iter, &elt, &node)) {
			gl_list_add_last(listener_list, elt);
		}
		gl_list_iterator_free(&iter);
		gl_list_free(accepted_list);
	}

	gnutls_certificate_free_credentials(cert_cred);

#ifdef ENABLE_SRP
	if (srp_cred)
		gnutls_srp_free_server_credentials(srp_cred);
#endif

#ifdef ENABLE_PSK
	if (psk_cred)
		gnutls_psk_free_server_credentials(psk_cred);
#endif

#ifdef ENABLE_ANON
	gnutls_anon_free_server_credentials(dh_cred);
#endif

	if (noticket == 0)
		gnutls_free(session_ticket_key.data);

	if (earlydata)
		gnutls_anti_replay_deinit(anti_replay);

	if (nodb == 0)
		wrap_db_deinit();
	gnutls_global_deinit();
}

static void cmd_parser(int argc, char **argv)
{
	optionProcess(&gnutls_servOptions, argc, argv);

	disable_client_cert = HAVE_OPT(DISABLE_CLIENT_CERT);
	require_cert = ENABLED_OPT(REQUIRE_CLIENT_CERT);
	if (HAVE_OPT(DEBUG))
		debug = OPT_VALUE_DEBUG;

	if (HAVE_OPT(QUIET))
		verbose = 0;

	if (HAVE_OPT(PRIORITY))
		priorities = OPT_ARG(PRIORITY);

	if (HAVE_OPT(LIST)) {
		print_list(priorities, verbose);
		exit(0);
	}

	nodb = HAVE_OPT(NODB);
	noticket = HAVE_OPT(NOTICKET);
	earlydata = HAVE_OPT(EARLYDATA);

	if (HAVE_OPT(ECHO)) {
		http = 0;
		if (HAVE_OPT(CRLF))
			strip_crlf = 0;
	} else
		http = 1;

	record_max_size = OPT_VALUE_RECORDSIZE;

	if (HAVE_OPT(X509FMTDER))
		x509ctype = GNUTLS_X509_FMT_DER;
	else
		x509ctype = GNUTLS_X509_FMT_PEM;

	generate = HAVE_OPT(GENERATE);

	if (HAVE_OPT(DHPARAMS))
		dh_params_file = OPT_ARG(DHPARAMS);

	if (HAVE_OPT(ALPN)) {
		alpn_protos = STACKLST_OPT(ALPN);
		alpn_protos_size = STACKCT_OPT(ALPN);
	}

	if (HAVE_OPT(X509KEYFILE)) {
		x509_keyfile = STACKLST_OPT(X509KEYFILE);
		x509_keyfile_size = STACKCT_OPT(X509KEYFILE);
	}

	if (HAVE_OPT(X509CERTFILE)) {
		x509_certfile = STACKLST_OPT(X509CERTFILE);
		x509_certfile_size = STACKCT_OPT(X509CERTFILE);
	}

	if (x509_certfile_size != x509_keyfile_size) {
		fprintf(stderr,
			"The certificate number provided (%u) doesn't match the keys (%u)\n",
			x509_certfile_size, x509_keyfile_size);
		exit(1);
	}

	if (HAVE_OPT(X509CAFILE))
		x509_cafile = OPT_ARG(X509CAFILE);
	if (HAVE_OPT(X509CRLFILE))
		x509_crlfile = OPT_ARG(X509CRLFILE);

	if (HAVE_OPT(RAWPKKEYFILE)) {
		rawpk_keyfile = STACKLST_OPT(RAWPKKEYFILE);
		rawpk_keyfile_size = STACKCT_OPT(RAWPKKEYFILE);
	}

	if (HAVE_OPT(RAWPKFILE)) {
		rawpk_file = STACKLST_OPT(RAWPKFILE);
		rawpk_file_size = STACKCT_OPT(RAWPKFILE);
	}

	if (rawpk_file_size != rawpk_keyfile_size) {
		fprintf(stderr,
			"The number of raw public-keys provided (%u) doesn't match the number of corresponding private keys (%u)\n",
			rawpk_file_size, rawpk_keyfile_size);
		exit(1);
	}

	if (HAVE_OPT(SRPPASSWD))
		srp_passwd = OPT_ARG(SRPPASSWD);
	if (HAVE_OPT(SRPPASSWDCONF))
		srp_passwd_conf = OPT_ARG(SRPPASSWDCONF);

	if (HAVE_OPT(PSKPASSWD))
		psk_passwd = OPT_ARG(PSKPASSWD);

	if (HAVE_OPT(OCSP_RESPONSE)) {
		ocsp_responses = STACKLST_OPT(OCSP_RESPONSE);
		ocsp_responses_size = STACKCT_OPT(OCSP_RESPONSE);
	}

	if (HAVE_OPT(SNI_HOSTNAME))
		sni_hostname = OPT_ARG(SNI_HOSTNAME);

	if (HAVE_OPT(SNI_HOSTNAME_FATAL))
		sni_hostname_fatal = 1;

	if (HAVE_OPT(HTTPDATA))
		http_data_file = OPT_ARG(HTTPDATA);
}

/* session resuming support */

#define SESSION_ID_SIZE 128
#define SESSION_DATA_SIZE (16 * 1024)

typedef struct {
	unsigned char session_id[SESSION_ID_SIZE];
	unsigned int session_id_size;

	gnutls_datum_t session_data;
} CACHE;

static CACHE *cache_db;
static int cache_db_ptr;
static int cache_db_alloc;

static void wrap_db_init(void)
{
}

static void wrap_db_deinit(void)
{
	int i;

	for (i = 0; i < cache_db_ptr; i++)
		free(cache_db[i].session_data.data);
	free(cache_db);
}

static int wrap_db_store(void *dbf, gnutls_datum_t key, gnutls_datum_t data)
{
	int i;
	time_t now = time(0);
	void *ptr = NULL;

	if (key.size > SESSION_ID_SIZE)
		return GNUTLS_E_DB_ERROR;
	if (data.size > SESSION_DATA_SIZE)
		return GNUTLS_E_DB_ERROR;

	if (cache_db_ptr < cache_db_alloc)
		i = cache_db_ptr++;
	else {
		/* find empty or expired slot to store the new entry */
		for (i = 0; i < cache_db_ptr; i++)
			if (cache_db[i].session_id_size == 0 ||
			    !(now < gnutls_db_check_entry_expire_time(
					    &cache_db[i].session_data)))
				break;

		if (i == cache_db_ptr) {
			/* try to allocate additional slots */
			if (cache_db_ptr == ssl_session_cache) {
				fprintf(stderr, "Error: too many sessions\n");
				return GNUTLS_E_DB_ERROR;
			}
			cache_db_alloc = cache_db_alloc * 2 + 1;
			ptr = realloc(cache_db, cache_db_alloc * sizeof(CACHE));
			if (!ptr)
				return GNUTLS_E_MEMORY_ERROR;
			cache_db = ptr;
			memset(cache_db + cache_db_ptr, 0,
			       (cache_db_alloc - cache_db_ptr) * sizeof(CACHE));
			cache_db_ptr++;
		}
	}

	memcpy(cache_db[i].session_id, key.data, key.size);
	cache_db[i].session_id_size = key.size;

	/* resize the data slot if needed */
	if (cache_db[i].session_data.size < data.size) {
		ptr = realloc(cache_db[i].session_data.data, data.size);
		if (!ptr)
			return GNUTLS_E_MEMORY_ERROR;
		cache_db[i].session_data.data = ptr;
	}
	if (data.size > 0) {
		memcpy(cache_db[i].session_data.data, data.data, data.size);
	}
	cache_db[i].session_data.size = data.size;

	return 0;
}

static gnutls_datum_t wrap_db_fetch(void *dbf, gnutls_datum_t key)
{
	gnutls_datum_t res = { NULL, 0 };
	time_t now = time(0);
	int i;

	for (i = 0; i < cache_db_ptr; i++) {
		if (key.size == cache_db[i].session_id_size &&
		    memcmp(key.data, cache_db[i].session_id, key.size) == 0 &&
		    now < gnutls_db_check_entry_expire_time(
				  &cache_db[i].session_data)) {
			res.size = cache_db[i].session_data.size;

			res.data = malloc(res.size);
			if (res.data == NULL)
				return res;

			memcpy(res.data, cache_db[i].session_data.data,
			       res.size);

			return res;
		}
	}
	return res;
}

static int wrap_db_delete(void *dbf, gnutls_datum_t key)
{
	int i;

	for (i = 0; i < cache_db_ptr; i++) {
		if (key.size == cache_db[i].session_id_size &&
		    memcmp(key.data, cache_db[i].session_id, key.size) == 0) {
			cache_db[i].session_id_size = 0;
			free(cache_db[i].session_data.data);
			cache_db[i].session_data.data = NULL;
			cache_db[i].session_data.size = 0;

			return 0;
		}
	}

	return GNUTLS_E_DB_ERROR;
}

static int anti_replay_db_add(void *dbf, time_t exp, const gnutls_datum_t *key,
			      const gnutls_datum_t *data)
{
	time_t now = time(0);
	int i;

	for (i = 0; i < cache_db_ptr; i++) {
		if (key->size == cache_db[i].session_id_size &&
		    memcmp(key->data, cache_db[i].session_id, key->size) == 0 &&
		    now < gnutls_db_check_entry_expire_time(
				  &cache_db[i].session_data))
			return GNUTLS_E_DB_ENTRY_EXISTS;
	}

	return wrap_db_store(dbf, *key, *data);
}
