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
This commit is contained in:
@@ -21,6 +21,7 @@ find_package(Iconv REQUIRED)
|
|||||||
find_package(Libuuid REQUIRED)
|
find_package(Libuuid REQUIRED)
|
||||||
find_package(Libidn)
|
find_package(Libidn)
|
||||||
find_package(SystemdDaemon)
|
find_package(SystemdDaemon)
|
||||||
|
find_package(Botan)
|
||||||
|
|
||||||
# To be able to include the config.h file generated by cmake
|
# To be able to include the config.h file generated by cmake
|
||||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")
|
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")
|
||||||
@@ -37,6 +38,10 @@ if(SYSTEMDDAEMON_FOUND)
|
|||||||
include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS})
|
include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(BOTAN_FOUND)
|
||||||
|
include_directories(${BOTAN_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)")
|
set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)")
|
||||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING})
|
set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING})
|
||||||
@@ -95,6 +100,9 @@ file(GLOB source_network
|
|||||||
src/network/*.[hc]pp)
|
src/network/*.[hc]pp)
|
||||||
add_library(network STATIC ${source_network})
|
add_library(network STATIC ${source_network})
|
||||||
target_link_libraries(network logger)
|
target_link_libraries(network logger)
|
||||||
|
if(BOTAN_FOUND)
|
||||||
|
target_link_libraries(network ${BOTAN_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
#
|
#
|
||||||
## irclib
|
## irclib
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# - Find botan
|
||||||
|
# Find the botan cryptographic library
|
||||||
|
#
|
||||||
|
# This module defines the following variables:
|
||||||
|
# BOTAN_FOUND - True if library and include directory are found
|
||||||
|
# If set to TRUE, the following are also defined:
|
||||||
|
# BOTAN_INCLUDE_DIRS - The directory where to find the header file
|
||||||
|
# BOTAN_LIBRARIES - Where to find the library file
|
||||||
|
#
|
||||||
|
# For conveniance, these variables are also set. They have the same values
|
||||||
|
# than the variables above. The user can thus choose his/her prefered way
|
||||||
|
# to write them.
|
||||||
|
# BOTAN_LIBRARY
|
||||||
|
# BOTAN_INCLUDE_DIR
|
||||||
|
#
|
||||||
|
# This file is in the public domain
|
||||||
|
|
||||||
|
find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h
|
||||||
|
DOC "The botan include directory")
|
||||||
|
|
||||||
|
find_library(BOTAN_LIBRARIES NAMES botan
|
||||||
|
DOC "The botan library")
|
||||||
|
|
||||||
|
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||||
|
# set BOTAN_FOUND to TRUE if these two variables are set.
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(Botan REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS)
|
||||||
|
|
||||||
|
if(BOTAN_FOUND)
|
||||||
|
set(BOTAN_LIBRARY ${BOTAN_LIBRARIES})
|
||||||
|
set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES)
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
#cmakedefine LIBIDN_FOUND
|
#cmakedefine LIBIDN_FOUND
|
||||||
#cmakedefine SYSTEMDDAEMON_FOUND
|
#cmakedefine SYSTEMDDAEMON_FOUND
|
||||||
#cmakedefine POLLER ${POLLER}
|
#cmakedefine POLLER ${POLLER}
|
||||||
|
#cmakedefine BOTAN_FOUND
|
||||||
+27
-9
@@ -14,6 +14,8 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
using namespace std::chrono_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. "
|
"alive without having to join a real channel of that server. "
|
||||||
"To disconnect from the IRC server, leave this room and all "
|
"To disconnect from the IRC server, leave this room and all "
|
||||||
"other IRC channels of that server.";
|
"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()
|
IrcClient::~IrcClient()
|
||||||
@@ -48,29 +57,39 @@ void IrcClient::start()
|
|||||||
{
|
{
|
||||||
if (this->connected || this->connecting)
|
if (this->connected || this->connecting)
|
||||||
return ;
|
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->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s +
|
||||||
this->hostname + ":" + "6667");
|
this->hostname + ":" + port + " (" +
|
||||||
this->connect(this->hostname, "6667");
|
(tls ? "encrypted" : "not encrypted") + ")");
|
||||||
|
this->connect(this->hostname, port, tls);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcClient::on_connection_failed(const std::string& reason)
|
void IrcClient::on_connection_failed(const std::string& reason)
|
||||||
{
|
{
|
||||||
this->bridge->send_xmpp_message(this->hostname, "",
|
this->bridge->send_xmpp_message(this->hostname, "",
|
||||||
"Connection failed: "s + reason);
|
"Connection failed: "s + reason);
|
||||||
// Send an error message for all room that the user wanted to join
|
if (this->ports_to_try.empty())
|
||||||
for (const std::string& channel: this->channels_to_join)
|
|
||||||
{
|
{
|
||||||
Iid iid(channel + "%" + this->hostname);
|
// Send an error message for all room that the user wanted to join
|
||||||
this->bridge->send_join_failed(iid, this->current_nick,
|
for (const std::string& channel: this->channels_to_join)
|
||||||
"cancel", "item-not-found", reason);
|
{
|
||||||
|
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()
|
void IrcClient::on_connected()
|
||||||
{
|
{
|
||||||
this->send_nick_command(this->username);
|
this->send_nick_command(this->username);
|
||||||
this->send_user_command(this->username, 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();
|
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);
|
const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
|
||||||
if (user->nick != channel->get_self()->nick)
|
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,
|
this->bridge->send_user_join(this->hostname, chan_name, user,
|
||||||
user->get_most_significant_mode(this->sorted_user_modes),
|
user->get_most_significant_mode(this->sorted_user_modes),
|
||||||
false);
|
false);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <stack>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
@@ -19,8 +20,6 @@ class Bridge;
|
|||||||
/**
|
/**
|
||||||
* Represent one IRC client, i.e. an endpoint connected to a single IRC
|
* Represent one IRC client, i.e. an endpoint connected to a single IRC
|
||||||
* server, through a TCP socket, receiving and sending commands to it.
|
* 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
|
class IrcClient: public SocketHandler
|
||||||
{
|
{
|
||||||
@@ -280,6 +279,12 @@ private:
|
|||||||
* (for example 'ahov' is a common order).
|
* (for example 'ahov' is a common order).
|
||||||
*/
|
*/
|
||||||
std::vector<char> sorted_user_modes;
|
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(const IrcClient&) = delete;
|
||||||
IrcClient(IrcClient&&) = delete;
|
IrcClient(IrcClient&&) = delete;
|
||||||
|
|||||||
+158
-22
@@ -10,26 +10,39 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <netdb.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <netdb.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std::string_literals;
|
#ifdef BOTAN_FOUND
|
||||||
|
# include <botan/hex.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef UIO_FASTIOV
|
#ifndef UIO_FASTIOV
|
||||||
# define UIO_FASTIOV 8
|
# define UIO_FASTIOV 8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
namespace ph = std::placeholders;
|
||||||
|
|
||||||
SocketHandler::SocketHandler(std::shared_ptr<Poller> poller):
|
SocketHandler::SocketHandler(std::shared_ptr<Poller> poller):
|
||||||
socket(-1),
|
socket(-1),
|
||||||
poller(poller),
|
poller(poller),
|
||||||
|
use_tls(false),
|
||||||
connected(false),
|
connected(false),
|
||||||
connecting(false)
|
connecting(false)
|
||||||
{
|
#ifdef BOTAN_FOUND
|
||||||
}
|
,
|
||||||
|
rng(),
|
||||||
|
credential_manager(),
|
||||||
|
policy(),
|
||||||
|
session_manager(rng)
|
||||||
|
#endif
|
||||||
|
{}
|
||||||
|
|
||||||
void SocketHandler::init_socket(const struct addrinfo* rp)
|
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));
|
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->address = address;
|
||||||
this->port = port;
|
this->port = port;
|
||||||
|
this->use_tls = tls;
|
||||||
|
|
||||||
utils::ScopeGuard sg;
|
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->poller->add_socket_handler(this);
|
||||||
this->connected = true;
|
this->connected = true;
|
||||||
this->connecting = false;
|
this->connecting = false;
|
||||||
|
#ifdef BOTAN_FOUND
|
||||||
|
if (this->use_tls)
|
||||||
|
this->start_tls();
|
||||||
|
#endif
|
||||||
this->on_connected();
|
this->on_connected();
|
||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
@@ -133,10 +151,20 @@ void SocketHandler::connect(const std::string& address, const std::string& port)
|
|||||||
|
|
||||||
void SocketHandler::connect()
|
void SocketHandler::connect()
|
||||||
{
|
{
|
||||||
this->connect(this->address, this->port);
|
this->connect(this->address, this->port, this->use_tls);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketHandler::on_recv()
|
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;
|
static constexpr size_t buf_size = 4096;
|
||||||
char buf[buf_size];
|
char buf[buf_size];
|
||||||
@@ -145,22 +173,9 @@ void SocketHandler::on_recv()
|
|||||||
if (recv_buf == nullptr)
|
if (recv_buf == nullptr)
|
||||||
recv_buf = buf;
|
recv_buf = buf;
|
||||||
|
|
||||||
ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0);
|
const ssize_t size = this->do_recv(recv_buf, buf_size);
|
||||||
if (0 == size)
|
|
||||||
{
|
if (size > 0)
|
||||||
this->on_connection_close();
|
|
||||||
this->close();
|
|
||||||
}
|
|
||||||
else if (-1 == size)
|
|
||||||
{
|
|
||||||
log_warning("Error while reading from socket: " << strerror(errno));
|
|
||||||
if (this->connecting)
|
|
||||||
this->on_connection_failed(strerror(errno));
|
|
||||||
else
|
|
||||||
this->on_connection_close();
|
|
||||||
this->close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (buf == recv_buf)
|
if (buf == recv_buf)
|
||||||
{
|
{
|
||||||
@@ -173,6 +188,31 @@ void SocketHandler::on_recv()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
this->on_connection_close();
|
||||||
|
this->close();
|
||||||
|
}
|
||||||
|
else if (-1 == size)
|
||||||
|
{
|
||||||
|
log_warning("Error while reading from socket: " << strerror(errno));
|
||||||
|
if (this->connecting)
|
||||||
|
{
|
||||||
|
this->close();
|
||||||
|
this->on_connection_failed(strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->close();
|
||||||
|
this->on_connection_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
void SocketHandler::on_send()
|
void SocketHandler::on_send()
|
||||||
{
|
{
|
||||||
struct iovec msg_iov[UIO_FASTIOV] = {};
|
struct iovec msg_iov[UIO_FASTIOV] = {};
|
||||||
@@ -241,6 +281,16 @@ socket_t SocketHandler::get_socket() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SocketHandler::send_data(std::string&& data)
|
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())
|
if (data.empty())
|
||||||
return ;
|
return ;
|
||||||
@@ -269,3 +319,89 @@ void* SocketHandler::get_receive_buffer(const size_t) const
|
|||||||
{
|
{
|
||||||
return nullptr;
|
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
|
||||||
|
|||||||
+156
-30
@@ -1,6 +1,8 @@
|
|||||||
#ifndef SOCKET_HANDLER_INCLUDED
|
#ifndef SOCKET_HANDLER_INCLUDED
|
||||||
# define SOCKET_HANDLER_INCLUDED
|
# define SOCKET_HANDLER_INCLUDED
|
||||||
|
|
||||||
|
#include <logger/logger.hpp>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
@@ -10,6 +12,26 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <list>
|
#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;
|
typedef int socket_t;
|
||||||
|
|
||||||
class Poller;
|
class Poller;
|
||||||
@@ -24,21 +46,19 @@ class SocketHandler
|
|||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
~SocketHandler() {}
|
~SocketHandler() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SocketHandler(std::shared_ptr<Poller> poller);
|
explicit SocketHandler(std::shared_ptr<Poller> poller);
|
||||||
/**
|
/**
|
||||||
* Initialize the socket with the parameters contained in the given
|
* Connect to the remote server, and call on_connected() if this
|
||||||
* addrinfo structure.
|
* succeeds. If tls is true, we set use_tls to true and will also call
|
||||||
|
* start_tls() when the connection succeeds.
|
||||||
*/
|
*/
|
||||||
void init_socket(const struct addrinfo* rp);
|
void connect(const std::string& address, const std::string& port, const bool tls);
|
||||||
/**
|
|
||||||
* Connect to the remote server, and call on_connected() if this succeeds
|
|
||||||
*/
|
|
||||||
void connect(const std::string& address, const std::string& port);
|
|
||||||
void connect();
|
void connect();
|
||||||
/**
|
/**
|
||||||
* Reads data in our in_buf and the call parse_in_buf, for the implementor
|
* Reads raw data from the socket. And pass it to parse_in_buffer()
|
||||||
* to handle the data received so far.
|
* If we are using TLS on this connection, we call tls_recv()
|
||||||
*/
|
*/
|
||||||
void on_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
|
* Add the given data to out_buf and tell our poller that we want to be
|
||||||
* notified when a send event is ready.
|
* 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);
|
void send_data(std::string&& data);
|
||||||
/**
|
/**
|
||||||
@@ -87,29 +110,94 @@ public:
|
|||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
bool is_connecting() const;
|
bool is_connecting() const;
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
/**
|
/**
|
||||||
* Provide a buffer in which data can be directly received. This can be
|
* Initialize the socket with the parameters contained in the given
|
||||||
* used to avoid copying data into in_buf before using it. If no buffer
|
* addrinfo structure.
|
||||||
* can provided, nullptr is returned (the default implementation does
|
|
||||||
* that).
|
|
||||||
*/
|
*/
|
||||||
virtual void* get_receive_buffer(const size_t size) const;
|
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.
|
* The handled socket.
|
||||||
*/
|
*/
|
||||||
socket_t socket;
|
socket_t socket;
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
/**
|
/**
|
||||||
* Where data is added, when we want to send something to the client.
|
* Where data is added, when we want to send something to the client.
|
||||||
*/
|
*/
|
||||||
std::list<std::string> out_buf;
|
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
|
* 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
|
* with it, sometimes (for example to tell it that he now needs to watch
|
||||||
@@ -119,6 +207,25 @@ protected:
|
|||||||
* (actually it is sharing our ownership with a Bridge).
|
* (actually it is sharing our ownership with a Bridge).
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Poller> poller;
|
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
|
* Hostname we are connected/connecting to
|
||||||
*/
|
*/
|
||||||
@@ -127,14 +234,6 @@ protected:
|
|||||||
* Port we are connected/connecting to
|
* Port we are connected/connecting to
|
||||||
*/
|
*/
|
||||||
std::string port;
|
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 connected;
|
||||||
bool connecting;
|
bool connecting;
|
||||||
@@ -144,6 +243,33 @@ private:
|
|||||||
SocketHandler(SocketHandler&&) = delete;
|
SocketHandler(SocketHandler&&) = delete;
|
||||||
SocketHandler& operator=(const SocketHandler&) = delete;
|
SocketHandler& operator=(const SocketHandler&) = delete;
|
||||||
SocketHandler& operator=(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
|
#endif // SOCKET_HANDLER_INCLUDED
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ XmppComponent::~XmppComponent()
|
|||||||
|
|
||||||
void XmppComponent::start()
|
void XmppComponent::start()
|
||||||
{
|
{
|
||||||
this->connect("127.0.0.1", "5347");
|
this->connect("127.0.0.1", "5347", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XmppComponent::is_document_open() const
|
bool XmppComponent::is_document_open() const
|
||||||
|
|||||||
Reference in New Issue
Block a user