summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt14
-rw-r--r--INSTALL15
-rw-r--r--doc/biboumi.1.md2
-rw-r--r--packaging/biboumi.spec10
-rw-r--r--src/bridge/bridge.cpp8
-rw-r--r--src/bridge/bridge.hpp5
-rw-r--r--src/irc/irc_client.cpp32
-rw-r--r--src/irc/irc_client.hpp11
-rw-r--r--src/main.cpp1
-rw-r--r--src/network/socket_handler.hpp273
-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.hpp261
-rw-r--r--src/xmpp/xmpp_component.cpp52
-rw-r--r--src/xmpp/xmpp_component.hpp23
-rw-r--r--unit/biboumi.service2
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
diff --git a/INSTALL b/INSTALL
index 825f549..051e203 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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