view libirccd/irccd/daemon/server.hpp @ 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 3c090c1ff4f0
line wrap: on
line source

/*
 * server.hpp -- an IRC server
 *
 * 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.
 */

#ifndef IRCCD_DAEMON_SERVER_HPP
#define IRCCD_DAEMON_SERVER_HPP

/**
 * \file server.hpp
 * \brief IRC Server.
 */

#include <irccd/sysconfig.hpp>

#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <variant>
#include <vector>

#include <json.hpp>

#include "irc.hpp"

namespace irccd {

class server;

/**
 * \brief Prefixes for nicknames.
 */
enum class channel_mode {
	creator         = 'O',                  //!< Channel creator
	half_op         = 'h',                  //!< Half operator
	op              = 'o',                  //!< Channel operator
	protection      = 'a',                  //!< Unkillable
	voiced          = 'v'                   //!< Voice power
};

/**
 * \brief A channel to join with an optional password.
 */
struct channel {
	std::string name;                       //!< the channel to join
	std::string password;                   //!< the optional password
};

/**
 * \brief Describe a whois information.
 */
struct whois_info {
	std::string nick;                       //!< user's nickname
	std::string user;                       //!< user's user
	std::string hostname;                   //!< hostname
	std::string realname;                   //!< realname
	std::vector<std::string> channels;      //!< the channels where the user is
};

/**
 * \brief Connection success event.
 */
struct connect_event {
	std::shared_ptr<class server> server;   //!< The server.
};

/**
 * \brief Connection success event.
 */
struct disconnect_event {
	std::shared_ptr<class server> server;   //!< The server.
};

/**
 * \brief Invite event.
 */
struct invite_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string nickname;                   //!< The nickname (you).
};

/**
 * \brief Join event.
 */
struct join_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
};

/**
 * \brief Kick event.
 */
struct kick_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string target;                     //!< The target.
	std::string reason;                     //!< The reason (Optional).
};

/**
 * \brief Message event.
 */
struct message_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string message;                    //!< The message.
};

/**
 * \brief CTCP action event.
 */
struct me_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string message;                    //!< The message.
};

/**
 * \brief Mode event.
 */
struct mode_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel or target.
	std::string mode;                       //!< The mode.
	std::string limit;                      //!< The optional limit.
	std::string user;                       //!< The optional user.
	std::string mask;                       //!< The optional ban mask.
};

/**
 * \brief Names listing event.
 */
struct names_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string channel;                    //!< The channel.
	std::vector<std::string> names;         //!< The names.
};

/**
 * \brief Nick change event.
 */
struct nick_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string nickname;                   //!< The new nickname.
};

/**
 * \brief Notice event.
 */
struct notice_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel or target.
	std::string message;                    //!< The message.
};

/**
 * \brief Part event.
 */
struct part_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string reason;                     //!< The reason.
};

/**
 * \brief Topic event.
 */
struct topic_event {
	std::shared_ptr<class server> server;   //!< The server.
	std::string origin;                     //!< The originator.
	std::string channel;                    //!< The channel.
	std::string topic;                      //!< The topic message.
};

/**
 * \brief Whois event.
 */
struct whois_event {
	std::shared_ptr<class server> server;   //!< The server.
	whois_info whois;                       //!< The whois information.
};

/**
 * \brief Store all possible events.
 */
using event = std::variant<
	std::monostate,
	connect_event,
	disconnect_event,
	invite_event,
	join_event,
	kick_event,
	me_event,
	message_event,
	mode_event,
	names_event,
	nick_event,
	notice_event,
	part_event,
	topic_event,
	whois_event
>;

/**
 * \brief The class that connect to a IRC server.
 *
 * This class is higher level than irc connection, it does identify process,
 * parsing message, translating messages and queue'ing user requests.
 */
class server : public std::enable_shared_from_this<server> {
public:
	/**
	 * Completion handler once network connection is complete.
	 */
	using connect_handler = std::function<void (std::error_code)>;

	/**
	 * Completion handler once a network message has arrived.
	 */
	using recv_handler = std::function<void (std::error_code, event)>;

	/**
	 * \brief Various options for server.
	 */
	enum class options : std::uint8_t {
		none            = 0,            //!< No options
		ipv6            = (1 << 0),     //!< Connect using IPv6
		ssl             = (1 << 1),     //!< Use SSL
		ssl_verify      = (1 << 2),     //!< Verify SSL
		auto_rejoin     = (1 << 3),     //!< Auto rejoin a kick
		auto_reconnect  = (1 << 4),     //!< Auto reconnect on disconnection
		join_invite     = (1 << 5)      //!< Join a channel on invitation
	};

	/**
	 * \brief Describe current server state.
	 */
	enum class state : std::uint8_t {
		disconnected,                   //!< not connected at all,
		connecting,                     //!< network connection in progress,
		identifying,                    //!< sending nick, user and password commands,
		connected                       //!< ready for use.
	};

protected:
	/*
	 * \brief Server state.
	 */
	state state_{state::disconnected};

private:
	// Requested and joined channels.
	std::vector<channel> rchannels_;
	std::set<std::string> jchannels_;

	// Identifier.
	std::string id_;

	// Connection information.
	std::string hostname_;
	std::string password_;
	std::uint16_t port_{6667};
	options flags_{options::none};

	// Identity.
	std::string nickname_;
	std::string username_;
	std::string realname_{"IRC Client Daemon"};
	std::string ctcpversion_{"IRC Client Daemon"};

	// Settings.
	std::string command_char_{"!"};
	std::uint16_t recodelay_{30};
	std::uint16_t timeout_{1000};

	// Server information.
	std::map<channel_mode, char> modes_;

	// Misc.
	boost::asio::io_service& service_;
	boost::asio::deadline_timer timer_;
	std::shared_ptr<irc::connection> conn_;
	std::deque<std::string> queue_;
	std::map<std::string, std::set<std::string>> names_map_;
	std::map<std::string, whois_info> whois_map_;

	auto dispatch_connect(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_endofnames(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_endofwhois(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_invite(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_isupport(const irc::message&) -> bool;
	auto dispatch_join(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_kick(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_mode(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_namreply(const irc::message&) -> bool;
	auto dispatch_nick(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_notice(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_part(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_ping(const irc::message&) -> bool;
	auto dispatch_privmsg(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_topic(const irc::message&, const recv_handler&) -> bool;
	auto dispatch_whoischannels(const irc::message&) -> bool;
	auto dispatch_whoisuser(const irc::message&) -> bool;
	auto dispatch(const irc::message&, const recv_handler&) -> bool;

	// I/O and connection.
	void flush();
	void identify();
	void handle_send(const std::error_code&);
	void handle_recv(const std::error_code&, const irc::message&, const recv_handler&);
	void handle_wait(const std::error_code&, const connect_handler&);
	void handle_connect(const std::error_code&, const connect_handler&);

public:
	/**
	 * Construct a server.
	 *
	 * \pre !host.empty()
	 * \param service the service
	 * \param name the identifier
	 * \param hostname the hostname
	 */
	server(boost::asio::io_service& service, std::string id, std::string hostname = "localhost");

	/**
	 * Destructor. Close the connection if needed.
	 */
	virtual ~server();

	/**
	 * Get the current server state.
	 *
	 * \return the state
	 */
	auto get_state() const noexcept -> state;

	/**
	 * Get the server identifier.
	 *
	 * \return the id
	 */
	auto get_id() const noexcept -> const std::string&;

	/**
	 * Get the hostname.
	 *
	 * \return the hostname
	 */
	auto get_hostname() const noexcept -> const std::string&;

	/**
	 * Get the password.
	 *
	 * \return the password
	 */
	auto get_password() const noexcept -> const std::string&;

	/**
	 * Set the password.
	 *
	 * An empty password means no password.
	 *
	 * \param password the password
	 */
	void set_password(std::string password) noexcept;

	/**
	 * Get the port.
	 *
	 * \return the port
	 */
	auto get_port() const noexcept -> std::uint16_t;

	/**
	 * Set the port.
	 *
	 * \param port the port
	 */
	void set_port(std::uint16_t port) noexcept;

	/**
	 * Get the options flags.
	 *
	 * \return the flags
	 */
	auto get_options() const noexcept -> options;

	/**
	 * Set the options flags.
	 *
	 * \param flags the flags
	 */
	void set_options(options flags) noexcept;

	/**
	 * Get the nickname.
	 *
	 * \return the nickname
	 */
	auto get_nickname() const noexcept -> const std::string&;

	/**
	 * Set the nickname.
	 *
	 * If the server is connected, send a nickname command to the IRC server,
	 * otherwise change it instantly.
	 *
	 * \param nickname the nickname
	 */
	void set_nickname(std::string nickname);

	/**
	 * Get the username.
	 *
	 * \return the username
	 */
	auto get_username() const noexcept -> const std::string&;

	/**
	 * Set the username.
	 *
	 * \param name the username
	 * \note the username will be changed on the next connection
	 */
	void set_username(std::string name) noexcept;

	/**
	 * Get the realname.
	 *
	 * \return the realname
	 */
	auto get_realname() const noexcept -> const std::string&;

	/**
	 * Set the realname.
	 *
	 * \param realname the username
	 * \note the username will be changed on the next connection
	 */
	void set_realname(std::string realname) noexcept;

	/**
	 * Get the CTCP version.
	 *
	 * \return the CTCP version
	 */
	auto get_ctcp_version() const noexcept -> const std::string&;

	/**
	 * Set the CTCP version.
	 *
	 * \param ctcpversion the version
	 */
	void set_ctcp_version(std::string ctcpversion);

	/**
	 * Get the command character.
	 *
	 * \return the character
	 */
	auto get_command_char() const noexcept -> const std::string&;

	/**
	 * Set the command character.
	 *
	 * \pre !command_char_.empty()
	 * \param command_char the command character
	 */
	void set_command_char(std::string command_char) noexcept;

	/**
	 * Get the reconnection delay before retrying.
	 *
	 * \return the number of seconds
	 */
	auto get_reconnect_delay() const noexcept -> std::uint16_t;

	/**
	 * Set the number of seconds before retrying.
	 *
	 * \param reconnect_delay the number of seconds
	 */
	void set_reconnect_delay(std::uint16_t reconnect_delay) noexcept;

	/**
	 * Get the ping timeout.
	 *
	 * \return the ping timeout
	 */
	auto get_ping_timeout() const noexcept -> std::uint16_t;

	/**
	 * Set the ping timeout before considering a server as dead.
	 *
	 * \param ping_timeout the delay in seconds
	 */
	void set_ping_timeout(std::uint16_t ping_timeout) noexcept;

	/**
	 * Get the list of channels joined.
	 *
	 * \return the channels
	 */
	auto get_channels() const noexcept -> const std::set<std::string>&;

	/**
	 * Determine if the nickname is the bot itself.
	 *
	 * \param nick the nickname to check
	 * \return true if it is the bot
	 */
	auto is_self(std::string_view nick) const noexcept -> bool;

	/**
	 * Start connecting.
	 *
	 * This only initiate TCP connection and/or SSL handshaking, the identifying
	 * process may take some time and you must repeatedly call recv() to wait
	 * for connect_event.
	 *
	 * \pre handler != nullptr
	 * \param handler the completion handler
	 * \note the server must be kept alive until completion
	 */
	virtual void connect(connect_handler handler) noexcept;

	/**
	 * Force disconnection.
	 */
	virtual void disconnect() noexcept;

	/**
	 * Receive next event.
	 *
	 * \pre handler != nullptr
	 * \param handler the handler
	 * \note the server must be kept alive until completion
	 */
	virtual void recv(recv_handler handler) noexcept;

	/**
	 * Invite a user to a channel.
	 *
	 * \param target the target nickname
	 * \param channel the channel
	 */
	virtual void invite(std::string_view target, std::string_view channel);

	/**
	 * Join a channel, the password is optional and can be kept empty.
	 *
	 * \param channel the channel to join
	 * \param password the optional password
	 */
	virtual void join(std::string_view channel, std::string_view password = "");

	/**
	 * Kick someone from the channel. Please be sure to have the rights
	 * on that channel because errors won't be reported.
	 *
	 * \param target the target to kick
	 * \param channel from which channel
	 * \param reason the optional reason
	 */
	virtual void kick(std::string_view target,
	                  std::string_view channel,
	                  std::string_view reason = "");

	/**
	 * Send a CTCP Action as known as /me. The target may be either a
	 * channel or a nickname.
	 *
	 * \param target the nickname or the channel
	 * \param message the message
	 */
	virtual void me(std::string_view target, std::string_view message);

	/**
	 * Send a message to the specified target or channel.
	 *
	 * \param target the target
	 * \param message the message
	 */
	virtual void message(std::string_view target, std::string_view message);

	/**
	 * Change channel/user mode.
	 *
	 * \param channel the channel or nickname
	 * \param mode the mode
	 * \param limit the optional limit
	 * \param user the optional user
	 * \param mask the optional ban mask
	 */
	virtual void mode(std::string_view channel,
	                  std::string_view mode,
	                  std::string_view limit = "",
	                  std::string_view user = "",
	                  std::string_view mask = "");

	/**
	 * Request the list of names.
	 *
	 * \param channel the channel
	 */
	virtual void names(std::string_view channel);

	/**
	 * Send a private notice.
	 *
	 * \param target the target
	 * \param message the notice message
	 */
	virtual void notice(std::string_view target, std::string_view message);

	/**
	 * Part from a channel.
	 *
	 * Please note that the reason is not supported on all servers so if you
	 * want portability, don't provide it.
	 *
	 * \param channel the channel to leave
	 * \param reason the optional reason
	 */
	virtual void part(std::string_view channel, std::string_view reason = "");

	/**
	 * Send a raw message to the IRC server. You don't need to add
	 * message terminators.
	 *
	 * If the server is not yet connected, the command is postponed and will be
	 * ran when ready.
	 *
	 * \param raw the raw message (without `\r\n\r\n`)
	 */
	virtual void send(std::string_view raw);

	/**
	 * Change the channel topic.
	 *
	 * \param channel the channel
	 * \param topic the desired topic
	 */
	virtual void topic(std::string_view channel, std::string_view topic);

	/**
	 * Request for whois information.
	 *
	 * \param target the target nickname
	 */
	virtual void whois(std::string_view target);
};

/**
 * \cond IRCCD_HIDDEN_SYMBOLS
 */

/**
 * Apply bitwise XOR.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator^(server::options v1, server::options v2) noexcept -> server::options
{
	return static_cast<server::options>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
}

/**
 * Apply bitwise AND.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator&(server::options v1, server::options v2) noexcept -> server::options
{
	return static_cast<server::options>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2));
}

/**
 * Apply bitwise OR.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator|(server::options v1, server::options v2) noexcept -> server::options
{
	return static_cast<server::options>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
}

/**
 * Apply bitwise NOT.
 *
 * \param v the value
 * \return the complement
 */
inline auto operator~(server::options v) noexcept -> server::options
{
	return static_cast<server::options>(~static_cast<unsigned>(v));
}

/**
 * Assign bitwise OR.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator|=(server::options& v1, server::options v2) noexcept -> server::options&
{
	return v1 = v1 | v2;
}

/**
 * Assign bitwise AND.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator&=(server::options& v1, server::options v2) noexcept -> server::options&
{
	return v1 = v1 & v2;
}

/**
 * Assign bitwise XOR.
 *
 * \param v1 the first value
 * \param v2 the second value
 * \return the new value
 */
inline auto operator^=(server::options& v1, server::options v2) noexcept -> server::options&
{
	return v1 = v1 ^ v2;
}

/**
 * \endcond
 */

/**
 * \brief Server error.
 */
class server_error : public std::system_error {
public:
	/**
	 * \brief Server related errors.
	 */
	enum error {
		//!< No error.
		no_error = 0,

		//!< The specified server was not found.
		not_found,

		//!< The specified identifier is invalid.
		invalid_identifier,

		//!< The server is not connected.
		not_connected,

		//!< The server is already connected.
		already_connected,

		//!< Server with same name already exists.
		already_exists,

		//!< The specified port number is invalid.
		invalid_port,

		//!< The specified reconnect delay number is invalid.
		invalid_reconnect_delay,

		//!< The specified host was invalid.
		invalid_hostname,

		//!< The channel was empty or invalid.
		invalid_channel,

		//!< The mode given was empty.
		invalid_mode,

		//!< The nickname was empty or invalid.
		invalid_nickname,

		//!< The username was empty or invalid.
		invalid_username,

		//!< The realname was empty or invalid.
		invalid_realname,

		//!< Invalid password property.
		invalid_password,

		//!< Invalid ping timeout.
		invalid_ping_timeout,

		//!< Invalid ctcp version.
		invalid_ctcp_version,

		//!< Invalid command character.
		invalid_command_char,

		//!< Message (PRIVMSG) was invalid
		invalid_message,

		//!< SSL was requested but is disabled.
		ssl_disabled,
	};

public:
	/**
	 * Constructor.
	 *
	 * \param code the error code
	 */
	server_error(error code) noexcept;
};

/**
 * Get the server error category singleton.
 *
 * \return the singleton
 */
auto server_category() -> const std::error_category&;

/**
 * Create a std::error_code from server_error::error enum.
 *
 * \param e the error code
 */
auto make_error_code(server_error::error e) -> std::error_code;

} // !irccd

namespace std {

template <>
struct is_error_code_enum<irccd::server_error::error> : public std::true_type {
};

} // !std

#endif // !IRCCD_DAEMON_SERVER_HPP