summaryrefslogtreecommitdiff
path: root/src/network
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/network
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/network')
-rw-r--r--src/network/socket_handler.cpp172
-rw-r--r--src/network/socket_handler.hpp186
2 files changed, 310 insertions, 48 deletions
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