biboumi: Update botan to version 3

The botan dependency has introduced a number of breaking changes with
version 3, a couple of which impact biboumi as well

This patch makes biboumi compatible with botan 3 and drops support for
botan 2 at the same time

As a consequence of the botan dependency update, C++ version has to be
upgraded to C++ 20

https://botan.randombit.net/handbook/migration_guide.html
This commit is contained in:
Luca Matei Pintilie
2025-07-29 15:53:20 +02:00
parent 8c4769e27b
commit e4d32f9392
14 changed files with 161 additions and 74 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ find_library(LIBUBSAN NAMES ubsan libubsan.so.0)
# #
## Set various debug flags (instrumentation libs, coverage, …) ## Set various debug flags (instrumentation libs, coverage, …)
# #
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra -Wconversion -fvisibility=hidden -fvisibility-inlines-hidden") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pedantic -Wall -Wextra -Wconversion -fvisibility=hidden -fvisibility-inlines-hidden")
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage")
endif() endif()
+1 -1
View File
@@ -5,7 +5,7 @@ Biboumi is an XMPP gateway that connects to IRC servers and translates
between the two protocols. It can be used to access IRC channels using any between the two protocols. It can be used to access IRC channels using any
XMPP client as if these channels were XMPP MUCs. XMPP client as if these channels were XMPP MUCs.
It is written in modern C++14 and makes great efforts to have as little It is written in modern C++20 and makes great efforts to have as little
dependencies and to be as simple as possible. dependencies and to be as simple as possible.
The goal is to provide a way to access most of IRC features using any XMPP The goal is to provide a way to access most of IRC features using any XMPP
+52 -29
View File
@@ -5,40 +5,63 @@
# BOTAN_FOUND - True if library and include directory are found # BOTAN_FOUND - True if library and include directory are found
# If set to TRUE, the following are also defined: # If set to TRUE, the following are also defined:
# BOTAN_INCLUDE_DIRS - The directory where to find the header file # BOTAN_INCLUDE_DIRS - The directory where to find the header file
# BOTAN_LIBRARIES - Where to find the library file # BOTAN_LIBRARIES - Where to find the library files
#
# 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 # This file is in the public domain
# Copied and modified from
# https://github.com/vistle/vistle/blob/master/cmake/Modules/FindBOTAN.cmake
include(FindPkgConfig) include(FindPackageHandleStandardArgs)
if(NOT BOTAN_FOUND) set(BOTAN_VERSIONS botan-3)
pkg_check_modules(BOTAN botan-2) set(BOTAN_NAMES botan-3 botan)
set(BOTAN_NAMES_DEBUG botand-3 botand)
find_path(
BOTAN_INCLUDE_DIR
NAMES botan/build.h
PATH_SUFFIXES ${BOTAN_VERSIONS}
DOC "The Botan include directory")
if(BOTAN_INCLUDE_DIR)
file(READ "${BOTAN_INCLUDE_DIR}/botan/build.h" build)
string(REGEX MATCH "BOTAN_VERSION_MAJOR ([0-9]*)" _ ${build})
set(BOTAN_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "BOTAN_VERSION_MINOR ([0-9]*)" _ ${build})
set(BOTAN_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "BOTAN_VERSION_PATCH ([0-9]*)" _ ${build})
set(BOTAN_VERSION_PATCH ${CMAKE_MATCH_1})
set(BOTAN_VERSION "${BOTAN_VERSION_MAJOR}.${BOTAN_VERSION_MINOR}.${BOTAN_VERSION_PATCH}")
endif() endif()
if(NOT BOTAN_FOUND) find_library(
find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h BOTAN_LIBRARY
PATH_SUFFIXES botan-2 NAMES ${BOTAN_NAMES}
DOC "The botan include directory") PATH_SUFFIXES release/lib lib
DOC "The Botan (release) library")
find_library(BOTAN_LIBRARIES NAMES botan botan-2 if(MSVC)
DOC "The botan library") find_library(
BOTAN_LIBRARY_DEBUG
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and NAMES ${BOTAN_NAMES_DEBUG}
# set BOTAN_FOUND to TRUE if these two variables are set. PATH_SUFFIXES debug/lib lib
include(FindPackageHandleStandardArgs) DOC "The Botan debug library")
find_package_handle_standard_args(BOTAN REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) find_package_handle_standard_args(
BOTAN
if(BOTAN_FOUND) REQUIRED_VARS BOTAN_LIBRARY BOTAN_LIBRARY_DEBUG BOTAN_INCLUDE_DIR
set(BOTAN_LIBRARY ${BOTAN_LIBRARIES} CACHE INTERNAL "") VERSION_VAR BOTAN_VERSION)
set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS} CACHE INTERNAL "") else()
set(BOTAN_FOUND ${BOTAN_FOUND} CACHE INTERNAL "") find_package_handle_standard_args(
endif() BOTAN
REQUIRED_VARS BOTAN_LIBRARY BOTAN_INCLUDE_DIR
VERSION_VAR BOTAN_VERSION)
endif() endif()
mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) if(BOTAN_FOUND)
set(BOTAN_INCLUDE_DIRS ${BOTAN_INCLUDE_DIR})
if(MSVC)
set(BOTAN_LIBRARIES optimized ${BOTAN_LIBRARY} debug ${BOTAN_LIBRARY_DEBUG})
else()
set(BOTAN_LIBRARIES ${BOTAN_LIBRARY})
endif()
endif()
mark_as_advanced(BOTAN_INCLUDE_DIR BOTAN_LIBRARY BOTAN_LIBRARY_DEBUG)
+2 -2
View File
@@ -20,7 +20,7 @@ biboumi by having all dependencies.
Tools: Tools:
~~~~~~ ~~~~~~
- A C++14 compiler (clang >= 3.4 or gcc >= 5.0 for example) - A C++20 compiler (clang >= 14 or gcc >= 11.2 for example)
- CMake - CMake
- sphinx (optional) to build the documentation - sphinx (optional) to build the documentation
@@ -51,7 +51,7 @@ udns_ (optional, but recommended)
performances when connecting to a big number of IRC servers at the same performances when connecting to a big number of IRC servers at the same
time. time.
libbotan_ 2.x (optional, but recommended) libbotan_ 3.x (optional, but recommended)
Provides TLS support. Without it, IRC connections are all made in Provides TLS support. Without it, IRC connections are all made in
plain-text mode. plain-text mode.
+1 -1
View File
@@ -16,7 +16,7 @@ udns-dev \
expat-dev \ expat-dev \
libidn-dev \ libidn-dev \
sqlite-dev \ sqlite-dev \
botan-dev \ botan3-dev \
util-linux-dev \ util-linux-dev \
libgcrypt-dev \ libgcrypt-dev \
postgresql-dev \ postgresql-dev \
+1 -1
View File
@@ -16,7 +16,7 @@ libudns-dev \
libexpat1-dev \ libexpat1-dev \
libidn11-dev \ libidn11-dev \
libsqlite3-dev \ libsqlite3-dev \
libbotan-2-dev \ libbotan-3-dev \
libsystemd-dev \ libsystemd-dev \
uuid-dev \ uuid-dev \
libgcrypt20-dev \ libgcrypt20-dev \
+1
View File
@@ -5,6 +5,7 @@ FROM fedora:latest
ENV LC_ALL=C.UTF-8 ENV LC_ALL=C.UTF-8
# TODO: upgrade to botan3
RUN dnf --refresh install -y \ RUN dnf --refresh install -y \
git \ git \
make \ make \
+1 -1
View File
@@ -213,7 +213,7 @@ void IrcClient::start()
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname()); this->get_hostname());
# ifdef BOTAN_FOUND # ifdef BOTAN_FOUND
this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>()); this->credential_manager->set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
# endif # endif
if (Config::get("fixed_irc_server", "").empty() && if (Config::get("fixed_irc_server", "").empty() &&
!options.col<Database::Address>().empty()) !options.col<Database::Address>().empty())
+1 -1
View File
@@ -39,7 +39,7 @@ const std::string& BasicCredentialsManager::get_trusted_fingerprint() const
} }
void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs, void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs,
const std::string& hostname, const std::string& trusted_fingerprint, const std::string_view hostname, const std::string& trusted_fingerprint,
const std::exception_ptr& exc) const std::exception_ptr& exc)
{ {
+1 -1
View File
@@ -19,7 +19,7 @@ class TCPSocketHandler;
* on a trusted fingerprint. * on a trusted fingerprint.
*/ */
void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs, void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs,
const std::string& hostname, const std::string& trusted_fingerprint, const std::string_view hostname, const std::string& trusted_fingerprint,
const std::exception_ptr& exc); const std::exception_ptr& exc);
class BasicCredentialsManager: public Botan::Credentials_Manager class BasicCredentialsManager: public Botan::Credentials_Manager
+32 -27
View File
@@ -16,23 +16,29 @@
# include <botan/hex.h> # include <botan/hex.h>
# include <botan/auto_rng.h> # include <botan/auto_rng.h>
# include <botan/tls_exceptn.h> # include <botan/tls_exceptn.h>
# include <botan/tls_session_manager_memory.h>
# include <botan/tls_session_manager_hybrid.h>
# include <config/config.hpp> # include <config/config.hpp>
# include <utils/dirname.hpp> # include <utils/dirname.hpp>
namespace namespace
{ {
Botan::AutoSeeded_RNG& get_rng() std::shared_ptr<Botan::RandomNumberGenerator>& get_rng()
{ {
static Botan::AutoSeeded_RNG rng{}; static std::shared_ptr<Botan::RandomNumberGenerator> rng;
if (!rng)
rng = std::make_shared<Botan::AutoSeeded_RNG>();
return rng; return rng;
} }
Botan::TLS::Session_Manager_In_Memory& get_session_manager() std::shared_ptr<Botan::TLS::Session_Manager>& get_session_manager(std::shared_ptr<BasicCredentialsManager> creds)
{ {
static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; static std::shared_ptr<Botan::TLS::Session_Manager> session_manager;
#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(2,4,0)
// workaround for https://github.com/randombit/botan/issues/1276 if (!session_manager)
session_manager.remove_all(); session_manager = std::make_shared<Botan::TLS::Session_Manager_Hybrid>(
#endif std::make_unique<Botan::TLS::Session_Manager_In_Memory>(get_rng()),
creds, get_rng());
return session_manager; return session_manager;
} }
} }
@@ -50,7 +56,8 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller>& poller):
SocketHandler(poller, -1), SocketHandler(poller, -1),
use_tls(false) use_tls(false)
#ifdef BOTAN_FOUND #ifdef BOTAN_FOUND
,credential_manager() ,credential_manager(std::make_shared<BasicCredentialsManager>())
,policy(std::make_shared<BiboumiTLSPolicy>())
#endif #endif
{} {}
@@ -242,11 +249,11 @@ void TCPSocketHandler::start_tls(const std::string& address, const std::string&
auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename())); auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename()));
if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/') if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/')
policy_directory += '/'; policy_directory += '/';
this->policy.load(policy_directory + "policy.txt"); this->policy->load(policy_directory + "policy.txt");
this->policy.load(policy_directory + address + ".policy.txt"); this->policy->load(policy_directory + address + ".policy.txt");
this->tls = std::make_unique<Botan::TLS::Client>( this->tls = std::make_unique<Botan::TLS::Client>(
*this, shared_from_this(),
get_session_manager(), this->credential_manager, this->policy, get_session_manager(this->credential_manager), this->credential_manager, this->policy,
get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version()); get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version());
} }
@@ -298,17 +305,16 @@ void TCPSocketHandler::tls_send(std::string&& data)
std::make_move_iterator(data.end())); std::make_move_iterator(data.end()));
} }
void TCPSocketHandler::tls_record_received(uint64_t, const Botan::byte *data, size_t size) void TCPSocketHandler::tls_record_received(uint64_t, std::span<const uint8_t> data)
{ {
this->in_buf += std::string(reinterpret_cast<const char*>(data), this->in_buf += std::string(data.begin(), data.end());
size);
if (!this->in_buf.empty()) if (!this->in_buf.empty())
this->parse_in_buffer(size); this->parse_in_buffer(data.size());
} }
void TCPSocketHandler::tls_emit_data(const Botan::byte *data, size_t size) void TCPSocketHandler::tls_emit_data(std::span<const uint8_t> data)
{ {
this->raw_send(std::string(reinterpret_cast<const char*>(data), size)); this->raw_send(std::string(data.begin(), data.end()));
} }
void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert) void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert)
@@ -316,25 +322,24 @@ void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert)
log_debug("tls_alert: ", alert.type_string()); log_debug("tls_alert: ", alert.type_string());
} }
bool TCPSocketHandler::tls_session_established(const Botan::TLS::Session& session) void TCPSocketHandler::tls_session_established(const Botan::TLS::Session_Summary& session)
{ {
log_debug("Handshake with ", session.server_info().hostname(), " complete.", log_debug("Handshake with ", session.server_info().hostname(), " complete.",
" Version: ", session.version().to_string(), " Version: ", session.version().to_string(),
" using ", session.ciphersuite().to_string()); " using ", session.ciphersuite().to_string());
if (!session.session_id().empty()) if (!session.session_id().empty())
log_debug("Session ID ", Botan::hex_encode(session.session_id())); log_debug("Session ID ", Botan::hex_encode(session.session_id()));
if (!session.session_ticket().empty()) if (session.session_ticket().has_value())
log_debug("Session ticket ", Botan::hex_encode(session.session_ticket())); log_debug("Session ticket ", Botan::hex_encode(*session.session_ticket()));
return true;
} }
void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain,
const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, const std::vector<std::optional<Botan::OCSP::Response>>& ocsp_responses,
const std::vector<Botan::Certificate_Store*>& trusted_roots, const std::vector<Botan::Certificate_Store*>& trusted_roots,
Botan::Usage_Type usage, const std::string& hostname, Botan::Usage_Type usage, std::string_view hostname,
const Botan::TLS::Policy& policy) const Botan::TLS::Policy& policy)
{ {
if (!this->policy.verify_certificate) if (!this->policy->verify_certificate)
{ {
log_debug("Not verifying certificate due to domain policy "); log_debug("Not verifying certificate due to domain policy ");
return; return;
@@ -352,7 +357,7 @@ void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certi
if (this->abort_on_invalid_cert()) if (this->abort_on_invalid_cert())
exception_ptr = std::current_exception(); exception_ptr = std::current_exception();
check_tls_certificate(cert_chain, hostname, this->credential_manager.get_trusted_fingerprint(), exception_ptr); check_tls_certificate(cert_chain, hostname, this->credential_manager->get_trusted_fingerprint(), exception_ptr);
} }
} }
+8 -7
View File
@@ -30,6 +30,7 @@
* But doesn’t do any connect() or accept() or anything else. * But doesn’t do any connect() or accept() or anything else.
*/ */
class TCPSocketHandler: public SocketHandler class TCPSocketHandler: public SocketHandler
,public std::enable_shared_from_this<TCPSocketHandler>
#ifdef BOTAN_FOUND #ifdef BOTAN_FOUND
,public Botan::TLS::Callbacks ,public Botan::TLS::Callbacks
#endif #endif
@@ -137,12 +138,12 @@ private:
* Called by the tls object that some data has been decrypt. We call * Called by the tls object that some data has been decrypt. We call
* parse_in_buffer() to handle that unencrypted data. * parse_in_buffer() to handle that unencrypted data.
*/ */
void tls_record_received(uint64_t rec_no, const Botan::byte* data, size_t size) override final; void tls_record_received(uint64_t rec_no, std::span<const uint8_t> data) override final;
/** /**
* Called by the tls object to indicate that some data has been encrypted * 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. * and is now ready to be sent on the socket as is.
*/ */
void tls_emit_data(const Botan::byte* data, size_t size) override final; void tls_emit_data(std::span<const uint8_t> data) override final;
/** /**
* Called by the tls object to indicate that a TLS alert has been * 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. * received. We don’t use it, we just log some message, at the moment.
@@ -152,13 +153,13 @@ private:
* Called by the tls object at the end of the TLS handshake. We don't do * 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. * anything here appart from logging the TLS session information.
*/ */
bool tls_session_established(const Botan::TLS::Session& session) override final; void tls_session_established(const Botan::TLS::Session_Summary& session) override final;
void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain,
const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, const std::vector<std::optional<Botan::OCSP::Response>>& ocsp_responses,
const std::vector<Botan::Certificate_Store*>& trusted_roots, const std::vector<Botan::Certificate_Store*>& trusted_roots,
Botan::Usage_Type usage, Botan::Usage_Type usage,
const std::string& hostname, std::string_view hostname,
const Botan::TLS::Policy& policy) override final; const Botan::TLS::Policy& policy) override final;
/** /**
* Called whenever the tls session goes from inactive to active. This * Called whenever the tls session goes from inactive to active. This
@@ -203,9 +204,9 @@ protected:
#ifdef BOTAN_FOUND #ifdef BOTAN_FOUND
protected: protected:
BasicCredentialsManager credential_manager; std::shared_ptr<BasicCredentialsManager> credential_manager;
private: private:
BiboumiTLSPolicy policy; std::shared_ptr<BiboumiTLSPolicy> policy;
/** /**
* We use a unique_ptr because we may not want to create the object at * 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 and * all. The Botan::TLS::Client object generates a handshake message and
+53 -2
View File
@@ -8,7 +8,6 @@
#include <network/tls_policy.hpp> #include <network/tls_policy.hpp>
#include <logger/logger.hpp> #include <logger/logger.hpp>
#include <botan/parsing.h>
#include <botan/exceptn.h> #include <botan/exceptn.h>
bool BiboumiTLSPolicy::load(const std::string& filename) bool BiboumiTLSPolicy::load(const std::string& filename)
@@ -31,7 +30,7 @@ bool BiboumiTLSPolicy::load(const std::string& filename)
void BiboumiTLSPolicy::load(std::istream& is) void BiboumiTLSPolicy::load(std::istream& is)
{ {
const auto dict = Botan::read_cfg(is); const auto dict = BiboumiTLSPolicy::read_cfg(is);
for (const auto& pair: dict) for (const auto& pair: dict)
{ {
// Workaround for options that are not overridden in Botan::TLS::Text_Policy // Workaround for options that are not overridden in Botan::TLS::Text_Policy
@@ -49,4 +48,56 @@ bool BiboumiTLSPolicy::require_cert_revocation_info() const
return this->req_cert_revocation_info; return this->req_cert_revocation_info;
} }
std::map<std::string, std::string> BiboumiTLSPolicy::read_cfg(std::istream& is) {
std::map<std::string, std::string> kv;
size_t line = 0;
while(is.good()) {
std::string s;
std::getline(is, s);
++line;
if(s.empty() || s[0] == '#') {
continue;
}
s = clean_ws(s.substr(0, s.find('#')));
if(s.empty()) {
continue;
}
auto eq = s.find('=');
if(eq == std::string::npos || eq == 0 || eq == s.size() - 1) {
throw Botan::Decoding_Error("Bad read_cfg input '" + s + "' on line " + std::to_string(line));
}
const std::string key = BiboumiTLSPolicy::clean_ws(s.substr(0, eq));
const std::string val = BiboumiTLSPolicy::clean_ws(s.substr(eq + 1, std::string::npos));
kv[key] = val;
}
return kv;
}
std::string BiboumiTLSPolicy::clean_ws(std::string_view s) {
const char* ws = " \t\n";
auto start = s.find_first_not_of(ws);
auto end = s.find_last_not_of(ws);
if(start == std::string::npos) {
return "";
}
if(end == std::string::npos) {
return std::string(s.substr(start, end));
} else {
return std::string(s.substr(start, start + end + 1));
}
}
#endif #endif
+6
View File
@@ -24,6 +24,12 @@ public:
bool verify_certificate{true}; bool verify_certificate{true};
protected: protected:
bool req_cert_revocation_info{true}; bool req_cert_revocation_info{true};
private:
// Copied from botan-3's src/lib/utils/read_cfg.cpp
// These were made internal in v3, but they are in use by biboumi so they have
// to be vendored in
static std::map<std::string, std::string> read_cfg(std::istream& is);
static std::string clean_ws(std::string_view s);
}; };
#endif #endif