summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2014-05-18 20:23:08 +0200
committerFlorent Le Coz <louiz@louiz.org>2014-06-08 05:07:54 +0200
commit23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4 (patch)
treeaacd08a1fa9c56d3949e659238071ea32402907f /src
parentfa071309917252cd76d1334eedc6703057d1f29f (diff)
downloadbiboumi-23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4.tar.gz
biboumi-23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4.tar.bz2
biboumi-23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4.tar.xz
biboumi-23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4.zip
Implement TLS support using Botan
For now, it tries two TLS ports and then connects to the non-tls port. In the future we would like the user to be able to configure that. fix #2435
Diffstat (limited to 'src')
-rw-r--r--src/config.h.cmake1
-rw-r--r--src/irc/irc_client.cpp36
-rw-r--r--src/irc/irc_client.hpp9
-rw-r--r--src/network/socket_handler.cpp172
-rw-r--r--src/network/socket_handler.hpp186
-rw-r--r--src/xmpp/xmpp_component.cpp2
6 files changed, 346 insertions, 60 deletions
diff --git a/src/config.h.cmake b/src/config.h.cmake
index 62186cc..66ea967 100644
--- a/src/config.h.cmake
+++ b/src/config.h.cmake
@@ -2,3 +2,4 @@
#cmakedefine LIBIDN_FOUND
#cmakedefine SYSTEMDDAEMON_FOUND
#cmakedefine POLLER ${POLLER}
+#cmakedefine BOTAN_FOUND \ No newline at end of file
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index 01bf569..c3765a6 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -14,6 +14,8 @@
#include <chrono>
#include <string>
+#include "config.h"
+
using namespace std::string_literals;
using namespace std::chrono_literals;
@@ -35,6 +37,13 @@ IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname
"alive without having to join a real channel of that server. "
"To disconnect from the IRC server, leave this room and all "
"other IRC channels of that server.";
+ // TODO: get the values from the preferences of the user, and only use the
+ // list of default ports if the user didn't specify anything
+ this->ports_to_try.emplace("6667", false); // standard non-encrypted port
+#ifdef BOTAN_FOUND
+ this->ports_to_try.emplace("6670", true); // non-standard but I want it for some servers
+ this->ports_to_try.emplace("6697", true); // standard encrypted port
+#endif // BOTAN_FOUND
}
IrcClient::~IrcClient()
@@ -48,29 +57,39 @@ void IrcClient::start()
{
if (this->connected || this->connecting)
return ;
+ std::string port;
+ bool tls;
+ std::tie(port, tls) = this->ports_to_try.top();
+ this->ports_to_try.pop();
this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s +
- this->hostname + ":" + "6667");
- this->connect(this->hostname, "6667");
+ this->hostname + ":" + port + " (" +
+ (tls ? "encrypted" : "not encrypted") + ")");
+ this->connect(this->hostname, port, tls);
}
void IrcClient::on_connection_failed(const std::string& reason)
{
this->bridge->send_xmpp_message(this->hostname, "",
"Connection failed: "s + reason);
- // Send an error message for all room that the user wanted to join
- for (const std::string& channel: this->channels_to_join)
+ if (this->ports_to_try.empty())
{
- Iid iid(channel + "%" + this->hostname);
- this->bridge->send_join_failed(iid, this->current_nick,
- "cancel", "item-not-found", reason);
+ // Send an error message for all room that the user wanted to join
+ 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);
+ }
}
+ else // try the next port
+ this->start();
}
void IrcClient::on_connected()
{
this->send_nick_command(this->username);
this->send_user_command(this->username, this->username);
- this->send_gateway_message("Connected to IRC server.");
+ this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
this->send_pending_data();
}
@@ -326,7 +345,6 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message)
const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
if (user->nick != channel->get_self()->nick)
{
- log_debug("Adding user [" << nick << "] to chan " << chan_name);
this->bridge->send_user_join(this->hostname, chan_name, user,
user->get_most_significant_mode(this->sorted_user_modes),
false);
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index 2f28c95..7dff1db 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -11,6 +11,7 @@
#include <memory>
#include <vector>
#include <string>
+#include <stack>
#include <map>
#include <set>
@@ -19,8 +20,6 @@ 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.
- *
- * TODO: TLS support, maybe, but that's not high priority
*/
class IrcClient: public SocketHandler
{
@@ -280,6 +279,12 @@ private:
* (for example 'ahov' is a common order).
*/
std::vector<char> sorted_user_modes;
+ /**
+ * A list of ports to which we will try to connect, in reverse. Each port
+ * is associated with a boolean telling if we should use TLS or not if the
+ * connection succeeds on that port.
+ */
+ std::stack<std::pair<std::string, bool>> ports_to_try;
IrcClient(const IrcClient&) = delete;
IrcClient(IrcClient&&) = delete;
diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp
index d509513..d989623 100644
--- a/src/network/socket_handler.cpp
+++ b/src/network/socket_handler.cpp
@@ -10,26 +10,39 @@
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
+#include <netdb.h>
#include <cstring>
#include <fcntl.h>
-#include <netdb.h>
#include <stdio.h>
#include <iostream>
-using namespace std::string_literals;
+#ifdef BOTAN_FOUND
+# include <botan/hex.h>
+#endif
#ifndef UIO_FASTIOV
# define UIO_FASTIOV 8
#endif
+using namespace std::string_literals;
+
+namespace ph = std::placeholders;
+
SocketHandler::SocketHandler(std::shared_ptr<Poller> poller):
socket(-1),
poller(poller),
+ use_tls(false),
connected(false),
connecting(false)
-{
-}
+#ifdef BOTAN_FOUND
+ ,
+ rng(),
+ credential_manager(),
+ policy(),
+ session_manager(rng)
+#endif
+{}
void SocketHandler::init_socket(const struct addrinfo* rp)
{
@@ -47,10 +60,11 @@ 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)
+void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
{
this->address = address;
this->port = port;
+ this->use_tls = tls;
utils::ScopeGuard sg;
@@ -106,6 +120,10 @@ void SocketHandler::connect(const std::string& address, const std::string& port)
this->poller->add_socket_handler(this);
this->connected = true;
this->connecting = false;
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->start_tls();
+#endif
this->on_connected();
return ;
}
@@ -133,11 +151,21 @@ void SocketHandler::connect(const std::string& address, const std::string& port)
void SocketHandler::connect()
{
- this->connect(this->address, this->port);
+ this->connect(this->address, this->port, this->use_tls);
}
void SocketHandler::on_recv()
{
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->tls_recv();
+ else
+#endif
+ this->plain_recv();
+}
+
+void SocketHandler::plain_recv()
+{
static constexpr size_t buf_size = 4096;
char buf[buf_size];
void* recv_buf = this->get_receive_buffer(buf_size);
@@ -145,6 +173,23 @@ void SocketHandler::on_recv()
if (recv_buf == nullptr)
recv_buf = buf;
+ const ssize_t size = this->do_recv(recv_buf, buf_size);
+
+ if (size > 0)
+ {
+ if (buf == recv_buf)
+ {
+ // data needs to be placed in the in_buf string, because no buffer
+ // was provided to receive that data directly. The in_buf buffer
+ // will be handled in parse_in_buffer()
+ this->in_buf += std::string(buf, size);
+ }
+ this->parse_in_buffer(size);
+ }
+}
+
+ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_size)
+{
ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0);
if (0 == size)
{
@@ -155,22 +200,17 @@ void SocketHandler::on_recv()
{
log_warning("Error while reading from socket: " << strerror(errno));
if (this->connecting)
- this->on_connection_failed(strerror(errno));
+ {
+ this->close();
+ this->on_connection_failed(strerror(errno));
+ }
else
- this->on_connection_close();
- this->close();
- }
- else
- {
- if (buf == recv_buf)
{
- // data needs to be placed in the in_buf string, because no buffer
- // was provided to receive that data directly. The in_buf buffer
- // will be handled in parse_in_buffer()
- this->in_buf += std::string(buf, size);
+ this->close();
+ this->on_connection_close();
}
- this->parse_in_buffer(size);
}
+ return size;
}
void SocketHandler::on_send()
@@ -242,6 +282,16 @@ socket_t SocketHandler::get_socket() const
void SocketHandler::send_data(std::string&& data)
{
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->tls_send(std::move(data));
+ else
+#endif
+ this->raw_send(std::move(data));
+}
+
+void SocketHandler::raw_send(std::string&& data)
+{
if (data.empty())
return ;
this->out_buf.emplace_back(std::move(data));
@@ -269,3 +319,89 @@ void* SocketHandler::get_receive_buffer(const size_t) const
{
return nullptr;
}
+
+#ifdef BOTAN_FOUND
+void SocketHandler::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),
+ session_manager, credential_manager, policy,
+ rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version());
+}
+
+void SocketHandler::tls_recv()
+{
+ static constexpr size_t buf_size = 4096;
+ char recv_buf[buf_size];
+
+ const ssize_t size = this->do_recv(recv_buf, buf_size);
+ if (size > 0)
+ {
+ const bool was_active = this->tls->is_active();
+ this->tls->received_data(reinterpret_cast<const Botan::byte*>(recv_buf),
+ static_cast<size_t>(size));
+ if (!was_active && this->tls->is_active())
+ this->on_tls_activated();
+ }
+}
+
+void SocketHandler::tls_send(std::string&& data)
+{
+ if (this->tls->is_active())
+ {
+ const bool was_active = this->tls->is_active();
+ if (!this->pre_buf.empty())
+ {
+ this->tls->send(reinterpret_cast<const Botan::byte*>(this->pre_buf.data()),
+ this->pre_buf.size());
+ this->pre_buf = "";
+ }
+ if (!data.empty())
+ this->tls->send(reinterpret_cast<const Botan::byte*>(data.data()),
+ data.size());
+ if (!was_active && this->tls->is_active())
+ this->on_tls_activated();
+ }
+ else
+ this->pre_buf += data;
+}
+
+void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size)
+{
+ this->in_buf += std::string(reinterpret_cast<const char*>(data),
+ size);
+ if (!this->in_buf.empty())
+ this->parse_in_buffer(size);
+}
+
+void SocketHandler::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)
+{
+ log_debug("tls_alert: " << alert.type_string());
+}
+
+bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session)
+{
+ log_debug("Handshake with " << session.server_info().hostname() << " complete."
+ << " Version: " << session.version().to_string()
+ << " using " << session.ciphersuite().to_string());
+ if (!session.session_id().empty())
+ log_debug("Session ID " << Botan::hex_encode(session.session_id()));
+ if (!session.session_ticket().empty())
+ log_debug("Session ticket " << Botan::hex_encode(session.session_ticket()));
+ return true;
+}
+
+void SocketHandler::on_tls_activated()
+{
+ this->send_data("");
+}
+#endif // BOTAN_FOUND
diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp
index 1be3db1..02311c4 100644
--- a/src/network/socket_handler.hpp
+++ b/src/network/socket_handler.hpp
@@ -1,6 +1,8 @@
#ifndef SOCKET_HANDLER_INCLUDED
# define SOCKET_HANDLER_INCLUDED
+#include <logger/logger.hpp>
+
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -10,6 +12,26 @@
#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
+
typedef int socket_t;
class Poller;
@@ -24,21 +46,19 @@ class SocketHandler
{
protected:
~SocketHandler() {}
+
public:
explicit SocketHandler(std::shared_ptr<Poller> poller);
/**
- * Initialize the socket with the parameters contained in the given
- * addrinfo structure.
- */
- void init_socket(const struct addrinfo* rp);
- /**
- * Connect to the remote server, and call on_connected() if this succeeds
+ * 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);
+ void connect(const std::string& address, const std::string& port, const bool tls);
void connect();
/**
- * Reads data in our in_buf and the call parse_in_buf, for the implementor
- * to handle the data received so far.
+ * 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();
/**
@@ -48,6 +68,9 @@ public:
/**
* 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);
/**
@@ -87,30 +110,95 @@ public:
bool is_connected() const;
bool is_connecting() const;
-protected:
+private:
/**
- * 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
- * can provided, nullptr is returned (the default implementation does
- * that).
+ * Initialize the socket with the parameters contained in the given
+ * addrinfo structure.
*/
- virtual void* get_receive_buffer(const size_t size) const;
+ void init_socket(const struct addrinfo* rp);
/**
- * The handled socket.
+ * 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.
*/
- socket_t socket;
+ ssize_t do_recv(void* recv_buf, const size_t buf_size);
/**
- * 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.
+ * Reads data from the socket and calls parse_in_buffer with it.
*/
- std::string in_buf;
+ 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 ai_addr;
+ socklen_t ai_addrlen;
+
+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.
@@ -120,6 +208,25 @@ protected:
*/
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;
@@ -127,14 +234,6 @@ protected:
* Port we are connected/connecting to
*/
std::string port;
- /**
- * 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 ai_addr;
- socklen_t ai_addrlen;
bool connected;
bool connecting;
@@ -144,6 +243,33 @@ private:
SocketHandler(SocketHandler&&) = delete;
SocketHandler& operator=(const SocketHandler&) = delete;
SocketHandler& operator=(SocketHandler&&) = delete;
+
+#ifdef BOTAN_FOUND
+ /**
+ * Botan stuff to manipulate a TLS session.
+ */
+ Botan::AutoSeeded_RNG rng;
+ Permissive_Credentials_Manager credential_manager;
+ Botan::TLS::Policy policy;
+ 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 3ec043c..06db085 100644
--- a/src/xmpp/xmpp_component.cpp
+++ b/src/xmpp/xmpp_component.cpp
@@ -68,7 +68,7 @@ XmppComponent::~XmppComponent()
void XmppComponent::start()
{
- this->connect("127.0.0.1", "5347");
+ this->connect("127.0.0.1", "5347", false);
}
bool XmppComponent::is_document_open() const