/*
 * TLS module - management interface
 *
 * Copyright (C) 2005 iptelorg GmbH
 * Copyright (C) 2013 Motorola Solutions, Inc.
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/** tls module management interface (rpc).
 * @file tls_rpc.c
 * @ingroup tls
 * Module: @ref tls
 */


#include "../../core/rpc.h"
#include "../../core/tcp_conn.h"
#include "../../core/tcp_info.h"
#include "../../core/timer.h"
#include "../../core/cfg/cfg.h"
#include "../../core/dprint.h"
#include "tls_init.h"
#include "tls_mod.h"
#include "tls_domain.h"
#include "tls_config.h"
#include "tls_util.h"
#include "tls_server.h"
#include "tls_ct_wrq.h"
#include "tls_rpc.h"
#include "tls_cfg.h"

static const char *tls_reload_doc[2] = {"Reload TLS configuration file", 0};

static void tls_reload(rpc_t *rpc, void *ctx)
{
	tls_domains_cfg_t *cfg;
	str tls_domains_cfg_file;

	tls_domains_cfg_file = cfg_get(tls, tls_cfg, config_file);
	if(!tls_domains_cfg_file.s) {
		rpc->fault(ctx, 500, "No TLS configuration file configured");
		return;
	}

	/* Try to delete old configurations first */
	collect_garbage();

	cfg = tls_load_config(&tls_domains_cfg_file);

	if(!cfg) {
		rpc->fault(ctx, 500,
				"Error while loading TLS configuration file"
				" (consult server log)");
		return;
	}

	if(tls_fix_domains_cfg(cfg, &srv_defaults, &cli_defaults) < 0) {
		rpc->fault(ctx, 500,
				"Error while fixing TLS configuration"
				" (consult server log)");
		goto error;
	}
	if(tls_check_sockets(cfg) < 0) {
		rpc->fault(ctx, 500,
				"No server listening socket found for one of"
				" TLS domains (consult server log)");
		goto error;
	}

	DBG("TLS configuration successfully loaded");

	lock_get(tls_domains_cfg_lock);

	cfg->next = (*tls_domains_cfg);
	*tls_domains_cfg = cfg;

	lock_release(tls_domains_cfg_lock);
	rpc->rpl_printf(ctx, "Ok. TLS configuration reloaded.");
	return;

error:
	tls_free_cfg(cfg);
}


static const char *tls_list_doc[2] = {"List currently open TLS connections", 0};

extern gen_lock_t *tcpconn_lock;
extern struct tcp_connection **tcpconn_id_hash;

static void tls_list(rpc_t *rpc, void *c)
{
	char buf[128];
	char src_ip[IP_ADDR_MAX_STR_SIZE];
	char dst_ip[IP_ADDR_MAX_STR_SIZE];
	void *handle;
	char *tls_info;
	char *state;
	struct tls_extra_data *tls_d;
	struct tcp_connection *con;
	int i, len, timeout;
	struct tm timestamp;
	char timestamp_s[128];
	const char *sni;

	TCPCONN_LOCK;
	for(i = 0; i < TCP_ID_HASH_SIZE; i++) {
		for(con = tcpconn_id_hash[i]; con; con = con->id_next) {
			if(con->rcv.proto != PROTO_TLS)
				continue;
			tls_d = con->extra_data;
			rpc->add(c, "{", &handle);
			/* tcp data */
			if((len = ip_addr2sbuf(&con->rcv.src_ip, src_ip, sizeof(src_ip)))
					== 0)
				BUG("failed to convert source ip");
			src_ip[len] = 0;
			if((len = ip_addr2sbuf(&con->rcv.dst_ip, dst_ip, sizeof(dst_ip)))
					== 0)
				BUG("failed to convert destination ip");
			dst_ip[len] = 0;
			timeout = TICKS_TO_S(con->timeout - get_ticks_raw());
			timestamp = *localtime(&con->timestamp);
			if(snprintf(timestamp_s, 128, "%d-%02d-%02d %02d:%02d:%02d",
					   timestamp.tm_year + 1900, timestamp.tm_mon + 1,
					   timestamp.tm_mday, timestamp.tm_hour, timestamp.tm_min,
					   timestamp.tm_sec)
					< 0) {
				timestamp_s[0] = 'N';
				timestamp_s[1] = '/';
				timestamp_s[2] = 'A';
				timestamp_s[3] = '\0';
			}

			if(tls_d) {
				sni = SSL_get_servername(tls_d->ssl, TLSEXT_NAMETYPE_host_name);
				if(sni == NULL) {
					sni = "N/A";
				}
			} else {
				sni = "N/A";
			}

			rpc->struct_add(handle, "dssdsdsd", "id", con->id, "sni", sni,
					"timestamp", timestamp_s, "timeout", timeout, "src_ip",
					src_ip, "src_port", con->rcv.src_port, "dst_ip", dst_ip,
					"dst_port", con->rcv.dst_port);
			if(tls_d) {
				if(SSL_get_current_cipher(tls_d->ssl)) {
					tls_info = SSL_CIPHER_description(
							SSL_get_current_cipher(tls_d->ssl), buf,
							sizeof(buf));
					len = strlen(buf);
					if(len && buf[len - 1] == '\n')
						buf[len - 1] = '\0';
				} else {
					tls_info = "unknown";
				}
				/* tls data */
				state = "unknown/error";
				lock_get(&con->write_lock);
				switch(tls_d->state) {
					case S_TLS_NONE:
						state = "none/init";
						break;
					case S_TLS_ACCEPTING:
						state = "tls_accept";
						break;
					case S_TLS_CONNECTING:
						state = "tls_connect";
						break;
					case S_TLS_ESTABLISHED:
						state = "established";
						break;
				}
				rpc->struct_add(handle, "sddds", "cipher", tls_info,
						"ct_wq_size", tls_d->ct_wq ? tls_d->ct_wq->queued : 0,
						"enc_rd_buf",
						tls_d->enc_rd_buf ? tls_d->enc_rd_buf->size : 0,
						"flags", tls_d->flags, "state", state);
				lock_release(&con->write_lock);
			} else {
				rpc->struct_add(handle, "sddds", "cipher", "unknown",
						"ct_wq_size", 0, "enc_rd_buf", 0, "flags", 0, "state",
						"pre-init");
			}
		}
	}
	TCPCONN_UNLOCK;
}


static const char *tls_info_doc[2] = {"Returns internal tls related info.", 0};

static void tls_info(rpc_t *rpc, void *c)
{
	struct tcp_gen_info ti;
	void *handle;

	tcp_get_info(&ti);
	rpc->add(c, "{", &handle);
	rpc->struct_add(handle, "ddd", "max_connections", ti.tls_max_connections,
			"opened_connections", ti.tls_connections_no,
			"clear_text_write_queued_bytes", tls_ct_wq_total_bytes());
}


static const char *tls_options_doc[2] = {
		"Dumps all the tls config options.", 0};

static void tls_options(rpc_t *rpc, void *c)
{
	void *handle;
	rpc->add(c, "{", &handle);
	rpc->struct_add(handle, "dSdddSSSSSdSSdddddddddddddd", "force_run",
			cfg_get(tls, tls_cfg, force_run), "method",
			&cfg_get(tls, tls_cfg, method), "verify_certificate",
			cfg_get(tls, tls_cfg, verify_cert),

			"verify_depth", cfg_get(tls, tls_cfg, verify_depth),
			"require_certificate", cfg_get(tls, tls_cfg, require_cert),
			"verify_client", &cfg_get(tls, tls_cfg, verify_client),
			"private_key", &cfg_get(tls, tls_cfg, private_key), "ca_list",
			&cfg_get(tls, tls_cfg, ca_list), "certificate",
			&cfg_get(tls, tls_cfg, certificate), "cipher_list",
			&cfg_get(tls, tls_cfg, cipher_list), "session_cache",
			cfg_get(tls, tls_cfg, session_cache), "session_id",
			&cfg_get(tls, tls_cfg, session_id), "config",
			&cfg_get(tls, tls_cfg, config_file), "log",
			cfg_get(tls, tls_cfg, log), "debug", cfg_get(tls, tls_cfg, debug),
			"connection_timeout",
			TICKS_TO_S(cfg_get(tls, tls_cfg, con_lifetime)),
			"disable_compression", cfg_get(tls, tls_cfg, disable_compression),
			"ssl_release_buffers", cfg_get(tls, tls_cfg, ssl_release_buffers),
			"ssl_freelist_max", cfg_get(tls, tls_cfg, ssl_freelist_max),
			"ssl_max_send_fragment",
			cfg_get(tls, tls_cfg, ssl_max_send_fragment), "ssl_read_ahead",
			cfg_get(tls, tls_cfg, ssl_read_ahead), "send_close_notify",
			cfg_get(tls, tls_cfg, send_close_notify), "low_mem_threshold1",
			cfg_get(tls, tls_cfg, low_mem_threshold1), "low_mem_threshold2",
			cfg_get(tls, tls_cfg, low_mem_threshold2), "ct_wq_max",
			cfg_get(tls, tls_cfg, ct_wq_max), "con_ct_wq_max",
			cfg_get(tls, tls_cfg, con_ct_wq_max), "ct_wq_blk_size",
			cfg_get(tls, tls_cfg, ct_wq_blk_size));
}

static const char *tls_kill_doc[2] = {
		"Kills a tls session, identified via id.", 0};

static void tls_kill(rpc_t *rpc, void *c)
{
	struct tcp_connection *con;
	int i, kill_id = 0;

	if(rpc->scan(c, "d", &kill_id) < 0) {
		/* Reply is set automatically by scan upon failure,
		* no need to do anything here
		*/
		return;
	}

	TCPCONN_LOCK;
	for(i = 0; i < TCP_ID_HASH_SIZE; i++) {
		for(con = tcpconn_id_hash[i]; con; con = con->id_next) {
			if(con->rcv.proto != PROTO_TLS)
				continue;
			if(con->id == kill_id) {
				con->state = -2;
				con->timeout = get_ticks_raw();

				TCPCONN_UNLOCK;

				rpc->add(c, "s", "OK");
				return;
			}
		}
	}
	TCPCONN_UNLOCK;

	rpc->add(c, "s", "TLS connection id not found");
}


rpc_export_t tls_rpc[] = {
		{"tls.reload", tls_reload, tls_reload_doc, RPC_EXEC_DELTA},
		{"tls.list", tls_list, tls_list_doc, RET_ARRAY},
		{"tls.info", tls_info, tls_info_doc, 0},
		{"tls.options", tls_options, tls_options_doc, 0},
		{"tls.kill", tls_kill, tls_kill_doc, 0}, {0, 0, 0, 0}};
