diff options
Diffstat (limited to 'src')
44 files changed, 695 insertions, 4558 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1a205bd..85049b9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,7 +1,7 @@ #include <bridge/bridge.hpp> #include <bridge/colors.hpp> #include <bridge/list_element.hpp> -#include <xmpp/xmpp_component.hpp> +#include <xmpp/biboumi_component.hpp> #include <xmpp/xmpp_stanza.hpp> #include <irc/irc_message.hpp> #include <network/poller.hpp> @@ -20,7 +20,7 @@ using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; -Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr<Poller> poller): +Bridge::Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr<Poller> poller): user_jid(user_jid), xmpp(xmpp), poller(poller) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 8f71846..c50b7ab 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -12,7 +12,7 @@ #include <string> #include <memory> -class XmppComponent; +class BiboumiComponent; class Poller; /** @@ -32,7 +32,7 @@ using irc_responder_callback_t = std::function<bool(const std::string& irc_hostn class Bridge { public: - explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr<Poller> poller); + explicit Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr<Poller> poller); ~Bridge(); /** * QUIT all connected IRC servers. @@ -211,7 +211,7 @@ private: * but we still need to communicate with it, when sending messages from * IRC to XMPP. */ - XmppComponent* xmpp; + BiboumiComponent* xmpp; /** * Poller, to give it the IrcClients that we spawn, to make it manage * their sockets. diff --git a/src/config.h.cmake b/src/config.h.cmake deleted file mode 100644 index 18d546f..0000000 --- a/src/config.h.cmake +++ /dev/null @@ -1,8 +0,0 @@ -#define SYSTEM_NAME "${CMAKE_SYSTEM}" -#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST -#cmakedefine LIBIDN_FOUND -#cmakedefine SYSTEMD_FOUND -#cmakedefine POLLER ${POLLER} -#cmakedefine BOTAN_FOUND -#cmakedefine CARES_FOUND -#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" diff --git a/src/config/config.cpp b/src/config/config.cpp deleted file mode 100644 index b870339..0000000 --- a/src/config/config.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include <config/config.hpp> - -#include <iostream> -#include <sstream> - -#include <stdlib.h> - -std::string Config::filename = "./biboumi.cfg"; -bool Config::file_must_exist = false; - -std::string Config::get(const std::string& option, const std::string& def) -{ - Config* self = Config::instance().get(); - auto it = self->values.find(option); - - if (it == self->values.end()) - return def; - return it->second; -} - -int Config::get_int(const std::string& option, const int& def) -{ - Config* self = Config::instance().get(); - std::string res = self->get(option, ""); - if (!res.empty()) - return atoi(res.c_str()); - else - return def; -} - -void Config::set(const std::string& option, const std::string& value, bool save) -{ - Config* self = Config::instance().get(); - self->values[option] = value; - if (save) - { - self->save_to_file(); - self->trigger_configuration_change(); - } -} - -void Config::connect(t_config_changed_callback callback) -{ - Config* self = Config::instance().get(); - self->callbacks.push_back(callback); -} - -void Config::close() -{ - Config* self = Config::instance().get(); - self->values.clear(); - Config::instance().reset(); -} - -/** - * Private methods - */ - -void Config::trigger_configuration_change() -{ - std::vector<t_config_changed_callback>::iterator it; - for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it) - (*it)(); -} - -std::unique_ptr<Config>& Config::instance() -{ - static std::unique_ptr<Config> instance; - - if (!instance) - { - instance = std::make_unique<Config>(); - instance->read_conf(); - } - return instance; -} - -bool Config::read_conf() -{ - std::ifstream file; - file.open(filename.data()); - if (!file.is_open()) - { - if (Config::file_must_exist) - { - perror(("Error while opening file " + filename + " for reading.").c_str()); - file.exceptions(std::ifstream::failbit); - } - return false; - } - - std::string line; - size_t pos; - std::string option; - std::string value; - while (file.good()) - { - std::getline(file, line); - if (line == "" || line[0] == '#') - continue ; - pos = line.find('='); - if (pos == std::string::npos) - continue ; - option = line.substr(0, pos); - value = line.substr(pos+1); - this->values[option] = value; - } - return true; -} - -void Config::save_to_file() const -{ - std::ofstream file(this->filename.data()); - if (file.fail()) - { - std::cerr << "Could not save config file." << std::endl; - return ; - } - for (auto& it: this->values) - file << it.first << "=" << it.second << std::endl; - file.close(); -} diff --git a/src/config/config.hpp b/src/config/config.hpp deleted file mode 100644 index e070816..0000000 --- a/src/config/config.hpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Read the config file and save all the values in a map. - * Also, a singleton. - * - * Use Config::filename = "bla" to set the filename you want to use. - * - * If you want to exit if the file does not exist when it is open for - * reading, set Config::file_must_exist = true. - * - * Config::get() can the be used to access the values in the conf. - * - * Use Config::close() when you're done getting/setting value. This will - * save the config into the file. - */ - -#ifndef CONFIG_INCLUDED -# define CONFIG_INCLUDED - -#include <functional> -#include <fstream> -#include <memory> -#include <vector> -#include <string> -#include <map> - -typedef std::function<void()> t_config_changed_callback; - -class Config -{ -public: - Config(){}; - ~Config(){}; - /** - * returns a value from the config. If it doesn’t exist, use - * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist - */ - static std::string get(const std::string&, const std::string&); - /** - * returns a value from the config. If it doesn’t exist, use - * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist - */ - static int get_int(const std::string&, const int&); - /** - * Set a value for the given option. And write all the config - * in the file from which it was read if boolean is set. - * @param option The option to set - * @param value The value to use - * @param save if true, save the config file - */ - static void set(const std::string&, const std::string&, bool save = false); - /** - * Adds a function to a list. This function will be called whenever a - * configuration change occurs. - */ - static void connect(t_config_changed_callback); - /** - * Close the config file, saving it to the file is save == true. - */ - static void close(); - - /** - * Set the value of the filename to use, before calling any method. - */ - static std::string filename; - /** - * Set to true if you want an exception to be raised if the file does not - * exist when reading it. - */ - static bool file_must_exist; - -private: - /** - * Get the singleton instance - */ - static std::unique_ptr<Config>& instance(); - /** - * Read the configuration file at the given path. - */ - bool read_conf(); - /** - * Write all the config values into the configuration file - */ - void save_to_file() const; - /** - * Call all the callbacks previously registered using connect(). - * This is used to notify any class that a configuration change occured. - */ - void trigger_configuration_change(); - - std::map<std::string, std::string> values; - std::vector<t_config_changed_callback> callbacks; - - Config(const Config&) = delete; - Config& operator=(const Config&) = delete; - Config(Config&&) = delete; - Config& operator=(Config&&) = delete; -}; - -#endif // CONFIG_INCLUDED diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index dedb5de..cbbd4ea 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -14,7 +14,7 @@ #include <chrono> #include <string> -#include "config.h" +#include "louloulibs.h" using namespace std::string_literals; using namespace std::chrono_literals; diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp deleted file mode 100644 index 7336579..0000000 --- a/src/logger/logger.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include <logger/logger.hpp> -#include <config/config.hpp> - -Logger::Logger(const int log_level): - log_level(log_level), - stream(std::cout.rdbuf()) -{ -} - -Logger::Logger(const int log_level, const std::string& log_file): - log_level(log_level), - ofstream(log_file.data(), std::ios_base::app), - stream(ofstream.rdbuf()) -{ -} - -std::unique_ptr<Logger>& Logger::instance() -{ - static std::unique_ptr<Logger> instance; - - if (!instance) - { - const std::string log_file = Config::get("log_file", ""); - const int log_level = Config::get_int("log_level", 0); - if (log_file.empty()) - instance = std::make_unique<Logger>(log_level); - else - instance = std::make_unique<Logger>(log_level, log_file); - } - return instance; -} - -std::ostream& Logger::get_stream(const int lvl) -{ - if (lvl >= this->log_level) - return this->stream; - return this->null_stream; -} diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp deleted file mode 100644 index b1ae20d..0000000 --- a/src/logger/logger.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef LOGGER_INCLUDED -# define LOGGER_INCLUDED - -/** - * Singleton used in logger macros to write into files or stdout, with - * various levels of severity. - * Only the macros should be used. - * @class Logger - */ - -#include <memory> -#include <iostream> -#include <fstream> - -#define debug_lvl 0 -#define info_lvl 1 -#define warning_lvl 2 -#define error_lvl 3 - -#include "config.h" - -#ifdef SYSTEMD_FOUND -# include <systemd/sd-daemon.h> -#else -# define SD_DEBUG "[DEBUG]: " -# define SD_INFO "[INFO]: " -# define SD_WARNING "[WARNING]: " -# define SD_ERR "[ERROR]: " -#endif - -// Macro defined to get the filename instead of the full path. But if it is -// not properly defined by the build system, we fallback to __FILE__ -#ifndef __FILENAME__ -# define __FILENAME__ __FILE__ -#endif - -#define WHERE\ - __FILENAME__ << ":" << __LINE__ - -#define log_debug(text)\ - Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl; - -#define log_info(text)\ - Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl; - -#define log_warning(text)\ - Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl; - -#define log_error(text)\ - Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl; - -/** - * Juste a structure representing a stream doing nothing with its input. - */ -class nullstream: public std::ostream -{ -public: - nullstream(): - std::ostream(0) - { } -}; - -class Logger -{ -public: - static std::unique_ptr<Logger>& instance(); - std::ostream& get_stream(const int); - Logger(const int log_level, const std::string& log_file); - Logger(const int log_level); - -private: - Logger(const Logger&); - Logger& operator=(const Logger&); - - const int log_level; - std::ofstream ofstream; - nullstream null_stream; - std::ostream stream; -}; - -#endif // LOGGER_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 148412e..9042a57 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include <xmpp/xmpp_component.hpp> +#include <xmpp/biboumi_component.hpp> #include <utils/timed_events.hpp> #include <network/poller.hpp> #include <config/config.hpp> @@ -72,7 +72,7 @@ int main(int ac, char** av) return config_help("hostname"); auto p = std::make_shared<Poller>(); - auto xmpp_component = std::make_shared<XmppComponent>(p, + auto xmpp_component = std::make_shared<BiboumiComponent>(p, hostname, password); diff --git a/src/network/dns_handler.cpp b/src/network/dns_handler.cpp deleted file mode 100644 index 45bf626..0000000 --- a/src/network/dns_handler.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include <config.h> -#ifdef CARES_FOUND - -#include <network/dns_socket_handler.hpp> -#include <network/tcp_socket_handler.hpp> -#include <network/dns_handler.hpp> -#include <network/poller.hpp> - -#include <algorithm> -#include <stdexcept> - -DNSHandler DNSHandler::instance; - -using namespace std::string_literals; - -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg); - socket_handler->on_hostname4_resolved(status, hostent); -} - -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg); - socket_handler->on_hostname6_resolved(status, hostent); -} - -DNSHandler::DNSHandler() -{ - int ares_error; - if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) - throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); - if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS) - throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); -} - -ares_channel& DNSHandler::get_channel() -{ - return this->channel; -} - -void DNSHandler::destroy() -{ - this->socket_handlers.clear(); - ::ares_destroy(this->channel); - ::ares_library_cleanup(); -} - -void DNSHandler::gethostbyname(const std::string& name, - TCPSocketHandler* socket_handler, int family) -{ - socket_handler->free_cares_addrinfo(); - if (family == AF_INET) - ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname4_resolved, socket_handler); - else - ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname6_resolved, socket_handler); -} - -void DNSHandler::watch_dns_sockets(std::shared_ptr<Poller>& poller) -{ - fd_set readers; - fd_set writers; - - FD_ZERO(&readers); - FD_ZERO(&writers); - - int ndfs = ::ares_fds(this->channel, &readers, &writers); - // For each existing DNS socket, see if we are still supposed to watch it, - // if not then erase it - this->socket_handlers.erase( - std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), - [&readers](const auto& dns_socket) - { - return !FD_ISSET(dns_socket->get_socket(), &readers); - }), - this->socket_handlers.end()); - - for (auto i = 0; i < ndfs; ++i) - { - bool read = FD_ISSET(i, &readers); - bool write = FD_ISSET(i, &writers); - // Look for the DNSSocketHandler with this fd - auto it = std::find_if(this->socket_handlers.begin(), - this->socket_handlers.end(), - [i](const auto& socket_handler) - { - return i == socket_handler->get_socket(); - }); - if (!read && !write) // No need to read or write to it - { // If found, erase it and stop watching it because it is not - // needed anymore - if (it != this->socket_handlers.end()) - // The socket destructor removes it from the poller - this->socket_handlers.erase(it); - } - else // We need to write and/or read to it - { // If not found, create it because we need to watch it - if (it == this->socket_handlers.end()) - { - this->socket_handlers.emplace_front(std::make_unique<DNSSocketHandler>(poller, i)); - it = this->socket_handlers.begin(); - } - poller->add_socket_handler(it->get()); - if (write) - poller->watch_send_events(it->get()); - } - } -} - -#endif /* CARES_FOUND */ diff --git a/src/network/dns_handler.hpp b/src/network/dns_handler.hpp deleted file mode 100644 index ec5b2fa..0000000 --- a/src/network/dns_handler.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef DNS_HANDLER_HPP_INCLUDED -#define DNS_HANDLER_HPP_INCLUDED - -#include <config.h> -#ifdef CARES_FOUND - -class TCPSocketHandler; -class Poller; -class DNSSocketHandler; - -# include <ares.h> -# include <memory> -# include <string> -# include <list> - -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); - -/** - * Class managing DNS resolution. It should only be statically instanciated - * once in SocketHandler. It manages ares channel and calls various - * functions of that library. - */ - -class DNSHandler -{ -public: - DNSHandler(); - ~DNSHandler() = default; - void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler, - int family); - /** - * Call ares_fds to know what fd needs to be watched by the poller, create - * or destroy DNSSocketHandlers depending on the result. - */ - void watch_dns_sockets(std::shared_ptr<Poller>& poller); - /** - * Destroy and stop watching all the DNS sockets. Then de-init the channel - * and library. - */ - void destroy(); - ares_channel& get_channel(); - - static DNSHandler instance; - -private: - /** - * The list of sockets that needs to be watched, according to the last - * call to ares_fds. DNSSocketHandlers are added to it or removed from it - * in the watch_dns_sockets() method - */ - std::list<std::unique_ptr<DNSSocketHandler>> socket_handlers; - ares_channel channel; - - DNSHandler(const DNSHandler&) = delete; - DNSHandler(DNSHandler&&) = delete; - DNSHandler& operator=(const DNSHandler&) = delete; - DNSHandler& operator=(DNSHandler&&) = delete; -}; - -#endif /* CARES_FOUND */ -#endif /* DNS_HANDLER_HPP_INCLUDED */ diff --git a/src/network/dns_socket_handler.cpp b/src/network/dns_socket_handler.cpp deleted file mode 100644 index 6563894..0000000 --- a/src/network/dns_socket_handler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include <config.h> -#ifdef CARES_FOUND - -#include <network/dns_socket_handler.hpp> -#include <network/dns_handler.hpp> -#include <network/poller.hpp> - -#include <ares.h> - -DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller> poller, - const socket_t socket): - SocketHandler(poller, socket) -{ -} - -DNSSocketHandler::~DNSSocketHandler() -{ -} - -void DNSSocketHandler::connect() -{ -} - -void DNSSocketHandler::on_recv() -{ - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); - ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); -} - -void DNSSocketHandler::on_send() -{ - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); - ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); -} - -bool DNSSocketHandler::is_connected() const -{ - return true; -} - -#endif /* CARES_FOUND */ diff --git a/src/network/dns_socket_handler.hpp b/src/network/dns_socket_handler.hpp deleted file mode 100644 index beb47d9..0000000 --- a/src/network/dns_socket_handler.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef DNS_SOCKET_HANDLER_HPP -# define DNS_SOCKET_HANDLER_HPP - -#include <config.h> -#ifdef CARES_FOUND - -#include <network/socket_handler.hpp> -#include <ares.h> - -/** - * Manage a socket returned by ares_fds. We do not create, open or close the - * socket ourself: this is done by c-ares. We just call ares_process_fd() - * with the correct parameters, depending on what can be done on that socket - * (Poller reported it to be writable or readeable) - */ - -class DNSSocketHandler: public SocketHandler -{ -public: - explicit DNSSocketHandler(std::shared_ptr<Poller> poller, const socket_t socket); - ~DNSSocketHandler(); - /** - * Just call dns_process_fd, c-ares will do its work of send()ing or - * recv()ing the data it wants on that socket. - */ - void on_recv() override final; - void on_send() override final; - /** - * Do nothing, because we are always considered to be connected, since the - * connection is done by c-ares and not by us. - */ - void connect() override final; - /** - * Always true, see the comment for connect() - */ - bool is_connected() const override final; - -private: - DNSSocketHandler(const DNSSocketHandler&) = delete; - DNSSocketHandler(DNSSocketHandler&&) = delete; - DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; - DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; -}; - -#endif // CARES_FOUND -#endif // DNS_SOCKET_HANDLER_HPP diff --git a/src/network/poller.cpp b/src/network/poller.cpp deleted file mode 100644 index ffc4f2d..0000000 --- a/src/network/poller.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include <network/poller.hpp> -#include <logger/logger.hpp> -#include <utils/timed_events.hpp> - -#include <assert.h> -#include <errno.h> -#include <stdio.h> - -#include <cstring> -#include <iostream> -#include <stdexcept> - -Poller::Poller() -{ -#if POLLER == POLL - this->nfds = 0; -#elif POLLER == EPOLL - this->epfd = ::epoll_create1(0); - if (this->epfd == -1) - { - log_error("epoll failed: " << strerror(errno)); - throw std::runtime_error("Could not create epoll instance"); - } -#endif -} - -Poller::~Poller() -{ -} - -void Poller::add_socket_handler(SocketHandler* socket_handler) -{ - // Don't do anything if the socket is already managed - const auto it = this->socket_handlers.find(socket_handler->get_socket()); - if (it != this->socket_handlers.end()) - return ; - - this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); - - // We always watch all sockets for receive events -#if POLLER == POLL - this->fds[this->nfds].fd = socket_handler->get_socket(); - this->fds[this->nfds].events = POLLIN; - this->nfds++; -#endif -#if POLLER == EPOLL - struct epoll_event event = {EPOLLIN, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not add socket to epoll"); - } -#endif -} - -void Poller::remove_socket_handler(const socket_t socket) -{ - const auto it = this->socket_handlers.find(socket); - if (it == this->socket_handlers.end()) - throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); - this->socket_handlers.erase(it); - -#if POLLER == POLL - for (size_t i = 0; i < this->nfds; i++) - { - if (this->fds[i].fd == socket) - { - // Move all subsequent pollfd by one on the left, erasing the - // value of the one we remove - for (size_t j = i; j < this->nfds - 1; ++j) - { - this->fds[j].fd = this->fds[j+1].fd; - this->fds[j].events= this->fds[j+1].events; - } - this->nfds--; - } - } -#elif POLLER == EPOLL - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not remove socket from epoll"); - } -#endif -} - -void Poller::watch_send_events(SocketHandler* socket_handler) -{ -#if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) - { - if (this->fds[i].fd == socket_handler->get_socket()) - { - this->fds[i].events = POLLIN|POLLOUT; - return; - } - } - throw std::runtime_error("Cannot watch a non-registered socket for send events"); -#elif POLLER == EPOLL - struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not modify socket flags in epoll"); - } -#endif -} - -void Poller::stop_watching_send_events(SocketHandler* socket_handler) -{ -#if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) - { - if (this->fds[i].fd == socket_handler->get_socket()) - { - this->fds[i].events = POLLIN; - return; - } - } - throw std::runtime_error("Cannot watch a non-registered socket for send events"); -#elif POLLER == EPOLL - struct epoll_event event = {EPOLLIN, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not modify socket flags in epoll"); - } -#endif -} - -int Poller::poll(const std::chrono::milliseconds& timeout) -{ - if (this->socket_handlers.empty() && timeout == utils::no_timeout) - return -1; -#if POLLER == POLL - int nb_events = ::poll(this->fds, this->nfds, timeout.count()); - if (nb_events < 0) - { - if (errno == EINTR) - return true; - log_error("poll failed: " << strerror(errno)); - throw std::runtime_error("Poll failed"); - } - // We cannot possibly have more ready events than the number of fds we are - // watching - assert(static_cast<unsigned int>(nb_events) <= this->nfds); - for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) - { - if (this->fds[i].revents == 0) - continue; - else if (this->fds[i].revents & POLLIN) - { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - socket_handler->on_recv(); - nb_events--; - } - else if (this->fds[i].revents & POLLOUT) - { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (socket_handler->is_connected()) - socket_handler->on_send(); - else - socket_handler->connect(); - nb_events--; - } - } - return 1; -#elif POLLER == EPOLL - static const size_t max_events = 12; - struct epoll_event revents[max_events]; - const int nb_events = ::epoll_wait(this->epfd, revents, max_events, timeout.count()); - if (nb_events == -1) - { - if (errno == EINTR) - return 0; - log_error("epoll wait: " << strerror(errno)); - throw std::runtime_error("Epoll_wait failed"); - } - for (int i = 0; i < nb_events; ++i) - { - auto socket_handler = static_cast<SocketHandler*>(revents[i].data.ptr); - if (revents[i].events & EPOLLIN) - socket_handler->on_recv(); - else if (revents[i].events & EPOLLOUT) - { - if (socket_handler->is_connected()) - socket_handler->on_send(); - else - socket_handler->connect(); - } - } - return nb_events; -#endif -} - -size_t Poller::size() const -{ - return this->socket_handlers.size(); -} diff --git a/src/network/poller.hpp b/src/network/poller.hpp deleted file mode 100644 index c674e4c..0000000 --- a/src/network/poller.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef POLLER_INCLUDED -# define POLLER_INCLUDED - -#include <network/socket_handler.hpp> - -#include <unordered_map> -#include <memory> -#include <chrono> - -#define POLL 1 -#define EPOLL 2 -#define KQUEUE 3 -#include <config.h> -#ifndef POLLER - #define POLLER POLL -#endif - -#if POLLER == POLL - #include <poll.h> - #define MAX_POLL_FD_NUMBER 4096 -#elif POLLER == EPOLL - #include <sys/epoll.h> -#else - #error Invalid POLLER value -#endif - -/** - * We pass some SocketHandlers to this Poller, which uses - * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, - * and call the callbacks when event occurs. - * - * TODO: support these pollers: - * - kqueue(2) - */ - -class Poller -{ -public: - explicit Poller(); - ~Poller(); - /** - * Add a SocketHandler to be monitored by this Poller. All receive events - * are always automatically watched. - */ - void add_socket_handler(SocketHandler* socket_handler); - /** - * Remove (and stop managing) a SocketHandler, designated by the given socket_t. - */ - void remove_socket_handler(const socket_t socket); - /** - * Signal the poller that he needs to watch for send events for the given - * SocketHandler. - */ - void watch_send_events(SocketHandler* socket_handler); - /** - * Signal the poller that he needs to stop watching for send events for - * this SocketHandler. - */ - void stop_watching_send_events(SocketHandler* socket_handler); - /** - * Wait for all watched events, and call the SocketHandlers' callbacks - * when one is ready. Returns if nothing happened before the provided - * timeout. If the timeout is 0, it waits forever. If there is no - * watched event, returns -1 immediately, ignoring the timeout value. - * Otherwise, returns the number of event handled. If 0 is returned this - * means that we were interrupted by a signal, or the timeout occured. - */ - int poll(const std::chrono::milliseconds& timeout); - /** - * Returns the number of SocketHandlers managed by the poller. - */ - size_t size() const; - -private: - /** - * A "list" of all the SocketHandlers that we manage, indexed by socket, - * because that's what is returned by select/poll/etc when an event - * occures. - */ - std::unordered_map<socket_t, SocketHandler*> socket_handlers; - -#if POLLER == POLL - struct pollfd fds[MAX_POLL_FD_NUMBER]; - nfds_t nfds; -#elif POLLER == EPOLL - int epfd; -#endif - - Poller(const Poller&) = delete; - Poller(Poller&&) = delete; - Poller& operator=(const Poller&) = delete; - Poller& operator=(Poller&&) = delete; -}; - -#endif // POLLER_INCLUDED diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp deleted file mode 100644 index 0858474..0000000 --- a/src/network/socket_handler.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef SOCKET_HANDLER_HPP -# define SOCKET_HANDLER_HPP - -#include <config.h> -#include <memory> - -class Poller; - -typedef int socket_t; - -class SocketHandler -{ -public: - explicit SocketHandler(std::shared_ptr<Poller> poller, const socket_t socket): - poller(poller), - socket(socket) - {} - virtual ~SocketHandler() {} - virtual void on_recv() = 0; - virtual void on_send() = 0; - virtual void connect() = 0; - virtual bool is_connected() const = 0; - - socket_t get_socket() const - { return this->socket; } - -protected: - /** - * A pointer to the poller that manages us, because we need to communicate - * with it. - */ - std::shared_ptr<Poller> poller; - /** - * The handled socket. - */ - socket_t socket; - -private: - SocketHandler(const SocketHandler&) = delete; - SocketHandler(SocketHandler&&) = delete; - SocketHandler& operator=(const SocketHandler&) = delete; - SocketHandler& operator=(SocketHandler&&) = delete; -}; - -#endif // SOCKET_HANDLER_HPP diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp deleted file mode 100644 index e9984e3..0000000 --- a/src/network/tcp_socket_handler.cpp +++ /dev/null @@ -1,572 +0,0 @@ -#include <network/tcp_socket_handler.hpp> -#include <network/dns_handler.hpp> - -#include <utils/timed_events.hpp> -#include <utils/scopeguard.hpp> -#include <network/poller.hpp> - -#include <logger/logger.hpp> -#include <sys/socket.h> -#include <sys/types.h> -#include <stdexcept> -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> -#include <netdb.h> -#include <cstring> -#include <fcntl.h> -#include <stdio.h> - -#include <iostream> - -#ifdef BOTAN_FOUND -# include <botan/hex.h> - -Botan::AutoSeeded_RNG TCPSocketHandler::rng; -Permissive_Credentials_Manager TCPSocketHandler::credential_manager; -Botan::TLS::Policy TCPSocketHandler::policy; -Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); - -#endif - -#ifndef UIO_FASTIOV -# define UIO_FASTIOV 8 -#endif - -using namespace std::string_literals; -using namespace std::chrono_literals; - -namespace ph = std::placeholders; - -TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller): - SocketHandler(poller, -1), - use_tls(false), - connected(false), - connecting(false) -#ifdef CARES_FOUND - ,resolved(false), - resolved4(false), - resolved6(false), - cares_addrinfo(nullptr), - cares_error() -#endif -{} - -TCPSocketHandler::~TCPSocketHandler() -{ -#ifdef CARES_FOUND - this->free_cares_addrinfo(); -#endif -} - -void TCPSocketHandler::init_socket(const struct addrinfo* rp) -{ - if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) - throw std::runtime_error("Could not create socket: "s + strerror(errno)); - int optval = 1; - if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) - log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); - // Set the socket on non-blocking mode. This is useful to receive a EAGAIN - // error when connect() would block, to not block the whole process if a - // remote is not responsive. - const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); - if ((existing_flags == -1) || - (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) - throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); -} - -void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) -{ - this->address = address; - this->port = port; - this->use_tls = tls; - - utils::ScopeGuard sg; - - struct addrinfo* addr_res; - - if (!this->connecting) - { - // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if - // this is the first call of this function. -#ifdef CARES_FOUND - if (!this->resolved) - { - log_info("Trying to connect to " << address << ":" << port); - // Start the asynchronous process of resolving the hostname. Once - // the addresses have been found and `resolved` has been set to true - // (but connecting will still be false), TCPSocketHandler::connect() - // needs to be called, again. - DNSHandler::instance.gethostbyname(address, this, AF_INET6); - DNSHandler::instance.gethostbyname(address, this, AF_INET); - return; - } - else - { - // The c-ares resolved the hostname and the available addresses - // where saved in the cares_addrinfo linked list. Now, just use - // this list to try to connect. - addr_res = this->cares_addrinfo; - if (!addr_res) - { - this->close(); - this->on_connection_failed(this->cares_error); - return ; - } - } -#else - log_info("Trying to connect to " << address << ":" << port); - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - - if (res != 0) - { - log_warning("getaddrinfo failed: "s + gai_strerror(res)); - this->close(); - this->on_connection_failed(gai_strerror(res)); - return ; - } - // Make sure the alloced structure is always freed at the end of the - // function - sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); -#endif - } - else - { // This function is called again, use the saved addrinfo structure, - // instead of re-doing the whole getaddrinfo process. - addr_res = &this->addrinfo; - } - - for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) - { - if (!this->connecting) - { - try { - this->init_socket(rp); - } - catch (const std::runtime_error& error) { - log_error("Failed to init socket: " << error.what()); - break; - } - } - if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 - || errno == EISCONN) - { - log_info("Connection success."); - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - this->poller->add_socket_handler(this); - this->connected = true; - this->connecting = false; -#ifdef BOTAN_FOUND - if (this->use_tls) - this->start_tls(); -#endif - this->on_connected(); - return ; - } - else if (errno == EINPROGRESS || errno == EALREADY) - { // retry this process later, when the socket - // is ready to be written on. - this->connecting = true; - this->poller->add_socket_handler(this); - this->poller->watch_send_events(this); - // Save the addrinfo structure, to use it on the next call - this->ai_addrlen = rp->ai_addrlen; - memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); - memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); - this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr); - this->addrinfo.ai_next = nullptr; - // If the connection has not succeeded or failed in 5s, we consider - // it to have failed - TimedEventsManager::instance().add_event( - TimedEvent(std::chrono::steady_clock::now() + 5s, - std::bind(&TCPSocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); - return ; - } - log_info("Connection failed:" << strerror(errno)); - } - log_error("All connection attempts failed."); - this->close(); - this->on_connection_failed(strerror(errno)); - return ; -} - -void TCPSocketHandler::on_connection_timeout() -{ - this->close(); - this->on_connection_failed("connection timed out"); -} - -void TCPSocketHandler::connect() -{ - this->connect(this->address, this->port, this->use_tls); -} - -void TCPSocketHandler::on_recv() -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_recv(); - else -#endif - this->plain_recv(); -} - -void TCPSocketHandler::plain_recv() -{ - static constexpr size_t buf_size = 4096; - char buf[buf_size]; - void* recv_buf = this->get_receive_buffer(buf_size); - - if (recv_buf == nullptr) - recv_buf = buf; - - const ssize_t size = this->do_recv(recv_buf, buf_size); - - if (size > 0) - { - if (buf == recv_buf) - { - // data needs to be placed in the in_buf string, because no buffer - // was provided to receive that data directly. The in_buf buffer - // will be handled in parse_in_buffer() - this->in_buf += std::string(buf, size); - } - this->parse_in_buffer(size); - } -} - -ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) -{ - ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); - if (0 == size) - { - this->on_connection_close(""); - this->close(); - } - else if (-1 == size) - { - log_warning("Error while reading from socket: " << strerror(errno)); - // Remember if we were connecting, or already connected when this - // happened, because close() sets this->connecting to false - const auto were_connecting = this->connecting; - this->close(); - if (were_connecting) - this->on_connection_failed(strerror(errno)); - else - this->on_connection_close(strerror(errno)); - } - return size; -} - -void TCPSocketHandler::on_send() -{ - struct iovec msg_iov[UIO_FASTIOV] = {}; - struct msghdr msg{nullptr, 0, - msg_iov, - 0, nullptr, 0, 0}; - for (std::string& s: this->out_buf) - { - // unconsting the content of s is ok, sendmsg will never modify it - msg_iov[msg.msg_iovlen].iov_base = const_cast<char*>(s.data()); - msg_iov[msg.msg_iovlen].iov_len = s.size(); - if (++msg.msg_iovlen == UIO_FASTIOV) - break; - } - ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); - if (res < 0) - { - log_error("sendmsg failed: " << strerror(errno)); - this->on_connection_close(strerror(errno)); - this->close(); - } - else - { - // remove all the strings that were successfully sent. - for (auto it = this->out_buf.begin(); - it != this->out_buf.end();) - { - if (static_cast<size_t>(res) >= (*it).size()) - { - res -= (*it).size(); - it = this->out_buf.erase(it); - } - else - { - // If one string has partially been sent, we use substr to - // crop it - if (res > 0) - (*it) = (*it).substr(res, std::string::npos); - break; - } - } - if (this->out_buf.empty()) - this->poller->stop_watching_send_events(this); - } -} - -void TCPSocketHandler::close() -{ - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - if (this->connected || this->connecting) - this->poller->remove_socket_handler(this->get_socket()); - if (this->socket != -1) - { - ::close(this->socket); - this->socket = -1; - } - this->connected = false; - this->connecting = false; - this->in_buf.clear(); - this->out_buf.clear(); - this->port.clear(); -} - -void TCPSocketHandler::send_data(std::string&& data) -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_send(std::move(data)); - else -#endif - this->raw_send(std::move(data)); -} - -void TCPSocketHandler::raw_send(std::string&& data) -{ - if (data.empty()) - return ; - this->out_buf.emplace_back(std::move(data)); - if (this->connected) - this->poller->watch_send_events(this); -} - -void TCPSocketHandler::send_pending_data() -{ - if (this->connected && !this->out_buf.empty()) - this->poller->watch_send_events(this); -} - -bool TCPSocketHandler::is_connected() const -{ - return this->connected; -} - -bool TCPSocketHandler::is_connecting() const -{ -#ifdef CARES_FOUND - return this->connecting || !this->resolved; -#else - return this->connecting; -#endif -} - -void* TCPSocketHandler::get_receive_buffer(const size_t) const -{ - return nullptr; -} - -#ifdef BOTAN_FOUND -void TCPSocketHandler::start_tls() -{ - Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); - this->tls = std::make_unique<Botan::TLS::Client>( - std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2), - std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2), - std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), - std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1), - session_manager, credential_manager, policy, - rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); -} - -void TCPSocketHandler::tls_recv() -{ - static constexpr size_t buf_size = 4096; - char recv_buf[buf_size]; - - const ssize_t size = this->do_recv(recv_buf, buf_size); - if (size > 0) - { - const bool was_active = this->tls->is_active(); - this->tls->received_data(reinterpret_cast<const Botan::byte*>(recv_buf), - static_cast<size_t>(size)); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } -} - -void TCPSocketHandler::tls_send(std::string&& data) -{ - if (this->tls->is_active()) - { - const bool was_active = this->tls->is_active(); - if (!this->pre_buf.empty()) - { - this->tls->send(reinterpret_cast<const Botan::byte*>(this->pre_buf.data()), - this->pre_buf.size()); - this->pre_buf = ""; - } - if (!data.empty()) - this->tls->send(reinterpret_cast<const Botan::byte*>(data.data()), - data.size()); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } - else - this->pre_buf += data; -} - -void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) -{ - this->in_buf += std::string(reinterpret_cast<const char*>(data), - size); - if (!this->in_buf.empty()) - this->parse_in_buffer(size); -} - -void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size) -{ - this->raw_send(std::string(reinterpret_cast<const char*>(data), size)); -} - -void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) -{ - log_debug("tls_alert: " << alert.type_string()); -} - -bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) -{ - log_debug("Handshake with " << session.server_info().hostname() << " complete." - << " Version: " << session.version().to_string() - << " using " << session.ciphersuite().to_string()); - if (!session.session_id().empty()) - log_debug("Session ID " << Botan::hex_encode(session.session_id())); - if (!session.session_ticket().empty()) - log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); - return true; -} - -void TCPSocketHandler::on_tls_activated() -{ - this->send_data(""); -} - -#endif // BOTAN_FOUND - -#ifdef CARES_FOUND - -void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent) -{ - this->resolved4 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo4(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->connect(); - } -} - -void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent) -{ - this->resolved6 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo6(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->connect(); - } -} - -void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in_addr** address = reinterpret_cast<struct in_addr**>(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - current->ai_protocol = 0; - current->ai_addrlen = sizeof(struct sockaddr_in); - - struct sockaddr_in* addr = new struct sockaddr_in; - addr->sin_family = hostent->h_addrtype; - addr->sin_port = htons(strtoul(this->port.data(), nullptr, 10)); - addr->sin_addr.s_addr = (*address)->s_addr; - - current->ai_addr = reinterpret_cast<struct sockaddr*>(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in6_addr** address = reinterpret_cast<struct in6_addr**>(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - current->ai_protocol = 0; - current->ai_addrlen = sizeof(struct sockaddr_in6); - - struct sockaddr_in6* addr = new struct sockaddr_in6; - addr->sin6_family = hostent->h_addrtype; - addr->sin6_port = htons(strtoul(this->port.data(), nullptr, 10)); - ::memcpy(addr->sin6_addr.s6_addr, (*address)->s6_addr, 16); - addr->sin6_flowinfo = 0; - addr->sin6_scope_id = 0; - - current->ai_addr = reinterpret_cast<struct sockaddr*>(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::free_cares_addrinfo() -{ - while (this->cares_addrinfo) - { - delete this->cares_addrinfo->ai_addr; - auto next = this->cares_addrinfo->ai_next; - delete this->cares_addrinfo; - this->cares_addrinfo = next; - } -} - -#endif // CARES_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp deleted file mode 100644 index 7f10cff..0000000 --- a/src/network/tcp_socket_handler.hpp +++ /dev/null @@ -1,297 +0,0 @@ -#ifndef SOCKET_HANDLER_INCLUDED -# define SOCKET_HANDLER_INCLUDED - -#include <network/socket_handler.hpp> - -#include <logger/logger.hpp> - -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <netdb.h> - -#include <utility> -#include <memory> -#include <string> -#include <list> - -#include "config.h" - -#ifdef CARES_FOUND -# include <ares.h> -#endif - -#ifdef BOTAN_FOUND -# include <botan/botan.h> -# include <botan/tls_client.h> - -/** - * A very simple credential manager that accepts any certificate. - */ -class Permissive_Credentials_Manager: public Botan::Credentials_Manager -{ -public: - void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector<Botan::X509_Certificate>&) - { // TODO: Offer the admin to disallow connection on untrusted - // certificates - log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); - } -}; -#endif // BOTAN_FOUND - -/** - * An interface, with a series of callbacks that should be implemented in - * subclasses that deal with a socket. These callbacks are called on various events - * (read/write/timeout, etc) when they are notified to a poller - * (select/poll/epoll etc) - */ -class TCPSocketHandler: public SocketHandler -{ -protected: - ~TCPSocketHandler(); - -public: - explicit TCPSocketHandler(std::shared_ptr<Poller> poller); - /** - * Connect to the remote server, and call on_connected() if this - * succeeds. If tls is true, we set use_tls to true and will also call - * start_tls() when the connection succeeds. - */ - void connect(const std::string& address, const std::string& port, const bool tls); - void connect() override final; - /** - * Reads raw data from the socket. And pass it to parse_in_buffer() - * If we are using TLS on this connection, we call tls_recv() - */ - void on_recv() override final; - /** - * Write as much data from out_buf as possible, in the socket. - */ - void on_send() override final; - /** - * Add the given data to out_buf and tell our poller that we want to be - * notified when a send event is ready. - * - * This can be overriden if we want to modify the data before sending - * it. For example if we want to encrypt it. - */ - void send_data(std::string&& data); - /** - * Watch the socket for send events, if our out buffer is not empty. - */ - void send_pending_data(); - /** - * Close the connection, remove us from the poller - */ - void close(); - /** - * Called by a TimedEvent, when the connection did not succeed or fail - * after a given time. - */ - void on_connection_timeout(); - /** - * Called when the connection is successful. - */ - virtual void on_connected() = 0; - /** - * Called when the connection fails. Not when it is closed later, just at - * the connect() call. - */ - virtual void on_connection_failed(const std::string& reason) = 0; - /** - * Called when we detect a disconnection from the remote host. - */ - virtual void on_connection_close(const std::string& error) = 0; - /** - * Handle/consume (some of) the data received so far. The data to handle - * may be in the in_buf buffer, or somewhere else, depending on what - * get_receive_buffer() returned. If some data is used from in_buf, it - * should be truncated, only the unused data should be left untouched. - * - * The size argument is the size of the last chunk of data that was added to the buffer. - */ - virtual void parse_in_buffer(const size_t size) = 0; - bool is_connected() const override final; - bool is_connecting() const; - -#ifdef CARES_FOUND - void on_hostname4_resolved(int status, struct hostent* hostent); - void on_hostname6_resolved(int status, struct hostent* hostent); - - void free_cares_addrinfo(); - - void fill_ares_addrinfo4(const struct hostent* hostent); - void fill_ares_addrinfo6(const struct hostent* hostent); -#endif - -private: - /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * Reads from the socket into the provided buffer. If an error occurs - * (read returns <= 0), the handling of the error is done here (close the - * connection, log a message, etc). - * - * Returns the value returned by ::recv(), so the buffer should not be - * used if it’s not positive. - */ - ssize_t do_recv(void* recv_buf, const size_t buf_size); - /** - * Reads data from the socket and calls parse_in_buffer with it. - */ - void plain_recv(); - /** - * Mark the given data as ready to be sent, as-is, on the socket, as soon - * as we can. - */ - void raw_send(std::string&& data); - -#ifdef BOTAN_FOUND - /** - * Create the TLS::Client object, with all the callbacks etc. This must be - * called only when we know we are able to send TLS-encrypted data over - * the socket. - */ - void start_tls(); - /** - * An additional step to pass the data into our tls object to decrypt it - * before passing it to parse_in_buffer. - */ - void tls_recv(); - /** - * Pass the data to the tls object in order to encrypt it. The tls object - * will then call raw_send as a callback whenever data as been encrypted - * and can be sent on the socket. - */ - void tls_send(std::string&& data); - /** - * Called by the tls object that some data has been decrypt. We call - * parse_in_buffer() to handle that unencrypted data. - */ - void tls_data_cb(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that some data has been encrypted - * and is now ready to be sent on the socket as is. - */ - void tls_output_fn(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that a TLS alert has been - * received. We don’t use it, we just log some message, at the moment. - */ - void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); - /** - * Called by the tls object at the end of the TLS handshake. We don't do - * anything here appart from logging the TLS session information. - */ - bool tls_handshake_cb(const Botan::TLS::Session& session); - /** - * Called whenever the tls session goes from inactive to active. This - * means that the handshake has just been successfully done, and we can - * now proceed to send any available data into our tls object. - */ - void on_tls_activated(); -#endif // BOTAN_FOUND - /** - * Where data is added, when we want to send something to the client. - */ - std::list<std::string> out_buf; - /** - * Keep the details of the addrinfo that triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. - */ - struct addrinfo addrinfo; - struct sockaddr_in6 ai_addr; - socklen_t ai_addrlen; - -protected: - /** - * Where data read from the socket is added until we can extract a full - * and meaningful “message” from it. - * - * TODO: something more efficient than a string. - */ - std::string in_buf; - /** - * Whether we are using TLS on this connection or not. - */ - bool use_tls; - /** - * Provide a buffer in which data can be directly received. This can be - * used to avoid copying data into in_buf before using it. If no buffer - * needs to be provided, nullptr is returned (the default implementation - * does that), in that case our internal in_buf will be used to save the - * data until it can be used by parse_in_buffer(). - */ - virtual void* get_receive_buffer(const size_t size) const; - /** - * Hostname we are connected/connecting to - */ - std::string address; - /** - * Port we are connected/connecting to - */ - std::string port; - - bool connected; - bool connecting; - -#ifdef CARES_FOUND - /** - * Whether or not the DNS resolution was successfully done - */ - bool resolved; - bool resolved4; - bool resolved6; - /** - * When using c-ares to resolve the host asynchronously, we need the - * c-ares callback to fill a structure (a struct addrinfo, for - * compatibility with getaddrinfo and the rest of the code that works when - * c-ares is not used) with all returned values (for example an IPv6 and - * an IPv4). The next call of connect() will then try all these values - * (exactly like we do with the result of getaddrinfo) and save the one - * that worked (or returned EINPROGRESS) in the other struct addrinfo (see - * the members addrinfo, ai_addrlen, and ai_addr). - */ - struct addrinfo* cares_addrinfo; - std::string cares_error; -#endif // CARES_FOUND - -private: - TCPSocketHandler(const TCPSocketHandler&) = delete; - TCPSocketHandler(TCPSocketHandler&&) = delete; - TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; - TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; - -#ifdef BOTAN_FOUND - /** - * Botan stuff to manipulate a TLS session. - */ - static Botan::AutoSeeded_RNG rng; - static Permissive_Credentials_Manager credential_manager; - static Botan::TLS::Policy policy; - static Botan::TLS::Session_Manager_In_Memory session_manager; - /** - * We use a unique_ptr because we may not want to create the object at - * all. The Botan::TLS::Client object generates a handshake message as - * soon and calls the output_fn callback with it as soon as it is - * created. Therefore, we do not want to create it if do not intend to do - * send any TLS-encrypted message. We create the object only when needed - * (for example after we have negociated a TLS session using a STARTTLS - * message, or stuf like that). - * - * See start_tls for the method where this object is created. - */ - std::unique_ptr<Botan::TLS::Client> tls; - /** - * An additional buffer to keep data that the user wants to send, but - * cannot because the handshake is not done. - */ - std::string pre_buf; -#endif // BOTAN_FOUND -}; - -#endif // SOCKET_HANDLER_INCLUDED diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp deleted file mode 100644 index 3e3580c..0000000 --- a/src/utils/encoding.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include <utils/encoding.hpp> - -#include <utils/scopeguard.hpp> - -#include <stdexcept> - -#include <assert.h> -#include <string.h> -#include <iconv.h> - -#include <config.h> - -#include <bitset> - -/** - * The UTF-8-encoded character used as a place holder when a character conversion fails. - * This is U+FFFD � "replacement character" - */ -static const char* invalid_char = "\xef\xbf\xbd"; -static const size_t invalid_char_len = 3; - -namespace utils -{ - /** - * Based on http://en.wikipedia.org/wiki/UTF-8#Description - */ - bool is_valid_utf8(const char* s) - { - if (!s) - return false; - - const unsigned char* str = reinterpret_cast<const unsigned char*>(s); - - while (*str) - { - // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 0b11111000) == 0b11110000) - { - if (!str[1] || !str[2] || !str[3] - || ((str[1] & 0b11000000) != 0b10000000) - || ((str[2] & 0b11000000) != 0b10000000) - || ((str[3] & 0b11000000) != 0b10000000)) - return false; - str += 4; - } - // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 0b11110000) == 0b11100000) - { - if (!str[1] || !str[2] - || ((str[1] & 0b11000000) != 0b10000000) - || ((str[2] & 0b11000000) != 0b10000000)) - return false; - str += 3; - } - // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 0b11100000) == 0b11000000) - { - if (!str[1] || - ((str[1] & 0b11000000) != 0b10000000)) - return false; - str += 2; - } - // 1 byte: 0xxxxxxx - else if ((str[0] & 0b10000000) != 0) - return false; - else - str++; - } - return true; - } - - std::string remove_invalid_xml_chars(const std::string& original) - { - // The given string MUST be a valid utf-8 string - unsigned char* res = new unsigned char[original.size()]; - ScopeGuard sg([&res]() { delete[] res;}); - - // pointer where we write valid chars - unsigned char* r = res; - - const unsigned char* str = reinterpret_cast<const unsigned char*>(original.c_str()); - std::bitset<20> codepoint; - - while (*str) - { - // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 0b11111000) == 0b11110000) - { - codepoint = ((str[0] & 0b00000111) << 18); - codepoint |= ((str[1] & 0b00111111) << 12); - codepoint |= ((str[2] & 0b00111111) << 6 ); - codepoint |= ((str[3] & 0b00111111) << 0 ); - if (codepoint.to_ulong() <= 0x10FFFF) - { - ::memcpy(r, str, 4); - r += 4; - } - str += 4; - } - // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 0b11110000) == 0b11100000) - { - codepoint = ((str[0] & 0b00001111) << 12); - codepoint |= ((str[1] & 0b00111111) << 6); - codepoint |= ((str[2] & 0b00111111) << 0 ); - if (codepoint.to_ulong() <= 0xD7FF || - (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) - { - ::memcpy(r, str, 3); - r += 3; - } - str += 3; - } - // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 0b11100000) == 0b11000000) - { - // All 2 bytes char are valid, don't even bother calculating - // the codepoint - ::memcpy(r, str, 2); - r += 2; - str += 2; - } - // 1 byte: 0xxxxxxx - else if ((str[0] & 0b10000000) == 0) - { - codepoint = ((str[0] & 0b01111111)); - if (codepoint.to_ulong() == 0x09 || - codepoint.to_ulong() == 0x0A || - codepoint.to_ulong() == 0x0D || - codepoint.to_ulong() >= 0x20) - { - ::memcpy(r, str, 1); - r += 1; - } - str += 1; - } - else - throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); - } - return std::string(reinterpret_cast<char*>(res), r-res); - } - - std::string convert_to_utf8(const std::string& str, const char* charset) - { - std::string res; - - const iconv_t cd = iconv_open("UTF-8", charset); - if (cd == (iconv_t)-1) - throw std::runtime_error("Cannot convert into UTF-8"); - - // Make sure cd is always closed when we leave this function - ScopeGuard sg([&]{ iconv_close(cd); }); - - size_t inbytesleft = str.size(); - - // iconv will not attempt to modify this buffer, but some plateform - // require a char** anyway -#ifdef ICONV_SECOND_ARGUMENT_IS_CONST - const char* inbuf_ptr = str.c_str(); -#else - char* inbuf_ptr = const_cast<char*>(str.c_str()); -#endif - - size_t outbytesleft = str.size() * 4; - char* outbuf = new char[outbytesleft]; - char* outbuf_ptr = outbuf; - - // Make sure outbuf is always deleted when we leave this function - sg.add_callback([&]{ delete[] outbuf; }); - - bool done = false; - while (done == false) - { - size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); - if ((size_t)-1 == error) - { - switch (errno) - { - case EILSEQ: - // Invalid byte found. Insert a placeholder instead of the - // converted character, jump one byte and continue - memcpy(outbuf_ptr, invalid_char, invalid_char_len); - outbuf_ptr += invalid_char_len; - inbytesleft--; - inbuf_ptr++; - break; - case EINVAL: - // A multibyte sequence is not terminated, but we can't - // provide any more data, so we just add a placeholder to - // indicate that the character is not properly converted, - // and we stop the conversion - memcpy(outbuf_ptr, invalid_char, invalid_char_len); - outbuf_ptr += invalid_char_len; - outbuf_ptr++; - done = true; - break; - case E2BIG: - // This should never happen - done = true; - break; - default: - // This should happen even neverer - done = true; - break; - } - } - else - { - // The conversion finished without any error, stop converting - done = true; - } - } - // Terminate the converted buffer, and copy that buffer it into the - // string we return - *outbuf_ptr = '\0'; - res = outbuf; - return res; - } - -} - diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp deleted file mode 100644 index a3bccfc..0000000 --- a/src/utils/encoding.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ENCODING_INCLUDED -# define ENCODING_INCLUDED - -#include <string> - -namespace utils -{ - /** - * Returns true if the given null-terminated string is valid utf-8. - * - * Based on http://en.wikipedia.org/wiki/UTF-8#Description - */ - bool is_valid_utf8(const char* s); - /** - * Remove all invalid codepoints from the given utf-8-encoded string. - * The value returned is a copy of the string, without the removed chars. - * - * See http://www.w3.org/TR/xml/#charsets for the list of valid characters - * in XML. - */ - std::string remove_invalid_xml_chars(const std::string& original); - /** - * Convert the given string (encoded is "encoding") into valid utf-8. - * If some decoding fails, insert an utf-8 placeholder character instead. - */ - std::string convert_to_utf8(const std::string& str, const char* encoding); -} - -#endif // ENCODING_INCLUDED diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp deleted file mode 100644 index 6600c75..0000000 --- a/src/utils/reload.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include <config/config.hpp> -#include <logger/logger.hpp> - -void reload_process() -{ - // Closing the config will just force it to be reopened the next time - // a configuration option is needed - Config::close(); - // Destroy the logger instance, to be recreated the next time a log - // line needs to be written - Logger::instance().reset(); - log_debug("Configuration and logger reloaded."); -} diff --git a/src/utils/reload.hpp b/src/utils/reload.hpp deleted file mode 100644 index 16d64f7..0000000 --- a/src/utils/reload.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef RELOAD_HPP_INCLUDED -#define RELOAD_HPP_INCLUDED - -/** - * Reload the server's configuration, and close the logger (so that it - * closes its files etc, to take into account the new configuration) - */ -void reload_process(); - -#endif /* RELOAD_HPP_INCLUDED */ diff --git a/src/utils/revstr.cpp b/src/utils/revstr.cpp deleted file mode 100644 index 87fd801..0000000 --- a/src/utils/revstr.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include <utils/revstr.hpp> - -namespace utils -{ - std::string revstr(const std::string& original) - { - return {original.rbegin(), original.rend()}; - } -} diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp deleted file mode 100644 index 27c9e3e..0000000 --- a/src/utils/revstr.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef REVSTR_HPP_INCLUDED -# define REVSTR_HPP_INCLUDED - -#include <string> - -namespace utils -{ - std::string revstr(const std::string& original); -} - -#endif // REVSTR_HPP_INCLUDED diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp deleted file mode 100644 index df78831..0000000 --- a/src/utils/scopeguard.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef SCOPEGUARD_HPP -#define SCOPEGUARD_HPP - -#include <functional> -#include <vector> - -/** - * A class to be used to make sure some functions are called when the scope - * is left, because they will be called in the ScopeGuard's destructor. It - * can for example be used to delete some pointer whenever any exception is - * called. Example: - - * { - * ScopeGuard scope; - * int* number = new int(2); - * scope.add_callback([number]() { delete number; }); - * // Do some other stuff with the number. But these stuff might throw an exception: - * throw std::runtime_error("Some error not caught here, but in our caller"); - * return true; - * } - - * In this example, our pointer will always be deleted, even when the - * exception is thrown. If we want the functions to be called only when the - * scope is left because of an unexpected exception, we can use - * ScopeGuard::disable(); - */ - -namespace utils -{ - -class ScopeGuard -{ -public: - /** - * The constructor can take a callback. But additional callbacks can be - * added later with add_callback() - */ - explicit ScopeGuard(std::function<void()>&& func): - enabled(true) - { - this->add_callback(std::move(func)); - } - /** - * default constructor, the scope guard is enabled but empty, use - * add_callback() - */ - explicit ScopeGuard(): - enabled(true) - { - } - /** - * Call all callbacks in the desctructor, unless it has been disabled. - */ - ~ScopeGuard() - { - if (this->enabled) - for (auto& func: this->callbacks) - func(); - } - /** - * Add a callback to be called in our destructor, one scope guard can be - * used for more than one task, if needed. - */ - void add_callback(std::function<void()>&& func) - { - this->callbacks.emplace_back(std::move(func)); - } - /** - * Disable that scope guard, nothing will be done when the scope is - * exited. - */ - void disable() - { - this->enabled = false; - } - -private: - bool enabled; - std::vector<std::function<void()>> callbacks; - - ScopeGuard(const ScopeGuard&) = delete; - ScopeGuard& operator=(ScopeGuard&&) = delete; - ScopeGuard(ScopeGuard&&) = delete; - ScopeGuard& operator=(const ScopeGuard&) = delete; -}; - -} - -#endif diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp deleted file mode 100644 index 76476df..0000000 --- a/src/utils/sha1.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#include "sha1.hpp" - -#define SHA1_K0 0x5a827999 -#define SHA1_K20 0x6ed9eba1 -#define SHA1_K40 0x8f1bbcdc -#define SHA1_K60 0xca62c1d6 - -const uint8_t sha1InitState[] = { - 0x01,0x23,0x45,0x67, // H0 - 0x89,0xab,0xcd,0xef, // H1 - 0xfe,0xdc,0xba,0x98, // H2 - 0x76,0x54,0x32,0x10, // H3 - 0xf0,0xe1,0xd2,0xc3 // H4 -}; - -void sha1_init(sha1nfo *s) { - memcpy(s->state.b,sha1InitState,HASH_LENGTH); - s->byteCount = 0; - s->bufferOffset = 0; -} - -uint32_t sha1_rol32(uint32_t number, uint8_t bits) { - return ((number << bits) | (number >> (32-bits))); -} - -void sha1_hashBlock(sha1nfo *s) { - uint8_t i; - uint32_t a,b,c,d,e,t; - - a=s->state.w[0]; - b=s->state.w[1]; - c=s->state.w[2]; - d=s->state.w[3]; - e=s->state.w[4]; - for (i=0; i<80; i++) { - if (i>=16) { - t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; - s->buffer.w[i&15] = sha1_rol32(t,1); - } - if (i<20) { - t = (d ^ (b & (c ^ d))) + SHA1_K0; - } else if (i<40) { - t = (b ^ c ^ d) + SHA1_K20; - } else if (i<60) { - t = ((b & c) | (d & (b | c))) + SHA1_K40; - } else { - t = (b ^ c ^ d) + SHA1_K60; - } - t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; - e=d; - d=c; - c=sha1_rol32(b,30); - b=a; - a=t; - } - s->state.w[0] += a; - s->state.w[1] += b; - s->state.w[2] += c; - s->state.w[3] += d; - s->state.w[4] += e; -} - -void sha1_addUncounted(sha1nfo *s, uint8_t data) { - s->buffer.b[s->bufferOffset ^ 3] = data; - s->bufferOffset++; - if (s->bufferOffset == BLOCK_LENGTH) { - sha1_hashBlock(s); - s->bufferOffset = 0; - } -} - -void sha1_writebyte(sha1nfo *s, uint8_t data) { - ++s->byteCount; - sha1_addUncounted(s, data); -} - -void sha1_write(sha1nfo *s, const char *data, size_t len) { - for (;len--;) sha1_writebyte(s, (uint8_t) *data++); -} - -void sha1_pad(sha1nfo *s) { - // Implement SHA-1 padding (fips180-2 §5.1.1) - - // Pad with 0x80 followed by 0x00 until the end of the block - sha1_addUncounted(s, 0x80); - while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); - - // Append length in the last 8 bytes - sha1_addUncounted(s, 0); // We're only using 32 bit lengths - sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths - sha1_addUncounted(s, 0); // So zero pad the top bits - sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 - sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as - sha1_addUncounted(s, s->byteCount >> 13); // byte. - sha1_addUncounted(s, s->byteCount >> 5); - sha1_addUncounted(s, s->byteCount << 3); -} - -uint8_t* sha1_result(sha1nfo *s) { - int i; - // Pad to complete the last block - sha1_pad(s); - - // Swap byte order back - for (i=0; i<5; i++) { - uint32_t a,b; - a=s->state.w[i]; - b=a<<24; - b|=(a<<8) & 0x00ff0000; - b|=(a>>8) & 0x0000ff00; - b|=a>>24; - s->state.w[i]=b; - } - - // Return pointer to hash (20 characters) - return s->state.b; -} - -#define HMAC_IPAD 0x36 -#define HMAC_OPAD 0x5c - -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { - uint8_t i; - memset(s->keyBuffer, 0, BLOCK_LENGTH); - if (keyLength > BLOCK_LENGTH) { - // Hash long keys - sha1_init(s); - for (;keyLength--;) sha1_writebyte(s, *key++); - memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); - } else { - // Block length keys are used as is - memcpy(s->keyBuffer, key, keyLength); - } - // Start inner hash - sha1_init(s); - for (i=0; i<BLOCK_LENGTH; i++) { - sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD); - } -} - -uint8_t* sha1_resultHmac(sha1nfo *s) { - uint8_t i; - // Complete inner hash - memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); - // Calculate outer hash - sha1_init(s); - for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); - for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]); - return sha1_result(s); -} diff --git a/src/utils/sha1.hpp b/src/utils/sha1.hpp deleted file mode 100644 index d02de75..0000000 --- a/src/utils/sha1.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#include <stdint.h> -#include <string.h> - -#define HASH_LENGTH 20 -#define BLOCK_LENGTH 64 - -union _buffer { - uint8_t b[BLOCK_LENGTH]; - uint32_t w[BLOCK_LENGTH/4]; -}; - -union _state { - uint8_t b[HASH_LENGTH]; - uint32_t w[HASH_LENGTH/4]; -}; - -typedef struct sha1nfo { - union _buffer buffer; - uint8_t bufferOffset; - union _state state; - uint32_t byteCount; - uint8_t keyBuffer[BLOCK_LENGTH]; - uint8_t innerHash[HASH_LENGTH]; -} sha1nfo; - -void sha1_init(sha1nfo *s); -void sha1_writebyte(sha1nfo *s, uint8_t data); -void sha1_write(sha1nfo *s, const char *data, size_t len); -uint8_t* sha1_result(sha1nfo *s); -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); -uint8_t* sha1_resultHmac(sha1nfo *s); diff --git a/src/utils/split.cpp b/src/utils/split.cpp deleted file mode 100644 index afe4300..0000000 --- a/src/utils/split.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include <utils/split.hpp> - -namespace utils -{ - std::vector<std::string> split(const std::string& s, const char delim, const bool allow_empty) - { - std::vector<std::string> ret; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) - { - if (item.empty() && !allow_empty) - continue ; - ret.emplace_back(std::move(item)); - } - return ret; - } -} diff --git a/src/utils/split.hpp b/src/utils/split.hpp deleted file mode 100644 index 9fee90a..0000000 --- a/src/utils/split.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SPLIT_INCLUDED -# define SPLIT_INCLUDED - -#include <string> -#include <sstream> -#include <vector> - -namespace utils -{ - std::vector<std::string> split(const std::string &s, const char delim, const bool allow_empty=true); -} - -#endif // SPLIT_INCLUDED diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp deleted file mode 100644 index 5010a3f..0000000 --- a/src/utils/timed_events.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include <utils/timed_events.hpp> - -TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function<void()> callback, const std::string& name): - time_point(std::move(time_point)), - callback(callback), - repeat(false), - repeat_delay(0), - name(name) -{ -} - -TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, - std::function<void()> callback, const std::string& name): - time_point(std::chrono::steady_clock::now() + duration), - callback(callback), - repeat(true), - repeat_delay(std::move(duration)), - name(name) -{ -} - -TimedEvent::TimedEvent(TimedEvent&& other): - time_point(std::move(other.time_point)), - callback(std::move(other.callback)), - repeat(other.repeat), - repeat_delay(std::move(other.repeat_delay)), - name(std::move(other.name)) -{ -} - -TimedEvent::~TimedEvent() -{ -} - -bool TimedEvent::is_after(const TimedEvent& other) const -{ - return this->is_after(other.time_point); -} - -bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const -{ - return this->time_point >= time_point; -} - -std::chrono::milliseconds TimedEvent::get_timeout() const -{ - auto now = std::chrono::steady_clock::now(); - if (now > this->time_point) - return std::chrono::milliseconds(0); - return std::chrono::duration_cast<std::chrono::milliseconds>(this->time_point - now); -} - -void TimedEvent::execute() -{ - this->callback(); -} - -const std::string& TimedEvent::get_name() const -{ - return this->name; -} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp deleted file mode 100644 index 4e2800c..0000000 --- a/src/utils/timed_events.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef TIMED_EVENTS_HPP -# define TIMED_EVENTS_HPP - -#include <functional> -#include <string> -#include <chrono> -#include <list> - -using namespace std::literals::chrono_literals; - -namespace utils { -static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); -} - -class TimedEventsManager; - -/** - * A callback with an associated date. - */ - -class TimedEvent -{ - friend class TimedEventsManager; -public: - /** - * An event the occurs only once, at the given time_point - */ - explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function<void()> callback, const std::string& name=""); - explicit TimedEvent(std::chrono::milliseconds&& duration, - std::function<void()> callback, const std::string& name=""); - - explicit TimedEvent(TimedEvent&&); - ~TimedEvent(); - /** - * Whether or not this event happens after the other one. - */ - bool is_after(const TimedEvent& other) const; - bool is_after(const std::chrono::steady_clock::time_point& time_point) const; - /** - * Return the duration difference between now and the event time point. - * If the difference would be negative (i.e. the event is expired), the - * returned value is 0 instead. The value cannot then be negative. - */ - std::chrono::milliseconds get_timeout() const; - void execute(); - const std::string& get_name() const; - -private: - /** - * The next time point at which the event is executed. - */ - std::chrono::steady_clock::time_point time_point; - /** - * The function to execute. - */ - const std::function<void()> callback; - /** - * Whether or not this events repeats itself until it is destroyed. - */ - const bool repeat; - /** - * This value is added to the time_point each time the event is executed, - * if repeat is true. Otherwise it is ignored. - */ - const std::chrono::milliseconds repeat_delay; - /** - * A name that is used to identify that event. If you want to find your - * event (for example if you want to cancel it), the name should be - * unique. - */ - const std::string name; - - TimedEvent(const TimedEvent&) = delete; - TimedEvent& operator=(const TimedEvent&) = delete; - TimedEvent& operator=(TimedEvent&&) = delete; -}; - -/** - * A class managing a list of TimedEvents. - * They are sorted, new events can be added, removed, fetch, etc. - */ - -class TimedEventsManager -{ -public: - ~TimedEventsManager(); - /** - * Return the unique instance of this class - */ - static TimedEventsManager& instance(); - /** - * Add an event to the list of managed events. The list is sorted after - * this call. - */ - void add_event(TimedEvent&& event); - /** - * Returns the duration, in milliseconds, between now and the next - * available event. If the event is already expired (the duration is - * negative), 0 is returned instead (as in “it's not too late, execute it - * now”) - * Returns a negative value if no event is available. - */ - std::chrono::milliseconds get_timeout() const; - /** - * Execute all the expired events (if their expiration time is exactly - * now, or before now). The event is then removed from the list. If the - * event does repeat, its expiration time is updated and it is reinserted - * in the list at the correct position. - * Returns the number of executed events. - */ - std::size_t execute_expired_events(); - /** - * Remove (and thus cancel) all the timed events with the given name. - * Returns the number of canceled events. - */ - std::size_t cancel(const std::string& name); - /** - * Return the number of managed events. - */ - std::size_t size() const; - -private: - explicit TimedEventsManager(); - std::list<TimedEvent> events; - TimedEventsManager(const TimedEventsManager&) = delete; - TimedEventsManager(TimedEventsManager&&) = delete; - TimedEventsManager& operator=(const TimedEventsManager&) = delete; - TimedEventsManager& operator=(TimedEventsManager&&) = delete; -}; - -#endif // TIMED_EVENTS_HPP diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp deleted file mode 100644 index 2c75e48..0000000 --- a/src/utils/timed_events_manager.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include <utils/timed_events.hpp> - -TimedEventsManager& TimedEventsManager::instance() -{ - static TimedEventsManager inst; - return inst; -} - -TimedEventsManager::TimedEventsManager() -{ -} - -TimedEventsManager::~TimedEventsManager() -{ -} - -void TimedEventsManager::add_event(TimedEvent&& event) -{ - for (auto it = this->events.begin(); it != this->events.end(); ++it) - { - if (it->is_after(event)) - { - this->events.emplace(it, std::move(event)); - return; - } - } - this->events.emplace_back(std::move(event)); -} - -std::chrono::milliseconds TimedEventsManager::get_timeout() const -{ - if (this->events.empty()) - return utils::no_timeout; - return this->events.front().get_timeout() + std::chrono::milliseconds(1); -} - -std::size_t TimedEventsManager::execute_expired_events() -{ - std::size_t count = 0; - const auto now = std::chrono::steady_clock::now(); - for (auto it = this->events.begin(); it != this->events.end();) - { - if (!it->is_after(now)) - { - TimedEvent copy(std::move(*it)); - it = this->events.erase(it); - ++count; - copy.execute(); - if (copy.repeat) - { - copy.time_point += copy.repeat_delay; - this->add_event(std::move(copy)); - } - continue; - } - else - break; - } - return count; -} - -std::size_t TimedEventsManager::cancel(const std::string& name) -{ - std::size_t res = 0; - for (auto it = this->events.begin(); it != this->events.end();) - { - if (it->get_name() == name) - { - it = this->events.erase(it); - res++; - } - else - ++it; - } - return res; -} - -std::size_t TimedEventsManager::size() const -{ - return this->events.size(); -} diff --git a/src/utils/tolower.cpp b/src/utils/tolower.cpp deleted file mode 100644 index 3e518bd..0000000 --- a/src/utils/tolower.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include <utils/tolower.hpp> - -namespace utils -{ - std::string tolower(const std::string& original) - { - std::string res; - res.reserve(original.size()); - for (const char c: original) - res += static_cast<char>(std::tolower(c)); - return res; - } -} diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp deleted file mode 100644 index 0019182..0000000 --- a/src/utils/tolower.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TOLOWER_INCLUDED -# define TOLOWER_INCLUDED - -#include <string> - -namespace utils -{ - std::string tolower(const std::string& original); -} - -#endif // SPLIT_INCLUDED diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index c4e8a44..ba20eba 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -1,5 +1,5 @@ #include <xmpp/adhoc_command.hpp> -#include <xmpp/xmpp_component.hpp> +#include <xmpp/biboumi_component.hpp> #include <bridge/bridge.hpp> @@ -98,6 +98,8 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { + auto biboumi_component = static_cast<BiboumiComponent*>(xmpp_component); + XmlNode x("jabber:x:data:x"); x["type"] = "form"; XmlNode title("title"); @@ -115,7 +117,7 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& XmlNode required("required"); required.close(); jids_field.add_child(std::move(required)); - for (Bridge* bridge: xmpp_component->get_bridges()) + for (Bridge* bridge: biboumi_component->get_bridges()) { XmlNode option("option"); option["label"] = bridge->get_jid(); @@ -145,6 +147,8 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) { + auto biboumi_component = static_cast<BiboumiComponent*>(xmpp_component); + // Find out if the jids, and the quit message are provided in the form. std::string quit_message; XmlNode* x = command_node.get_child("x", "jabber:x:data"); @@ -168,7 +172,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X std::size_t num = 0; for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) { - Bridge* bridge = xmpp_component->find_user_bridge(value->get_inner()); + Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); if (bridge) { bridge->shutdown(quit_message); diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp deleted file mode 100644 index def1dcb..0000000 --- a/src/xmpp/adhoc_commands_handler.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include <xmpp/adhoc_commands_handler.hpp> -#include <xmpp/xmpp_component.hpp> - -#include <utils/timed_events.hpp> -#include <logger/logger.hpp> -#include <config/config.hpp> -#include <xmpp/jid.hpp> - -#include <iostream> - -using namespace std::string_literals; - -AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component): - xmpp_component(xmpp_component), - commands{ - {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, - {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, - {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} - } -{ -} - -AdhocCommandsHandler::~AdhocCommandsHandler() -{ -} - -const std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get_commands() const -{ - return this->commands; -} - -XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) -{ - std::string action = command_node.get_tag("action"); - if (action.empty()) - action = "execute"; - command_node.del_tag("action"); - - Jid jid(executor_jid); - - const std::string node = command_node.get_tag("node"); - auto command_it = this->commands.find(node); - if (command_it == this->commands.end()) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "cancel"; - XmlNode condition(STANZA_NS":item-not-found"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else if (command_it->second.is_admin_only() && - Config::get("admin", "") != jid.local + "@" + jid.domain) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "cancel"; - XmlNode condition(STANZA_NS":forbidden"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else - { - std::string sessionid = command_node.get_tag("sessionid"); - if (sessionid.empty()) - { // create a new session, with a new id - sessionid = XmppComponent::next_id(); - command_node["sessionid"] = sessionid; - this->sessions.emplace(std::piecewise_construct, - std::forward_as_tuple(sessionid, executor_jid), - std::forward_as_tuple(command_it->second, executor_jid)); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, - std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), - "adhocsession"s + sessionid + executor_jid)); - } - auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); - if (session_it == this->sessions.end()) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else if (action == "execute" || action == "next" || action == "complete") - { - // execute the step - AdhocSession& session = session_it->second; - const AdhocStep& step = session.get_next_step(); - step(this->xmpp_component, session, command_node); - if (session.remaining_steps() == 0 || - session.is_terminated()) - { - this->sessions.erase(session_it); - command_node["status"] = "completed"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else - { - command_node["status"] = "executing"; - XmlNode actions("actions"); - XmlNode next("next"); - next.close(); - actions.add_child(std::move(next)); - actions.close(); - command_node.add_child(std::move(actions)); - } - } - else if (action == "cancel") - { - this->sessions.erase(session_it); - command_node["status"] = "canceled"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else // unsupported action - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - } - return command_node; -} - -void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) -{ - auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid)); - if (session_it != this->sessions.end()) - { - this->sessions.erase(session_it); - return ; - } - log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found"); -} diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp deleted file mode 100644 index 7ddad47..0000000 --- a/src/xmpp/adhoc_commands_handler.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef ADHOC_COMMANDS_HANDLER_HPP -# define ADHOC_COMMANDS_HANDLER_HPP - -/** - * Manage a list of available AdhocCommands and the list of ongoing - * AdhocCommandSessions. - */ - -#include <xmpp/adhoc_command.hpp> -#include <xmpp/xmpp_stanza.hpp> - -#include <utility> -#include <string> -#include <map> - -class XmppComponent; - -class AdhocCommandsHandler -{ -public: - explicit AdhocCommandsHandler(XmppComponent* xmpp_component); - ~AdhocCommandsHandler(); - /** - * Returns the list of available commands. - */ - const std::map<const std::string, const AdhocCommand>& get_commands() const; - /** - * Find the requested command, create a new session or use an existing - * one, and process the request (provide a new form, an error, or a - * result). - * - * Returns a (moved) XmlNode that will be inserted in the iq response. It - * should be a <command/> node containing one or more useful children. If - * it contains an <error/> node, the iq response will have an error type. - * - * Takes a copy of the <command/> node so we can actually edit it and use - * it as our return value. - */ - XmlNode handle_request(const std::string& executor_jid, XmlNode command_node); - /** - * Remove the session from the list. This is done to avoid filling the - * memory with waiting session (for example due to a client that starts - * multi-steps command but never finishes them). - */ - void remove_session(const std::string& session_id, const std::string& initiator_jid); -private: - /** - * A pointer to the XmppComponent, to access to basically anything in the - * gateway. - */ - XmppComponent* xmpp_component; - /** - * The list of all available commands. - */ - const std::map<const std::string, const AdhocCommand> commands; - /** - * The list of all currently on-going commands. - * - * Of the form: {{session_id, owner_jid}, session}. - */ - std::map<std::pair<const std::string, const std::string>, AdhocSession> sessions; - - AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; - AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; -}; - -#endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp deleted file mode 100644 index fc60bb7..0000000 --- a/src/xmpp/adhoc_session.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include <xmpp/adhoc_session.hpp> -#include <xmpp/adhoc_command.hpp> - -#include <assert.h> - -AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid): - command(command), - owner_jid(jid), - current_step(0), - terminated(false) -{ -} - -AdhocSession::~AdhocSession() -{ -} - -const AdhocStep& AdhocSession::get_next_step() -{ - assert(this->current_step < this->command.callbacks.size()); - return this->command.callbacks[this->current_step++]; -} - -size_t AdhocSession::remaining_steps() const -{ - return this->command.callbacks.size() - this->current_step; -} - -bool AdhocSession::is_terminated() const -{ - return this->terminated; -} - -void AdhocSession::terminate() -{ - this->terminated = true; -} diff --git a/src/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp deleted file mode 100644 index ddfb2fe..0000000 --- a/src/xmpp/adhoc_session.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ADHOC_SESSION_HPP -# define ADHOC_SESSION_HPP - -#include <xmpp/xmpp_stanza.hpp> - -#include <functional> -#include <string> - -class XmppComponent; - -class AdhocCommand; -class AdhocSession; - -/** - * A function executed as an ad-hoc command step. It takes a <command/> - * XmlNode and modifies it accordingly (inserting for example an <error/> - * node, or a data form…). - * TODO fix this: - * It also must call one of step_passed(), cancel() etc on the AdhocSession object. - */ -typedef std::function<void(XmppComponent*, AdhocSession&, XmlNode&)> AdhocStep; - -class AdhocSession -{ -public: - explicit AdhocSession(const AdhocCommand& command, const std::string& jid); - ~AdhocSession(); - /** - * Return the function to be executed, found in our AdhocCommand, for the - * current_step. And increment the current_step. - */ - const AdhocStep& get_next_step(); - /** - * Return the number of remaining steps. - */ - size_t remaining_steps() const; - /** - * This may be modified by an AdhocStep, to indicate that this session - * should no longer exist, because we encountered an error, and we can't - * execute any more step of it. - */ - void terminate(); - bool is_terminated() const; - -private: - /** - * A reference of the command concerned by this session. Used for example - * to get the next step of that command, things like that. - */ - const AdhocCommand& command; - /** - * The full JID of the XMPP user that created this session by executing - * the first step of a command. Only that JID must be allowed to access - * this session. - */ - const std::string& owner_jid; - /** - * The current step we are at. It starts at zero. It is used to index the - * associated AdhocCommand::callbacks vector. - */ - size_t current_step; - bool terminated; - - AdhocSession(const AdhocSession&) = delete; - AdhocSession(AdhocSession&&) = delete; - AdhocSession& operator=(const AdhocSession&) = delete; - AdhocSession& operator=(AdhocSession&&) = delete; -}; - -#endif // ADHOC_SESSION_HPP diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp new file mode 100644 index 0000000..2ecf247 --- /dev/null +++ b/src/xmpp/biboumi_component.cpp @@ -0,0 +1,568 @@ +#include <xmpp/biboumi_component.hpp> + +#include <utils/timed_events.hpp> +#include <utils/scopeguard.hpp> +#include <utils/tolower.hpp> +#include <logger/logger.hpp> +#include <xmpp/adhoc_command.hpp> +#include <bridge/list_element.hpp> +#include <config/config.hpp> +#include <xmpp/jid.hpp> +#include <utils/sha1.hpp> + +#include <stdexcept> +#include <iostream> + +#include <stdio.h> + +#include <louloulibs.h> + +#include <uuid.h> + +#ifdef SYSTEMD_FOUND +# include <systemd/sd-daemon.h> +#endif + +using namespace std::string_literals; + +static std::set<std::string> kickable_errors{ + "gone", + "internal-server-error", + "item-not-found", + "jid-malformed", + "recipient-unavailable", + "redirect", + "remote-server-not-found", + "remote-server-timeout", + "service-unavailable", + "malformed-error" + }; + + +BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret): + XmppComponent(poller, hostname, secret) +{ + this->stanza_handlers.emplace("presence", + std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1)); + this->stanza_handlers.emplace("message", + std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1)); + this->stanza_handlers.emplace("iq", + std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1)); + + this->adhoc_commands_handler.get_commands()= { + {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, + {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, + {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, + {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} + }; +} + +void BiboumiComponent::shutdown() +{ + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + { + it->second->shutdown("Gateway shutdown"); + } +} + +void BiboumiComponent::clean() +{ + auto it = this->bridges.begin(); + while (it != this->bridges.end()) + { + it->second->clean(); + if (it->second->active_clients() == 0) + it = this->bridges.erase(it); + else + ++it; + } +} + +void BiboumiComponent::handle_presence(const Stanza& stanza) +{ + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + // Check for mandatory tags + if (from.empty()) + { + log_warning("Received an invalid presence stanza: tag 'from' is missing."); + return; + } + if (to_str.empty()) + { + this->send_stanza_error("presence", from, this->served_hostname, id, + "modify", "bad-request", "Missing 'to' tag"); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + Iid iid(to.local); + + // An error stanza is sent whenever we exit this function without + // disabling this scopeguard. If error_type and error_name are not + // changed, the error signaled is internal-server-error. Change their + // value to signal an other kind of error. For example + // feature-not-implemented, etc. Any non-error process should reach the + // stanza_error.disable() call at the end of the function. + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("presence", from, to_str, id, + error_type, error_name, ""); + }); + + if (iid.is_channel && !iid.get_server().empty()) + { // presence toward a MUC that corresponds to an irc channel, or a + // dummy channel if iid.chan is empty + if (type.empty()) + { + const std::string own_nick = bridge->get_own_nick(iid); + if (!own_nick.empty() && own_nick != to.resource) + bridge->send_irc_nick_change(iid, to.resource); + XmlNode* x = stanza.get_child("x", MUC_NS); + XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; + bridge->join_irc_channel(iid, to.resource, + password ? password->get_inner() : ""); + } + else if (type == "unavailable") + { + XmlNode* status = stanza.get_child("status", COMPONENT_NS); + bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); + } + } + else + { + // An user wants to join an invalid IRC channel, return a presence error to him + if (type.empty()) + this->send_invalid_room_error(to.local, to.resource, from); + } + stanza_error.disable(); +} + +void BiboumiComponent::handle_message(const Stanza& stanza) +{ + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + if (from.empty()) + return; + if (type.empty()) + type = "normal"; + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + Iid iid(to.local); + + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("message", from, to_str, id, + error_type, error_name, ""); + }); + XmlNode* body = stanza.get_child("body", COMPONENT_NS); + if (type == "groupchat" && iid.is_channel) + { + if (body && !body->get_inner().empty()) + { + bridge->send_channel_message(iid, body->get_inner()); + } + XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); + if (subject) + bridge->set_channel_topic(iid, subject->get_inner()); + } + else if (type == "error") + { + const XmlNode* error = stanza.get_child("error", COMPONENT_NS); + // Only a set of errors are considered “fatal”. If we encounter one of + // them, we purge (we disconnect the user from all the IRC servers). + // We consider this to be true, unless the error condition is + // specified and is not in the kickable_errors set + bool kickable_error = true; + if (error && error->has_children()) + { + const XmlNode* condition = error->get_last_child(); + if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) + kickable_error = false; + } + if (kickable_error) + bridge->shutdown("Error from remote client"); + } + else if (type == "chat") + { + if (body && !body->get_inner().empty()) + { + // a message for nick!server + if (iid.is_user && !iid.get_local().empty()) + { + bridge->send_private_message(iid, body->get_inner()); + bridge->remove_preferred_from_jid(iid.get_local()); + } + else if (!iid.is_user && !to.resource.empty()) + { // a message for chan%server@biboumi/Nick or + // server@biboumi/Nick + // Convert that into a message to nick!server + Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); + bridge->send_private_message(user_iid, body->get_inner()); + bridge->set_preferred_from_jid(user_iid.get_local(), to_str); + } + } + } + else if (iid.is_user) + this->send_invalid_user_error(to.local, from); + stanza_error.disable(); +} + +// We MUST return an iq, whatever happens, except if the type is +// "result". +// To do this, we use a scopeguard. If an exception is raised somewhere, an +// iq of type error "internal-server-error" is sent. If we handle the +// request properly (by calling a function that registers an iq to be sent +// later, or that directly sends an iq), we disable the ScopeGuard. If we +// reach the end of the function without having disabled the scopeguard, we +// send a "feature-not-implemented" iq as a result. If an other kind of +// error is found (for example the feature is implemented in biboumi, but +// the request is missing some attribute) we can just change the values of +// error_type and error_name and return from the function (without disabling +// the scopeguard); an iq error will be sent +void BiboumiComponent::handle_iq(const Stanza& stanza) +{ + std::string id = stanza.get_tag("id"); + std::string from = stanza.get_tag("from"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + if (from.empty()) + return; + if (id.empty() || to_str.empty() || type.empty()) + { + this->send_stanza_error("iq", from, this->served_hostname, id, + "modify", "bad-request", ""); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + + // These two values will be used in the error iq sent if we don't disable + // the scopeguard. + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("iq", from, to_str, id, + error_type, error_name, ""); + }); + if (type == "set") + { + XmlNode* query; + if ((query = stanza.get_child("query", MUC_ADMIN_NS))) + { + const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); + if (child) + { + std::string nick = child->get_tag("nick"); + std::string role = child->get_tag("role"); + std::string affiliation = child->get_tag("affiliation"); + if (!nick.empty()) + { + Iid iid(to.local); + if (role == "none") + { // This is a kick + std::string reason; + XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); + if (reason_el) + reason = reason_el->get_inner(); + bridge->send_irc_kick(iid, nick, reason, id, from); + } + else + bridge->forward_affiliation_role_change(iid, nick, affiliation, role); + stanza_error.disable(); + } + } + } + else if ((query = stanza.get_child("command", ADHOC_NS))) + { + Stanza response("iq"); + response["to"] = from; + response["from"] = this->served_hostname; + response["id"] = id; + XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); + if (inner_node.get_child("error", ADHOC_NS)) + response["type"] = "error"; + else + response["type"] = "result"; + response.add_child(std::move(inner_node)); + response.close(); + this->send_stanza(response); + stanza_error.disable(); + } + } + else if (type == "get") + { + XmlNode* query; + if ((query = stanza.get_child("query", DISCO_INFO_NS))) + { // Disco info + if (to_str == this->served_hostname) + { + const std::string node = query->get_tag("node"); + if (node.empty()) + { + // On the gateway itself + this->send_self_disco_info(id, from); + stanza_error.disable(); + } + } + } + else if ((query = stanza.get_child("query", VERSION_NS))) + { + Iid iid(to.local); + if (iid.is_user || + (iid.is_channel && !to.resource.empty())) + { + // Get the IRC user version + std::string target; + if (iid.is_user) + target = iid.get_local(); + else + target = to.resource; + bridge->send_irc_version_request(iid.get_server(), target, id, + from, to_str); + } + else + { + // On the gateway itself or on a channel + this->send_version(id, from, to_str); + } + stanza_error.disable(); + } + else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) + { + Iid iid(to.local); + const std::string node = query->get_tag("node"); + if (node == ADHOC_NS) + { + this->send_adhoc_commands_list(id, from); + stanza_error.disable(); + } + else if (node.empty() && !iid.is_user && !iid.is_channel) + { // Disco on an IRC server: get the list of channels + bridge->send_irc_channel_list_request(iid, id, from); + stanza_error.disable(); + } + } + else if ((query = stanza.get_child("ping", PING_NS))) + { + Iid iid(to.local); + if (iid.is_user) + { // Ping any user (no check on the nick done ourself) + bridge->send_irc_user_ping_request(iid.get_server(), + iid.get_local(), id, from, to_str); + } + else if (iid.is_channel && !to.resource.empty()) + { // Ping a room participant (we check if the nick is in the room) + bridge->send_irc_participant_ping_request(iid, + to.resource, id, from, to_str); + } + else + { // Ping a channel, a server or the gateway itself + bridge->on_gateway_ping(iid.get_server(), + id, from, to_str); + } + stanza_error.disable(); + } + } + else if (type == "result") + { + stanza_error.disable(); + XmlNode* query; + if ((query = stanza.get_child("query", VERSION_NS))) + { + XmlNode* name_node = query->get_child("name", VERSION_NS); + XmlNode* version_node = query->get_child("version", VERSION_NS); + XmlNode* os_node = query->get_child("os", VERSION_NS); + std::string name; + std::string version; + std::string os; + if (name_node) + name = name_node->get_inner() + " (through the biboumi gateway)"; + if (version_node) + version = version_node->get_inner(); + if (os_node) + os = os_node->get_inner(); + const Iid iid(to.local); + bridge->send_xmpp_version_to_irc(iid, name, version, os); + } + else + { + const auto it = this->waiting_iq.find(id); + if (it != this->waiting_iq.end()) + { + it->second(bridge, stanza); + this->waiting_iq.erase(it); + } + } + } + error_type = "cancel"; + error_name = "feature-not-implemented"; +} + +Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller)); + return this->bridges.at(user_jid).get(); + } +} + +Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + return nullptr; + } +} + +std::list<Bridge*> BiboumiComponent::get_bridges() const +{ + std::list<Bridge*> res; + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + res.push_back(it->second.get()); + return res; +} + +void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + XmlNode identity("identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "Biboumi XMPP-IRC gateway"; + identity.close(); + query.add_child(std::move(identity)); + for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) + { + XmlNode feature("feature"); + feature["var"] = ns; + feature.close(); + query.add_child(std::move(feature)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = requester_jid; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + query["node"] = ADHOC_NS; + for (const auto& kv: this->adhoc_commands_handler.get_commands()) + { + XmlNode item("item"); + item["jid"] = this->served_hostname; + item["node"] = kv.first; + item["name"] = kv.second.name; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_iq_version_request(const std::string& from, + const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = "version_"s + this->next_id(); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = id; + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode ping("ping"); + ping["xmlns"] = PING_NS; + ping.close(); + iq.add_child(std::move(ping)); + iq.close(); + this->send_stanza(iq); + + auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) + { + Jid to(stanza.get_tag("to")); + if (to.local != from) + { + log_error("Received a corresponding ping result, but the 'to' from " + "the response mismatches the 'from' of the request"); + } + else + bridge->send_irc_ping_result(from, id); + }; + this->waiting_iq[id] = result_cb; +} + +void BiboumiComponent::send_iq_room_list_result(const std::string& id, + const std::string to_jid, + const std::string& from, + const std::vector<ListElement>& rooms_list) +{ + Stanza iq("iq"); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + for (const auto& room: rooms_list) + { + XmlNode item("item"); + item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp new file mode 100644 index 0000000..e9b5d72 --- /dev/null +++ b/src/xmpp/biboumi_component.hpp @@ -0,0 +1,111 @@ +#ifndef BIBOUMI_COMPONENT_INCLUDED +# define BIBOUMI_COMPONENT_INCLUDED + +#include <xmpp/xmpp_component.hpp> + +#include <bridge/bridge.hpp> + +#include <memory> +#include <string> +#include <map> + +class ListElement; + +/** + * A callback called when the waited iq result is received (it is matched + * against the iq id) + */ +using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza& stanza)>; + +/** + * Interact with the Biboumi Bridge + */ +class BiboumiComponent: public XmppComponent +{ +public: + explicit BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret); + ~BiboumiComponent() = default; + + /** + * Returns the bridge for the given user. If it does not exist, return + * nullptr. + */ + Bridge* find_user_bridge(const std::string& user_jid); + /** + * Return a list of all the managed bridges. + */ + std::list<Bridge*> get_bridges() const; + + /** + * Send a "close" message to all our connected peers. That message + * depends on the protocol used (this may be a QUIT irc message, or a + * <stream/>, etc). We may also directly close the connection, or we may + * wait for the remote peer to acknowledge it before closing. + */ + void shutdown(); + /** + * Run a check on all bridges, to remove all disconnected (socket is + * closed, or no channel is joined) IrcClients. Some kind of garbage collector. + */ + void clean(); + /** + * Send a result IQ with the gateway disco informations. + */ + void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send the list of all available ad-hoc commands to that JID. The list is + * different depending on what JID made the request. + */ + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); + /** + * Send an iq version request + */ + void send_iq_version_request(const std::string& from, + const std::string& jid_to); + /** + * Send a ping request + */ + void send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id); + /** + * Send the channels list in one big stanza + */ + void send_iq_room_list_result(const std::string& id, const std::string to_jid, + const std::string& from, + const std::vector<ListElement>& rooms_list); + /** + * Handle the various stanza types + */ + void handle_presence(const Stanza& stanza); + void handle_message(const Stanza& stanza); + void handle_iq(const Stanza& stanza); + +private: + /** + * Return the bridge associated with the given full JID. Create a new one + * if none already exist. + */ + Bridge* get_user_bridge(const std::string& user_jid); + + /** + * A map of id -> callback. When we want to wait for an iq result, we add + * the callback to this map, with the iq id as the key. When an iq result + * is received, we look for a corresponding callback in this map. If + * found, we call it and remove it. + */ + std::map<std::string, iq_responder_callback_t> waiting_iq; + + /** + * One bridge for each user of the component. Indexed by the user's full + * jid + */ + std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges; + + BiboumiComponent(const BiboumiComponent&) = delete; + BiboumiComponent(BiboumiComponent&&) = delete; + BiboumiComponent& operator=(const BiboumiComponent&) = delete; + BiboumiComponent& operator=(BiboumiComponent&&) = delete; +}; + +#endif // BIBOUMI_COMPONENT_INCLUDED diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index c51e011..6149ceb 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -1,8 +1,8 @@ #include <xmpp/jid.hpp> -#include <config.h> #include <cstring> #include <map> +#include <louloulibs.h> #ifdef LIBIDN_FOUND #include <stringprep.h> #endif diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp deleted file mode 100644 index 0e2531d..0000000 --- a/src/xmpp/xmpp_component.cpp +++ /dev/null @@ -1,1194 +0,0 @@ -#include <utils/timed_events.hpp> -#include <utils/scopeguard.hpp> -#include <utils/tolower.hpp> -#include <logger/logger.hpp> - -#include <xmpp/xmpp_component.hpp> -#include <bridge/list_element.hpp> -#include <config/config.hpp> -#include <xmpp/jid.hpp> -#include <utils/sha1.hpp> - -#include <stdexcept> -#include <iostream> - -#include <stdio.h> - -#include <config.h> - -#include <uuid.h> - -#ifdef SYSTEMD_FOUND -# include <systemd/sd-daemon.h> -#endif - -using namespace std::string_literals; - -static std::set<std::string> kickable_errors{ - "gone", - "internal-server-error", - "item-not-found", - "jid-malformed", - "recipient-unavailable", - "redirect", - "remote-server-not-found", - "remote-server-timeout", - "service-unavailable", - "malformed-error" - }; - -XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret): - TCPSocketHandler(poller), - ever_auth(false), - first_connection_try(true), - served_hostname(hostname), - secret(secret), - authenticated(false), - doc_open(false), - adhoc_commands_handler(this) -{ - this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, - std::placeholders::_1)); - this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this, - std::placeholders::_1)); - this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, - std::placeholders::_1)); - this->stanza_handlers.emplace("handshake", - std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); - this->stanza_handlers.emplace("presence", - std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); - this->stanza_handlers.emplace("message", - std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); - this->stanza_handlers.emplace("iq", - std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1)); - this->stanza_handlers.emplace("error", - std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); -} - -XmppComponent::~XmppComponent() -{ -} - -void XmppComponent::start() -{ - this->connect("localhost", Config::get("port", "5347"), false); -} - -bool XmppComponent::is_document_open() const -{ - return this->doc_open; -} - -void XmppComponent::send_stanza(const Stanza& stanza) -{ - std::string str = stanza.to_string(); - log_debug("XMPP SENDING: " << str); - this->send_data(std::move(str)); -} - -void XmppComponent::on_connection_failed(const std::string& reason) -{ - this->first_connection_try = false; - log_error("Failed to connect to the XMPP server: " << reason); -#ifdef SYSTEMD_FOUND - sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); -#endif -} - -void XmppComponent::on_connected() -{ - log_info("connected to XMPP server"); - this->first_connection_try = true; - XmlNode node("", nullptr); - node.set_name("stream:stream"); - node["xmlns"] = COMPONENT_NS; - node["xmlns:stream"] = STREAM_NS; - node["to"] = this->served_hostname; - this->send_stanza(node); - this->doc_open = true; - // We may have some pending data to send: this happens when we try to send - // some data before we are actually connected. We send that data right now, if any - this->send_pending_data(); -} - -void XmppComponent::on_connection_close(const std::string& error) -{ - if (error.empty()) - { - log_info("XMPP server closed connection"); - } - else - { - log_info("XMPP server closed connection: " << error); - } -} - -void XmppComponent::parse_in_buffer(const size_t size) -{ - if (!this->in_buf.empty()) - { // This may happen if the parser could not allocate enough space for - // us. We try to feed it the data that was read into our in_buf - // instead. If this fails again we are in trouble. - this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); - this->in_buf.clear(); - } - else - { // Just tell the parser to parse the data that was placed into the - // buffer it provided to us with GetBuffer - this->parser.parse(size, false); - } -} - -void XmppComponent::shutdown() -{ - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - { - it->second->shutdown("Gateway shutdown"); - } -} - -void XmppComponent::clean() -{ - auto it = this->bridges.begin(); - while (it != this->bridges.end()) - { - it->second->clean(); - if (it->second->active_clients() == 0) - it = this->bridges.erase(it); - else - ++it; - } -} - -void XmppComponent::on_remote_stream_open(const XmlNode& node) -{ - log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); - this->stream_id = node.get_tag("id"); - if (this->stream_id.empty()) - { - log_error("Error: no attribute 'id' found"); - this->send_stream_error("bad-format", "missing 'id' attribute"); - this->close_document(); - return ; - } - - // Try to authenticate - char digest[HASH_LENGTH * 2 + 1]; - sha1nfo sha1; - sha1_init(&sha1); - sha1_write(&sha1, this->stream_id.data(), this->stream_id.size()); - sha1_write(&sha1, this->secret.data(), this->secret.size()); - const uint8_t* result = sha1_result(&sha1); - for (int i=0; i < HASH_LENGTH; i++) - sprintf(digest + (i*2), "%02x", result[i]); - digest[HASH_LENGTH * 2] = '\0'; - - Stanza handshake(COMPONENT_NS":handshake"); - handshake.set_inner(digest); - handshake.close(); - this->send_stanza(handshake); -} - -void XmppComponent::on_remote_stream_close(const XmlNode& node) -{ - log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); - this->doc_open = false; -} - -void XmppComponent::reset() -{ - this->parser.reset(); -} - -void XmppComponent::on_stanza(const Stanza& stanza) -{ - log_debug("XMPP RECEIVING: " << stanza.to_string()); - std::function<void(const Stanza&)> handler; - try - { - handler = this->stanza_handlers.at(stanza.get_name()); - } - catch (const std::out_of_range& exception) - { - log_warning("No handler for stanza of type " << stanza.get_name()); - return; - } - handler(stanza); -} - -void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) -{ - XmlNode node("stream:error", nullptr); - XmlNode error(name, nullptr); - error["xmlns"] = STREAM_NS; - if (!explanation.empty()) - error.set_inner(explanation); - error.close(); - node.add_child(std::move(error)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid) -{ - Stanza node(kind); - if (!to.empty()) - node["to"] = to; - if (!from.empty()) - { - if (fulljid) - node["from"] = from; - else - node["from"] = from + "@" + this->served_hostname; - } - if (!id.empty()) - node["id"] = id; - node["type"] = "error"; - XmlNode error("error"); - error["type"] = error_type; - XmlNode inner_error(defined_condition); - inner_error["xmlns"] = STANZA_NS; - inner_error.close(); - error.add_child(std::move(inner_error)); - if (!text.empty()) - { - XmlNode text_node("text"); - text_node["xmlns"] = STANZA_NS; - text_node.set_inner(text); - text_node.close(); - error.add_child(std::move(text_node)); - } - error.close(); - node.add_child(std::move(error)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::close_document() -{ - log_debug("XMPP SENDING: </stream:stream>"); - this->send_data("</stream:stream>"); - this->doc_open = false; -} - -void XmppComponent::handle_handshake(const Stanza& stanza) -{ - (void)stanza; - this->authenticated = true; - this->ever_auth = true; - log_info("Authenticated with the XMPP server"); -#ifdef SYSTEMD_FOUND - sd_notify(0, "READY=1"); - // Install an event that sends a keepalive to systemd. If biboumi crashes - // or hangs for too long, systemd will restart it. - uint64_t usec; - if (sd_watchdog_enabled(0, &usec) > 0) - { - TimedEventsManager::instance().add_event(TimedEvent( - std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)), - []() { sd_notify(0, "WATCHDOG=1"); })); - } -#endif -} - -void XmppComponent::handle_presence(const Stanza& stanza) -{ - std::string from = stanza.get_tag("from"); - std::string id = stanza.get_tag("id"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - // Check for mandatory tags - if (from.empty()) - { - log_warning("Received an invalid presence stanza: tag 'from' is missing."); - return; - } - if (to_str.empty()) - { - this->send_stanza_error("presence", from, this->served_hostname, id, - "modify", "bad-request", "Missing 'to' tag"); - return; - } - - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - Iid iid(to.local); - - // An error stanza is sent whenever we exit this function without - // disabling this scopeguard. If error_type and error_name are not - // changed, the error signaled is internal-server-error. Change their - // value to signal an other kind of error. For example - // feature-not-implemented, etc. Any non-error process should reach the - // stanza_error.disable() call at the end of the function. - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("presence", from, to_str, id, - error_type, error_name, ""); - }); - - if (iid.is_channel && !iid.get_server().empty()) - { // presence toward a MUC that corresponds to an irc channel, or a - // dummy channel if iid.chan is empty - if (type.empty()) - { - const std::string own_nick = bridge->get_own_nick(iid); - if (!own_nick.empty() && own_nick != to.resource) - bridge->send_irc_nick_change(iid, to.resource); - XmlNode* x = stanza.get_child("x", MUC_NS); - XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; - bridge->join_irc_channel(iid, to.resource, - password ? password->get_inner() : ""); - } - else if (type == "unavailable") - { - XmlNode* status = stanza.get_child("status", COMPONENT_NS); - bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); - } - } - else - { - // An user wants to join an invalid IRC channel, return a presence error to him - if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, from); - } - stanza_error.disable(); -} - -void XmppComponent::handle_message(const Stanza& stanza) -{ - std::string from = stanza.get_tag("from"); - std::string id = stanza.get_tag("id"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - if (from.empty()) - return; - if (type.empty()) - type = "normal"; - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - Iid iid(to.local); - - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("message", from, to_str, id, - error_type, error_name, ""); - }); - XmlNode* body = stanza.get_child("body", COMPONENT_NS); - if (type == "groupchat" && iid.is_channel) - { - if (body && !body->get_inner().empty()) - { - bridge->send_channel_message(iid, body->get_inner()); - } - XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); - if (subject) - bridge->set_channel_topic(iid, subject->get_inner()); - } - else if (type == "error") - { - const XmlNode* error = stanza.get_child("error", COMPONENT_NS); - // Only a set of errors are considered “fatal”. If we encounter one of - // them, we purge (we disconnect the user from all the IRC servers). - // We consider this to be true, unless the error condition is - // specified and is not in the kickable_errors set - bool kickable_error = true; - if (error && error->has_children()) - { - const XmlNode* condition = error->get_last_child(); - if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) - kickable_error = false; - } - if (kickable_error) - bridge->shutdown("Error from remote client"); - } - else if (type == "chat") - { - if (body && !body->get_inner().empty()) - { - // a message for nick!server - if (iid.is_user && !iid.get_local().empty()) - { - bridge->send_private_message(iid, body->get_inner()); - bridge->remove_preferred_from_jid(iid.get_local()); - } - else if (!iid.is_user && !to.resource.empty()) - { // a message for chan%server@biboumi/Nick or - // server@biboumi/Nick - // Convert that into a message to nick!server - Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); - bridge->send_private_message(user_iid, body->get_inner()); - bridge->set_preferred_from_jid(user_iid.get_local(), to_str); - } - } - } - else if (iid.is_user) - this->send_invalid_user_error(to.local, from); - stanza_error.disable(); -} - -// We MUST return an iq, whatever happens, except if the type is -// "result". -// To do this, we use a scopeguard. If an exception is raised somewhere, an -// iq of type error "internal-server-error" is sent. If we handle the -// request properly (by calling a function that registers an iq to be sent -// later, or that directly sends an iq), we disable the ScopeGuard. If we -// reach the end of the function without having disabled the scopeguard, we -// send a "feature-not-implemented" iq as a result. If an other kind of -// error is found (for example the feature is implemented in biboumi, but -// the request is missing some attribute) we can just change the values of -// error_type and error_name and return from the function (without disabling -// the scopeguard); an iq error will be sent -void XmppComponent::handle_iq(const Stanza& stanza) -{ - std::string id = stanza.get_tag("id"); - std::string from = stanza.get_tag("from"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - if (from.empty()) - return; - if (id.empty() || to_str.empty() || type.empty()) - { - this->send_stanza_error("iq", from, this->served_hostname, id, - "modify", "bad-request", ""); - return; - } - - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - - // These two values will be used in the error iq sent if we don't disable - // the scopeguard. - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("iq", from, to_str, id, - error_type, error_name, ""); - }); - if (type == "set") - { - XmlNode* query; - if ((query = stanza.get_child("query", MUC_ADMIN_NS))) - { - const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); - if (child) - { - std::string nick = child->get_tag("nick"); - std::string role = child->get_tag("role"); - std::string affiliation = child->get_tag("affiliation"); - if (!nick.empty()) - { - Iid iid(to.local); - if (role == "none") - { // This is a kick - std::string reason; - XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); - if (reason_el) - reason = reason_el->get_inner(); - bridge->send_irc_kick(iid, nick, reason, id, from); - } - else - bridge->forward_affiliation_role_change(iid, nick, affiliation, role); - stanza_error.disable(); - } - } - } - else if ((query = stanza.get_child("command", ADHOC_NS))) - { - Stanza response("iq"); - response["to"] = from; - response["from"] = this->served_hostname; - response["id"] = id; - XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); - if (inner_node.get_child("error", ADHOC_NS)) - response["type"] = "error"; - else - response["type"] = "result"; - response.add_child(std::move(inner_node)); - response.close(); - this->send_stanza(response); - stanza_error.disable(); - } - } - else if (type == "get") - { - XmlNode* query; - if ((query = stanza.get_child("query", DISCO_INFO_NS))) - { // Disco info - if (to_str == this->served_hostname) - { - const std::string node = query->get_tag("node"); - if (node.empty()) - { - // On the gateway itself - this->send_self_disco_info(id, from); - stanza_error.disable(); - } - } - } - else if ((query = stanza.get_child("query", VERSION_NS))) - { - Iid iid(to.local); - if (iid.is_user || - (iid.is_channel && !to.resource.empty())) - { - // Get the IRC user version - std::string target; - if (iid.is_user) - target = iid.get_local(); - else - target = to.resource; - bridge->send_irc_version_request(iid.get_server(), target, id, - from, to_str); - } - else - { - // On the gateway itself or on a channel - this->send_version(id, from, to_str); - } - stanza_error.disable(); - } - else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) - { - Iid iid(to.local); - const std::string node = query->get_tag("node"); - if (node == ADHOC_NS) - { - this->send_adhoc_commands_list(id, from); - stanza_error.disable(); - } - else if (node.empty() && !iid.is_user && !iid.is_channel) - { // Disco on an IRC server: get the list of channels - bridge->send_irc_channel_list_request(iid, id, from); - stanza_error.disable(); - } - } - else if ((query = stanza.get_child("ping", PING_NS))) - { - Iid iid(to.local); - if (iid.is_user) - { // Ping any user (no check on the nick done ourself) - bridge->send_irc_user_ping_request(iid.get_server(), - iid.get_local(), id, from, to_str); - } - else if (iid.is_channel && !to.resource.empty()) - { // Ping a room participant (we check if the nick is in the room) - bridge->send_irc_participant_ping_request(iid, - to.resource, id, from, to_str); - } - else - { // Ping a channel, a server or the gateway itself - bridge->on_gateway_ping(iid.get_server(), - id, from, to_str); - } - stanza_error.disable(); - } - } - else if (type == "result") - { - stanza_error.disable(); - XmlNode* query; - if ((query = stanza.get_child("query", VERSION_NS))) - { - XmlNode* name_node = query->get_child("name", VERSION_NS); - XmlNode* version_node = query->get_child("version", VERSION_NS); - XmlNode* os_node = query->get_child("os", VERSION_NS); - std::string name; - std::string version; - std::string os; - if (name_node) - name = name_node->get_inner() + " (through the biboumi gateway)"; - if (version_node) - version = version_node->get_inner(); - if (os_node) - os = os_node->get_inner(); - const Iid iid(to.local); - bridge->send_xmpp_version_to_irc(iid, name, version, os); - } - else - { - const auto it = this->waiting_iq.find(id); - if (it != this->waiting_iq.end()) - { - it->second(bridge, stanza); - this->waiting_iq.erase(it); - } - } - } - error_type = "cancel"; - error_name = "feature-not-implemented"; -} - -void XmppComponent::handle_error(const Stanza& stanza) -{ - XmlNode* text = stanza.get_child("text", STREAMS_NS); - std::string error_message("Unspecified error"); - if (text) - error_message = text->get_inner(); - log_error("Stream error received from the XMPP server: " << error_message); -#ifdef SYSTEMD_FOUND - if (!this->ever_auth) - sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); -#endif - -} - -Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) -{ - try - { - return this->bridges.at(user_jid).get(); - } - catch (const std::out_of_range& exception) - { - this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller)); - return this->bridges.at(user_jid).get(); - } -} - -Bridge* XmppComponent::find_user_bridge(const std::string& user_jid) -{ - try - { - return this->bridges.at(user_jid).get(); - } - catch (const std::out_of_range& exception) - { - return nullptr; - } -} - -std::list<Bridge*> XmppComponent::get_bridges() const -{ - std::list<Bridge*> res; - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - res.push_back(it->second.get()); - return res; -} - -void* XmppComponent::get_receive_buffer(const size_t size) const -{ - return this->parser.get_buffer(size); -} - -void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid) -{ - XmlNode node("message"); - node["to"] = to; - if (fulljid) - node["from"] = from; - else - node["from"] = from + "@" + this->served_hostname; - if (!type.empty()) - node["type"] = type; - XmlNode body_node("body"); - body_node.set_inner(std::get<0>(body)); - body_node.close(); - node.add_child(std::move(body_node)); - if (std::get<1>(body)) - { - XmlNode html("html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(body).release()); - html.close(); - node.add_child(std::move(html)); - } - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self) -{ - XmlNode node("presence"); - node["to"] = to; - node["from"] = from + "@" + this->served_hostname + "/" + nick; - - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - - XmlNode item("item"); - if (!affiliation.empty()) - item["affiliation"] = affiliation; - if (!role.empty()) - item["role"] = role; - if (!realjid.empty()) - { - const std::string preped_jid = jidprep(realjid); - if (!preped_jid.empty()) - item["jid"] = preped_jid; - } - item.close(); - x.add_child(std::move(item)); - - if (self) - { - XmlNode status("status"); - status["code"] = "110"; - status.close(); - x.add_child(std::move(status)); - } - x.close(); - node.add_child(std::move(x)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_invalid_room_error(const std::string& muc_name, - const std::string& nick, - const std::string& to) -{ - Stanza presence("presence"); - if (!muc_name.empty()) - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else - presence["from"] = this->served_hostname; - presence["to"] = to; - presence["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - presence.add_child(std::move(x)); - XmlNode error("error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "cancel"; - XmlNode item_not_found("item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - item_not_found.close(); - error.add_child(std::move(item_not_found)); - XmlNode text("text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(muc_name + - " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" + - this->served_hostname); - text.close(); - error.add_child(std::move(text)); - error.close(); - presence.add_child(std::move(error)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_invalid_user_error(const std::string& user_name, const std::string& to) -{ - Stanza message("message"); - message["from"] = user_name + "@" + this->served_hostname; - message["to"] = to; - message["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - message.add_child(std::move(x)); - XmlNode error("error"); - error["type"] = "cancel"; - XmlNode item_not_found("item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - item_not_found.close(); - error.add_child(std::move(item_not_found)); - XmlNode text("text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(user_name + - " is not a valid IRC user name. A correct user jid is of the form: <nick>!<server>@" + - this->served_hostname); - text.close(); - error.add_child(std::move(text)); - error.close(); - message.add_child(std::move(error)); - message.close(); - this->send_stanza(message); -} - -void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) -{ - XmlNode message("message"); - message["to"] = to; - message["from"] = from + "@" + this->served_hostname; - message["type"] = "groupchat"; - XmlNode subject("subject"); - subject.set_inner(std::get<0>(topic)); - subject.close(); - message.add_child(std::move(subject)); - message.close(); - this->send_stanza(message); -} - -void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to) -{ - Stanza message("message"); - message["to"] = jid_to; - if (!nick.empty()) - message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else // Message from the room itself - message["from"] = muc_name + "@" + this->served_hostname; - message["type"] = "groupchat"; - XmlNode body("body"); - body.set_inner(std::get<0>(xmpp_body)); - body.close(); - message.add_child(std::move(body)); - if (std::get<1>(xmpp_body)) - { - XmlNode html("html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(xmpp_body).release()); - html.close(); - message.add_child(std::move(html)); - } - message.close(); - this->send_stanza(message); -} - -void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) -{ - Stanza presence("presence"); - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - presence["type"] = "unavailable"; - const std::string message_str = std::get<0>(message); - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - if (self) - { - XmlNode status("status"); - status["code"] = "110"; - status.close(); - x.add_child(std::move(status)); - } - x.close(); - presence.add_child(std::move(x)); - if (!message_str.empty()) - { - XmlNode status("status"); - status.set_inner(message_str); - status.close(); - presence.add_child(std::move(status)); - } - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self) -{ - Stanza presence("presence"); - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick; - presence["type"] = "unavailable"; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["nick"] = new_nick; - item.close(); - x.add_child(std::move(item)); - XmlNode status("status"); - status["code"] = "303"; - status.close(); - x.add_child(std::move(status)); - if (self) - { - XmlNode status2("status"); - status2["code"] = "110"; - status2.close(); - x.add_child(std::move(status2)); - } - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); - - this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); -} - -void XmppComponent::kick_user(const std::string& muc_name, - const std::string& target, - const std::string& txt, - const std::string& author, - const std::string& jid_to) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - presence["type"] = "unavailable"; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["affiliation"] = "none"; - item["role"] = "none"; - XmlNode actor("actor"); - actor["nick"] = author; - actor["jid"] = author; // backward compatibility with old clients - actor.close(); - item.add_child(std::move(actor)); - XmlNode reason("reason"); - reason.set_inner(txt); - reason.close(); - item.add_child(std::move(reason)); - item.close(); - x.add_child(std::move(item)); - XmlNode status("status"); - status["code"] = "307"; - status.close(); - x.add_child(std::move(status)); - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& /* text */) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; - presence["to"] = jid_to; - presence["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - presence.add_child(std::move(x)); - XmlNode error("error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = type; - if (!error_code.empty()) - error["code"] = error_code; - XmlNode subnode(condition); - subnode["xmlns"] = STANZA_NS; - subnode.close(); - error.add_child(std::move(subnode)); - error.close(); - presence.add_child(std::move(error)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["affiliation"] = affiliation; - item["role"] = role; - item.close(); - x.add_child(std::move(item)); - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_self_disco_info(const std::string& id, const std::string& jid_to) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = this->served_hostname; - XmlNode query("query"); - query["xmlns"] = DISCO_INFO_NS; - XmlNode identity("identity"); - identity["category"] = "conference"; - identity["type"] = "irc"; - identity["name"] = "Biboumi XMPP-IRC gateway"; - identity.close(); - query.add_child(std::move(identity)); - for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) - { - XmlNode feature("feature"); - feature["var"] = ns; - feature.close(); - query.add_child(std::move(feature)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = jid_from; - XmlNode query("query"); - query["xmlns"] = VERSION_NS; - if (version.empty()) - { - XmlNode name("name"); - name.set_inner("biboumi"); - name.close(); - query.add_child(std::move(name)); - XmlNode version("version"); - version.set_inner(BIBOUMI_VERSION); - version.close(); - query.add_child(std::move(version)); - XmlNode os("os"); - os.set_inner(SYSTEM_NAME); - os.close(); - query.add_child(std::move(os)); - } - else - { - XmlNode name("name"); - name.set_inner(version); - name.close(); - query.add_child(std::move(name)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = requester_jid; - iq["from"] = this->served_hostname; - XmlNode query("query"); - query["xmlns"] = DISCO_ITEMS_NS; - query["node"] = ADHOC_NS; - for (const auto& kv: this->adhoc_commands_handler.get_commands()) - { - XmlNode item("item"); - item["jid"] = this->served_hostname; - item["node"] = kv.first; - item["name"] = kv.second.name; - item.close(); - query.add_child(std::move(item)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_iq_version_request(const std::string& from, - const std::string& jid_to) -{ - Stanza iq("iq"); - iq["type"] = "get"; - iq["id"] = "version_"s + XmppComponent::next_id(); - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = jid_to; - XmlNode query("query"); - query["xmlns"] = VERSION_NS; - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_ping_request(const std::string& from, - const std::string& jid_to, - const std::string& id) -{ - Stanza iq("iq"); - iq["type"] = "get"; - iq["id"] = id; - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = jid_to; - XmlNode ping("ping"); - ping["xmlns"] = PING_NS; - ping.close(); - iq.add_child(std::move(ping)); - iq.close(); - this->send_stanza(iq); - - auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) - { - Jid to(stanza.get_tag("to")); - if (to.local != from) - { - log_error("Received a corresponding ping result, but the 'to' from " - "the response mismatches the 'from' of the request"); - } - else - bridge->send_irc_ping_result(from, id); - }; - this->waiting_iq[id] = result_cb; -} - -void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) -{ - Stanza iq("iq"); - if (!from_local_part.empty()) - iq["from"] = from_local_part + "@" + this->served_hostname; - else - iq["from"] = this->served_hostname; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_iq_room_list_result(const std::string& id, - const std::string to_jid, - const std::string& from, - const std::vector<ListElement>& rooms_list) -{ - Stanza iq("iq"); - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - XmlNode query("query"); - query["xmlns"] = DISCO_ITEMS_NS; - for (const auto& room: rooms_list) - { - XmlNode item("item"); - item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; - item.close(); - query.add_child(std::move(item)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -std::string XmppComponent::next_id() -{ - char uuid_str[37]; - uuid_t uuid; - uuid_generate(uuid); - uuid_unparse(uuid, uuid_str); - return uuid_str; -} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp deleted file mode 100644 index a0b06a6..0000000 --- a/src/xmpp/xmpp_component.hpp +++ /dev/null @@ -1,303 +0,0 @@ -#ifndef XMPP_COMPONENT_INCLUDED -# define XMPP_COMPONENT_INCLUDED - -#include <xmpp/adhoc_commands_handler.hpp> -#include <network/tcp_socket_handler.hpp> -#include <xmpp/xmpp_parser.hpp> -#include <bridge/bridge.hpp> - -#include <unordered_map> -#include <memory> -#include <string> -#include <map> - -#define STREAM_NS "http://etherx.jabber.org/streams" -#define COMPONENT_NS "jabber:component:accept" -#define MUC_NS "http://jabber.org/protocol/muc" -#define MUC_USER_NS MUC_NS"#user" -#define MUC_ADMIN_NS MUC_NS"#admin" -#define DISCO_NS "http://jabber.org/protocol/disco" -#define DISCO_ITEMS_NS DISCO_NS"#items" -#define DISCO_INFO_NS DISCO_NS"#info" -#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" -#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" -#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" -#define VERSION_NS "jabber:iq:version" -#define ADHOC_NS "http://jabber.org/protocol/commands" -#define PING_NS "urn:xmpp:ping" - -class ListElement; - -/** - * A callback called when the waited iq result is received (it is matched - * against the iq id) - */ -using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza& stanza)>; - -/** - * An XMPP component, communicating with an XMPP server using the protocole - * described in XEP-0114: Jabber Component Protocol - * - * TODO: implement XEP-0225: Component Connections - */ -class XmppComponent: public TCPSocketHandler -{ -public: - explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret); - ~XmppComponent(); - - void on_connection_failed(const std::string& reason) override final; - void on_connected() override final; - void on_connection_close(const std::string& error) override final; - void parse_in_buffer(const size_t size) override final; - - /** - * Returns the bridge for the given user. If it does not exist, return - * nullptr. - */ - Bridge* find_user_bridge(const std::string& user_jid); - /** - * Return a list of all the managed bridges. - */ - std::list<Bridge*> get_bridges() const; - - /** - * Returns a unique id, to be used in the 'id' element of our iq stanzas. - */ - static std::string next_id(); - /** - * Send a "close" message to all our connected peers. That message - * depends on the protocol used (this may be a QUIT irc message, or a - * <stream/>, etc). We may also directly close the connection, or we may - * wait for the remote peer to acknowledge it before closing. - */ - void shutdown(); - bool is_document_open() const; - /** - * Run a check on all bridges, to remove all disconnected (socket is - * closed, or no channel is joined) IrcClients. Some kind of garbage collector. - */ - void clean(); - /** - * Connect to the XMPP server. - */ - void start(); - /** - * Reset the component so we can use the component on a new XMPP stream - */ - void reset(); - /** - * Serialize the stanza and add it to the out_buf to be sent to the - * server. - */ - void send_stanza(const Stanza& stanza); - /** - * Handle the opening of the remote stream - */ - void on_remote_stream_open(const XmlNode& node); - /** - * Handle the closing of the remote stream - */ - void on_remote_stream_close(const XmlNode& node); - /** - * Handle received stanzas - */ - void on_stanza(const Stanza& stanza); - /** - * Send an error stanza. Message being the name of the element inside the - * stanza, and explanation being a short human-readable sentence - * describing the error. - */ - void send_stream_error(const std::string& message, const std::string& explanation); - /** - * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error - */ - void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid=true); - /** - * Send the closing signal for our document (not closing the connection though). - */ - void close_document(); - /** - * Send a message from from@served_hostname, with the given body - * - * If fulljid is false, the provided 'from' doesn't contain the - * server-part of the JID and must be added. - */ - void send_message(const std::string& from, Xmpp::body&& body, - const std::string& to, const std::string& type, - const bool fulljid=false); - /** - * Send a join from a new participant - */ - void send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self); - /** - * Send an error to indicate that the user tried to join an invalid room - */ - void send_invalid_room_error(const std::string& muc_jid, - const std::string& nick, - const std::string& to); - /** - * Send an error to indicate that the user tried to send a message to an - * invalid user. - */ - void send_invalid_user_error(const std::string& user_name, - const std::string& to); - /** - * Send the MUC topic to the user - */ - void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to); - /** - * Send a (non-private) message to the MUC - */ - void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); - /** - * Send an unavailable presence for this nick - */ - void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); - /** - * Indicate that a participant changed his nick - */ - void send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self); - /** - * An user is kicked from a room - */ - void kick_user(const std::string& muc_name, - const std::string& target, - const std::string& reason, - const std::string& author, - const std::string& jid_to); - /** - * Send a generic presence error - */ - void send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& text); - /** - * Send a presence from the MUC indicating a change in the role and/or - * affiliation of a participant - */ - void send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to); - /** - * Send a result IQ with the gateway disco informations. - */ - void send_self_disco_info(const std::string& id, const std::string& jid_to); - /** - * Send a result IQ with the given version, or the gateway version if the - * passed string is empty. - */ - void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version=""); - /** - * Send the list of all available ad-hoc commands to that JID. The list is - * different depending on what JID made the request. - */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); - /** - * Send an iq version request - */ - void send_iq_version_request(const std::string& from, - const std::string& jid_to); - /** - * Send a ping request - */ - void send_ping_request(const std::string& from, - const std::string& jid_to, - const std::string& id); - /** - * Send an empty iq of type result - */ - void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); - /** - * Send the channels list in one big stanza - */ - void send_iq_room_list_result(const std::string& id, const std::string to_jid, - const std::string& from, - const std::vector<ListElement>& rooms_list); - /** - * Handle the various stanza types - */ - void handle_handshake(const Stanza& stanza); - void handle_presence(const Stanza& stanza); - void handle_message(const Stanza& stanza); - void handle_iq(const Stanza& stanza); - void handle_error(const Stanza& stanza); - - /** - * Whether or not we ever succeeded our authentication to the XMPP server - */ - bool ever_auth; - /** - * Whether or not this is the first consecutive try on connecting to the - * XMPP server. We use this to delay the connection attempt for a few - * seconds, if it is not the first try. - */ - bool first_connection_try; - -private: - /** - * Return the bridge associated with the given full JID. Create a new one - * if none already exist. - */ - Bridge* get_user_bridge(const std::string& user_jid); - /** - * Return a buffer provided by the XML parser, to read data directly into - * it, and avoiding some unnecessary copy. - */ - void* get_receive_buffer(const size_t size) const override final; - XmppParser parser; - std::string stream_id; - std::string served_hostname; - std::string secret; - bool authenticated; - /** - * Whether or not OUR XMPP document is open - */ - bool doc_open; - - std::unordered_map<std::string, std::function<void(const Stanza&)>> stanza_handlers; - AdhocCommandsHandler adhoc_commands_handler; - - /** - * A map of id -> callback. When we want to wait for an iq result, we add - * the callback to this map, with the iq id as the key. When an iq result - * is received, we look for a corresponding callback in this map. If - * found, we call it and remove it. - */ - std::map<std::string, iq_responder_callback_t> waiting_iq; - - /** - * One bridge for each user of the component. Indexed by the user's full - * jid - */ - std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges; - - XmppComponent(const XmppComponent&) = delete; - XmppComponent(XmppComponent&&) = delete; - XmppComponent& operator=(const XmppComponent&) = delete; - XmppComponent& operator=(XmppComponent&&) = delete; -}; - -#endif // XMPP_COMPONENT_INCLUDED |