diff options
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | INSTALL | 12 | ||||
-rw-r--r-- | packaging/biboumi.spec | 10 | ||||
-rw-r--r-- | src/bridge/bridge.cpp | 8 | ||||
-rw-r--r-- | src/bridge/bridge.hpp | 5 | ||||
-rw-r--r-- | src/irc/irc_client.cpp | 32 | ||||
-rw-r--r-- | src/irc/irc_client.hpp | 11 | ||||
-rw-r--r-- | src/main.cpp | 1 | ||||
-rw-r--r-- | src/network/socket_handler.hpp | 273 | ||||
-rw-r--r-- | src/network/tcp_socket_handler.cpp (renamed from src/network/socket_handler.cpp) | 91 | ||||
-rw-r--r-- | src/network/tcp_socket_handler.hpp | 261 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.cpp | 52 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.hpp | 23 | ||||
-rw-r--r-- | unit/biboumi.service | 2 |
14 files changed, 412 insertions, 383 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3af1a66..14c002c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.6) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 1) -set(${PROJECT_NAME}_VERSION_MINOR 1) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_MAJOR 2) +set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") @@ -21,8 +21,12 @@ find_package(EXPAT REQUIRED) find_package(Iconv REQUIRED) find_package(Libuuid REQUIRED) find_package(Libidn) -find_package(SystemdDaemon) -find_package(Botan) +if(NOT WITHOUT_SYSTEMD) + find_package(SystemdDaemon) +endif() +if(NOT WITHOUT_BOTAN) + find_package(Botan) +endif() # ## Get the software version @@ -78,10 +78,20 @@ using the POLLER cmake option. Available values are: EPOLL: use the Linux-specific epoll(7). This is the default on Linux. POLL: use the standard poll(2). This is the default value on all non-Linux platforms. -Example, configure the poller with cmake: +Examples, configure the poller with cmake: % cmake . -DPOLLER=EPOLL +You can also decide not to use two of the optional dependencies, even if they are present on your system, for example if Botan is available but you do not want to use it, you can set the value of WITHOUT_BOTAN to 1, like this: + +% cmake . -DWITHOUT_BOTAN=1 + +This way, the binary will not be linked with libotan at all (and all +connection will then be made in clear text). +You can also decide not to link with systemd, like this: + +% cmake . -DWITHOUT_SYSTEMD=1 + ============== Build diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index b436647..ebcecac 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -15,8 +15,6 @@ BuildRequires: cmake BuildRequires: systemd BuildRequires: rubygem-ronn -%global biboumi_user %{name} -%global biboumi_group %{biboumi_user} %global biboumi_confdir %{_sysconfdir}/%{name} @@ -52,14 +50,6 @@ install -D -p -m 644 unit/%{name}.service \ %{buildroot}%{_unitdir}/%{name}.service -%pre -getent group %{biboumi_group} > /dev/null || groupadd -r %{biboumi_group} -getent passwd %{biboumi_user} > /dev/null || \ - useradd -r -g %{biboumi_group} \ - -s /sbin/nologin -c "Biboumi XMPP to IRC gateway" %{biboumi_user} -exit 0 - - %check make test_suite/fast VERBOSE=1 diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9501d47..ba4911a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -331,9 +331,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st } } -void Bridge::send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text) +void Bridge::send_presence_error(const Iid& iid, const std::string& nick, + const std::string& type, const std::string& condition, + const std::string& error_code, const std::string& text) { - this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, text); + this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) @@ -412,7 +414,7 @@ void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::stri void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp->send_nickname_conflict_error(std::to_string(iid), nickname, this->user_jid); + this->xmpp->send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index a75b319..b0e6a27 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -100,10 +100,9 @@ public: */ void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc); /** - * Send a presence of type error, from a room. This is used to indicate - * why joining a room failed. + * Send a presence of type error, from a room. */ - void send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text); + void send_presence_error(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& error_code, const std::string& text); /** * Send an unavailable presence from this participant */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d179aaa..f9ebfa0 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -20,7 +20,7 @@ using namespace std::string_literals; using namespace std::chrono_literals; IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& username, Bridge* bridge): - SocketHandler(poller), + TCPSocketHandler(poller), hostname(hostname), username(username), current_nick(username), @@ -77,8 +77,9 @@ void IrcClient::on_connection_failed(const std::string& reason) for (const std::string& channel: this->channels_to_join) { Iid iid(channel + "%" + this->hostname); - this->bridge->send_join_failed(iid, this->current_nick, - "cancel", "item-not-found", reason); + this->bridge->send_presence_error(iid, this->current_nick, + "cancel", "item-not-found", + "", reason); } } else // try the next port @@ -93,9 +94,11 @@ void IrcClient::on_connected() this->send_pending_data(); } -void IrcClient::on_connection_close() +void IrcClient::on_connection_close(const std::string& error_msg) { - static const std::string message = "Connection closed by remote server."; + std::string message = "Connection closed by remote server."; + if (!error_msg.empty()) + message += ": " + error_msg; const IrcMessage error{"ERROR", {message}}; this->on_error(error); log_warning(message); @@ -475,6 +478,25 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) } } +void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) +{ + const std::string nickname = message.arguments[1]; + std::string txt; + if (message.arguments.size() >= 3) + txt = message.arguments[2]; + this->on_generic_error(message); + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + Iid iid; + iid.set_local(it->first); + iid.set_server(this->hostname); + iid.is_channel = true; + this->bridge->send_presence_error(iid, nickname, + "cancel", "not-acceptable", + "", txt); + } +} + void IrcClient::on_generic_error(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7dff1db..9bef04a 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -5,7 +5,7 @@ #include <irc/irc_channel.hpp> #include <irc/iid.hpp> -#include <network/socket_handler.hpp> +#include <network/tcp_socket_handler.hpp> #include <unordered_map> #include <memory> @@ -21,7 +21,7 @@ class Bridge; * Represent one IRC client, i.e. an endpoint connected to a single IRC * server, through a TCP socket, receiving and sending commands to it. */ -class IrcClient: public SocketHandler +class IrcClient: public TCPSocketHandler { public: explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& username, Bridge* bridge); @@ -41,7 +41,7 @@ public: /** * Close the connection, remove us from the poller */ - void on_connection_close() override final; + void on_connection_close(const std::string& error) override final; /** * Parse the data we have received so far and try to get one or more * complete messages from it. @@ -172,6 +172,10 @@ public: */ void on_nickname_conflict(const IrcMessage& message); /** + * Idem, but for when the user changes their nickname too quickly + */ + void on_nickname_change_too_fast(const IrcMessage& message); + /** * Handles most errors from the server by just forwarding the message to the user. */ void on_generic_error(const IrcMessage& message); @@ -317,6 +321,7 @@ static const std::unordered_map<std::string, irc_callback_t> irc_callbacks = { {"366", &IrcClient::on_channel_completely_joined}, {"432", &IrcClient::on_erroneous_nickname}, {"433", &IrcClient::on_nickname_conflict}, + {"438", &IrcClient::on_nickname_change_too_fast}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, {"ERROR", &IrcClient::on_error}, diff --git a/src/main.cpp b/src/main.cpp index 2e2f1b2..d9f6a9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include <network/tcp_socket_handler.hpp> #include <xmpp/xmpp_component.hpp> #include <utils/timed_events.hpp> #include <network/poller.hpp> diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 061658e..9a894a4 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,280 +1,43 @@ -#ifndef SOCKET_HANDLER_INCLUDED -# define SOCKET_HANDLER_INCLUDED +#ifndef SOCKET_HANDLER_HPP +# define SOCKET_HANDLER_HPP -#include <logger/logger.hpp> - -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> - -#include <utility> #include <memory> -#include <string> -#include <list> - -#include "config.h" -#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 +class Poller; typedef int socket_t; -class Poller; - -/** - * 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 SocketHandler { -protected: - ~SocketHandler() {} - public: - explicit SocketHandler(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(); - /** - * 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(); - /** - * Write as much data from out_buf as possible, in the socket. - */ - void on_send(); - /** - * 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(); - /** - * Returns the socket that should be handled by the poller. - */ - socket_t get_socket() const; - /** - * 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() = 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; - bool is_connecting() const; - -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 - /** - * The handled socket. - */ - socket_t socket; - /** - * 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 the 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; + 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, sometimes (for example to tell it that he now needs to watch - * write events for us). Do not ever try to delete it. - * - * And a raw pointer because we are not owning it, it is owning us - * (actually it is sharing our ownership with a Bridge). + * with it. */ std::shared_ptr<Poller> poller; /** - * 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 + * The handled socket. */ - std::string port; - - bool connected; - bool connecting; + socket_t socket; private: SocketHandler(const SocketHandler&) = delete; SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; SocketHandler& operator=(SocketHandler&&) = 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 +#endif // SOCKET_HANDLER_HPP diff --git a/src/network/socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 73e1ad1..c5d254e 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -1,4 +1,4 @@ -#include <network/socket_handler.hpp> +#include <network/tcp_socket_handler.hpp> #include <utils/timed_events.hpp> #include <utils/scopeguard.hpp> @@ -21,10 +21,10 @@ #ifdef BOTAN_FOUND # include <botan/hex.h> -Botan::AutoSeeded_RNG SocketHandler::rng; -Permissive_Credentials_Manager SocketHandler::credential_manager; -Botan::TLS::Policy SocketHandler::policy; -Botan::TLS::Session_Manager_In_Memory SocketHandler::session_manager(SocketHandler::rng); +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 @@ -37,15 +37,14 @@ using namespace std::chrono_literals; namespace ph = std::placeholders; -SocketHandler::SocketHandler(std::shared_ptr<Poller> poller): - socket(-1), - poller(poller), +TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller): + SocketHandler(poller, -1), use_tls(false), connected(false), connecting(false) {} -void SocketHandler::init_socket(const struct addrinfo* rp) +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)); @@ -61,7 +60,7 @@ void SocketHandler::init_socket(const struct addrinfo* rp) throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); } -void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls) +void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) { this->address = address; this->port = port; @@ -146,7 +145,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port, // it to have failed TimedEventsManager::instance().add_event( TimedEvent(std::chrono::steady_clock::now() + 5s, - std::bind(&SocketHandler::on_connection_timeout, this), + std::bind(&TCPSocketHandler::on_connection_timeout, this), "connection_timeout"s + std::to_string(this->socket))); return ; } @@ -158,18 +157,18 @@ void SocketHandler::connect(const std::string& address, const std::string& port, return ; } -void SocketHandler::on_connection_timeout() +void TCPSocketHandler::on_connection_timeout() { this->close(); this->on_connection_failed("connection timed out"); } -void SocketHandler::connect() +void TCPSocketHandler::connect() { this->connect(this->address, this->port, this->use_tls); } -void SocketHandler::on_recv() +void TCPSocketHandler::on_recv() { #ifdef BOTAN_FOUND if (this->use_tls) @@ -179,7 +178,7 @@ void SocketHandler::on_recv() this->plain_recv(); } -void SocketHandler::plain_recv() +void TCPSocketHandler::plain_recv() { static constexpr size_t buf_size = 4096; char buf[buf_size]; @@ -203,32 +202,27 @@ void SocketHandler::plain_recv() } } -ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_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->on_connection_close(""); this->close(); } else if (-1 == size) { log_warning("Error while reading from socket: " << strerror(errno)); + this->close(); if (this->connecting) - { - this->close(); - this->on_connection_failed(strerror(errno)); - } + this->on_connection_failed(strerror(errno)); else - { - this->close(); - this->on_connection_close(); - } + this->on_connection_close(strerror(errno)); } return size; } -void SocketHandler::on_send() +void TCPSocketHandler::on_send() { struct iovec msg_iov[UIO_FASTIOV] = {}; struct msghdr msg{nullptr, 0, @@ -246,7 +240,7 @@ void SocketHandler::on_send() if (res < 0) { log_error("sendmsg failed: " << strerror(errno)); - this->on_connection_close(); + this->on_connection_close(strerror(errno)); this->close(); } else @@ -274,7 +268,7 @@ void SocketHandler::on_send() } } -void SocketHandler::close() +void TCPSocketHandler::close() { TimedEventsManager::instance().cancel("connection_timeout"s + std::to_string(this->socket)); @@ -292,12 +286,7 @@ void SocketHandler::close() this->port.clear(); } -socket_t SocketHandler::get_socket() const -{ - return this->socket; -} - -void SocketHandler::send_data(std::string&& data) +void TCPSocketHandler::send_data(std::string&& data) { #ifdef BOTAN_FOUND if (this->use_tls) @@ -307,7 +296,7 @@ void SocketHandler::send_data(std::string&& data) this->raw_send(std::move(data)); } -void SocketHandler::raw_send(std::string&& data) +void TCPSocketHandler::raw_send(std::string&& data) { if (data.empty()) return ; @@ -316,41 +305,41 @@ void SocketHandler::raw_send(std::string&& data) this->poller->watch_send_events(this); } -void SocketHandler::send_pending_data() +void TCPSocketHandler::send_pending_data() { if (this->connected && !this->out_buf.empty()) this->poller->watch_send_events(this); } -bool SocketHandler::is_connected() const +bool TCPSocketHandler::is_connected() const { return this->connected; } -bool SocketHandler::is_connecting() const +bool TCPSocketHandler::is_connecting() const { return this->connecting; } -void* SocketHandler::get_receive_buffer(const size_t) const +void* TCPSocketHandler::get_receive_buffer(const size_t) const { return nullptr; } #ifdef BOTAN_FOUND -void SocketHandler::start_tls() +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(&SocketHandler::tls_output_fn, this, ph::_1, ph::_2), - std::bind(&SocketHandler::tls_data_cb, this, ph::_1, ph::_2), - std::bind(&SocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), - std::bind(&SocketHandler::tls_handshake_cb, this, ph::_1), + 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 SocketHandler::tls_recv() +void TCPSocketHandler::tls_recv() { static constexpr size_t buf_size = 4096; char recv_buf[buf_size]; @@ -366,7 +355,7 @@ void SocketHandler::tls_recv() } } -void SocketHandler::tls_send(std::string&& data) +void TCPSocketHandler::tls_send(std::string&& data) { if (this->tls->is_active()) { @@ -387,7 +376,7 @@ void SocketHandler::tls_send(std::string&& data) this->pre_buf += data; } -void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size) +void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) { this->in_buf += std::string(reinterpret_cast<const char*>(data), size); @@ -395,17 +384,17 @@ void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size) this->parse_in_buffer(size); } -void SocketHandler::tls_output_fn(const Botan::byte* data, size_t 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 SocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) +void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) { log_debug("tls_alert: " << alert.type_string()); } -bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) +bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) { log_debug("Handshake with " << session.server_info().hostname() << " complete." << " Version: " << session.version().to_string() @@ -417,7 +406,7 @@ bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) return true; } -void SocketHandler::on_tls_activated() +void TCPSocketHandler::on_tls_activated() { this->send_data(""); } diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp new file mode 100644 index 0000000..c25ad83 --- /dev/null +++ b/src/network/tcp_socket_handler.hpp @@ -0,0 +1,261 @@ +#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 <netdb.h> + +#include <utility> +#include <memory> +#include <string> +#include <list> + +#include "config.h" + +#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(); + /** + * 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(); + /** + * Write as much data from out_buf as possible, in the socket. + */ + void on_send(); + /** + * 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; + bool is_connecting() const; + +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 the 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; + +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/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1e8f9e0..1aa98b0 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -37,7 +37,7 @@ static std::set<std::string> kickable_errors{ }; XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret): - SocketHandler(poller), + TCPSocketHandler(poller), ever_auth(false), last_auth(false), served_hostname(hostname), @@ -108,9 +108,16 @@ void XmppComponent::on_connected() this->send_pending_data(); } -void XmppComponent::on_connection_close() +void XmppComponent::on_connection_close(const std::string& error) { - log_info("XMPP server closed connection"); + 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) @@ -904,41 +911,18 @@ void XmppComponent::kick_user(const std::string& muc_name, this->send_stanza(presence); } -void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; - presence["to"] = jid_to; - 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"; - error["code"] = "409"; - XmlNode conflict("conflict"); - conflict["xmlns"] = STANZA_NS; - conflict.close(); - error.add_child(std::move(conflict)); - error.close(); - presence.add_child(std::move(error)); - 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&) + 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(); @@ -946,6 +930,8 @@ void XmppComponent::send_presence_error(const std::string& muc_name, 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(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index ce594ec..9f1cec3 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -2,7 +2,7 @@ # define XMPP_COMPONENT_INCLUDED #include <xmpp/adhoc_commands_handler.hpp> -#include <network/socket_handler.hpp> +#include <network/tcp_socket_handler.hpp> #include <xmpp/xmpp_parser.hpp> #include <bridge/bridge.hpp> @@ -30,7 +30,7 @@ * * TODO: implement XEP-0225: Component Connections */ -class XmppComponent: public SocketHandler +class XmppComponent: public TCPSocketHandler { public: explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret); @@ -38,7 +38,7 @@ public: void on_connection_failed(const std::string& reason) override final; void on_connected() override final; - void on_connection_close() override final; + void on_connection_close(const std::string& error) override final; void parse_in_buffer(const size_t size) override final; /** @@ -172,20 +172,15 @@ public: const std::string& author, const std::string& jid_to); /** - * Send a presence type=error with a conflict element - */ - void send_nickname_conflict_error(const std::string& muc_name, - const std::string& nickname, - 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& text); + 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 diff --git a/unit/biboumi.service b/unit/biboumi.service index 579ac4e..1bb4f63 100644 --- a/unit/biboumi.service +++ b/unit/biboumi.service @@ -9,6 +9,8 @@ ExecReload=/bin/kill -s USR1 $MAINPID ExecStop=/bin/kill -s INT $MAINPID WatchdogSec=10 Restart=always +User=nobody +Group=nobody [Install] WantedBy=multi-user.target |