diff options
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | INSTALL | 15 | ||||
-rw-r--r-- | doc/biboumi.1.md | 2 | ||||
-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 |
15 files changed, 416 insertions, 384 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,23 @@ 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/doc/biboumi.1.md b/doc/biboumi.1.md index fb48d4b..a35d4fc 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -221,7 +221,7 @@ as toto on the channel #bar (as long as these two channels are on the same IRC server). By default you will receive private messages from the “global” user (aka nickname!irc.example.com@biboumi.example.com), unless you previously sent a message to an in-room participant (something like -#test%irc.example.com@biboumi.example.com/nickname), in which case future +\#test%irc.example.com@biboumi.example.com/nickname), in which case future messages from that same user will be received from that same “in-room” JID. ### Notices diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 83b6eb8..cffff09 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 |