// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

/// Option to indicate if any listener sockets should be allowed to bind. If
/// false then no listeners should bind. This is only effective on startup
/// unless a conf item updated function is implemented here.
decltype(ircd::net::listen)
ircd::net::listen
{
	{ "name",     "ircd.net.listen" },
	{ "default",  true              },
	{ "persist",  false             },
};

bool
ircd::net::stop(acceptor &a)
{
	a.close();
	return true;
}

bool
ircd::net::start(acceptor &a)
{
	if(!a.a.is_open())
		a.open();

	allow(a);
	return true;
}

bool
ircd::net::allow(acceptor &a)
{
	if(unlikely(!a.a.is_open()))
		return false;

	if(a.accepting > 0)
		return false;

	a.set_handle();
	return true;
}

ircd::string_view
ircd::net::loghead(const acceptor &a)
{
	thread_local char buf[512];
	return loghead(buf, a);
}

ircd::string_view
ircd::net::loghead(const mutable_buffer &out,
                   const acceptor &a)
{
	char addrbuf[128];
	return fmt::sprintf
	{
		out, "[%s] @ [%s]:%u",
		name(a),
		string(addrbuf, a.ep.address()),
		a.ep.port(),
	};
}

size_t
ircd::net::accepting_count(const acceptor &a)
{
	return a.accepting;
}

size_t
ircd::net::handshaking_count(const acceptor &a)
{
	return a.handshaking.size();
}

size_t
ircd::net::handshaking_count(const acceptor &a,
                             const ipaddr &ipaddr)
{
	return std::count_if(begin(a.handshaking), end(a.handshaking), [&ipaddr]
	(const auto &socket_p)
	{
		return remote_ipport(*socket_p) == ipaddr;
	});
}

ircd::net::ipport
ircd::net::local(const acceptor &a)
{
	return make_ipport(a.a.local_endpoint());
}

ircd::net::ipport
ircd::net::binder(const acceptor &a)
{
	return make_ipport(a.ep);
}

ircd::string_view
ircd::net::name(const acceptor &a)
{
	return a.name;
}

ircd::json::object
ircd::net::config(const acceptor &a)
{
	return a.opts;
}

std::string
ircd::net::cipher_list(const acceptor &a)
{
	auto &ssl(mutable_cast(a).ssl);
	return openssl::cipher_list(*ssl.native_handle());
}

//
// listener::listener
//

ircd::net::listener::listener(const string_view &name,
                              const std::string &opts,
                              callback cb,
                              proffer pcb)
:listener
{
	name,
	json::object{opts},
	std::move(cb),
	std::move(pcb)
}
{
}

ircd::net::listener::listener(const string_view &name,
                              const json::object &opts,
                              callback cb,
                              proffer pcb)
:acceptor
{
	std::make_shared<struct acceptor>
	(
		*this,
		name,
		opts,
		std::move(cb),
		std::move(pcb)
	)
}
{
}

/// Cancels all pending accepts and handshakes and waits (yields ircd::ctx)
/// until report.
///
ircd::net::listener::~listener()
noexcept
{
	if(acceptor)
		acceptor->close();
}

ircd::string_view
ircd::net::listener::name()
const
{
	const net::acceptor &a(*this);
	return net::name(a);
}

ircd::net::listener::operator
ircd::json::object()
const
{
	const net::acceptor &a(*this);
	return net::config(a);
}

ircd::net::listener::operator
net::acceptor &()
{
	assert(acceptor);
	return *acceptor;
}

ircd::net::listener::operator
const net::acceptor &()
const
{
	assert(acceptor);
	return *acceptor;
}

///////////////////////////////////////////////////////////////////////////////
//
// net/acceptor.h
//

decltype(ircd::net::acceptor::log)
ircd::net::acceptor::log
{
	"net.listen"
};

[[clang::always_destroy]]
decltype(ircd::net::acceptor::accept_desc)
ircd::net::acceptor::accept_desc
{
	"ircd.net.acceptor.accept"
};

[[clang::always_destroy]]
decltype(ircd::net::acceptor::handshake_desc)
ircd::net::acceptor::handshake_desc
{
	"ircd.net.acceptor.handshake"
};

decltype(ircd::net::acceptor::timeout)
ircd::net::acceptor::timeout
{
	{ "name",     "ircd.net.acceptor.timeout" },
	{ "default",  12000L                      },
};

/// The number of simultaneous handshakes we conduct across all clients.
decltype(ircd::net::acceptor::handshaking_max)
ircd::net::acceptor::handshaking_max
{
	{ "name",     "ircd.net.acceptor.handshaking.max" },
	{ "default",  1024L                               },
};

/// The number of simultaneous handshakes we conduct for a single peer (which
/// is an IP without a port in this context). This prevents a peer from
/// reaching the handshaking.max limit to DoS out other peers.
decltype(ircd::net::acceptor::handshaking_max_per_peer)
ircd::net::acceptor::handshaking_max_per_peer
{
	{ "name",     "ircd.net.acceptor.handshaking.max_per_peer" },
	{ "default",  16L                                          },
};

decltype(ircd::net::acceptor::ssl_curve_list)
ircd::net::acceptor::ssl_curve_list
{
	{ "name",     "ircd.net.acceptor.ssl.curve.list"     },
	{ "default",  string_view{ircd::net::ssl_curve_list} },
};

decltype(ircd::net::acceptor::ssl_cipher_list)
ircd::net::acceptor::ssl_cipher_list
{
	{ "name",     "ircd.net.acceptor.ssl.cipher.list"     },
	{ "default",  string_view{ircd::net::ssl_cipher_list} },
};

decltype(ircd::net::acceptor::ssl_cipher_blacklist)
ircd::net::acceptor::ssl_cipher_blacklist
{
	{ "name",     "ircd.net.acceptor.ssl.cipher.blacklist"     },
	{ "default",  string_view{ircd::net::ssl_cipher_blacklist} },
};

//
// acceptor::acceptor
//

ircd::net::acceptor::acceptor(net::listener &listener,
                              const string_view &name,
                              const json::object &opts,
                              listener::callback cb,
                              listener::proffer pcb)
try
:name
{
	name
}
,opts
{
	opts
}
,backlog
{
	//TODO: XXX
	//boost::asio::ip::tcp::socket::max_connections   <-- linkage failed?
	std::min(opts.get<uint>("backlog", SOMAXCONN), uint(SOMAXCONN))
}
,cb
{
	std::move(cb)
}
,pcb
{
	pcb?
		std::move(pcb):
		proffer_default
}
,filter
{
	const_buffer
	{
		nullptr, nullptr
	},
}
,ssl
{
	asio::ssl::context::method::sslv23_server
}
,ep
{
	make_address(unquote(opts.get("host", "*"_sv))),
	opts.at<uint16_t>("port")
}
,a
{
	ios::get()
}
,secure
{
	configure(opts)
}
{
	open();
}
catch(const boost::system::system_error &e)
{
	throw_system_error(e);
}

ircd::net::acceptor::~acceptor()
noexcept
{
	if(accepting || !handshaking.empty())
		log::critical
		{
			"The acceptor must not have clients during destruction!"
			" (accepting:%zu handshaking:%zu)",
			accepting,
			handshaking.size(),
		};
}

void
ircd::net::acceptor::open()
{
	static const auto &max_connections
	{
		//TODO: XXX
		//boost::asio::ip::tcp::socket::max_connections   <-- linkage failed?
		std::min(json::object(opts).get<uint>("max_connections", SOMAXCONN), uint(SOMAXCONN))
	};

	static const ip::tcp::acceptor::reuse_address reuse_address
	{
		true
	};

	assert(!interrupting);
	interrupting = false;
	a.open(ep.protocol());
	a.set_option(reuse_address);
	a.non_blocking(true);
	log::debug
	{
		log, "%s opened listener socket:%d",
		loghead(*this),
		int(a.native_handle()),
	};

	if(filter)
	{
		net::attach(a.native_handle(), filter.fd);
		log::debug
		{
			log, "%s attach filter fd:%d",
			loghead(*this),
			int(filter.fd),
		};
	}

	a.bind(ep);
	log::debug
	{
		log, "%s bound listener socket",
		loghead(*this)
	};

	a.listen(backlog);
	log::debug
	{
		log, "%s listening (backlog: %lu, max connections: %zu)",
		loghead(*this),
		backlog,
		max_connections
	};
}

void
ircd::net::acceptor::close()
{
	if(!interrupting)
		interrupt();

	if(a.is_open())
		a.close();

	for(const auto &sock : handshaking)
		sock->cancel();

	join();
	log::debug
	{
		log, "%s listener finished",
		loghead(*this)
	};
}

void
ircd::net::acceptor::join()
noexcept try
{
	if(!interrupting)
		interrupt();

	if(!ctx::current)
		return;

	joining.wait([this]() noexcept
	{
		return !accepting && handshaking.empty();
	});

	interrupting = false;
}
catch(const std::exception &e)
{
	log::error
	{
		log, "acceptor(%p) join :%s",
		this,
		e.what()
	};
}

bool
ircd::net::acceptor::interrupt()
noexcept try
{
	if(interrupting)
		return false;

	if(!a.is_open())
		return false;

	interrupting = true;
	a.cancel();
	return true;
}
catch(const boost::system::system_error &e)
{
	log::error
	{
		log, "acceptor(%p) interrupt :%s",
		this,
		string(e)
	};

	return false;
}

/// Sets the next asynchronous handler to start the next accept sequence.
/// Each call to next() sets one handler which handles the connect for one
/// socket. After the connect, an asynchronous SSL handshake handler is set
/// for the socket.
bool
ircd::net::acceptor::set_handle()
try
{
	const auto &sock
	{
		secure?
			std::make_shared<ircd::socket>(ssl):
			std::make_shared<ircd::socket>()
	};

	auto handler
	{
		std::bind(&acceptor::accept, this, ph::_1, sock)
	};

	sock->local = ep;
	ip::tcp::socket &sd(*sock);
	a.async_accept(sd, sock->remote, ios::handle(accept_desc, std::move(handler)));
	++accepting;
	return true;
}
catch(const std::exception &e)
{
	throw panic
	{
		"%s :%s",
		loghead(*this),
		e.what()
	};
}

/// Callback for a socket connected. This handler then invokes the
/// asynchronous SSL handshake sequence.
///
void
ircd::net::acceptor::accept(const error_code &ec,
                            const std::shared_ptr<socket> sock)
noexcept try
{
	assert(bool(sock));
	assert(accepting > 0);
	assert(accepting == 1); // for now

	char ecbuf[64];
	log::debug
	{
		log, "%s %s accepted(%zu) %s",
		loghead(*sock),
		loghead(*this),
		accepting,
		string(ecbuf, ec)
	};

	--accepting;
	if(unlikely(!check_accept_error(ec, *sock)))
	{
		allow(*this);
		net::close(*sock, dc::RST, close_ignore);
		return;
	}

	const auto remote
	{
		remote_ipport(*sock)
	};

	if(unlikely(secure && !check_handshake_limit(*sock, remote)))
	{
		allow(*this);
		net::close(*sock, dc::RST, close_ignore);
		return;
	}

	// Call the proffer-callback. This allows the application to check whether
	// to allow or deny this remote before the handshake, as well as setting
	// the next accept to shape the kernel's queue.
	if(!pcb(*this, remote))
	{
		net::close(*sock, dc::RST, close_ignore);
		return;
	}

	if(!secure)
	{
		accepted(sock);
		return;
	}

	static const socket::handshake_type handshake_type
	{
		socket::handshake_type::server
	};

	const auto it
	{
		handshaking.emplace(end(handshaking), sock)
	};

	auto handshake
	{
		std::bind(&acceptor::handshake, this, ph::_1, sock, it)
	};

	sock->set_timeout(milliseconds(timeout));
	sock->ssl->async_handshake(handshake_type, ios::handle(handshake_desc, std::move(handshake)));
	assert(!openssl::get_app_data(*sock));
	openssl::set_app_data(*sock, sock.get());
}
catch(const ctx::interrupted &e)
{
	assert(bool(sock));

	char ecbuf[64];
	log::debug
	{
		log, "%s acceptor interrupted %s :%s",
		loghead(*this),
		loghead(*sock),
		string(ecbuf, ec)
	};

	error_code ec_;
	sock->sd.close(ec_);
	assert(!ec_);
	joining.notify_all();
}
catch(const std::exception &e)
{
	assert(bool(sock));
	log::error
	{
		log, "%s acceptor error in accept() %s :%s",
		loghead(*this),
		loghead(*sock),
		e.what()
	};

	error_code ec_;
	sock->sd.close(ec_);
	assert(!ec_);
	joining.notify_all();
}

/// Error handler for the accept socket callback. This handler determines
/// whether or not the handler should return or continue processing the
/// result.
///
bool
ircd::net::acceptor::check_accept_error(const error_code &ec,
                                        socket &sock)
const
{
	using std::errc;

	if(unlikely(interrupting))
		throw ctx::interrupted();

	if(likely(!ec))
		return true;

	if(system_category(ec)) switch(ec.value())
	{
		case int(errc::operation_canceled):
			throw ctx::interrupted();

		default:
			break;
	}

	char ecbuf[64];
	log::derror
	{
		log, "%s in accept %s :%s",
		loghead(*this),
		loghead(sock),
		string(ecbuf, ec),
	};

	return false;
}

/// Checks performed for whether handshaking limits have been reached before
/// allowing a handshake.
bool
ircd::net::acceptor::check_handshake_limit(socket &sock,
                                           const ipport &remote)
const
{
	if(unlikely(handshaking_count(*this) >= size_t(handshaking_max)))
	{
		log::warning
		{
			log, "%s refusing to handshake %s; exceeds maximum of %zu handshakes.",
			loghead(sock),
			loghead(*this),
			size_t(handshaking_max),
		};

		return false;
	}

	if(unlikely(handshaking_count(*this, remote) >= size_t(handshaking_max_per_peer)))
	{
		log::dwarning
		{
			log, "%s refusing to handshake %s; exceeds maximum of %zu handshakes to them.",
			loghead(sock),
			loghead(*this),
			size_t(handshaking_max_per_peer),
		};

		return false;
	}

	return true;
}

/// Default proffer callback which accepts this connection and allows the
/// next accept to take place as well. This is generally overriden by a
/// user callback to control this behavior.
bool
ircd::net::acceptor::proffer_default(acceptor &acceptor,
                                     const ipport &ipport)
{
	allow(acceptor);
	return true;
}

void
ircd::net::acceptor::handshake(const error_code &ec,
                               const std::shared_ptr<socket> sock,
                               const decltype(handshaking)::const_iterator it)
noexcept try
{
	assert(bool(sock));
	assert(!handshaking.empty());
	assert(it != end(handshaking));
	assert(openssl::get_app_data(*sock) == sock.get());

	if constexpr(RB_DEBUG_LEVEL)
	{
		const auto *const current_cipher
		{
			!ec?
				openssl::current_cipher(*sock):
				nullptr
		};

		char ecbuf[64];
		log::debug
		{
			log, "%s %s handshook(%zd:%zu) cipher:%s %s",
			loghead(*sock),
			loghead(*this),
			std::distance(cbegin(handshaking), it),
			handshaking.size(),
			current_cipher?
				openssl::name(*current_cipher):
				"<NO CIPHER>"_sv,
			string(ecbuf, ec)
		};
	}

	handshaking.erase(it);
	openssl::set_app_data(*sock, nullptr);
	check_handshake_error(ec, *sock);
	sock->cancel_timeout();
	accepted(sock);
}
catch(const ctx::interrupted &e)
{
	assert(bool(sock));
	char ecbuf[64];
	log::debug
	{
		log, "%s SSL handshake interrupted %s %s",
		loghead(*sock),
		loghead(*this),
		string(ecbuf, ec)
	};

	net::close(*sock, dc::RST, close_ignore);
	joining.notify_all();
}
catch(const std::system_error &e)
{
	assert(bool(sock));
	log::derror
	{
		log, "%s %s in handshake() :%s",
		loghead(*sock),
		loghead(*this),
		e.what()
	};

	net::close(*sock, dc::RST, close_ignore);
	joining.notify_all();
}
catch(const std::exception &e)
{
	assert(bool(sock));
	log::error
	{
		log, "%s %s in handshake() :%s",
		loghead(*sock),
		loghead(*this),
		e.what()
	};

	net::close(*sock, dc::RST, close_ignore);
	joining.notify_all();
}

/// Error handler for the SSL handshake callback. This handler determines
/// whether or not the handler should return or continue processing the
/// result.
///
void
ircd::net::acceptor::check_handshake_error(const error_code &ec,
                                           socket &sock)
const
{
	using std::errc;

	if(unlikely(interrupting))
		throw ctx::interrupted();

	if(likely(system_category(ec))) switch(ec.value())
	{
		case 0:
			return;

		case int(errc::operation_canceled):
			if(sock.timedout)
				throw_system_error(errc::timed_out);
			else
				break;

		default:
			break;
	}

	throw_system_error(ec);
	__builtin_unreachable();
}

ircd::string_view
ircd::net::acceptor::handle_alpn(socket &socket,
                                 const vector_view<const string_view> &in)
{
	if(empty(in))
		return {};

	log::debug
	{
		log, "%s %s offered %zu ALPN protocols",
		loghead(socket),
		loghead(*this),
		size(in),
	};

	if constexpr(debug_alpn)
		for(size_t i(0); i < size(in); ++i)
		{
			log::debug
			{
				log, "%s ALPN protocol %zu of %zu: '%s'",
				loghead(socket),
				i,
				size(in),
				in[i],
			};
		}

	//NOTE: proto == "h2" condition goes here
	for(const auto &proto : in)
		if(proto == "http/1.1")
		{
			strlcpy(socket.alpn, proto);
			return proto;
		}

	return {};
}

static int
ircd_net_acceptor_handle_alpn(SSL *const s,
                              const unsigned char **out,
                              unsigned char *const outlen,
                              const unsigned char *const in,
                              unsigned int inlen,
                              void *const arg)
noexcept try
{
	static const size_t PROTOS_MAX
	{
		8
	};

	auto &acceptor
	{
		*reinterpret_cast<ircd::net::acceptor *>(arg)
	};

	size_t p(0), i(0);
	ircd::string_view protos[PROTOS_MAX];
	while(i < inlen && p < PROTOS_MAX)
	{
		const uint8_t &len(in[i++]);
		if(unlikely(!len || i + len >= inlen))
			break;

		protos[p++] = ircd::string_view
		{
			reinterpret_cast<const char *>(in + i), len
		};

		i += len;
	}

	const ircd::vector_view<const ircd::string_view> vec
	{
		protos, p
	};

	assert(s);
	assert(ircd::openssl::get_app_data(*s));
	if(unlikely(!ircd::openssl::get_app_data(*s)))
		return SSL_TLSEXT_ERR_ALERT_FATAL;

	auto &socket
	{
		*static_cast<ircd::net::socket *>(ircd::openssl::get_app_data(*s))
	};

	const ircd::string_view sel
	{
		acceptor.handle_alpn(socket, vec)
	};

	if(!sel)
		return SSL_TLSEXT_ERR_NOACK;

	*out = reinterpret_cast<const unsigned char *>(data(sel));
	*outlen = size(sel);
	return SSL_TLSEXT_ERR_OK;
}
catch(const std::exception &)
{
	return SSL_TLSEXT_ERR_ALERT_FATAL;
}
catch(...)
{
	ircd::log::critical
	{
		ircd::net::acceptor::log,
		"Acceptor ALPN callback unhandled."
	};

	ircd::terminate();
	__builtin_unreachable();
}

bool
ircd::net::acceptor::handle_sni(socket &socket,
                                int &client_server)
try
{
	const string_view &name
	{
		openssl::server_name(socket)
	};

	if(!name)
		return true;

	const bool accepts
	{
		name == this->cname
	};

	if(!accepts)
	{
		log::dwarning
		{
			log, "%s %s unrecognized SNI '%s' offered.",
			loghead(socket),
			loghead(*this),
			name,
		};

		return false;
	}

	log::debug
	{
		log, "%s %s offered SNI '%s'",
		loghead(socket),
		loghead(*this),
		name,
	};

	return true;
}
catch(const sni_warning &e)
{
	log::warning
	{
		log, "%s %s during SNI :%s",
		loghead(socket),
		loghead(*this),
		e.what(),
	};

	throw;
}
catch(const std::exception &e)
{
	log::error
	{
		log, "%s %s during SNI :%s",
		loghead(socket),
		loghead(*this),
		e.what(),
	};

	throw;
}

static int
ircd_net_acceptor_handle_sni(SSL *const s,
                             int *const i,
                             void *const a)
noexcept try
{
	if(unlikely(!s || !i || !a))
		throw ircd::panic
		{
			"Missing arguments to callback s:%p i:%p a:%p", s, i, a
		};

	auto &acceptor
	{
		*reinterpret_cast<ircd::net::acceptor *>(a)
	};

	assert(s);
	assert(ircd::openssl::get_app_data(*s));
	if(unlikely(!ircd::openssl::get_app_data(*s)))
		return SSL_TLSEXT_ERR_ALERT_FATAL;

	auto &socket
	{
		*static_cast<ircd::net::socket *>(ircd::openssl::get_app_data(*s))
	};

	return acceptor.handle_sni(socket, *i)?
		SSL_TLSEXT_ERR_OK:
		SSL_TLSEXT_ERR_NOACK;
}
catch(const ircd::net::acceptor::sni_warning &)
{
	return SSL_TLSEXT_ERR_ALERT_WARNING;
}
catch(const std::exception &)
{
	return SSL_TLSEXT_ERR_ALERT_FATAL;
}
catch(...)
{
	ircd::log::critical
	{
		ircd::net::acceptor::log,
		"Acceptor SNI callback unhandled."
	};

	ircd::terminate();
	__builtin_unreachable();
}

void
ircd::net::acceptor::accepted(const std::shared_ptr<socket> &sock)
{
	assert(bool(cb));
	assert(bool(sock));

	#if !defined(BSD_BASED_OS)
	// Toggles the behavior of non-async functions; see func comment
	// This is not needed on BSD because the socket inherits the listener's
	// non-blocking disposition.
	blocking(*sock, false);
	#endif

	cb(*this, sock);
}

bool
ircd::net::acceptor::configure(const json::object &opts)
{
	log::debug
	{
		log, "%s preparing listener socket configuration...",
		loghead(*this)
	};

	configure_flags(opts);
	if(!configure_certs(opts))
		return false;

	configure_password(opts);
	configure_ciphers(opts);
	configure_curves(opts);
	configure_sni(opts);
	log::debug
	{
		log, "%s configured listener SSL",
		loghead(*this)
	};

	return true;
}

void
ircd::net::acceptor::configure_sni(const json::object &opts)
{
	SSL_CTX_set_alpn_select_cb(ssl.native_handle(), ircd_net_acceptor_handle_alpn, this);
	SSL_CTX_set_tlsext_servername_callback(ssl.native_handle(), ircd_net_acceptor_handle_sni);
	SSL_CTX_set_tlsext_servername_arg(ssl.native_handle(), this);
}

void
ircd::net::acceptor::configure_flags(const json::object &opts)
{
	ulong flags(0);

	if(opts.get<bool>("ssl_default_workarounds", false))
		flags |= ssl.default_workarounds;

	if(opts.get<bool>("ssl_single_dh_use", false))
		flags |= ssl.single_dh_use;

	if(opts.get<bool>("ssl_no_sslv2", false))
		flags |= ssl.no_sslv2;

	if(opts.get<bool>("ssl_no_sslv3", false))
		flags |= ssl.no_sslv3;

	if(opts.get<bool>("ssl_no_tlsv1", false))
		flags |= ssl.no_tlsv1;

	if(opts.get<bool>("ssl_no_tlsv1_1", false))
		flags |= ssl.no_tlsv1_1;

	if(opts.get<bool>("ssl_no_tlsv1_2", false))
		flags |= ssl.no_tlsv1_2;

	if(flags)
		ssl.set_options(flags);
}

void
ircd::net::acceptor::configure_ciphers(const json::object &opts)
{
	if(!empty(unquote(opts["ssl_cipher_list"])))
	{
		const json::string &list
		{
			opts["ssl_cipher_list"]
		};

		assert(ssl.native_handle());
		openssl::set_cipher_list(*ssl.native_handle(), list);
	}
	else if(!empty(string_view(ssl_cipher_list)))
	{
		assert(ssl.native_handle());
		const string_view &list
		{
			ssl_cipher_list
		};

		openssl::set_cipher_list(*ssl.native_handle(), list);
	}
	else if(!empty(string_view(ssl_cipher_blacklist)))
	{
		assert(ssl.native_handle());

		std::stringstream res;
		const string_view &blacklist
		{
			ssl_cipher_blacklist
		};

		const auto ciphers
		{
			openssl::cipher_list(*ssl.native_handle(), 0)
		};

		ircd::tokens(ciphers, ':', [&res, &blacklist]
		(const string_view &cipher)
		{
			assert(cipher);
			if(!has(blacklist, cipher))
				res << cipher << ':';
		});

		std::string list(res.str());
		assert(list.empty() || list.back() == ':');
		list.pop_back();

		openssl::set_cipher_list(*ssl.native_handle(), list);
	}
}

void
ircd::net::acceptor::configure_curves(const json::object &opts)
{
	if(!empty(unquote(opts["ssl_curve_list"])))
	{
		const json::string &list
		{
			opts["ssl_curve_list"]
		};

		assert(ssl.native_handle());
		openssl::set_curves(*ssl.native_handle(), list);
	}
	else if(!empty(string_view(ssl_curve_list)))
	{
		const string_view &list
		{
			ssl_curve_list
		};

		assert(ssl.native_handle());
		openssl::set_curves(*ssl.native_handle(), list);
	}
}

bool
ircd::net::acceptor::configure_certs(const json::object &opts)
{
	uint ret(0);
	if(!empty(unquote(opts["certificate_chain_path"])))
	{
		const json::string filename
		{
			opts["certificate_chain_path"]
		};

		if(!fs::exists(filename))
			throw error
			{
				"%s SSL certificate chain file @ `%s' not found",
				loghead(*this),
				filename
			};

		ssl.use_certificate_chain_file(filename);
		ret += 1;
		log::info
		{
			log, "%s using certificate chain file '%s'",
			loghead(*this),
			filename
		};
	}

	if(!empty(unquote(opts["certificate_pem_path"])))
	{
		const std::string filename
		{
			unquote(opts.get("certificate_pem_path", name + ".crt"))
		};

		if(!fs::exists(filename))
			throw error
			{
				"%s SSL certificate pem file @ `%s' not found",
				loghead(*this),
				filename
			};

		ssl.use_certificate_file(filename, asio::ssl::context::pem);
		const auto *const x509
		{
			SSL_CTX_get0_certificate(ssl.native_handle())
		};

		this->cname = ircd::string(rfc3986::DOMAIN_BUFSIZE | SHRINK_TO_FIT, [&x509]
		(const mutable_buffer &buf)
		{
			return x509?
				openssl::subject_common_name(buf, *x509):
				string_view{};
		});

		ret += 1;
		log::info
		{
			log, "%s using file '%s' with certificate for '%s'",
			loghead(*this),
			filename,
			this->cname,
		};
	}

	if(!empty(unquote(opts["private_key_pem_path"])))
	{
		const std::string filename
		{
			unquote(opts.get("private_key_pem_path", name + ".crt.key"))
		};

		if(!fs::exists(filename))
			throw error
			{
				"%s SSL private key file @ `%s' not found",
				loghead(*this),
				filename
			};

		ssl.use_private_key_file(filename, asio::ssl::context::pem);

		ret += 1;
		log::info
		{
			log, "%s using private key file '%s'",
			loghead(*this),
			filename
		};
	}

	if(ret != 0 && ret != 3)
		log::warning
		{
			"%s missing some paths to PEM files in its options."
			" SSL is probably misconfigured.",
			loghead(*this),
		};

	return ret;
}

void
ircd::net::acceptor::configure_dh(const json::object &opts)
{
	if(!empty(unquote(opts["tmp_dh_path"])))
	{
		const json::string filename
		{
			opts.at("tmp_dh_path")
		};

		if(!fs::exists(filename))
			throw error
			{
				"%s SSL tmp dh file @ `%s' not found",
				loghead(*this),
				filename
			};

		ssl.use_tmp_dh_file(filename);
		log::info
		{
			log, "%s using tmp dh file '%s'",
			loghead(*this),
			filename,
		};

		return;
	}

	assert(ssl.native_handle());
	openssl::set_ecdh_auto(*ssl.native_handle(), true);
}

void
ircd::net::acceptor::configure_password(const json::object &opts)
{
	//TODO: XXX
	ssl.set_password_callback([this]
	(const auto &size, const auto &purpose)
	{
		log::notice
		{
			log, "%s asking for password with purpose '%s' (size: %zu)",
			loghead(*this),
			purpose,
			size
		};

		//XXX: TODO
		assert(0);
		return "foobar";
	});
}
