/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "module-dir.h"
#include "iostream-ssl-private.h"

#define OFFSET(name) offsetof(struct ssl_iostream_settings, name)
static const size_t ssl_iostream_settings_string_offsets[] = {
	OFFSET(min_protocol),
	OFFSET(cipher_list),
	OFFSET(ciphersuites),
	OFFSET(curve_list),
	OFFSET(ca),
	OFFSET(ca_file),
	OFFSET(ca_dir),
	OFFSET(cert.cert),
	OFFSET(cert.key),
	OFFSET(cert.key_password),
	OFFSET(alt_cert.cert),
	OFFSET(alt_cert.key),
	OFFSET(alt_cert.key_password),
	OFFSET(dh),
	OFFSET(cert_username_field),
	OFFSET(crypto_device),
};

static bool ssl_module_loaded = FALSE;
#ifdef HAVE_SSL
static struct module *ssl_module = NULL;
#endif
static const struct iostream_ssl_vfuncs *ssl_vfuncs = NULL;

#ifdef HAVE_SSL
static void ssl_module_unload(void)
{
	ssl_iostream_context_cache_free();
	module_dir_unload(&ssl_module);
}
#endif

void iostream_ssl_module_init(const struct iostream_ssl_vfuncs *vfuncs)
{
	ssl_vfuncs = vfuncs;
	ssl_module_loaded = TRUE;
}

int ssl_module_load(const char **error_r)
{
#ifdef HAVE_SSL
	const char *plugin_name = "ssl_iostream_openssl";
	struct module_dir_load_settings mod_set;

	i_zero(&mod_set);
	mod_set.abi_version = DOVECOT_ABI_VERSION;
	mod_set.setting_name = "<built-in lib-ssl-iostream lookup>";
	mod_set.require_init_funcs = TRUE;
	ssl_module = module_dir_load(MODULE_DIR, plugin_name, &mod_set);
	if (module_dir_try_load_missing(&ssl_module, MODULE_DIR, plugin_name,
					&mod_set, error_r) < 0)
		return -1;
	module_dir_init(ssl_module);
	if (!ssl_module_loaded) {
		*error_r = t_strdup_printf(
			"%s didn't call iostream_ssl_module_init() - SSL not initialized",
			plugin_name);
		module_dir_unload(&ssl_module);
		return -1;
	}

	/* Destroy SSL module after (most of) the others. Especially lib-fs
	   backends may still want to access SSL module in their own
	   atexit-callbacks. */
	lib_atexit_priority(ssl_module_unload, LIB_ATEXIT_PRIORITY_LOW);
	return 0;
#else
	*error_r = "SSL support not compiled in";
	return -1;
#endif
}

int io_stream_ssl_global_init(const struct ssl_iostream_settings *set,
			      const char **error_r)
{
	return ssl_vfuncs->global_init(set, error_r);
}

int ssl_iostream_context_init_client(const struct ssl_iostream_settings *set,
				     struct ssl_iostream_context **ctx_r,
				     const char **error_r)
{
	struct ssl_iostream_settings set_copy = *set;

	/* ensure this is set to TRUE */
	set_copy.verify_remote_cert = TRUE;

	if (!ssl_module_loaded) {
		if (ssl_module_load(error_r) < 0)
			return -1;
	}
	if (io_stream_ssl_global_init(&set_copy, error_r) < 0)
		return -1;
	return ssl_vfuncs->context_init_client(&set_copy, ctx_r, error_r);
}

int ssl_iostream_context_init_server(const struct ssl_iostream_settings *set,
				     struct ssl_iostream_context **ctx_r,
				     const char **error_r)
{
	if (!ssl_module_loaded) {
		if (ssl_module_load(error_r) < 0)
			return -1;
	}
	if (io_stream_ssl_global_init(set, error_r) < 0)
		return -1;
	return ssl_vfuncs->context_init_server(set, ctx_r, error_r);
}

void ssl_iostream_context_ref(struct ssl_iostream_context *ctx)
{
	ssl_vfuncs->context_ref(ctx);
}

void ssl_iostream_context_unref(struct ssl_iostream_context **_ctx)
{
	struct ssl_iostream_context *ctx = *_ctx;

	if (*_ctx == NULL)
		return;
	*_ctx = NULL;
	ssl_vfuncs->context_unref(ctx);
}

int io_stream_create_ssl_client(struct ssl_iostream_context *ctx, const char *host,
				const struct ssl_iostream_settings *set,
				struct istream **input, struct ostream **output,
				struct ssl_iostream **iostream_r,
				const char **error_r)
{
	struct ssl_iostream_settings set_copy = *set;
	set_copy.verify_remote_cert = TRUE;
	return ssl_vfuncs->create(ctx, host, &set_copy, input, output,
				  iostream_r, error_r);
}

int io_stream_create_ssl_server(struct ssl_iostream_context *ctx,
				const struct ssl_iostream_settings *set,
				struct istream **input, struct ostream **output,
				struct ssl_iostream **iostream_r,
				const char **error_r)
{
	return ssl_vfuncs->create(ctx, NULL, set, input, output,
				  iostream_r, error_r);
}

void ssl_iostream_unref(struct ssl_iostream **_ssl_io)
{
	struct ssl_iostream *ssl_io = *_ssl_io;

	*_ssl_io = NULL;
	ssl_vfuncs->unref(ssl_io);
}

void ssl_iostream_destroy(struct ssl_iostream **_ssl_io)
{
	struct ssl_iostream *ssl_io;

	if (_ssl_io == NULL || *_ssl_io == NULL)
		return;

	ssl_io = *_ssl_io;
	*_ssl_io = NULL;
	ssl_vfuncs->destroy(ssl_io);
}

void ssl_iostream_set_log_prefix(struct ssl_iostream *ssl_io,
				 const char *prefix)
{
	ssl_vfuncs->set_log_prefix(ssl_io, prefix);
}

int ssl_iostream_handshake(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->handshake(ssl_io);
}

void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
					 ssl_iostream_handshake_callback_t *callback,
					 void *context)
{
	ssl_vfuncs->set_handshake_callback(ssl_io, callback, context);
}

void ssl_iostream_set_sni_callback(struct ssl_iostream *ssl_io,
				   ssl_iostream_sni_callback_t *callback,
				   void *context)
{
	ssl_vfuncs->set_sni_callback(ssl_io, callback, context);
}

void ssl_iostream_change_context(struct ssl_iostream *ssl_io,
				 struct ssl_iostream_context *ctx)
{
	ssl_vfuncs->change_context(ssl_io, ctx);
}

bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->is_handshaked(ssl_io);
}

bool ssl_iostream_has_handshake_failed(const struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->has_handshake_failed(ssl_io);
}

bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->has_valid_client_cert(ssl_io);
}

bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->has_broken_client_cert(ssl_io);
}

bool ssl_iostream_cert_match_name(struct ssl_iostream *ssl_io, const char *name,
				  const char **reason_r)
{
	return ssl_vfuncs->cert_match_name(ssl_io, name, reason_r);
}

int ssl_iostream_check_cert_validity(struct ssl_iostream *ssl_io,
				     const char *host, const char **error_r)
{
	const char *reason;

	if (!ssl_iostream_has_valid_client_cert(ssl_io)) {
		if (!ssl_iostream_has_broken_client_cert(ssl_io))
			*error_r = "SSL certificate not received";
		else {
			*error_r = t_strdup(ssl_iostream_get_last_error(ssl_io));
			if (*error_r == NULL)
				*error_r = "Received invalid SSL certificate";
		}
		return -1;
	} else if (!ssl_iostream_cert_match_name(ssl_io, host, &reason)) {
		*error_r = t_strdup_printf(
			"SSL certificate doesn't match expected host name %s: %s",
			host, reason);
		return -1;
	}
	return 0;
}

const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_peer_name(ssl_io);
}

const char *ssl_iostream_get_server_name(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_server_name(ssl_io);
}

const char *ssl_iostream_get_compression(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_compression(ssl_io);
}

const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_security_string(ssl_io);
}

const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_last_error(ssl_io);
}

struct ssl_iostream_settings *ssl_iostream_settings_dup(pool_t pool,
			const struct ssl_iostream_settings *old_set)
{
	struct ssl_iostream_settings *new_set;

	new_set = p_new(pool, struct ssl_iostream_settings, 1);
	ssl_iostream_settings_init_from(pool, new_set, old_set);
	return new_set;
}

void ssl_iostream_settings_init_from(pool_t pool,
				     struct ssl_iostream_settings *dest,
				     const struct ssl_iostream_settings *src)
{
	unsigned int i;

	*dest = *src;
	for (i = 0; i < N_ELEMENTS(ssl_iostream_settings_string_offsets); i++) {
		const size_t offset = ssl_iostream_settings_string_offsets[i];
		const char *const *src_str = CONST_PTR_OFFSET(src, offset);
		const char **dest_str = PTR_OFFSET(dest, offset);
		*dest_str = p_strdup(pool, *src_str);
	}
}

bool ssl_iostream_settings_equals(const struct ssl_iostream_settings *set1,
				  const struct ssl_iostream_settings *set2)
{
	struct ssl_iostream_settings set1_nonstr, set2_nonstr;
	unsigned int i;

	set1_nonstr = *set1;
	set2_nonstr = *set2;
	for (i = 0; i < N_ELEMENTS(ssl_iostream_settings_string_offsets); i++) {
		const size_t offset = ssl_iostream_settings_string_offsets[i];
		const char **str1 = PTR_OFFSET(&set1_nonstr, offset);
		const char **str2 = PTR_OFFSET(&set2_nonstr, offset);

		if (null_strcmp(*str1, *str2) != 0)
			return FALSE;

		/* clear away the string pointer from the settings struct */
		*str1 = NULL;
		*str2 = NULL;
	}
	/* The set*_nonstr no longer have any pointers, so we can compare them
	   directly. */
	return memcmp(&set1_nonstr, &set2_nonstr, sizeof(set1_nonstr)) == 0;
}

void ssl_iostream_settings_drop_stream_only(struct ssl_iostream_settings *set)
{
	set->verbose = FALSE;
	set->verbose_invalid_cert = FALSE;
	set->allow_invalid_cert = FALSE;
}

const char *ssl_iostream_get_cipher(struct ssl_iostream *ssl_io,
				    unsigned int *bits_r)
{
	return ssl_vfuncs->get_cipher(ssl_io, bits_r);
}

const char *ssl_iostream_get_pfs(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_pfs(ssl_io);
}

const char *ssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io)
{
	return ssl_vfuncs->get_protocol_name(ssl_io);
}
