/************************************************************************
 *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_server.c
 *   (c) 2022- Bram Matthys and The UnrealIRCd team
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers. 
 *
 *   This program 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 1, or (at your option)
 *   any later version.
 *
 *   This program 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, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/** @file
 * @brief Inter-process I/O
 */
#include "unrealircd.h"
#include <ares.h>

/* Forward declarations */
CMD_FUNC(procio_status);
CMD_FUNC(procio_modules);
CMD_FUNC(procio_rehash);
CMD_FUNC(procio_exit);
CMD_FUNC(procio_help);
void start_of_control_client_handshake(Client *client);
int procio_accept(Client *client);

/** Create the unrealircd.ctl socket (server-side) */
void add_proc_io_server(void)
{
	ConfigItem_listen *listener;

#ifdef _WIN32
	/* Ignore silently on Windows versions older than W10 build 17061 */
	if (!unix_sockets_capable())
		return;
#endif
	listener = safe_alloc(sizeof(ConfigItem_listen));
	safe_strdup(listener->file, CONTROLFILE);
	listener->socket_type = SOCKET_TYPE_UNIX;
	listener->options = LISTENER_CONTROL|LISTENER_NO_CHECK_CONNECT_FLOOD|LISTENER_NO_CHECK_ZLINED;
	listener->start_handshake = start_of_control_client_handshake;
	listener->fd = -1;
	AddListItem(listener, conf_listen);
	if (add_listener(listener) == -1)
		exit(-1);
	CommandAdd(NULL, "STATUS", procio_status, MAXPARA, CMD_CONTROL);
	CommandAdd(NULL, "MODULES", procio_modules, MAXPARA, CMD_CONTROL);
	CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL);
	CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL);
	CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL);
	HookAdd(NULL, HOOKTYPE_ACCEPT, -1000000, procio_accept);
}

int procio_accept(Client *client)
{
	if (client->local->listener->options & LISTENER_CONTROL)
	{
		irccounts.unknown--;
		client->status = CLIENT_STATUS_CONTROL;
		list_del(&client->lclient_node);
		list_add(&client->lclient_node, &control_list);
	}
	return 0;
}

/** Start of "control channel" client handshake - this is minimal
 * @param client	The client
 */
void start_of_control_client_handshake(Client *client)
{
	sendto_one(client, NULL, "READY %s %s", me.name, version);
	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
}

CMD_FUNC(procio_status)
{
	sendto_one(client, NULL, "REPLY servername %s", me.name);
	sendto_one(client, NULL, "REPLY unrealircd_version %s", version);
	sendto_one(client, NULL, "REPLY libssl_version %s", SSLeay_version(SSLEAY_VERSION));
	sendto_one(client, NULL, "REPLY libsodium_version %s", sodium_version_string());
#ifdef USE_LIBCURL
	sendto_one(client, NULL, "REPLY libcurl_version %s", curl_version());
#endif
	sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL));
	sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version());
#if JANSSON_VERSION_HEX >= 0x020D00
	sendto_one(client, NULL, "REPLY libjansson %s\n", jansson_version_str());
#endif
	sendto_one(client, NULL, "REPLY global_clients %ld", (long)irccounts.clients);
	sendto_one(client, NULL, "REPLY local_clients %ld", (long)irccounts.me_clients);
	sendto_one(client, NULL, "REPLY operators %ld", (long)irccounts.operators);
	sendto_one(client, NULL, "REPLY servers %ld", (long)irccounts.servers);
	sendto_one(client, NULL, "REPLY channels %ld", (long)irccounts.channels);
	sendto_one(client, NULL, "END 0");
}

extern MODVAR Module *Modules;
CMD_FUNC(procio_modules)
{
	char tmp[1024];
	Module *m;

	for (m = Modules; m; m = m->next)
	{
		tmp[0] = '\0';
		if (m->flags & MODFLAG_DELAYED)
			strlcat(tmp, "[Unloading] ", sizeof(tmp));
		if (m->options & MOD_OPT_PERM_RELOADABLE)
			strlcat(tmp, "[PERM-BUT-RELOADABLE] ", sizeof(tmp));
		if (m->options & MOD_OPT_PERM)
			strlcat(tmp, "[PERM] ", sizeof(tmp));
		if (!(m->options & MOD_OPT_OFFICIAL))
			strlcat(tmp, "[3RD] ", sizeof(tmp));
		sendto_one(client, NULL, "REPLY %s %s - %s - by %s %s",
		           m->header->name,
		           m->header->version,
		           m->header->description,
		           m->header->author,
		           tmp);
	}
	sendto_one(client, NULL, "END 0");
}

CMD_FUNC(procio_rehash)
{
	if (loop.rehashing)
	{
		sendto_one(client, NULL, "REPLY ERROR: A rehash is already in progress");
		sendto_one(client, NULL, "END 1");
		return;
	}
	

	if (parv[1] && !strcmp(parv[1], "-tls"))
	{
		int ret;
		SetMonitorRehash(client);
		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)");
		ret = reinit_tls();
		sendto_one(client, NULL, "END %d", ret == 0 ? -1 : 0);
		ClearMonitorRehash(client);
	} else {
		SetMonitorRehash(client);
		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
		request_rehash(client);
		/* completion will go via procio_post_rehash() */
	}
}

CMD_FUNC(procio_exit)
{
	sendto_one(client, NULL, "END 0");
	exit_client(client, NULL, "");
}

CMD_FUNC(procio_help)
{
	sendto_one(client, NULL, "REPLY Commands available:");
	sendto_one(client, NULL, "REPLY EXIT");
	sendto_one(client, NULL, "REPLY HELP");
	sendto_one(client, NULL, "REPLY REHASH");
	sendto_one(client, NULL, "REPLY STATUS");
	sendto_one(client, NULL, "REPLY MODULES");
	sendto_one(client, NULL, "END 0");
}

/** Called upon REHASH completion (with or without failure) */
void procio_post_rehash(int failure)
{
	Client *client;

	list_for_each_entry(client, &control_list, lclient_node)
	{
		if (IsMonitorRehash(client))
		{
			sendto_one(client, NULL, "END %d", failure);
			ClearMonitorRehash(client);
		}
	}
}
