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(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")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage")
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
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.
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
# 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
# BOTAN_LIBRARIES - Where to find the library files
#
# 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)
pkg_check_modules(BOTAN botan-2)
set(BOTAN_VERSIONS botan-3)
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()
if(NOT BOTAN_FOUND)
find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h
PATH_SUFFIXES botan-2
DOC "The botan include directory")
find_library(BOTAN_LIBRARIES NAMES botan botan-2
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} CACHE INTERNAL "")
set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS} CACHE INTERNAL "")
set(BOTAN_FOUND ${BOTAN_FOUND} CACHE INTERNAL "")
endif()
find_library(
BOTAN_LIBRARY
NAMES ${BOTAN_NAMES}
PATH_SUFFIXES release/lib lib
DOC "The Botan (release) library")
if(MSVC)
find_library(
BOTAN_LIBRARY_DEBUG
NAMES ${BOTAN_NAMES_DEBUG}
PATH_SUFFIXES debug/lib lib
DOC "The Botan debug library")
find_package_handle_standard_args(
BOTAN
REQUIRED_VARS BOTAN_LIBRARY BOTAN_LIBRARY_DEBUG BOTAN_INCLUDE_DIR
VERSION_VAR BOTAN_VERSION)
else()
find_package_handle_standard_args(
BOTAN
REQUIRED_VARS BOTAN_LIBRARY BOTAN_INCLUDE_DIR
VERSION_VAR BOTAN_VERSION)
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:
~~~~~~
- 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
- 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
time.
libbotan_ 2.x (optional, but recommended)
libbotan_ 3.x (optional, but recommended)
Provides TLS support. Without it, IRC connections are all made in
plain-text mode.
+1 -1
View File
@@ -16,7 +16,7 @@ udns-dev \
expat-dev \
libidn-dev \
sqlite-dev \
botan-dev \
botan3-dev \
util-linux-dev \
libgcrypt-dev \
postgresql-dev \
+1 -1
View File
@@ -16,7 +16,7 @@ libudns-dev \
libexpat1-dev \
libidn11-dev \
libsqlite3-dev \
libbotan-2-dev \
libbotan-3-dev \
libsystemd-dev \
uuid-dev \
libgcrypt20-dev \
+1
View File
@@ -5,6 +5,7 @@ FROM fedora:latest
ENV LC_ALL=C.UTF-8
# TODO: upgrade to botan3
RUN dnf --refresh install -y \
git \
make \
+1 -1
View File
@@ -213,7 +213,7 @@ void IrcClient::start()
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
# ifdef BOTAN_FOUND
this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
this->credential_manager->set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
# endif
if (Config::get("fixed_irc_server", "").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,
const std::string& hostname, const std::string& trusted_fingerprint,
const std::string_view hostname, const std::string& trusted_fingerprint,
const std::exception_ptr& exc)
{
+1 -1
View File
@@ -19,7 +19,7 @@ class TCPSocketHandler;
* on a trusted fingerprint.
*/
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);
class BasicCredentialsManager: public Botan::Credentials_Manager
+32 -27
View File
@@ -16,23 +16,29 @@
# include <botan/hex.h>
# include <botan/auto_rng.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 <utils/dirname.hpp>
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;
}
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()};
#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(2,4,0)
// workaround for https://github.com/randombit/botan/issues/1276
session_manager.remove_all();
#endif
static std::shared_ptr<Botan::TLS::Session_Manager> session_manager;
if (!session_manager)
session_manager = std::make_shared<Botan::TLS::Session_Manager_Hybrid>(
std::make_unique<Botan::TLS::Session_Manager_In_Memory>(get_rng()),
creds, get_rng());
return session_manager;
}
}
@@ -50,7 +56,8 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller>& poller):
SocketHandler(poller, -1),
use_tls(false)
#ifdef BOTAN_FOUND
,credential_manager()
,credential_manager(std::make_shared<BasicCredentialsManager>())
,policy(std::make_shared<BiboumiTLSPolicy>())
#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()));
if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/')
policy_directory += '/';
this->policy.load(policy_directory + "policy.txt");
this->policy.load(policy_directory + address + ".policy.txt");
this->policy->load(policy_directory + "policy.txt");
this->policy->load(policy_directory + address + ".policy.txt");
this->tls = std::make_unique<Botan::TLS::Client>(
*this,
get_session_manager(), this->credential_manager, this->policy,
shared_from_this(),
get_session_manager(this->credential_manager), this->credential_manager, this->policy,
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()));
}
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),
size);
this->in_buf += std::string(data.begin(), data.end());
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)
@@ -316,25 +322,24 @@ void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert)
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.",
" 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;
if (session.session_ticket().has_value())
log_debug("Session ticket ", Botan::hex_encode(*session.session_ticket()));
}
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,
Botan::Usage_Type usage, const std::string& hostname,
Botan::Usage_Type usage, std::string_view hostname,
const Botan::TLS::Policy& policy)
{
if (!this->policy.verify_certificate)
if (!this->policy->verify_certificate)
{
log_debug("Not verifying certificate due to domain policy ");
return;
@@ -352,7 +357,7 @@ void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certi
if (this->abort_on_invalid_cert())
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.
*/
class TCPSocketHandler: public SocketHandler
,public std::enable_shared_from_this<TCPSocketHandler>
#ifdef BOTAN_FOUND
,public Botan::TLS::Callbacks
#endif
@@ -137,12 +138,12 @@ private:
* Called by the tls object that some data has been decrypt. We call
* 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
* 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
* 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
* 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,
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,
Botan::Usage_Type usage,
const std::string& hostname,
std::string_view hostname,
const Botan::TLS::Policy& policy) override final;
/**
* Called whenever the tls session goes from inactive to active. This
@@ -203,9 +204,9 @@ protected:
#ifdef BOTAN_FOUND
protected:
BasicCredentialsManager credential_manager;
std::shared_ptr<BasicCredentialsManager> credential_manager;
private:
BiboumiTLSPolicy policy;
std::shared_ptr<BiboumiTLSPolicy> policy;
/**
* 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
+53 -2
View File
@@ -8,7 +8,6 @@
#include <network/tls_policy.hpp>
#include <logger/logger.hpp>
#include <botan/parsing.h>
#include <botan/exceptn.h>
bool BiboumiTLSPolicy::load(const std::string& filename)
@@ -31,7 +30,7 @@ bool BiboumiTLSPolicy::load(const std::string& filename)
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)
{
// 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;
}
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
+6
View File
@@ -24,6 +24,12 @@ public:
bool verify_certificate{true};
protected:
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