view libirccd/irccd/daemon/server_service.cpp @ 785:7145a3df4cb7

misc: rename host to hostname, closes #941 @2h
author David Demelier <markand@malikania.fr>
date Wed, 07 Nov 2018 12:55:00 +0100
parents 317c66a131be
children
line wrap: on
line source

/*
 * server_service.cpp -- server service
 *
 * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
 *
 * 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 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.
 */

#include <irccd/json_util.hpp>
#include <irccd/string_util.hpp>

#include "irccd.hpp"
#include "logger.hpp"
#include "plugin_service.hpp"
#include "rule_service.hpp"
#include "server.hpp"
#include "server_service.hpp"
#include "server_util.hpp"
#include "transport_service.hpp"

namespace irccd {

namespace {

class dispatcher {
private:
	irccd& irccd_;

	template <typename EventNameFunc, typename ExecFunc>
	void dispatch(std::string_view, std::string_view, std::string_view, EventNameFunc&&, ExecFunc);

public:
	dispatcher(irccd& irccd);
	void operator()(const std::monostate&);
	void operator()(const connect_event&);
	void operator()(const disconnect_event&);
	void operator()(const invite_event&);
	void operator()(const join_event&);
	void operator()(const kick_event&);
	void operator()(const message_event&);
	void operator()(const me_event&);
	void operator()(const mode_event&);
	void operator()(const names_event&);
	void operator()(const nick_event&);
	void operator()(const notice_event&);
	void operator()(const part_event&);
	void operator()(const topic_event&);
	void operator()(const whois_event&);
};

template <typename EventNameFunc, typename ExecFunc>
void dispatcher::dispatch(std::string_view server,
                          std::string_view origin,
                          std::string_view target,
                          EventNameFunc&& name_func,
                          ExecFunc exec_func)
{
	for (const auto& plugin : irccd_.plugins().list()) {
		const auto eventname = name_func(*plugin);
		const auto allowed = irccd_.rules().solve(server, target, origin, plugin->get_name(), eventname);

		if (!allowed) {
			irccd_.get_log().debug("rule", "") << "event skipped on match" << std::endl;
			continue;
		}

		irccd_.get_log().debug("rule", "") << "event allowed" << std::endl;

		try {
			exec_func(*plugin);
		} catch (const std::exception& ex) {
			irccd_.get_log().warning(*plugin) << ex.what() << std::endl;
		}
	}
}

dispatcher::dispatcher(irccd& irccd)
	: irccd_(irccd)
{
}

void dispatcher::operator()(const std::monostate&)
{
}

void dispatcher::operator()(const connect_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onConnect" << std::endl;
	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onConnect"             },
		{ "server",     ev.server->get_id()     }
	}));

	dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
		[=] (plugin&) -> std::string {
			return "onConnect";
		},
		[=] (plugin& plugin) {
			plugin.handle_connect(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const disconnect_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onDisconnect" << std::endl;
	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onDisconnect"          },
		{ "server",     ev.server->get_id()     }
	}));

	dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
		[=] (plugin&) -> std::string {
			return "onDisconnect";
		},
		[=] (plugin& plugin) {
			plugin.handle_disconnect(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const invite_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onInvite:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  target: " << ev.nickname << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onInvite"              },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onInvite";
		},
		[=] (plugin& plugin) {
			plugin.handle_invite(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const join_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onJoin:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onJoin"                },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onJoin";
		},
		[=] (plugin& plugin) {
			plugin.handle_join(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const kick_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onKick:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  target: " << ev.target << std::endl;
	irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onKick"                },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "target",     ev.target               },
		{ "reason",     ev.reason               }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onKick";
		},
		[=] (plugin& plugin) {
			plugin.handle_kick(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const message_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onMessage:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onMessage"             },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "message",    ev.message              }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin& plugin) -> std::string {
			return server_util::message_type::parse(
				ev.message,
				ev.server->get_command_char(),
				plugin.get_id()
			).type == server_util::message_type::is_command ? "onCommand" : "onMessage";
		},
		[=] (plugin& plugin) mutable {
			auto copy = ev;
			auto pack = server_util::message_type::parse(
				copy.message,
				copy.server->get_command_char(),
				plugin.get_id()
			);

			copy.message = pack.message;

			if (pack.type == server_util::message_type::is_command)
				plugin.handle_command(irccd_, copy);
			else
				plugin.handle_message(irccd_, copy);
		}
	);
}

void dispatcher::operator()(const me_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onMe:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  target: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onMe"                  },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "target",     ev.channel              },
		{ "message",    ev.message              }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onMe";
		},
		[=] (plugin& plugin) {
			plugin.handle_me(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const mode_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onMode" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  mode: " << ev.mode << std::endl;
	irccd_.get_log().debug(*ev.server) << "  limit: " << ev.limit << std::endl;
	irccd_.get_log().debug(*ev.server) << "  user: " << ev.user << std::endl;
	irccd_.get_log().debug(*ev.server) << "  mask: " << ev.mask << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onMode"                },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "mode",       ev.mode                 },
		{ "limit",      ev.limit                },
		{ "user",       ev.user                 },
		{ "mask",       ev.mask                 }
	}));

	dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
		[=] (plugin &) -> std::string {
			return "onMode";
		},
		[=] (plugin &plugin) {
			plugin.handle_mode(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const names_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onNames:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;

	auto names = nlohmann::json::array();

	for (const auto& v : ev.names)
		names.push_back(v);

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onNames"               },
		{ "server",     ev.server->get_id()     },
		{ "channel",    ev.channel              },
		{ "names",      std::move(names)        }
	}));

	dispatch(ev.server->get_id(), /* origin */ "", ev.channel,
		[=] (plugin&) -> std::string {
			return "onNames";
		},
		[=] (plugin& plugin) {
			plugin.handle_names(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const nick_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onNick:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.nickname << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onNick"                },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "nickname",   ev.nickname             }
	}));

	dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
		[=] (plugin&) -> std::string {
			return "onNick";
		},
		[=] (plugin& plugin) {
			plugin.handle_nick(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const notice_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onNotice:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onNotice"              },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "message",    ev.message              }
	}));

	dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
		[=] (plugin&) -> std::string {
			return "onNotice";
		},
		[=] (plugin& plugin) {
			plugin.handle_notice(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const part_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onPart:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onPart"                },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "reason",     ev.reason               }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onPart";
		},
		[=] (plugin& plugin) {
			plugin.handle_part(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const topic_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onTopic:" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
	irccd_.get_log().debug(*ev.server) << "  topic: " << ev.topic << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onTopic"               },
		{ "server",     ev.server->get_id()     },
		{ "origin",     ev.origin               },
		{ "channel",    ev.channel              },
		{ "topic",      ev.topic                }
	}));

	dispatch(ev.server->get_id(), ev.origin, ev.channel,
		[=] (plugin&) -> std::string {
			return "onTopic";
		},
		[=] (plugin& plugin) {
			plugin.handle_topic(irccd_, ev);
		}
	);
}

void dispatcher::operator()(const whois_event& ev)
{
	irccd_.get_log().debug(*ev.server) << "event onWhois" << std::endl;
	irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.whois.nick << std::endl;
	irccd_.get_log().debug(*ev.server) << "  username: " << ev.whois.user << std::endl;
	irccd_.get_log().debug(*ev.server) << "  hostname: " << ev.whois.hostname << std::endl;
	irccd_.get_log().debug(*ev.server) << "  realname: " << ev.whois.realname << std::endl;
	irccd_.get_log().debug(*ev.server) << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;

	irccd_.transports().broadcast(nlohmann::json::object({
		{ "event",      "onWhois"               },
		{ "server",     ev.server->get_id()     },
		{ "nickname",   ev.whois.nick           },
		{ "username",   ev.whois.user           },
		{ "hostname",   ev.whois.hostname       },
		{ "realname",   ev.whois.realname       }
	}));

	dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
		[=] (plugin&) -> std::string {
			return "onWhois";
		},
		[=] (plugin& plugin) {
			plugin.handle_whois(irccd_, ev);
		}
	);
}

} // !namespace

void server_service::handle_error(const std::shared_ptr<server>& server,
                                  const std::error_code& code)
{
	assert(server);

	irccd_.get_log().warning(*server) << code.message() << std::endl;
	irccd_.get_log().warning(*server) << int(server->get_options()) << std::endl;

	if ((server->get_options() & server::options::auto_reconnect) != server::options::auto_reconnect)
		remove(server->get_id());
	else {
		irccd_.get_log().info(*server) << "reconnecting in "
			<< server->get_reconnect_delay() << " second(s)" << std::endl;
		wait(server);
	}
}

void server_service::handle_wait(const std::shared_ptr<server>& server, const std::error_code& code)
{
	/*
	 * The timer runs on his own control, it will complete either if the delay
	 * was reached, there was an error or if the io_context was called to cancel
	 * all pending operations.
	 *
	 * This means while the timer is running someone may already have ask a
	 * server for explicit reconnection (e.g. remote command, plugin). Thus we
	 * check for server state and if it is still present in service.
	 */
	if (code && code != std::errc::operation_canceled) {
		irccd_.get_log().warning(*server) << code.message() << std::endl;
		return;
	}

	if (server->get_state() == server::state::connected || !has(server->get_id()))
		return;

	connect(server);
}

void server_service::handle_recv(const std::shared_ptr<server>& server,
                                 const std::error_code& code,
                                 const event& event)
{
	assert(server);

	if (code)
		handle_error(server, code);
	else {
		recv(server);
		std::visit(dispatcher(irccd_), event);
	}
}

void server_service::handle_connect(const std::shared_ptr<server>& server, const std::error_code& code)
{
	if (code)
		handle_error(server, code);
	else
		recv(server);
}

void server_service::wait(const std::shared_ptr<server>& server)
{
	assert(server);

	auto timer = std::make_shared<boost::asio::deadline_timer>(irccd_.get_service());

	timer->expires_from_now(boost::posix_time::seconds(server->get_reconnect_delay()));
	timer->async_wait([this, server, timer] (auto code) {
		handle_wait(server, code);
	});
}

void server_service::recv(const std::shared_ptr<server>& server)
{
	assert(server);

	server->recv([this, server] (auto code, auto event) {
		handle_recv(server, code, event);
	});
}

void server_service::connect(const std::shared_ptr<server>& server)
{
	assert(server);

	server->connect([this, server] (auto code) {
		handle_connect(server, code);
	});
}

server_service::server_service(irccd &irccd)
	: irccd_(irccd)
{
}

auto server_service::list() const noexcept -> const std::vector<std::shared_ptr<server>>&
{
	return servers_;
}

auto server_service::has(const std::string& name) const noexcept -> bool
{
	return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
		return server->get_id() == name;
	}) > 0;
}

void server_service::add(std::shared_ptr<server> server)
{
	assert(server);
	assert(!has(server->get_id()));

	servers_.push_back(server);
	connect(server);
}

auto server_service::get(std::string_view name) const noexcept -> std::shared_ptr<server>
{
	const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
		return server->get_id() == name;
	});

	if (it == servers_.end())
		return nullptr;

	return *it;
}

auto server_service::require(std::string_view name) const -> std::shared_ptr<server>
{
	if (!string_util::is_identifier(name))
		throw server_error(server_error::invalid_identifier);

	const auto s = get(name);

	if (!s)
		throw server_error(server_error::not_found);

	return s;
}

void server_service::disconnect(std::string_view id)
{
	const auto s = require(id);

	s->disconnect();
	dispatcher{irccd_}(disconnect_event{s});
}

void server_service::reconnect(std::string_view id)
{
	disconnect(id);
	connect(require(id));
}

void server_service::reconnect()
{
	for (const auto& s : servers_) {
		try {
			s->disconnect();
			dispatcher{irccd_}(disconnect_event{s});
			connect(s);
		} catch (const server_error& ex) {
			irccd_.get_log().warning(*s) << ex.what() << std::endl;
		}
	}
}

void server_service::remove(std::string_view name)
{
	const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
		return server->get_id() == name;
	});

	if (it != servers_.end()) {
		(*it)->disconnect();
		servers_.erase(it);
	}
}

void server_service::clear() noexcept
{
	/*
	 * Copy the array, because disconnect() may trigger on_die signal which
	 * erase the server from itself.
	 */
	const auto save = servers_;

	for (const auto& server : save)
		server->disconnect();

	servers_.clear();
}

void server_service::load(const config& cfg) noexcept
{
	for (const auto& section : cfg) {
		if (section.get_key() != "server")
			continue;

		const auto id = section.get("name").get_value();

		try {
			auto server = server_util::from_config(irccd_.get_service(), section);

			if (has(server->get_id()))
				throw server_error(server_error::already_exists);

			add(std::move(server));
		} catch (const std::exception& ex) {
			irccd_.get_log().warning("server", id) << ex.what() << std::endl;
		}
	}
}

namespace logger {

auto loggable_traits<server>::get_category(const server&) -> std::string_view
{
	return "server";
}

auto loggable_traits<server>::get_component(const server& sv) -> std::string_view
{
	return sv.get_id();
}

} // !logger

} // !irccd