Remove all the libs that are now in louloulibs
This commit is contained in:
+19
-106
@@ -15,35 +15,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE
|
||||
#
|
||||
## Look for external libraries
|
||||
#
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
|
||||
include(FindEXPAT)
|
||||
find_package(EXPAT REQUIRED)
|
||||
find_package(ICONV REQUIRED)
|
||||
find_package(LIBUUID REQUIRED)
|
||||
|
||||
if(WITH_LIBIDN)
|
||||
find_package(LIBIDN REQUIRED)
|
||||
elseif(NOT WITHOUT_LIBIDN)
|
||||
find_package(LIBIDN)
|
||||
endif()
|
||||
|
||||
if(WITH_SYSTEMD)
|
||||
find_package(SYSTEMD REQUIRED)
|
||||
elseif(NOT WITHOUT_SYSTEMD)
|
||||
find_package(SYSTEMD)
|
||||
endif()
|
||||
|
||||
if(WITH_BOTAN)
|
||||
find_package(BOTAN REQUIRED)
|
||||
elseif(NOT WITHOUT_BOTAN)
|
||||
find_package(BOTAN)
|
||||
endif()
|
||||
|
||||
if(WITH_CARES)
|
||||
find_package(CARES REQUIRED)
|
||||
elseif(NOT WITHOUT_CARES)
|
||||
find_package(CARES)
|
||||
endif()
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
|
||||
|
||||
#
|
||||
## Get the software version
|
||||
@@ -68,42 +40,12 @@ if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BIBOUMI_VERSION
|
||||
set(SOFTWARE_VERSION
|
||||
${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX})
|
||||
|
||||
# To be able to include the config.h file generated by cmake
|
||||
include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/")
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
include_directories(${ICONV_INCLUDE_DIRS})
|
||||
include_directories(${LIBUUID_INCLUDE_DIRS})
|
||||
|
||||
if(LIBIDN_FOUND)
|
||||
include_directories(${LIBIDN_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(SYSTEMD_FOUND)
|
||||
include_directories(${SYSTEMD_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(BOTAN_FOUND)
|
||||
include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(CARES_FOUND)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)")
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING})
|
||||
else()
|
||||
set(POLLER "POLL" CACHE STRING ${POLLER_DOCSTRING})
|
||||
endif()
|
||||
if((NOT ${POLLER} MATCHES "POLL") AND
|
||||
(NOT ${POLLER} MATCHES "EPOLL"))
|
||||
message(FATAL_ERROR "POLLER must be either POLL or EPOLL")
|
||||
endif()
|
||||
|
||||
#
|
||||
## Documentation
|
||||
@@ -121,43 +63,21 @@ if(RONN_EXECUTABLE)
|
||||
add_custom_target(doc DEPENDS ${MAN_PAGE})
|
||||
endif()
|
||||
|
||||
#
|
||||
## utils
|
||||
#
|
||||
file(GLOB source_utils
|
||||
src/utils/*.[hc]pp)
|
||||
add_library(utils STATIC ${source_utils})
|
||||
target_link_libraries(utils ${ICONV_LIBRARIES})
|
||||
|
||||
#
|
||||
## config
|
||||
#
|
||||
file(GLOB source_config
|
||||
src/config/*.[hc]pp)
|
||||
add_library(config STATIC ${source_config})
|
||||
target_link_libraries(config utils)
|
||||
|
||||
#
|
||||
## logger
|
||||
#
|
||||
file(GLOB source_logger
|
||||
src/logger/*.[hc]pp)
|
||||
add_library(logger STATIC ${source_logger})
|
||||
target_link_libraries(logger config)
|
||||
|
||||
#
|
||||
## network
|
||||
#
|
||||
file(GLOB source_network
|
||||
src/network/*.[hc]pp)
|
||||
add_library(network STATIC ${source_network})
|
||||
target_link_libraries(network logger)
|
||||
if(BOTAN_FOUND)
|
||||
target_link_libraries(network ${BOTAN_LIBRARIES})
|
||||
endif()
|
||||
if(CARES_FOUND)
|
||||
target_link_libraries(network ${CARES_LIBRARIES})
|
||||
if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs")
|
||||
message(FATAL_ERROR "louloulibs/ directory not found. Make sure you fetched the submodules with 'git submodule init && git submodule update'")
|
||||
endif()
|
||||
add_subdirectory("louloulibs")
|
||||
include_directories("louloulibs")
|
||||
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
include_directories(${ICONV_INCLUDE_DIRS})
|
||||
include_directories(${LIBUUID_INCLUDE_DIRS})
|
||||
|
||||
# If they are found in louloulibs CMakeLists.txt, we inherite these values
|
||||
include_directories(${LIBIDN_INCLUDE_DIRS})
|
||||
include_directories(${SYSTEMD_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS})
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
#
|
||||
## irclib
|
||||
@@ -168,17 +88,12 @@ add_library(irc STATIC ${source_irc})
|
||||
target_link_libraries(irc network utils logger)
|
||||
|
||||
#
|
||||
## xmpplib
|
||||
## xmpp
|
||||
#
|
||||
file(GLOB source_xmpp
|
||||
src/xmpp/*.[hc]pp)
|
||||
add_library(xmpp STATIC ${source_xmpp})
|
||||
target_link_libraries(xmpp bridge network utils logger
|
||||
${EXPAT_LIBRARIES}
|
||||
${LIBUUID_LIBRARIES})
|
||||
if(LIBIDN_FOUND)
|
||||
target_link_libraries(xmpp ${LIBIDN_LIBRARIES})
|
||||
endif()
|
||||
target_link_libraries(xmpp xmpplib bridge network utils logger)
|
||||
|
||||
#
|
||||
## bridge
|
||||
@@ -209,6 +124,7 @@ endif()
|
||||
add_executable(test_suite EXCLUDE_FROM_ALL
|
||||
src/test.cpp)
|
||||
target_link_libraries(test_suite
|
||||
xmpplib
|
||||
xmpp
|
||||
irc
|
||||
bridge
|
||||
@@ -216,9 +132,6 @@ target_link_libraries(test_suite
|
||||
config
|
||||
logger)
|
||||
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/config.h)
|
||||
|
||||
#
|
||||
## Install target
|
||||
#
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# - 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
|
||||
PATH_SUFFIXES botan-1.11
|
||||
DOC "The botan include directory")
|
||||
|
||||
find_library(BOTAN_LIBRARIES NAMES botan botan-1.11
|
||||
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)
|
||||
@@ -1,37 +0,0 @@
|
||||
# - Find c-ares
|
||||
# Find the c-ares library, and more particularly the stringprep header.
|
||||
#
|
||||
# This module defines the following variables:
|
||||
# CARES_FOUND - True if library and include directory are found
|
||||
# If set to TRUE, the following are also defined:
|
||||
# CARES_INCLUDE_DIRS - The directory where to find the header file
|
||||
# CARES_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.
|
||||
# CARES_INCLUDE_DIR
|
||||
# CARES_LIBRARY
|
||||
#
|
||||
# This file is in the public domain
|
||||
|
||||
if(NOT CARES_FOUND)
|
||||
find_path(CARES_INCLUDE_DIRS NAMES ares.h
|
||||
DOC "The c-ares include directory")
|
||||
|
||||
find_library(CARES_LIBRARIES NAMES cares
|
||||
DOC "The c-ares library")
|
||||
|
||||
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||
# set CARES_FOUND to TRUE if these two variables are set.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(CARES REQUIRED_VARS CARES_LIBRARIES CARES_INCLUDE_DIRS)
|
||||
|
||||
# Compatibility for all the ways of writing these variables
|
||||
if(CARES_FOUND)
|
||||
set(CARES_INCLUDE_DIR ${CARES_INCLUDE_DIRS})
|
||||
set(CARES_LIBRARY ${CARES_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(CARES_INCLUDE_DIRS CARES_LIBRARIES)
|
||||
@@ -1,60 +0,0 @@
|
||||
# - Find iconv
|
||||
# Find the iconv (character set conversion) library
|
||||
#
|
||||
# This module defines the following variables:
|
||||
# ICONV_FOUND - True if library and include directory are found
|
||||
# If set to TRUE, the following are also defined:
|
||||
# ICONV_INCLUDE_DIRS - The directory where to find the header file
|
||||
# ICONV_LIBRARIES - Where to find the library file
|
||||
# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const
|
||||
#
|
||||
# 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.
|
||||
# ICONV_LIBRARY
|
||||
# ICONV_INCLUDE_DIR
|
||||
#
|
||||
# This file is in the public domain
|
||||
|
||||
find_path(ICONV_INCLUDE_DIRS NAMES iconv.h
|
||||
DOC "The iconv include directory")
|
||||
|
||||
find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c
|
||||
DOC "The iconv library")
|
||||
|
||||
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||
# set ICONV_FOUND to TRUE if these two variables are set.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS)
|
||||
|
||||
# Check if the prototype is
|
||||
# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft,
|
||||
# char** outbuf, size_t* outbytesleft);
|
||||
# or
|
||||
# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft,
|
||||
# char** outbuf, size_t* outbytesleft);
|
||||
if(ICONV_FOUND)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
# Set the parameters needed to compile the following code.
|
||||
set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS})
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES})
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <iconv.h>
|
||||
int main(){
|
||||
iconv_t conv = 0;
|
||||
const char* in = 0;
|
||||
size_t ilen = 0;
|
||||
char* out = 0;
|
||||
size_t olen = 0;
|
||||
iconv(conv, &in, &ilen, &out, &olen);
|
||||
return 0;}"
|
||||
ICONV_SECOND_ARGUMENT_IS_CONST)
|
||||
|
||||
# Compatibility for all the ways of writing these variables
|
||||
set(ICONV_LIBRARY ${ICONV_LIBRARIES})
|
||||
set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST)
|
||||
@@ -1,41 +0,0 @@
|
||||
# - Find libidn
|
||||
# Find the libidn library, and more particularly the stringprep header.
|
||||
#
|
||||
# This module defines the following variables:
|
||||
# LIBIDN_FOUND - True if library and include directory are found
|
||||
# If set to TRUE, the following are also defined:
|
||||
# LIBIDN_INCLUDE_DIRS - The directory where to find the header file
|
||||
# LIBIDN_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.
|
||||
# LIBIDN_INCLUDE_DIR
|
||||
# LIBIDN_LIBRARY
|
||||
#
|
||||
# This file is in the public domain
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(LIBIDN libidn)
|
||||
|
||||
if(NOT LIBIDN_FOUND)
|
||||
find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h
|
||||
DOC "The libidn include directory")
|
||||
|
||||
# The library containing the stringprep module is libidn
|
||||
find_library(LIBIDN_LIBRARIES NAMES idn
|
||||
DOC "The libidn library")
|
||||
|
||||
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||
# set LIBIDN_FOUND to TRUE if these two variables are set.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS)
|
||||
|
||||
# Compatibility for all the ways of writing these variables
|
||||
if(LIBIDN_FOUND)
|
||||
set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS})
|
||||
set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES)
|
||||
@@ -1,41 +0,0 @@
|
||||
# - Find libuuid
|
||||
# Find the libuuid library
|
||||
#
|
||||
# This module defines the following variables:
|
||||
# LIBUUID_FOUND - True if library and include directory are found
|
||||
# If set to TRUE, the following are also defined:
|
||||
# LIBUUID_INCLUDE_DIRS - The directory where to find the header file
|
||||
# LIBUUID_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.
|
||||
# LIBUUID_INCLUDE_DIR
|
||||
# LIBUUID_LIBRARY
|
||||
#
|
||||
# This file is in the public domain
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(LIBUUID uuid)
|
||||
|
||||
if(NOT LIBUUID_FOUND)
|
||||
find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h
|
||||
PATH_SUFFIXES uuid
|
||||
DOC "The libuuid include directory")
|
||||
|
||||
find_library(LIBUUID_LIBRARIES NAMES uuid
|
||||
DOC "The libuuid library")
|
||||
|
||||
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||
# set LIBUUID_FOUND to TRUE if these two variables are set.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS)
|
||||
|
||||
# Compatibility for all the ways of writing these variables
|
||||
if(LIBUUID_FOUND)
|
||||
set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS})
|
||||
set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES)
|
||||
@@ -1,39 +0,0 @@
|
||||
# - Find SystemdDaemon
|
||||
# Find the systemd daemon library
|
||||
#
|
||||
# This module defines the following variables:
|
||||
# SYSTEMD_FOUND - True if library and include directory are found
|
||||
# If set to TRUE, the following are also defined:
|
||||
# SYSTEMD_INCLUDE_DIRS - The directory where to find the header file
|
||||
# SYSTEMD_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.
|
||||
# SYSTEMD_LIBRARY
|
||||
# SYSTEMD_INCLUDE_DIR
|
||||
#
|
||||
# This file is in the public domain
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(SYSTEMD libsystemd)
|
||||
|
||||
if(NOT SYSTEMD_FOUND)
|
||||
find_path(SYSTEMD_INCLUDE_DIRS NAMES systemd/sd-daemon.h
|
||||
DOC "The Systemd include directory")
|
||||
|
||||
find_library(SYSTEMD_LIBRARIES NAMES systemd
|
||||
DOC "The Systemd library")
|
||||
|
||||
# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
|
||||
# set SYSTEMD_FOUND to TRUE if these two variables are set.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(SYSTEMD REQUIRED_VARS SYSTEMD_LIBRARIES SYSTEMD_INCLUDE_DIRS)
|
||||
|
||||
if(SYSTEMD_FOUND)
|
||||
set(SYSTEMD_LIBRARY ${SYSTEMD_LIBRARIES})
|
||||
set(SYSTEMD_INCLUDE_DIR ${SYSTEMD_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES)
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <bridge/bridge.hpp>
|
||||
#include <bridge/colors.hpp>
|
||||
#include <bridge/list_element.hpp>
|
||||
#include <xmpp/xmpp_component.hpp>
|
||||
#include <xmpp/biboumi_component.hpp>
|
||||
#include <xmpp/xmpp_stanza.hpp>
|
||||
#include <irc/irc_message.hpp>
|
||||
#include <network/poller.hpp>
|
||||
@@ -20,7 +20,7 @@ using namespace std::string_literals;
|
||||
|
||||
static const char* action_prefix = "\01ACTION ";
|
||||
|
||||
Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr<Poller> poller):
|
||||
Bridge::Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr<Poller> poller):
|
||||
user_jid(user_jid),
|
||||
xmpp(xmpp),
|
||||
poller(poller)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
class XmppComponent;
|
||||
class BiboumiComponent;
|
||||
class Poller;
|
||||
|
||||
/**
|
||||
@@ -32,7 +32,7 @@ using irc_responder_callback_t = std::function<bool(const std::string& irc_hostn
|
||||
class Bridge
|
||||
{
|
||||
public:
|
||||
explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr<Poller> poller);
|
||||
explicit Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr<Poller> poller);
|
||||
~Bridge();
|
||||
/**
|
||||
* QUIT all connected IRC servers.
|
||||
@@ -211,7 +211,7 @@ private:
|
||||
* but we still need to communicate with it, when sending messages from
|
||||
* IRC to XMPP.
|
||||
*/
|
||||
XmppComponent* xmpp;
|
||||
BiboumiComponent* xmpp;
|
||||
/**
|
||||
* Poller, to give it the IrcClients that we spawn, to make it manage
|
||||
* their sockets.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#define SYSTEM_NAME "${CMAKE_SYSTEM}"
|
||||
#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST
|
||||
#cmakedefine LIBIDN_FOUND
|
||||
#cmakedefine SYSTEMD_FOUND
|
||||
#cmakedefine POLLER ${POLLER}
|
||||
#cmakedefine BOTAN_FOUND
|
||||
#cmakedefine CARES_FOUND
|
||||
#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}"
|
||||
@@ -1,122 +0,0 @@
|
||||
#include <config/config.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
std::string Config::filename = "./biboumi.cfg";
|
||||
bool Config::file_must_exist = false;
|
||||
|
||||
std::string Config::get(const std::string& option, const std::string& def)
|
||||
{
|
||||
Config* self = Config::instance().get();
|
||||
auto it = self->values.find(option);
|
||||
|
||||
if (it == self->values.end())
|
||||
return def;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int Config::get_int(const std::string& option, const int& def)
|
||||
{
|
||||
Config* self = Config::instance().get();
|
||||
std::string res = self->get(option, "");
|
||||
if (!res.empty())
|
||||
return atoi(res.c_str());
|
||||
else
|
||||
return def;
|
||||
}
|
||||
|
||||
void Config::set(const std::string& option, const std::string& value, bool save)
|
||||
{
|
||||
Config* self = Config::instance().get();
|
||||
self->values[option] = value;
|
||||
if (save)
|
||||
{
|
||||
self->save_to_file();
|
||||
self->trigger_configuration_change();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::connect(t_config_changed_callback callback)
|
||||
{
|
||||
Config* self = Config::instance().get();
|
||||
self->callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void Config::close()
|
||||
{
|
||||
Config* self = Config::instance().get();
|
||||
self->values.clear();
|
||||
Config::instance().reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private methods
|
||||
*/
|
||||
|
||||
void Config::trigger_configuration_change()
|
||||
{
|
||||
std::vector<t_config_changed_callback>::iterator it;
|
||||
for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it)
|
||||
(*it)();
|
||||
}
|
||||
|
||||
std::unique_ptr<Config>& Config::instance()
|
||||
{
|
||||
static std::unique_ptr<Config> instance;
|
||||
|
||||
if (!instance)
|
||||
{
|
||||
instance = std::make_unique<Config>();
|
||||
instance->read_conf();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Config::read_conf()
|
||||
{
|
||||
std::ifstream file;
|
||||
file.open(filename.data());
|
||||
if (!file.is_open())
|
||||
{
|
||||
if (Config::file_must_exist)
|
||||
{
|
||||
perror(("Error while opening file " + filename + " for reading.").c_str());
|
||||
file.exceptions(std::ifstream::failbit);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
size_t pos;
|
||||
std::string option;
|
||||
std::string value;
|
||||
while (file.good())
|
||||
{
|
||||
std::getline(file, line);
|
||||
if (line == "" || line[0] == '#')
|
||||
continue ;
|
||||
pos = line.find('=');
|
||||
if (pos == std::string::npos)
|
||||
continue ;
|
||||
option = line.substr(0, pos);
|
||||
value = line.substr(pos+1);
|
||||
this->values[option] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Config::save_to_file() const
|
||||
{
|
||||
std::ofstream file(this->filename.data());
|
||||
if (file.fail())
|
||||
{
|
||||
std::cerr << "Could not save config file." << std::endl;
|
||||
return ;
|
||||
}
|
||||
for (auto& it: this->values)
|
||||
file << it.first << "=" << it.second << std::endl;
|
||||
file.close();
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* Read the config file and save all the values in a map.
|
||||
* Also, a singleton.
|
||||
*
|
||||
* Use Config::filename = "bla" to set the filename you want to use.
|
||||
*
|
||||
* If you want to exit if the file does not exist when it is open for
|
||||
* reading, set Config::file_must_exist = true.
|
||||
*
|
||||
* Config::get() can the be used to access the values in the conf.
|
||||
*
|
||||
* Use Config::close() when you're done getting/setting value. This will
|
||||
* save the config into the file.
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_INCLUDED
|
||||
# define CONFIG_INCLUDED
|
||||
|
||||
#include <functional>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
typedef std::function<void()> t_config_changed_callback;
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
Config(){};
|
||||
~Config(){};
|
||||
/**
|
||||
* returns a value from the config. If it doesn’t exist, use
|
||||
* the second argument as the default.
|
||||
* @param option The option we want
|
||||
* @param def The default value in case the option does not exist
|
||||
*/
|
||||
static std::string get(const std::string&, const std::string&);
|
||||
/**
|
||||
* returns a value from the config. If it doesn’t exist, use
|
||||
* the second argument as the default.
|
||||
* @param option The option we want
|
||||
* @param def The default value in case the option does not exist
|
||||
*/
|
||||
static int get_int(const std::string&, const int&);
|
||||
/**
|
||||
* Set a value for the given option. And write all the config
|
||||
* in the file from which it was read if boolean is set.
|
||||
* @param option The option to set
|
||||
* @param value The value to use
|
||||
* @param save if true, save the config file
|
||||
*/
|
||||
static void set(const std::string&, const std::string&, bool save = false);
|
||||
/**
|
||||
* Adds a function to a list. This function will be called whenever a
|
||||
* configuration change occurs.
|
||||
*/
|
||||
static void connect(t_config_changed_callback);
|
||||
/**
|
||||
* Close the config file, saving it to the file is save == true.
|
||||
*/
|
||||
static void close();
|
||||
|
||||
/**
|
||||
* Set the value of the filename to use, before calling any method.
|
||||
*/
|
||||
static std::string filename;
|
||||
/**
|
||||
* Set to true if you want an exception to be raised if the file does not
|
||||
* exist when reading it.
|
||||
*/
|
||||
static bool file_must_exist;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*/
|
||||
static std::unique_ptr<Config>& instance();
|
||||
/**
|
||||
* Read the configuration file at the given path.
|
||||
*/
|
||||
bool read_conf();
|
||||
/**
|
||||
* Write all the config values into the configuration file
|
||||
*/
|
||||
void save_to_file() const;
|
||||
/**
|
||||
* Call all the callbacks previously registered using connect().
|
||||
* This is used to notify any class that a configuration change occured.
|
||||
*/
|
||||
void trigger_configuration_change();
|
||||
|
||||
std::map<std::string, std::string> values;
|
||||
std::vector<t_config_changed_callback> callbacks;
|
||||
|
||||
Config(const Config&) = delete;
|
||||
Config& operator=(const Config&) = delete;
|
||||
Config(Config&&) = delete;
|
||||
Config& operator=(Config&&) = delete;
|
||||
};
|
||||
|
||||
#endif // CONFIG_INCLUDED
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "config.h"
|
||||
#include "louloulibs.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include <logger/logger.hpp>
|
||||
#include <config/config.hpp>
|
||||
|
||||
Logger::Logger(const int log_level):
|
||||
log_level(log_level),
|
||||
stream(std::cout.rdbuf())
|
||||
{
|
||||
}
|
||||
|
||||
Logger::Logger(const int log_level, const std::string& log_file):
|
||||
log_level(log_level),
|
||||
ofstream(log_file.data(), std::ios_base::app),
|
||||
stream(ofstream.rdbuf())
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Logger>& Logger::instance()
|
||||
{
|
||||
static std::unique_ptr<Logger> instance;
|
||||
|
||||
if (!instance)
|
||||
{
|
||||
const std::string log_file = Config::get("log_file", "");
|
||||
const int log_level = Config::get_int("log_level", 0);
|
||||
if (log_file.empty())
|
||||
instance = std::make_unique<Logger>(log_level);
|
||||
else
|
||||
instance = std::make_unique<Logger>(log_level, log_file);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::ostream& Logger::get_stream(const int lvl)
|
||||
{
|
||||
if (lvl >= this->log_level)
|
||||
return this->stream;
|
||||
return this->null_stream;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
#ifndef LOGGER_INCLUDED
|
||||
# define LOGGER_INCLUDED
|
||||
|
||||
/**
|
||||
* Singleton used in logger macros to write into files or stdout, with
|
||||
* various levels of severity.
|
||||
* Only the macros should be used.
|
||||
* @class Logger
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#define debug_lvl 0
|
||||
#define info_lvl 1
|
||||
#define warning_lvl 2
|
||||
#define error_lvl 3
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef SYSTEMD_FOUND
|
||||
# include <systemd/sd-daemon.h>
|
||||
#else
|
||||
# define SD_DEBUG "[DEBUG]: "
|
||||
# define SD_INFO "[INFO]: "
|
||||
# define SD_WARNING "[WARNING]: "
|
||||
# define SD_ERR "[ERROR]: "
|
||||
#endif
|
||||
|
||||
// Macro defined to get the filename instead of the full path. But if it is
|
||||
// not properly defined by the build system, we fallback to __FILE__
|
||||
#ifndef __FILENAME__
|
||||
# define __FILENAME__ __FILE__
|
||||
#endif
|
||||
|
||||
#define WHERE\
|
||||
__FILENAME__ << ":" << __LINE__
|
||||
|
||||
#define log_debug(text)\
|
||||
Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl;
|
||||
|
||||
#define log_info(text)\
|
||||
Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl;
|
||||
|
||||
#define log_warning(text)\
|
||||
Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl;
|
||||
|
||||
#define log_error(text)\
|
||||
Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl;
|
||||
|
||||
/**
|
||||
* Juste a structure representing a stream doing nothing with its input.
|
||||
*/
|
||||
class nullstream: public std::ostream
|
||||
{
|
||||
public:
|
||||
nullstream():
|
||||
std::ostream(0)
|
||||
{ }
|
||||
};
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<Logger>& instance();
|
||||
std::ostream& get_stream(const int);
|
||||
Logger(const int log_level, const std::string& log_file);
|
||||
Logger(const int log_level);
|
||||
|
||||
private:
|
||||
Logger(const Logger&);
|
||||
Logger& operator=(const Logger&);
|
||||
|
||||
const int log_level;
|
||||
std::ofstream ofstream;
|
||||
nullstream null_stream;
|
||||
std::ostream stream;
|
||||
};
|
||||
|
||||
#endif // LOGGER_INCLUDED
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
#include <xmpp/xmpp_component.hpp>
|
||||
#include <xmpp/biboumi_component.hpp>
|
||||
#include <utils/timed_events.hpp>
|
||||
#include <network/poller.hpp>
|
||||
#include <config/config.hpp>
|
||||
@@ -72,7 +72,7 @@ int main(int ac, char** av)
|
||||
return config_help("hostname");
|
||||
|
||||
auto p = std::make_shared<Poller>();
|
||||
auto xmpp_component = std::make_shared<XmppComponent>(p,
|
||||
auto xmpp_component = std::make_shared<BiboumiComponent>(p,
|
||||
hostname,
|
||||
password);
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
#include <config.h>
|
||||
#ifdef CARES_FOUND
|
||||
|
||||
#include <network/dns_socket_handler.hpp>
|
||||
#include <network/tcp_socket_handler.hpp>
|
||||
#include <network/dns_handler.hpp>
|
||||
#include <network/poller.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
DNSHandler DNSHandler::instance;
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent)
|
||||
{
|
||||
TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg);
|
||||
socket_handler->on_hostname4_resolved(status, hostent);
|
||||
}
|
||||
|
||||
void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent)
|
||||
{
|
||||
TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg);
|
||||
socket_handler->on_hostname6_resolved(status, hostent);
|
||||
}
|
||||
|
||||
DNSHandler::DNSHandler()
|
||||
{
|
||||
int ares_error;
|
||||
if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0)
|
||||
throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error));
|
||||
if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS)
|
||||
throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error));
|
||||
}
|
||||
|
||||
ares_channel& DNSHandler::get_channel()
|
||||
{
|
||||
return this->channel;
|
||||
}
|
||||
|
||||
void DNSHandler::destroy()
|
||||
{
|
||||
this->socket_handlers.clear();
|
||||
::ares_destroy(this->channel);
|
||||
::ares_library_cleanup();
|
||||
}
|
||||
|
||||
void DNSHandler::gethostbyname(const std::string& name,
|
||||
TCPSocketHandler* socket_handler, int family)
|
||||
{
|
||||
socket_handler->free_cares_addrinfo();
|
||||
if (family == AF_INET)
|
||||
::ares_gethostbyname(this->channel, name.data(), family,
|
||||
&::on_hostname4_resolved, socket_handler);
|
||||
else
|
||||
::ares_gethostbyname(this->channel, name.data(), family,
|
||||
&::on_hostname6_resolved, socket_handler);
|
||||
}
|
||||
|
||||
void DNSHandler::watch_dns_sockets(std::shared_ptr<Poller>& poller)
|
||||
{
|
||||
fd_set readers;
|
||||
fd_set writers;
|
||||
|
||||
FD_ZERO(&readers);
|
||||
FD_ZERO(&writers);
|
||||
|
||||
int ndfs = ::ares_fds(this->channel, &readers, &writers);
|
||||
// For each existing DNS socket, see if we are still supposed to watch it,
|
||||
// if not then erase it
|
||||
this->socket_handlers.erase(
|
||||
std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(),
|
||||
[&readers](const auto& dns_socket)
|
||||
{
|
||||
return !FD_ISSET(dns_socket->get_socket(), &readers);
|
||||
}),
|
||||
this->socket_handlers.end());
|
||||
|
||||
for (auto i = 0; i < ndfs; ++i)
|
||||
{
|
||||
bool read = FD_ISSET(i, &readers);
|
||||
bool write = FD_ISSET(i, &writers);
|
||||
// Look for the DNSSocketHandler with this fd
|
||||
auto it = std::find_if(this->socket_handlers.begin(),
|
||||
this->socket_handlers.end(),
|
||||
[i](const auto& socket_handler)
|
||||
{
|
||||
return i == socket_handler->get_socket();
|
||||
});
|
||||
if (!read && !write) // No need to read or write to it
|
||||
{ // If found, erase it and stop watching it because it is not
|
||||
// needed anymore
|
||||
if (it != this->socket_handlers.end())
|
||||
// The socket destructor removes it from the poller
|
||||
this->socket_handlers.erase(it);
|
||||
}
|
||||
else // We need to write and/or read to it
|
||||
{ // If not found, create it because we need to watch it
|
||||
if (it == this->socket_handlers.end())
|
||||
{
|
||||
this->socket_handlers.emplace_front(std::make_unique<DNSSocketHandler>(poller, i));
|
||||
it = this->socket_handlers.begin();
|
||||
}
|
||||
poller->add_socket_handler(it->get());
|
||||
if (write)
|
||||
poller->watch_send_events(it->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CARES_FOUND */
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef DNS_HANDLER_HPP_INCLUDED
|
||||
#define DNS_HANDLER_HPP_INCLUDED
|
||||
|
||||
#include <config.h>
|
||||
#ifdef CARES_FOUND
|
||||
|
||||
class TCPSocketHandler;
|
||||
class Poller;
|
||||
class DNSSocketHandler;
|
||||
|
||||
# include <ares.h>
|
||||
# include <memory>
|
||||
# include <string>
|
||||
# include <list>
|
||||
|
||||
void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent);
|
||||
void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent);
|
||||
|
||||
/**
|
||||
* Class managing DNS resolution. It should only be statically instanciated
|
||||
* once in SocketHandler. It manages ares channel and calls various
|
||||
* functions of that library.
|
||||
*/
|
||||
|
||||
class DNSHandler
|
||||
{
|
||||
public:
|
||||
DNSHandler();
|
||||
~DNSHandler() = default;
|
||||
void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler,
|
||||
int family);
|
||||
/**
|
||||
* Call ares_fds to know what fd needs to be watched by the poller, create
|
||||
* or destroy DNSSocketHandlers depending on the result.
|
||||
*/
|
||||
void watch_dns_sockets(std::shared_ptr<Poller>& poller);
|
||||
/**
|
||||
* Destroy and stop watching all the DNS sockets. Then de-init the channel
|
||||
* and library.
|
||||
*/
|
||||
void destroy();
|
||||
ares_channel& get_channel();
|
||||
|
||||
static DNSHandler instance;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The list of sockets that needs to be watched, according to the last
|
||||
* call to ares_fds. DNSSocketHandlers are added to it or removed from it
|
||||
* in the watch_dns_sockets() method
|
||||
*/
|
||||
std::list<std::unique_ptr<DNSSocketHandler>> socket_handlers;
|
||||
ares_channel channel;
|
||||
|
||||
DNSHandler(const DNSHandler&) = delete;
|
||||
DNSHandler(DNSHandler&&) = delete;
|
||||
DNSHandler& operator=(const DNSHandler&) = delete;
|
||||
DNSHandler& operator=(DNSHandler&&) = delete;
|
||||
};
|
||||
|
||||
#endif /* CARES_FOUND */
|
||||
#endif /* DNS_HANDLER_HPP_INCLUDED */
|
||||
@@ -1,45 +0,0 @@
|
||||
#include <config.h>
|
||||
#ifdef CARES_FOUND
|
||||
|
||||
#include <network/dns_socket_handler.hpp>
|
||||
#include <network/dns_handler.hpp>
|
||||
#include <network/poller.hpp>
|
||||
|
||||
#include <ares.h>
|
||||
|
||||
DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller> poller,
|
||||
const socket_t socket):
|
||||
SocketHandler(poller, socket)
|
||||
{
|
||||
}
|
||||
|
||||
DNSSocketHandler::~DNSSocketHandler()
|
||||
{
|
||||
}
|
||||
|
||||
void DNSSocketHandler::connect()
|
||||
{
|
||||
}
|
||||
|
||||
void DNSSocketHandler::on_recv()
|
||||
{
|
||||
// always stop watching send and read events. We will re-watch them if the
|
||||
// next call to ares_fds tell us to
|
||||
this->poller->remove_socket_handler(this->socket);
|
||||
::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD);
|
||||
}
|
||||
|
||||
void DNSSocketHandler::on_send()
|
||||
{
|
||||
// always stop watching send and read events. We will re-watch them if the
|
||||
// next call to ares_fds tell us to
|
||||
this->poller->remove_socket_handler(this->socket);
|
||||
::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket);
|
||||
}
|
||||
|
||||
bool DNSSocketHandler::is_connected() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* CARES_FOUND */
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef DNS_SOCKET_HANDLER_HPP
|
||||
# define DNS_SOCKET_HANDLER_HPP
|
||||
|
||||
#include <config.h>
|
||||
#ifdef CARES_FOUND
|
||||
|
||||
#include <network/socket_handler.hpp>
|
||||
#include <ares.h>
|
||||
|
||||
/**
|
||||
* Manage a socket returned by ares_fds. We do not create, open or close the
|
||||
* socket ourself: this is done by c-ares. We just call ares_process_fd()
|
||||
* with the correct parameters, depending on what can be done on that socket
|
||||
* (Poller reported it to be writable or readeable)
|
||||
*/
|
||||
|
||||
class DNSSocketHandler: public SocketHandler
|
||||
{
|
||||
public:
|
||||
explicit DNSSocketHandler(std::shared_ptr<Poller> poller, const socket_t socket);
|
||||
~DNSSocketHandler();
|
||||
/**
|
||||
* Just call dns_process_fd, c-ares will do its work of send()ing or
|
||||
* recv()ing the data it wants on that socket.
|
||||
*/
|
||||
void on_recv() override final;
|
||||
void on_send() override final;
|
||||
/**
|
||||
* Do nothing, because we are always considered to be connected, since the
|
||||
* connection is done by c-ares and not by us.
|
||||
*/
|
||||
void connect() override final;
|
||||
/**
|
||||
* Always true, see the comment for connect()
|
||||
*/
|
||||
bool is_connected() const override final;
|
||||
|
||||
private:
|
||||
DNSSocketHandler(const DNSSocketHandler&) = delete;
|
||||
DNSSocketHandler(DNSSocketHandler&&) = delete;
|
||||
DNSSocketHandler& operator=(const DNSSocketHandler&) = delete;
|
||||
DNSSocketHandler& operator=(DNSSocketHandler&&) = delete;
|
||||
};
|
||||
|
||||
#endif // CARES_FOUND
|
||||
#endif // DNS_SOCKET_HANDLER_HPP
|
||||
@@ -1,203 +0,0 @@
|
||||
#include <network/poller.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <utils/timed_events.hpp>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
Poller::Poller()
|
||||
{
|
||||
#if POLLER == POLL
|
||||
this->nfds = 0;
|
||||
#elif POLLER == EPOLL
|
||||
this->epfd = ::epoll_create1(0);
|
||||
if (this->epfd == -1)
|
||||
{
|
||||
log_error("epoll failed: " << strerror(errno));
|
||||
throw std::runtime_error("Could not create epoll instance");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Poller::~Poller()
|
||||
{
|
||||
}
|
||||
|
||||
void Poller::add_socket_handler(SocketHandler* socket_handler)
|
||||
{
|
||||
// Don't do anything if the socket is already managed
|
||||
const auto it = this->socket_handlers.find(socket_handler->get_socket());
|
||||
if (it != this->socket_handlers.end())
|
||||
return ;
|
||||
|
||||
this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler);
|
||||
|
||||
// We always watch all sockets for receive events
|
||||
#if POLLER == POLL
|
||||
this->fds[this->nfds].fd = socket_handler->get_socket();
|
||||
this->fds[this->nfds].events = POLLIN;
|
||||
this->nfds++;
|
||||
#endif
|
||||
#if POLLER == EPOLL
|
||||
struct epoll_event event = {EPOLLIN, {socket_handler}};
|
||||
const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event);
|
||||
if (res == -1)
|
||||
{
|
||||
log_error("epoll_ctl failed: " << strerror(errno));
|
||||
throw std::runtime_error("Could not add socket to epoll");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Poller::remove_socket_handler(const socket_t socket)
|
||||
{
|
||||
const auto it = this->socket_handlers.find(socket);
|
||||
if (it == this->socket_handlers.end())
|
||||
throw std::runtime_error("Trying to remove a SocketHandler that is not managed");
|
||||
this->socket_handlers.erase(it);
|
||||
|
||||
#if POLLER == POLL
|
||||
for (size_t i = 0; i < this->nfds; i++)
|
||||
{
|
||||
if (this->fds[i].fd == socket)
|
||||
{
|
||||
// Move all subsequent pollfd by one on the left, erasing the
|
||||
// value of the one we remove
|
||||
for (size_t j = i; j < this->nfds - 1; ++j)
|
||||
{
|
||||
this->fds[j].fd = this->fds[j+1].fd;
|
||||
this->fds[j].events= this->fds[j+1].events;
|
||||
}
|
||||
this->nfds--;
|
||||
}
|
||||
}
|
||||
#elif POLLER == EPOLL
|
||||
const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr);
|
||||
if (res == -1)
|
||||
{
|
||||
log_error("epoll_ctl failed: " << strerror(errno));
|
||||
throw std::runtime_error("Could not remove socket from epoll");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Poller::watch_send_events(SocketHandler* socket_handler)
|
||||
{
|
||||
#if POLLER == POLL
|
||||
for (size_t i = 0; i <= this->nfds; ++i)
|
||||
{
|
||||
if (this->fds[i].fd == socket_handler->get_socket())
|
||||
{
|
||||
this->fds[i].events = POLLIN|POLLOUT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot watch a non-registered socket for send events");
|
||||
#elif POLLER == EPOLL
|
||||
struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}};
|
||||
const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event);
|
||||
if (res == -1)
|
||||
{
|
||||
log_error("epoll_ctl failed: " << strerror(errno));
|
||||
throw std::runtime_error("Could not modify socket flags in epoll");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Poller::stop_watching_send_events(SocketHandler* socket_handler)
|
||||
{
|
||||
#if POLLER == POLL
|
||||
for (size_t i = 0; i <= this->nfds; ++i)
|
||||
{
|
||||
if (this->fds[i].fd == socket_handler->get_socket())
|
||||
{
|
||||
this->fds[i].events = POLLIN;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot watch a non-registered socket for send events");
|
||||
#elif POLLER == EPOLL
|
||||
struct epoll_event event = {EPOLLIN, {socket_handler}};
|
||||
const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event);
|
||||
if (res == -1)
|
||||
{
|
||||
log_error("epoll_ctl failed: " << strerror(errno));
|
||||
throw std::runtime_error("Could not modify socket flags in epoll");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int Poller::poll(const std::chrono::milliseconds& timeout)
|
||||
{
|
||||
if (this->socket_handlers.empty() && timeout == utils::no_timeout)
|
||||
return -1;
|
||||
#if POLLER == POLL
|
||||
int nb_events = ::poll(this->fds, this->nfds, timeout.count());
|
||||
if (nb_events < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
return true;
|
||||
log_error("poll failed: " << strerror(errno));
|
||||
throw std::runtime_error("Poll failed");
|
||||
}
|
||||
// We cannot possibly have more ready events than the number of fds we are
|
||||
// watching
|
||||
assert(static_cast<unsigned int>(nb_events) <= this->nfds);
|
||||
for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i)
|
||||
{
|
||||
if (this->fds[i].revents == 0)
|
||||
continue;
|
||||
else if (this->fds[i].revents & POLLIN)
|
||||
{
|
||||
auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
|
||||
socket_handler->on_recv();
|
||||
nb_events--;
|
||||
}
|
||||
else if (this->fds[i].revents & POLLOUT)
|
||||
{
|
||||
auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
|
||||
if (socket_handler->is_connected())
|
||||
socket_handler->on_send();
|
||||
else
|
||||
socket_handler->connect();
|
||||
nb_events--;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
#elif POLLER == EPOLL
|
||||
static const size_t max_events = 12;
|
||||
struct epoll_event revents[max_events];
|
||||
const int nb_events = ::epoll_wait(this->epfd, revents, max_events, timeout.count());
|
||||
if (nb_events == -1)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
return 0;
|
||||
log_error("epoll wait: " << strerror(errno));
|
||||
throw std::runtime_error("Epoll_wait failed");
|
||||
}
|
||||
for (int i = 0; i < nb_events; ++i)
|
||||
{
|
||||
auto socket_handler = static_cast<SocketHandler*>(revents[i].data.ptr);
|
||||
if (revents[i].events & EPOLLIN)
|
||||
socket_handler->on_recv();
|
||||
else if (revents[i].events & EPOLLOUT)
|
||||
{
|
||||
if (socket_handler->is_connected())
|
||||
socket_handler->on_send();
|
||||
else
|
||||
socket_handler->connect();
|
||||
}
|
||||
}
|
||||
return nb_events;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t Poller::size() const
|
||||
{
|
||||
return this->socket_handlers.size();
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#ifndef POLLER_INCLUDED
|
||||
# define POLLER_INCLUDED
|
||||
|
||||
#include <network/socket_handler.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#define POLL 1
|
||||
#define EPOLL 2
|
||||
#define KQUEUE 3
|
||||
#include <config.h>
|
||||
#ifndef POLLER
|
||||
#define POLLER POLL
|
||||
#endif
|
||||
|
||||
#if POLLER == POLL
|
||||
#include <poll.h>
|
||||
#define MAX_POLL_FD_NUMBER 4096
|
||||
#elif POLLER == EPOLL
|
||||
#include <sys/epoll.h>
|
||||
#else
|
||||
#error Invalid POLLER value
|
||||
#endif
|
||||
|
||||
/**
|
||||
* We pass some SocketHandlers to this Poller, which uses
|
||||
* poll/epoll/kqueue/select etc to wait for events on these SocketHandlers,
|
||||
* and call the callbacks when event occurs.
|
||||
*
|
||||
* TODO: support these pollers:
|
||||
* - kqueue(2)
|
||||
*/
|
||||
|
||||
class Poller
|
||||
{
|
||||
public:
|
||||
explicit Poller();
|
||||
~Poller();
|
||||
/**
|
||||
* Add a SocketHandler to be monitored by this Poller. All receive events
|
||||
* are always automatically watched.
|
||||
*/
|
||||
void add_socket_handler(SocketHandler* socket_handler);
|
||||
/**
|
||||
* Remove (and stop managing) a SocketHandler, designated by the given socket_t.
|
||||
*/
|
||||
void remove_socket_handler(const socket_t socket);
|
||||
/**
|
||||
* Signal the poller that he needs to watch for send events for the given
|
||||
* SocketHandler.
|
||||
*/
|
||||
void watch_send_events(SocketHandler* socket_handler);
|
||||
/**
|
||||
* Signal the poller that he needs to stop watching for send events for
|
||||
* this SocketHandler.
|
||||
*/
|
||||
void stop_watching_send_events(SocketHandler* socket_handler);
|
||||
/**
|
||||
* Wait for all watched events, and call the SocketHandlers' callbacks
|
||||
* when one is ready. Returns if nothing happened before the provided
|
||||
* timeout. If the timeout is 0, it waits forever. If there is no
|
||||
* watched event, returns -1 immediately, ignoring the timeout value.
|
||||
* Otherwise, returns the number of event handled. If 0 is returned this
|
||||
* means that we were interrupted by a signal, or the timeout occured.
|
||||
*/
|
||||
int poll(const std::chrono::milliseconds& timeout);
|
||||
/**
|
||||
* Returns the number of SocketHandlers managed by the poller.
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* A "list" of all the SocketHandlers that we manage, indexed by socket,
|
||||
* because that's what is returned by select/poll/etc when an event
|
||||
* occures.
|
||||
*/
|
||||
std::unordered_map<socket_t, SocketHandler*> socket_handlers;
|
||||
|
||||
#if POLLER == POLL
|
||||
struct pollfd fds[MAX_POLL_FD_NUMBER];
|
||||
nfds_t nfds;
|
||||
#elif POLLER == EPOLL
|
||||
int epfd;
|
||||
#endif
|
||||
|
||||
Poller(const Poller&) = delete;
|
||||
Poller(Poller&&) = delete;
|
||||
Poller& operator=(const Poller&) = delete;
|
||||
Poller& operator=(Poller&&) = delete;
|
||||
};
|
||||
|
||||
#endif // POLLER_INCLUDED
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef SOCKET_HANDLER_HPP
|
||||
# define SOCKET_HANDLER_HPP
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
|
||||
class Poller;
|
||||
|
||||
typedef int socket_t;
|
||||
|
||||
class SocketHandler
|
||||
{
|
||||
public:
|
||||
explicit SocketHandler(std::shared_ptr<Poller> poller, const socket_t socket):
|
||||
poller(poller),
|
||||
socket(socket)
|
||||
{}
|
||||
virtual ~SocketHandler() {}
|
||||
virtual void on_recv() = 0;
|
||||
virtual void on_send() = 0;
|
||||
virtual void connect() = 0;
|
||||
virtual bool is_connected() const = 0;
|
||||
|
||||
socket_t get_socket() const
|
||||
{ return this->socket; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* A pointer to the poller that manages us, because we need to communicate
|
||||
* with it.
|
||||
*/
|
||||
std::shared_ptr<Poller> poller;
|
||||
/**
|
||||
* The handled socket.
|
||||
*/
|
||||
socket_t socket;
|
||||
|
||||
private:
|
||||
SocketHandler(const SocketHandler&) = delete;
|
||||
SocketHandler(SocketHandler&&) = delete;
|
||||
SocketHandler& operator=(const SocketHandler&) = delete;
|
||||
SocketHandler& operator=(SocketHandler&&) = delete;
|
||||
};
|
||||
|
||||
#endif // SOCKET_HANDLER_HPP
|
||||
@@ -1,572 +0,0 @@
|
||||
#include <network/tcp_socket_handler.hpp>
|
||||
#include <network/dns_handler.hpp>
|
||||
|
||||
#include <utils/timed_events.hpp>
|
||||
#include <utils/scopeguard.hpp>
|
||||
#include <network/poller.hpp>
|
||||
|
||||
#include <logger/logger.hpp>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifdef BOTAN_FOUND
|
||||
# include <botan/hex.h>
|
||||
|
||||
Botan::AutoSeeded_RNG TCPSocketHandler::rng;
|
||||
Permissive_Credentials_Manager TCPSocketHandler::credential_manager;
|
||||
Botan::TLS::Policy TCPSocketHandler::policy;
|
||||
Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng);
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef UIO_FASTIOV
|
||||
# define UIO_FASTIOV 8
|
||||
#endif
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
|
||||
SocketHandler(poller, -1),
|
||||
use_tls(false),
|
||||
connected(false),
|
||||
connecting(false)
|
||||
#ifdef CARES_FOUND
|
||||
,resolved(false),
|
||||
resolved4(false),
|
||||
resolved6(false),
|
||||
cares_addrinfo(nullptr),
|
||||
cares_error()
|
||||
#endif
|
||||
{}
|
||||
|
||||
TCPSocketHandler::~TCPSocketHandler()
|
||||
{
|
||||
#ifdef CARES_FOUND
|
||||
this->free_cares_addrinfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TCPSocketHandler::init_socket(const struct addrinfo* rp)
|
||||
{
|
||||
if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
|
||||
throw std::runtime_error("Could not create socket: "s + strerror(errno));
|
||||
int optval = 1;
|
||||
if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1)
|
||||
log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno));
|
||||
// Set the socket on non-blocking mode. This is useful to receive a EAGAIN
|
||||
// error when connect() would block, to not block the whole process if a
|
||||
// remote is not responsive.
|
||||
const int existing_flags = ::fcntl(this->socket, F_GETFL, 0);
|
||||
if ((existing_flags == -1) ||
|
||||
(::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1))
|
||||
throw std::runtime_error("Could not initialize socket: "s + strerror(errno));
|
||||
}
|
||||
|
||||
void TCPSocketHandler::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;
|
||||
|
||||
struct addrinfo* addr_res;
|
||||
|
||||
if (!this->connecting)
|
||||
{
|
||||
// Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if
|
||||
// this is the first call of this function.
|
||||
#ifdef CARES_FOUND
|
||||
if (!this->resolved)
|
||||
{
|
||||
log_info("Trying to connect to " << address << ":" << port);
|
||||
// Start the asynchronous process of resolving the hostname. Once
|
||||
// the addresses have been found and `resolved` has been set to true
|
||||
// (but connecting will still be false), TCPSocketHandler::connect()
|
||||
// needs to be called, again.
|
||||
DNSHandler::instance.gethostbyname(address, this, AF_INET6);
|
||||
DNSHandler::instance.gethostbyname(address, this, AF_INET);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The c-ares resolved the hostname and the available addresses
|
||||
// where saved in the cares_addrinfo linked list. Now, just use
|
||||
// this list to try to connect.
|
||||
addr_res = this->cares_addrinfo;
|
||||
if (!addr_res)
|
||||
{
|
||||
this->close();
|
||||
this->on_connection_failed(this->cares_error);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
#else
|
||||
log_info("Trying to connect to " << address << ":" << port);
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_flags = 0;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
|
||||
const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
log_warning("getaddrinfo failed: "s + gai_strerror(res));
|
||||
this->close();
|
||||
this->on_connection_failed(gai_strerror(res));
|
||||
return ;
|
||||
}
|
||||
// Make sure the alloced structure is always freed at the end of the
|
||||
// function
|
||||
sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); });
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{ // This function is called again, use the saved addrinfo structure,
|
||||
// instead of re-doing the whole getaddrinfo process.
|
||||
addr_res = &this->addrinfo;
|
||||
}
|
||||
|
||||
for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next)
|
||||
{
|
||||
if (!this->connecting)
|
||||
{
|
||||
try {
|
||||
this->init_socket(rp);
|
||||
}
|
||||
catch (const std::runtime_error& error) {
|
||||
log_error("Failed to init socket: " << error.what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0
|
||||
|| errno == EISCONN)
|
||||
{
|
||||
log_info("Connection success.");
|
||||
TimedEventsManager::instance().cancel("connection_timeout"s +
|
||||
std::to_string(this->socket));
|
||||
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 ;
|
||||
}
|
||||
else if (errno == EINPROGRESS || errno == EALREADY)
|
||||
{ // retry this process later, when the socket
|
||||
// is ready to be written on.
|
||||
this->connecting = true;
|
||||
this->poller->add_socket_handler(this);
|
||||
this->poller->watch_send_events(this);
|
||||
// Save the addrinfo structure, to use it on the next call
|
||||
this->ai_addrlen = rp->ai_addrlen;
|
||||
memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen);
|
||||
memcpy(&this->addrinfo, rp, sizeof(struct addrinfo));
|
||||
this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr);
|
||||
this->addrinfo.ai_next = nullptr;
|
||||
// If the connection has not succeeded or failed in 5s, we consider
|
||||
// it to have failed
|
||||
TimedEventsManager::instance().add_event(
|
||||
TimedEvent(std::chrono::steady_clock::now() + 5s,
|
||||
std::bind(&TCPSocketHandler::on_connection_timeout, this),
|
||||
"connection_timeout"s + std::to_string(this->socket)));
|
||||
return ;
|
||||
}
|
||||
log_info("Connection failed:" << strerror(errno));
|
||||
}
|
||||
log_error("All connection attempts failed.");
|
||||
this->close();
|
||||
this->on_connection_failed(strerror(errno));
|
||||
return ;
|
||||
}
|
||||
|
||||
void TCPSocketHandler::on_connection_timeout()
|
||||
{
|
||||
this->close();
|
||||
this->on_connection_failed("connection timed out");
|
||||
}
|
||||
|
||||
void TCPSocketHandler::connect()
|
||||
{
|
||||
this->connect(this->address, this->port, this->use_tls);
|
||||
}
|
||||
|
||||
void TCPSocketHandler::on_recv()
|
||||
{
|
||||
#ifdef BOTAN_FOUND
|
||||
if (this->use_tls)
|
||||
this->tls_recv();
|
||||
else
|
||||
#endif
|
||||
this->plain_recv();
|
||||
}
|
||||
|
||||
void TCPSocketHandler::plain_recv()
|
||||
{
|
||||
static constexpr size_t buf_size = 4096;
|
||||
char buf[buf_size];
|
||||
void* recv_buf = this->get_receive_buffer(buf_size);
|
||||
|
||||
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 TCPSocketHandler::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));
|
||||
// Remember if we were connecting, or already connected when this
|
||||
// happened, because close() sets this->connecting to false
|
||||
const auto were_connecting = this->connecting;
|
||||
this->close();
|
||||
if (were_connecting)
|
||||
this->on_connection_failed(strerror(errno));
|
||||
else
|
||||
this->on_connection_close(strerror(errno));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void TCPSocketHandler::on_send()
|
||||
{
|
||||
struct iovec msg_iov[UIO_FASTIOV] = {};
|
||||
struct msghdr msg{nullptr, 0,
|
||||
msg_iov,
|
||||
0, nullptr, 0, 0};
|
||||
for (std::string& s: this->out_buf)
|
||||
{
|
||||
// unconsting the content of s is ok, sendmsg will never modify it
|
||||
msg_iov[msg.msg_iovlen].iov_base = const_cast<char*>(s.data());
|
||||
msg_iov[msg.msg_iovlen].iov_len = s.size();
|
||||
if (++msg.msg_iovlen == UIO_FASTIOV)
|
||||
break;
|
||||
}
|
||||
ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL);
|
||||
if (res < 0)
|
||||
{
|
||||
log_error("sendmsg failed: " << strerror(errno));
|
||||
this->on_connection_close(strerror(errno));
|
||||
this->close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove all the strings that were successfully sent.
|
||||
for (auto it = this->out_buf.begin();
|
||||
it != this->out_buf.end();)
|
||||
{
|
||||
if (static_cast<size_t>(res) >= (*it).size())
|
||||
{
|
||||
res -= (*it).size();
|
||||
it = this->out_buf.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If one string has partially been sent, we use substr to
|
||||
// crop it
|
||||
if (res > 0)
|
||||
(*it) = (*it).substr(res, std::string::npos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->out_buf.empty())
|
||||
this->poller->stop_watching_send_events(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TCPSocketHandler::close()
|
||||
{
|
||||
TimedEventsManager::instance().cancel("connection_timeout"s +
|
||||
std::to_string(this->socket));
|
||||
if (this->connected || this->connecting)
|
||||
this->poller->remove_socket_handler(this->get_socket());
|
||||
if (this->socket != -1)
|
||||
{
|
||||
::close(this->socket);
|
||||
this->socket = -1;
|
||||
}
|
||||
this->connected = false;
|
||||
this->connecting = false;
|
||||
this->in_buf.clear();
|
||||
this->out_buf.clear();
|
||||
this->port.clear();
|
||||
}
|
||||
|
||||
void TCPSocketHandler::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 TCPSocketHandler::raw_send(std::string&& data)
|
||||
{
|
||||
if (data.empty())
|
||||
return ;
|
||||
this->out_buf.emplace_back(std::move(data));
|
||||
if (this->connected)
|
||||
this->poller->watch_send_events(this);
|
||||
}
|
||||
|
||||
void TCPSocketHandler::send_pending_data()
|
||||
{
|
||||
if (this->connected && !this->out_buf.empty())
|
||||
this->poller->watch_send_events(this);
|
||||
}
|
||||
|
||||
bool TCPSocketHandler::is_connected() const
|
||||
{
|
||||
return this->connected;
|
||||
}
|
||||
|
||||
bool TCPSocketHandler::is_connecting() const
|
||||
{
|
||||
#ifdef CARES_FOUND
|
||||
return this->connecting || !this->resolved;
|
||||
#else
|
||||
return this->connecting;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* TCPSocketHandler::get_receive_buffer(const size_t) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef BOTAN_FOUND
|
||||
void TCPSocketHandler::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(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2),
|
||||
std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2),
|
||||
std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3),
|
||||
std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1),
|
||||
session_manager, credential_manager, policy,
|
||||
rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version());
|
||||
}
|
||||
|
||||
void TCPSocketHandler::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 TCPSocketHandler::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 TCPSocketHandler::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 TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size)
|
||||
{
|
||||
this->raw_send(std::string(reinterpret_cast<const char*>(data), size));
|
||||
}
|
||||
|
||||
void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t)
|
||||
{
|
||||
log_debug("tls_alert: " << alert.type_string());
|
||||
}
|
||||
|
||||
bool TCPSocketHandler::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 TCPSocketHandler::on_tls_activated()
|
||||
{
|
||||
this->send_data("");
|
||||
}
|
||||
|
||||
#endif // BOTAN_FOUND
|
||||
|
||||
#ifdef CARES_FOUND
|
||||
|
||||
void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent)
|
||||
{
|
||||
this->resolved4 = true;
|
||||
if (status == ARES_SUCCESS)
|
||||
this->fill_ares_addrinfo4(hostent);
|
||||
else
|
||||
this->cares_error = ::ares_strerror(status);
|
||||
|
||||
if (this->resolved4 && this->resolved6)
|
||||
{
|
||||
this->resolved = true;
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent)
|
||||
{
|
||||
this->resolved6 = true;
|
||||
if (status == ARES_SUCCESS)
|
||||
this->fill_ares_addrinfo6(hostent);
|
||||
else
|
||||
this->cares_error = ::ares_strerror(status);
|
||||
|
||||
if (this->resolved4 && this->resolved6)
|
||||
{
|
||||
this->resolved = true;
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent)
|
||||
{
|
||||
struct addrinfo* prev = this->cares_addrinfo;
|
||||
struct in_addr** address = reinterpret_cast<struct in_addr**>(hostent->h_addr_list);
|
||||
|
||||
while (*address)
|
||||
{
|
||||
// Create a new addrinfo list element, and fill it
|
||||
struct addrinfo* current = new struct addrinfo;
|
||||
current->ai_flags = 0;
|
||||
current->ai_family = hostent->h_addrtype;
|
||||
current->ai_socktype = SOCK_STREAM;
|
||||
current->ai_protocol = 0;
|
||||
current->ai_addrlen = sizeof(struct sockaddr_in);
|
||||
|
||||
struct sockaddr_in* addr = new struct sockaddr_in;
|
||||
addr->sin_family = hostent->h_addrtype;
|
||||
addr->sin_port = htons(strtoul(this->port.data(), nullptr, 10));
|
||||
addr->sin_addr.s_addr = (*address)->s_addr;
|
||||
|
||||
current->ai_addr = reinterpret_cast<struct sockaddr*>(addr);
|
||||
current->ai_next = nullptr;
|
||||
current->ai_canonname = nullptr;
|
||||
|
||||
current->ai_next = prev;
|
||||
this->cares_addrinfo = current;
|
||||
prev = current;
|
||||
++address;
|
||||
}
|
||||
}
|
||||
|
||||
void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent)
|
||||
{
|
||||
struct addrinfo* prev = this->cares_addrinfo;
|
||||
struct in6_addr** address = reinterpret_cast<struct in6_addr**>(hostent->h_addr_list);
|
||||
|
||||
while (*address)
|
||||
{
|
||||
// Create a new addrinfo list element, and fill it
|
||||
struct addrinfo* current = new struct addrinfo;
|
||||
current->ai_flags = 0;
|
||||
current->ai_family = hostent->h_addrtype;
|
||||
current->ai_socktype = SOCK_STREAM;
|
||||
current->ai_protocol = 0;
|
||||
current->ai_addrlen = sizeof(struct sockaddr_in6);
|
||||
|
||||
struct sockaddr_in6* addr = new struct sockaddr_in6;
|
||||
addr->sin6_family = hostent->h_addrtype;
|
||||
addr->sin6_port = htons(strtoul(this->port.data(), nullptr, 10));
|
||||
::memcpy(addr->sin6_addr.s6_addr, (*address)->s6_addr, 16);
|
||||
addr->sin6_flowinfo = 0;
|
||||
addr->sin6_scope_id = 0;
|
||||
|
||||
current->ai_addr = reinterpret_cast<struct sockaddr*>(addr);
|
||||
current->ai_next = nullptr;
|
||||
current->ai_canonname = nullptr;
|
||||
|
||||
current->ai_next = prev;
|
||||
this->cares_addrinfo = current;
|
||||
prev = current;
|
||||
++address;
|
||||
}
|
||||
}
|
||||
|
||||
void TCPSocketHandler::free_cares_addrinfo()
|
||||
{
|
||||
while (this->cares_addrinfo)
|
||||
{
|
||||
delete this->cares_addrinfo->ai_addr;
|
||||
auto next = this->cares_addrinfo->ai_next;
|
||||
delete this->cares_addrinfo;
|
||||
this->cares_addrinfo = next;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CARES_FOUND
|
||||
@@ -1,297 +0,0 @@
|
||||
#ifndef SOCKET_HANDLER_INCLUDED
|
||||
# define SOCKET_HANDLER_INCLUDED
|
||||
|
||||
#include <network/socket_handler.hpp>
|
||||
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef CARES_FOUND
|
||||
# include <ares.h>
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* An interface, with a series of callbacks that should be implemented in
|
||||
* subclasses that deal with a socket. These callbacks are called on various events
|
||||
* (read/write/timeout, etc) when they are notified to a poller
|
||||
* (select/poll/epoll etc)
|
||||
*/
|
||||
class TCPSocketHandler: public SocketHandler
|
||||
{
|
||||
protected:
|
||||
~TCPSocketHandler();
|
||||
|
||||
public:
|
||||
explicit TCPSocketHandler(std::shared_ptr<Poller> poller);
|
||||
/**
|
||||
* 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, const bool tls);
|
||||
void connect() override final;
|
||||
/**
|
||||
* 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() override final;
|
||||
/**
|
||||
* Write as much data from out_buf as possible, in the socket.
|
||||
*/
|
||||
void on_send() override final;
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Watch the socket for send events, if our out buffer is not empty.
|
||||
*/
|
||||
void send_pending_data();
|
||||
/**
|
||||
* Close the connection, remove us from the poller
|
||||
*/
|
||||
void close();
|
||||
/**
|
||||
* Called by a TimedEvent, when the connection did not succeed or fail
|
||||
* after a given time.
|
||||
*/
|
||||
void on_connection_timeout();
|
||||
/**
|
||||
* Called when the connection is successful.
|
||||
*/
|
||||
virtual void on_connected() = 0;
|
||||
/**
|
||||
* Called when the connection fails. Not when it is closed later, just at
|
||||
* the connect() call.
|
||||
*/
|
||||
virtual void on_connection_failed(const std::string& reason) = 0;
|
||||
/**
|
||||
* Called when we detect a disconnection from the remote host.
|
||||
*/
|
||||
virtual void on_connection_close(const std::string& error) = 0;
|
||||
/**
|
||||
* Handle/consume (some of) the data received so far. The data to handle
|
||||
* may be in the in_buf buffer, or somewhere else, depending on what
|
||||
* get_receive_buffer() returned. If some data is used from in_buf, it
|
||||
* should be truncated, only the unused data should be left untouched.
|
||||
*
|
||||
* The size argument is the size of the last chunk of data that was added to the buffer.
|
||||
*/
|
||||
virtual void parse_in_buffer(const size_t size) = 0;
|
||||
bool is_connected() const override final;
|
||||
bool is_connecting() const;
|
||||
|
||||
#ifdef CARES_FOUND
|
||||
void on_hostname4_resolved(int status, struct hostent* hostent);
|
||||
void on_hostname6_resolved(int status, struct hostent* hostent);
|
||||
|
||||
void free_cares_addrinfo();
|
||||
|
||||
void fill_ares_addrinfo4(const struct hostent* hostent);
|
||||
void fill_ares_addrinfo6(const struct hostent* hostent);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the socket with the parameters contained in the given
|
||||
* addrinfo structure.
|
||||
*/
|
||||
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
|
||||
/**
|
||||
* 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 that triggered a EINPROGRESS error when
|
||||
* connect()ing to it, to reuse it directly when connect() is called
|
||||
* again.
|
||||
*/
|
||||
struct addrinfo addrinfo;
|
||||
struct sockaddr_in6 ai_addr;
|
||||
socklen_t ai_addrlen;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Port we are connected/connecting to
|
||||
*/
|
||||
std::string port;
|
||||
|
||||
bool connected;
|
||||
bool connecting;
|
||||
|
||||
#ifdef CARES_FOUND
|
||||
/**
|
||||
* Whether or not the DNS resolution was successfully done
|
||||
*/
|
||||
bool resolved;
|
||||
bool resolved4;
|
||||
bool resolved6;
|
||||
/**
|
||||
* When using c-ares to resolve the host asynchronously, we need the
|
||||
* c-ares callback to fill a structure (a struct addrinfo, for
|
||||
* compatibility with getaddrinfo and the rest of the code that works when
|
||||
* c-ares is not used) with all returned values (for example an IPv6 and
|
||||
* an IPv4). The next call of connect() will then try all these values
|
||||
* (exactly like we do with the result of getaddrinfo) and save the one
|
||||
* that worked (or returned EINPROGRESS) in the other struct addrinfo (see
|
||||
* the members addrinfo, ai_addrlen, and ai_addr).
|
||||
*/
|
||||
struct addrinfo* cares_addrinfo;
|
||||
std::string cares_error;
|
||||
#endif // CARES_FOUND
|
||||
|
||||
private:
|
||||
TCPSocketHandler(const TCPSocketHandler&) = delete;
|
||||
TCPSocketHandler(TCPSocketHandler&&) = delete;
|
||||
TCPSocketHandler& operator=(const TCPSocketHandler&) = delete;
|
||||
TCPSocketHandler& operator=(TCPSocketHandler&&) = delete;
|
||||
|
||||
#ifdef BOTAN_FOUND
|
||||
/**
|
||||
* Botan stuff to manipulate a TLS session.
|
||||
*/
|
||||
static Botan::AutoSeeded_RNG rng;
|
||||
static Permissive_Credentials_Manager credential_manager;
|
||||
static Botan::TLS::Policy policy;
|
||||
static 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
|
||||
@@ -1,221 +0,0 @@
|
||||
#include <utils/encoding.hpp>
|
||||
|
||||
#include <utils/scopeguard.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <iconv.h>
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <bitset>
|
||||
|
||||
/**
|
||||
* The UTF-8-encoded character used as a place holder when a character conversion fails.
|
||||
* This is U+FFFD � "replacement character"
|
||||
*/
|
||||
static const char* invalid_char = "\xef\xbf\xbd";
|
||||
static const size_t invalid_char_len = 3;
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/**
|
||||
* Based on http://en.wikipedia.org/wiki/UTF-8#Description
|
||||
*/
|
||||
bool is_valid_utf8(const char* s)
|
||||
{
|
||||
if (!s)
|
||||
return false;
|
||||
|
||||
const unsigned char* str = reinterpret_cast<const unsigned char*>(s);
|
||||
|
||||
while (*str)
|
||||
{
|
||||
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if ((str[0] & 0b11111000) == 0b11110000)
|
||||
{
|
||||
if (!str[1] || !str[2] || !str[3]
|
||||
|| ((str[1] & 0b11000000) != 0b10000000)
|
||||
|| ((str[2] & 0b11000000) != 0b10000000)
|
||||
|| ((str[3] & 0b11000000) != 0b10000000))
|
||||
return false;
|
||||
str += 4;
|
||||
}
|
||||
// 3 bytes: 1110xxx 10xxxxxx 10xxxxxx
|
||||
else if ((str[0] & 0b11110000) == 0b11100000)
|
||||
{
|
||||
if (!str[1] || !str[2]
|
||||
|| ((str[1] & 0b11000000) != 0b10000000)
|
||||
|| ((str[2] & 0b11000000) != 0b10000000))
|
||||
return false;
|
||||
str += 3;
|
||||
}
|
||||
// 2 bytes: 110xxxxx 10xxxxxx
|
||||
else if (((str[0]) & 0b11100000) == 0b11000000)
|
||||
{
|
||||
if (!str[1] ||
|
||||
((str[1] & 0b11000000) != 0b10000000))
|
||||
return false;
|
||||
str += 2;
|
||||
}
|
||||
// 1 byte: 0xxxxxxx
|
||||
else if ((str[0] & 0b10000000) != 0)
|
||||
return false;
|
||||
else
|
||||
str++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string remove_invalid_xml_chars(const std::string& original)
|
||||
{
|
||||
// The given string MUST be a valid utf-8 string
|
||||
unsigned char* res = new unsigned char[original.size()];
|
||||
ScopeGuard sg([&res]() { delete[] res;});
|
||||
|
||||
// pointer where we write valid chars
|
||||
unsigned char* r = res;
|
||||
|
||||
const unsigned char* str = reinterpret_cast<const unsigned char*>(original.c_str());
|
||||
std::bitset<20> codepoint;
|
||||
|
||||
while (*str)
|
||||
{
|
||||
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if ((str[0] & 0b11111000) == 0b11110000)
|
||||
{
|
||||
codepoint = ((str[0] & 0b00000111) << 18);
|
||||
codepoint |= ((str[1] & 0b00111111) << 12);
|
||||
codepoint |= ((str[2] & 0b00111111) << 6 );
|
||||
codepoint |= ((str[3] & 0b00111111) << 0 );
|
||||
if (codepoint.to_ulong() <= 0x10FFFF)
|
||||
{
|
||||
::memcpy(r, str, 4);
|
||||
r += 4;
|
||||
}
|
||||
str += 4;
|
||||
}
|
||||
// 3 bytes: 1110xxx 10xxxxxx 10xxxxxx
|
||||
else if ((str[0] & 0b11110000) == 0b11100000)
|
||||
{
|
||||
codepoint = ((str[0] & 0b00001111) << 12);
|
||||
codepoint |= ((str[1] & 0b00111111) << 6);
|
||||
codepoint |= ((str[2] & 0b00111111) << 0 );
|
||||
if (codepoint.to_ulong() <= 0xD7FF ||
|
||||
(codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD))
|
||||
{
|
||||
::memcpy(r, str, 3);
|
||||
r += 3;
|
||||
}
|
||||
str += 3;
|
||||
}
|
||||
// 2 bytes: 110xxxxx 10xxxxxx
|
||||
else if (((str[0]) & 0b11100000) == 0b11000000)
|
||||
{
|
||||
// All 2 bytes char are valid, don't even bother calculating
|
||||
// the codepoint
|
||||
::memcpy(r, str, 2);
|
||||
r += 2;
|
||||
str += 2;
|
||||
}
|
||||
// 1 byte: 0xxxxxxx
|
||||
else if ((str[0] & 0b10000000) == 0)
|
||||
{
|
||||
codepoint = ((str[0] & 0b01111111));
|
||||
if (codepoint.to_ulong() == 0x09 ||
|
||||
codepoint.to_ulong() == 0x0A ||
|
||||
codepoint.to_ulong() == 0x0D ||
|
||||
codepoint.to_ulong() >= 0x20)
|
||||
{
|
||||
::memcpy(r, str, 1);
|
||||
r += 1;
|
||||
}
|
||||
str += 1;
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars");
|
||||
}
|
||||
return std::string(reinterpret_cast<char*>(res), r-res);
|
||||
}
|
||||
|
||||
std::string convert_to_utf8(const std::string& str, const char* charset)
|
||||
{
|
||||
std::string res;
|
||||
|
||||
const iconv_t cd = iconv_open("UTF-8", charset);
|
||||
if (cd == (iconv_t)-1)
|
||||
throw std::runtime_error("Cannot convert into UTF-8");
|
||||
|
||||
// Make sure cd is always closed when we leave this function
|
||||
ScopeGuard sg([&]{ iconv_close(cd); });
|
||||
|
||||
size_t inbytesleft = str.size();
|
||||
|
||||
// iconv will not attempt to modify this buffer, but some plateform
|
||||
// require a char** anyway
|
||||
#ifdef ICONV_SECOND_ARGUMENT_IS_CONST
|
||||
const char* inbuf_ptr = str.c_str();
|
||||
#else
|
||||
char* inbuf_ptr = const_cast<char*>(str.c_str());
|
||||
#endif
|
||||
|
||||
size_t outbytesleft = str.size() * 4;
|
||||
char* outbuf = new char[outbytesleft];
|
||||
char* outbuf_ptr = outbuf;
|
||||
|
||||
// Make sure outbuf is always deleted when we leave this function
|
||||
sg.add_callback([&]{ delete[] outbuf; });
|
||||
|
||||
bool done = false;
|
||||
while (done == false)
|
||||
{
|
||||
size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft);
|
||||
if ((size_t)-1 == error)
|
||||
{
|
||||
switch (errno)
|
||||
{
|
||||
case EILSEQ:
|
||||
// Invalid byte found. Insert a placeholder instead of the
|
||||
// converted character, jump one byte and continue
|
||||
memcpy(outbuf_ptr, invalid_char, invalid_char_len);
|
||||
outbuf_ptr += invalid_char_len;
|
||||
inbytesleft--;
|
||||
inbuf_ptr++;
|
||||
break;
|
||||
case EINVAL:
|
||||
// A multibyte sequence is not terminated, but we can't
|
||||
// provide any more data, so we just add a placeholder to
|
||||
// indicate that the character is not properly converted,
|
||||
// and we stop the conversion
|
||||
memcpy(outbuf_ptr, invalid_char, invalid_char_len);
|
||||
outbuf_ptr += invalid_char_len;
|
||||
outbuf_ptr++;
|
||||
done = true;
|
||||
break;
|
||||
case E2BIG:
|
||||
// This should never happen
|
||||
done = true;
|
||||
break;
|
||||
default:
|
||||
// This should happen even neverer
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The conversion finished without any error, stop converting
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
// Terminate the converted buffer, and copy that buffer it into the
|
||||
// string we return
|
||||
*outbuf_ptr = '\0';
|
||||
res = outbuf;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef ENCODING_INCLUDED
|
||||
# define ENCODING_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/**
|
||||
* Returns true if the given null-terminated string is valid utf-8.
|
||||
*
|
||||
* Based on http://en.wikipedia.org/wiki/UTF-8#Description
|
||||
*/
|
||||
bool is_valid_utf8(const char* s);
|
||||
/**
|
||||
* Remove all invalid codepoints from the given utf-8-encoded string.
|
||||
* The value returned is a copy of the string, without the removed chars.
|
||||
*
|
||||
* See http://www.w3.org/TR/xml/#charsets for the list of valid characters
|
||||
* in XML.
|
||||
*/
|
||||
std::string remove_invalid_xml_chars(const std::string& original);
|
||||
/**
|
||||
* Convert the given string (encoded is "encoding") into valid utf-8.
|
||||
* If some decoding fails, insert an utf-8 placeholder character instead.
|
||||
*/
|
||||
std::string convert_to_utf8(const std::string& str, const char* encoding);
|
||||
}
|
||||
|
||||
#endif // ENCODING_INCLUDED
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <config/config.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
void reload_process()
|
||||
{
|
||||
// Closing the config will just force it to be reopened the next time
|
||||
// a configuration option is needed
|
||||
Config::close();
|
||||
// Destroy the logger instance, to be recreated the next time a log
|
||||
// line needs to be written
|
||||
Logger::instance().reset();
|
||||
log_debug("Configuration and logger reloaded.");
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef RELOAD_HPP_INCLUDED
|
||||
#define RELOAD_HPP_INCLUDED
|
||||
|
||||
/**
|
||||
* Reload the server's configuration, and close the logger (so that it
|
||||
* closes its files etc, to take into account the new configuration)
|
||||
*/
|
||||
void reload_process();
|
||||
|
||||
#endif /* RELOAD_HPP_INCLUDED */
|
||||
@@ -1,9 +0,0 @@
|
||||
#include <utils/revstr.hpp>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string revstr(const std::string& original)
|
||||
{
|
||||
return {original.rbegin(), original.rend()};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef REVSTR_HPP_INCLUDED
|
||||
# define REVSTR_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string revstr(const std::string& original);
|
||||
}
|
||||
|
||||
#endif // REVSTR_HPP_INCLUDED
|
||||
@@ -1,89 +0,0 @@
|
||||
#ifndef SCOPEGUARD_HPP
|
||||
#define SCOPEGUARD_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* A class to be used to make sure some functions are called when the scope
|
||||
* is left, because they will be called in the ScopeGuard's destructor. It
|
||||
* can for example be used to delete some pointer whenever any exception is
|
||||
* called. Example:
|
||||
|
||||
* {
|
||||
* ScopeGuard scope;
|
||||
* int* number = new int(2);
|
||||
* scope.add_callback([number]() { delete number; });
|
||||
* // Do some other stuff with the number. But these stuff might throw an exception:
|
||||
* throw std::runtime_error("Some error not caught here, but in our caller");
|
||||
* return true;
|
||||
* }
|
||||
|
||||
* In this example, our pointer will always be deleted, even when the
|
||||
* exception is thrown. If we want the functions to be called only when the
|
||||
* scope is left because of an unexpected exception, we can use
|
||||
* ScopeGuard::disable();
|
||||
*/
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
class ScopeGuard
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The constructor can take a callback. But additional callbacks can be
|
||||
* added later with add_callback()
|
||||
*/
|
||||
explicit ScopeGuard(std::function<void()>&& func):
|
||||
enabled(true)
|
||||
{
|
||||
this->add_callback(std::move(func));
|
||||
}
|
||||
/**
|
||||
* default constructor, the scope guard is enabled but empty, use
|
||||
* add_callback()
|
||||
*/
|
||||
explicit ScopeGuard():
|
||||
enabled(true)
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Call all callbacks in the desctructor, unless it has been disabled.
|
||||
*/
|
||||
~ScopeGuard()
|
||||
{
|
||||
if (this->enabled)
|
||||
for (auto& func: this->callbacks)
|
||||
func();
|
||||
}
|
||||
/**
|
||||
* Add a callback to be called in our destructor, one scope guard can be
|
||||
* used for more than one task, if needed.
|
||||
*/
|
||||
void add_callback(std::function<void()>&& func)
|
||||
{
|
||||
this->callbacks.emplace_back(std::move(func));
|
||||
}
|
||||
/**
|
||||
* Disable that scope guard, nothing will be done when the scope is
|
||||
* exited.
|
||||
*/
|
||||
void disable()
|
||||
{
|
||||
this->enabled = false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool enabled;
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
ScopeGuard& operator=(ScopeGuard&&) = delete;
|
||||
ScopeGuard(ScopeGuard&&) = delete;
|
||||
ScopeGuard& operator=(const ScopeGuard&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,154 +0,0 @@
|
||||
/* This code is public-domain - it is based on libcrypt
|
||||
* placed in the public domain by Wei Dai and other contributors.
|
||||
*/
|
||||
|
||||
#include "sha1.hpp"
|
||||
|
||||
#define SHA1_K0 0x5a827999
|
||||
#define SHA1_K20 0x6ed9eba1
|
||||
#define SHA1_K40 0x8f1bbcdc
|
||||
#define SHA1_K60 0xca62c1d6
|
||||
|
||||
const uint8_t sha1InitState[] = {
|
||||
0x01,0x23,0x45,0x67, // H0
|
||||
0x89,0xab,0xcd,0xef, // H1
|
||||
0xfe,0xdc,0xba,0x98, // H2
|
||||
0x76,0x54,0x32,0x10, // H3
|
||||
0xf0,0xe1,0xd2,0xc3 // H4
|
||||
};
|
||||
|
||||
void sha1_init(sha1nfo *s) {
|
||||
memcpy(s->state.b,sha1InitState,HASH_LENGTH);
|
||||
s->byteCount = 0;
|
||||
s->bufferOffset = 0;
|
||||
}
|
||||
|
||||
uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
|
||||
return ((number << bits) | (number >> (32-bits)));
|
||||
}
|
||||
|
||||
void sha1_hashBlock(sha1nfo *s) {
|
||||
uint8_t i;
|
||||
uint32_t a,b,c,d,e,t;
|
||||
|
||||
a=s->state.w[0];
|
||||
b=s->state.w[1];
|
||||
c=s->state.w[2];
|
||||
d=s->state.w[3];
|
||||
e=s->state.w[4];
|
||||
for (i=0; i<80; i++) {
|
||||
if (i>=16) {
|
||||
t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15];
|
||||
s->buffer.w[i&15] = sha1_rol32(t,1);
|
||||
}
|
||||
if (i<20) {
|
||||
t = (d ^ (b & (c ^ d))) + SHA1_K0;
|
||||
} else if (i<40) {
|
||||
t = (b ^ c ^ d) + SHA1_K20;
|
||||
} else if (i<60) {
|
||||
t = ((b & c) | (d & (b | c))) + SHA1_K40;
|
||||
} else {
|
||||
t = (b ^ c ^ d) + SHA1_K60;
|
||||
}
|
||||
t+=sha1_rol32(a,5) + e + s->buffer.w[i&15];
|
||||
e=d;
|
||||
d=c;
|
||||
c=sha1_rol32(b,30);
|
||||
b=a;
|
||||
a=t;
|
||||
}
|
||||
s->state.w[0] += a;
|
||||
s->state.w[1] += b;
|
||||
s->state.w[2] += c;
|
||||
s->state.w[3] += d;
|
||||
s->state.w[4] += e;
|
||||
}
|
||||
|
||||
void sha1_addUncounted(sha1nfo *s, uint8_t data) {
|
||||
s->buffer.b[s->bufferOffset ^ 3] = data;
|
||||
s->bufferOffset++;
|
||||
if (s->bufferOffset == BLOCK_LENGTH) {
|
||||
sha1_hashBlock(s);
|
||||
s->bufferOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void sha1_writebyte(sha1nfo *s, uint8_t data) {
|
||||
++s->byteCount;
|
||||
sha1_addUncounted(s, data);
|
||||
}
|
||||
|
||||
void sha1_write(sha1nfo *s, const char *data, size_t len) {
|
||||
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
|
||||
}
|
||||
|
||||
void sha1_pad(sha1nfo *s) {
|
||||
// Implement SHA-1 padding (fips180-2 §5.1.1)
|
||||
|
||||
// Pad with 0x80 followed by 0x00 until the end of the block
|
||||
sha1_addUncounted(s, 0x80);
|
||||
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
|
||||
|
||||
// Append length in the last 8 bytes
|
||||
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
|
||||
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
|
||||
sha1_addUncounted(s, 0); // So zero pad the top bits
|
||||
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
|
||||
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
|
||||
sha1_addUncounted(s, s->byteCount >> 13); // byte.
|
||||
sha1_addUncounted(s, s->byteCount >> 5);
|
||||
sha1_addUncounted(s, s->byteCount << 3);
|
||||
}
|
||||
|
||||
uint8_t* sha1_result(sha1nfo *s) {
|
||||
int i;
|
||||
// Pad to complete the last block
|
||||
sha1_pad(s);
|
||||
|
||||
// Swap byte order back
|
||||
for (i=0; i<5; i++) {
|
||||
uint32_t a,b;
|
||||
a=s->state.w[i];
|
||||
b=a<<24;
|
||||
b|=(a<<8) & 0x00ff0000;
|
||||
b|=(a>>8) & 0x0000ff00;
|
||||
b|=a>>24;
|
||||
s->state.w[i]=b;
|
||||
}
|
||||
|
||||
// Return pointer to hash (20 characters)
|
||||
return s->state.b;
|
||||
}
|
||||
|
||||
#define HMAC_IPAD 0x36
|
||||
#define HMAC_OPAD 0x5c
|
||||
|
||||
void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) {
|
||||
uint8_t i;
|
||||
memset(s->keyBuffer, 0, BLOCK_LENGTH);
|
||||
if (keyLength > BLOCK_LENGTH) {
|
||||
// Hash long keys
|
||||
sha1_init(s);
|
||||
for (;keyLength--;) sha1_writebyte(s, *key++);
|
||||
memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH);
|
||||
} else {
|
||||
// Block length keys are used as is
|
||||
memcpy(s->keyBuffer, key, keyLength);
|
||||
}
|
||||
// Start inner hash
|
||||
sha1_init(s);
|
||||
for (i=0; i<BLOCK_LENGTH; i++) {
|
||||
sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* sha1_resultHmac(sha1nfo *s) {
|
||||
uint8_t i;
|
||||
// Complete inner hash
|
||||
memcpy(s->innerHash,sha1_result(s),HASH_LENGTH);
|
||||
// Calculate outer hash
|
||||
sha1_init(s);
|
||||
for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
|
||||
for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
|
||||
return sha1_result(s);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/* This code is public-domain - it is based on libcrypt
|
||||
* placed in the public domain by Wei Dai and other contributors.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define HASH_LENGTH 20
|
||||
#define BLOCK_LENGTH 64
|
||||
|
||||
union _buffer {
|
||||
uint8_t b[BLOCK_LENGTH];
|
||||
uint32_t w[BLOCK_LENGTH/4];
|
||||
};
|
||||
|
||||
union _state {
|
||||
uint8_t b[HASH_LENGTH];
|
||||
uint32_t w[HASH_LENGTH/4];
|
||||
};
|
||||
|
||||
typedef struct sha1nfo {
|
||||
union _buffer buffer;
|
||||
uint8_t bufferOffset;
|
||||
union _state state;
|
||||
uint32_t byteCount;
|
||||
uint8_t keyBuffer[BLOCK_LENGTH];
|
||||
uint8_t innerHash[HASH_LENGTH];
|
||||
} sha1nfo;
|
||||
|
||||
void sha1_init(sha1nfo *s);
|
||||
void sha1_writebyte(sha1nfo *s, uint8_t data);
|
||||
void sha1_write(sha1nfo *s, const char *data, size_t len);
|
||||
uint8_t* sha1_result(sha1nfo *s);
|
||||
void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength);
|
||||
uint8_t* sha1_resultHmac(sha1nfo *s);
|
||||
@@ -1,18 +0,0 @@
|
||||
#include <utils/split.hpp>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::vector<std::string> split(const std::string& s, const char delim, const bool allow_empty)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, delim))
|
||||
{
|
||||
if (item.empty() && !allow_empty)
|
||||
continue ;
|
||||
ret.emplace_back(std::move(item));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef SPLIT_INCLUDED
|
||||
# define SPLIT_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::vector<std::string> split(const std::string &s, const char delim, const bool allow_empty=true);
|
||||
}
|
||||
|
||||
#endif // SPLIT_INCLUDED
|
||||
@@ -1,62 +0,0 @@
|
||||
#include <utils/timed_events.hpp>
|
||||
|
||||
TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point,
|
||||
std::function<void()> callback, const std::string& name):
|
||||
time_point(std::move(time_point)),
|
||||
callback(callback),
|
||||
repeat(false),
|
||||
repeat_delay(0),
|
||||
name(name)
|
||||
{
|
||||
}
|
||||
|
||||
TimedEvent::TimedEvent(std::chrono::milliseconds&& duration,
|
||||
std::function<void()> callback, const std::string& name):
|
||||
time_point(std::chrono::steady_clock::now() + duration),
|
||||
callback(callback),
|
||||
repeat(true),
|
||||
repeat_delay(std::move(duration)),
|
||||
name(name)
|
||||
{
|
||||
}
|
||||
|
||||
TimedEvent::TimedEvent(TimedEvent&& other):
|
||||
time_point(std::move(other.time_point)),
|
||||
callback(std::move(other.callback)),
|
||||
repeat(other.repeat),
|
||||
repeat_delay(std::move(other.repeat_delay)),
|
||||
name(std::move(other.name))
|
||||
{
|
||||
}
|
||||
|
||||
TimedEvent::~TimedEvent()
|
||||
{
|
||||
}
|
||||
|
||||
bool TimedEvent::is_after(const TimedEvent& other) const
|
||||
{
|
||||
return this->is_after(other.time_point);
|
||||
}
|
||||
|
||||
bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const
|
||||
{
|
||||
return this->time_point >= time_point;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds TimedEvent::get_timeout() const
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now > this->time_point)
|
||||
return std::chrono::milliseconds(0);
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(this->time_point - now);
|
||||
}
|
||||
|
||||
void TimedEvent::execute()
|
||||
{
|
||||
this->callback();
|
||||
}
|
||||
|
||||
const std::string& TimedEvent::get_name() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#ifndef TIMED_EVENTS_HPP
|
||||
# define TIMED_EVENTS_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
namespace utils {
|
||||
static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1);
|
||||
}
|
||||
|
||||
class TimedEventsManager;
|
||||
|
||||
/**
|
||||
* A callback with an associated date.
|
||||
*/
|
||||
|
||||
class TimedEvent
|
||||
{
|
||||
friend class TimedEventsManager;
|
||||
public:
|
||||
/**
|
||||
* An event the occurs only once, at the given time_point
|
||||
*/
|
||||
explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point,
|
||||
std::function<void()> callback, const std::string& name="");
|
||||
explicit TimedEvent(std::chrono::milliseconds&& duration,
|
||||
std::function<void()> callback, const std::string& name="");
|
||||
|
||||
explicit TimedEvent(TimedEvent&&);
|
||||
~TimedEvent();
|
||||
/**
|
||||
* Whether or not this event happens after the other one.
|
||||
*/
|
||||
bool is_after(const TimedEvent& other) const;
|
||||
bool is_after(const std::chrono::steady_clock::time_point& time_point) const;
|
||||
/**
|
||||
* Return the duration difference between now and the event time point.
|
||||
* If the difference would be negative (i.e. the event is expired), the
|
||||
* returned value is 0 instead. The value cannot then be negative.
|
||||
*/
|
||||
std::chrono::milliseconds get_timeout() const;
|
||||
void execute();
|
||||
const std::string& get_name() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The next time point at which the event is executed.
|
||||
*/
|
||||
std::chrono::steady_clock::time_point time_point;
|
||||
/**
|
||||
* The function to execute.
|
||||
*/
|
||||
const std::function<void()> callback;
|
||||
/**
|
||||
* Whether or not this events repeats itself until it is destroyed.
|
||||
*/
|
||||
const bool repeat;
|
||||
/**
|
||||
* This value is added to the time_point each time the event is executed,
|
||||
* if repeat is true. Otherwise it is ignored.
|
||||
*/
|
||||
const std::chrono::milliseconds repeat_delay;
|
||||
/**
|
||||
* A name that is used to identify that event. If you want to find your
|
||||
* event (for example if you want to cancel it), the name should be
|
||||
* unique.
|
||||
*/
|
||||
const std::string name;
|
||||
|
||||
TimedEvent(const TimedEvent&) = delete;
|
||||
TimedEvent& operator=(const TimedEvent&) = delete;
|
||||
TimedEvent& operator=(TimedEvent&&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* A class managing a list of TimedEvents.
|
||||
* They are sorted, new events can be added, removed, fetch, etc.
|
||||
*/
|
||||
|
||||
class TimedEventsManager
|
||||
{
|
||||
public:
|
||||
~TimedEventsManager();
|
||||
/**
|
||||
* Return the unique instance of this class
|
||||
*/
|
||||
static TimedEventsManager& instance();
|
||||
/**
|
||||
* Add an event to the list of managed events. The list is sorted after
|
||||
* this call.
|
||||
*/
|
||||
void add_event(TimedEvent&& event);
|
||||
/**
|
||||
* Returns the duration, in milliseconds, between now and the next
|
||||
* available event. If the event is already expired (the duration is
|
||||
* negative), 0 is returned instead (as in “it's not too late, execute it
|
||||
* now”)
|
||||
* Returns a negative value if no event is available.
|
||||
*/
|
||||
std::chrono::milliseconds get_timeout() const;
|
||||
/**
|
||||
* Execute all the expired events (if their expiration time is exactly
|
||||
* now, or before now). The event is then removed from the list. If the
|
||||
* event does repeat, its expiration time is updated and it is reinserted
|
||||
* in the list at the correct position.
|
||||
* Returns the number of executed events.
|
||||
*/
|
||||
std::size_t execute_expired_events();
|
||||
/**
|
||||
* Remove (and thus cancel) all the timed events with the given name.
|
||||
* Returns the number of canceled events.
|
||||
*/
|
||||
std::size_t cancel(const std::string& name);
|
||||
/**
|
||||
* Return the number of managed events.
|
||||
*/
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
explicit TimedEventsManager();
|
||||
std::list<TimedEvent> events;
|
||||
TimedEventsManager(const TimedEventsManager&) = delete;
|
||||
TimedEventsManager(TimedEventsManager&&) = delete;
|
||||
TimedEventsManager& operator=(const TimedEventsManager&) = delete;
|
||||
TimedEventsManager& operator=(TimedEventsManager&&) = delete;
|
||||
};
|
||||
|
||||
#endif // TIMED_EVENTS_HPP
|
||||
@@ -1,81 +0,0 @@
|
||||
#include <utils/timed_events.hpp>
|
||||
|
||||
TimedEventsManager& TimedEventsManager::instance()
|
||||
{
|
||||
static TimedEventsManager inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
TimedEventsManager::TimedEventsManager()
|
||||
{
|
||||
}
|
||||
|
||||
TimedEventsManager::~TimedEventsManager()
|
||||
{
|
||||
}
|
||||
|
||||
void TimedEventsManager::add_event(TimedEvent&& event)
|
||||
{
|
||||
for (auto it = this->events.begin(); it != this->events.end(); ++it)
|
||||
{
|
||||
if (it->is_after(event))
|
||||
{
|
||||
this->events.emplace(it, std::move(event));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->events.emplace_back(std::move(event));
|
||||
}
|
||||
|
||||
std::chrono::milliseconds TimedEventsManager::get_timeout() const
|
||||
{
|
||||
if (this->events.empty())
|
||||
return utils::no_timeout;
|
||||
return this->events.front().get_timeout() + std::chrono::milliseconds(1);
|
||||
}
|
||||
|
||||
std::size_t TimedEventsManager::execute_expired_events()
|
||||
{
|
||||
std::size_t count = 0;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
for (auto it = this->events.begin(); it != this->events.end();)
|
||||
{
|
||||
if (!it->is_after(now))
|
||||
{
|
||||
TimedEvent copy(std::move(*it));
|
||||
it = this->events.erase(it);
|
||||
++count;
|
||||
copy.execute();
|
||||
if (copy.repeat)
|
||||
{
|
||||
copy.time_point += copy.repeat_delay;
|
||||
this->add_event(std::move(copy));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t TimedEventsManager::cancel(const std::string& name)
|
||||
{
|
||||
std::size_t res = 0;
|
||||
for (auto it = this->events.begin(); it != this->events.end();)
|
||||
{
|
||||
if (it->get_name() == name)
|
||||
{
|
||||
it = this->events.erase(it);
|
||||
res++;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::size_t TimedEventsManager::size() const
|
||||
{
|
||||
return this->events.size();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <utils/tolower.hpp>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string tolower(const std::string& original)
|
||||
{
|
||||
std::string res;
|
||||
res.reserve(original.size());
|
||||
for (const char c: original)
|
||||
res += static_cast<char>(std::tolower(c));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef TOLOWER_INCLUDED
|
||||
# define TOLOWER_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string tolower(const std::string& original);
|
||||
}
|
||||
|
||||
#endif // SPLIT_INCLUDED
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xmpp/adhoc_command.hpp>
|
||||
#include <xmpp/xmpp_component.hpp>
|
||||
#include <xmpp/biboumi_component.hpp>
|
||||
|
||||
#include <bridge/bridge.hpp>
|
||||
|
||||
@@ -98,6 +98,8 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node)
|
||||
|
||||
void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node)
|
||||
{
|
||||
auto biboumi_component = static_cast<BiboumiComponent*>(xmpp_component);
|
||||
|
||||
XmlNode x("jabber:x:data:x");
|
||||
x["type"] = "form";
|
||||
XmlNode title("title");
|
||||
@@ -115,7 +117,7 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode&
|
||||
XmlNode required("required");
|
||||
required.close();
|
||||
jids_field.add_child(std::move(required));
|
||||
for (Bridge* bridge: xmpp_component->get_bridges())
|
||||
for (Bridge* bridge: biboumi_component->get_bridges())
|
||||
{
|
||||
XmlNode option("option");
|
||||
option["label"] = bridge->get_jid();
|
||||
@@ -145,6 +147,8 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode&
|
||||
|
||||
void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node)
|
||||
{
|
||||
auto biboumi_component = static_cast<BiboumiComponent*>(xmpp_component);
|
||||
|
||||
// Find out if the jids, and the quit message are provided in the form.
|
||||
std::string quit_message;
|
||||
XmlNode* x = command_node.get_child("x", "jabber:x:data");
|
||||
@@ -168,7 +172,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X
|
||||
std::size_t num = 0;
|
||||
for (XmlNode* value: jids_field->get_children("value", "jabber:x:data"))
|
||||
{
|
||||
Bridge* bridge = xmpp_component->find_user_bridge(value->get_inner());
|
||||
Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner());
|
||||
if (bridge)
|
||||
{
|
||||
bridge->shutdown(quit_message);
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#include <xmpp/adhoc_commands_handler.hpp>
|
||||
#include <xmpp/xmpp_component.hpp>
|
||||
|
||||
#include <utils/timed_events.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <config/config.hpp>
|
||||
#include <xmpp/jid.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component):
|
||||
xmpp_component(xmpp_component),
|
||||
commands{
|
||||
{"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
|
||||
{"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)},
|
||||
{"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)},
|
||||
{"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
AdhocCommandsHandler::~AdhocCommandsHandler()
|
||||
{
|
||||
}
|
||||
|
||||
const std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get_commands() const
|
||||
{
|
||||
return this->commands;
|
||||
}
|
||||
|
||||
XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node)
|
||||
{
|
||||
std::string action = command_node.get_tag("action");
|
||||
if (action.empty())
|
||||
action = "execute";
|
||||
command_node.del_tag("action");
|
||||
|
||||
Jid jid(executor_jid);
|
||||
|
||||
const std::string node = command_node.get_tag("node");
|
||||
auto command_it = this->commands.find(node);
|
||||
if (command_it == this->commands.end())
|
||||
{
|
||||
XmlNode error(ADHOC_NS":error");
|
||||
error["type"] = "cancel";
|
||||
XmlNode condition(STANZA_NS":item-not-found");
|
||||
condition.close();
|
||||
error.add_child(std::move(condition));
|
||||
error.close();
|
||||
command_node.add_child(std::move(error));
|
||||
}
|
||||
else if (command_it->second.is_admin_only() &&
|
||||
Config::get("admin", "") != jid.local + "@" + jid.domain)
|
||||
{
|
||||
XmlNode error(ADHOC_NS":error");
|
||||
error["type"] = "cancel";
|
||||
XmlNode condition(STANZA_NS":forbidden");
|
||||
condition.close();
|
||||
error.add_child(std::move(condition));
|
||||
error.close();
|
||||
command_node.add_child(std::move(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string sessionid = command_node.get_tag("sessionid");
|
||||
if (sessionid.empty())
|
||||
{ // create a new session, with a new id
|
||||
sessionid = XmppComponent::next_id();
|
||||
command_node["sessionid"] = sessionid;
|
||||
this->sessions.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(sessionid, executor_jid),
|
||||
std::forward_as_tuple(command_it->second, executor_jid));
|
||||
TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s,
|
||||
std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid),
|
||||
"adhocsession"s + sessionid + executor_jid));
|
||||
}
|
||||
auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid));
|
||||
if (session_it == this->sessions.end())
|
||||
{
|
||||
XmlNode error(ADHOC_NS":error");
|
||||
error["type"] = "modify";
|
||||
XmlNode condition(STANZA_NS":bad-request");
|
||||
condition.close();
|
||||
error.add_child(std::move(condition));
|
||||
error.close();
|
||||
command_node.add_child(std::move(error));
|
||||
}
|
||||
else if (action == "execute" || action == "next" || action == "complete")
|
||||
{
|
||||
// execute the step
|
||||
AdhocSession& session = session_it->second;
|
||||
const AdhocStep& step = session.get_next_step();
|
||||
step(this->xmpp_component, session, command_node);
|
||||
if (session.remaining_steps() == 0 ||
|
||||
session.is_terminated())
|
||||
{
|
||||
this->sessions.erase(session_it);
|
||||
command_node["status"] = "completed";
|
||||
TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid);
|
||||
}
|
||||
else
|
||||
{
|
||||
command_node["status"] = "executing";
|
||||
XmlNode actions("actions");
|
||||
XmlNode next("next");
|
||||
next.close();
|
||||
actions.add_child(std::move(next));
|
||||
actions.close();
|
||||
command_node.add_child(std::move(actions));
|
||||
}
|
||||
}
|
||||
else if (action == "cancel")
|
||||
{
|
||||
this->sessions.erase(session_it);
|
||||
command_node["status"] = "canceled";
|
||||
TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid);
|
||||
}
|
||||
else // unsupported action
|
||||
{
|
||||
XmlNode error(ADHOC_NS":error");
|
||||
error["type"] = "modify";
|
||||
XmlNode condition(STANZA_NS":bad-request");
|
||||
condition.close();
|
||||
error.add_child(std::move(condition));
|
||||
error.close();
|
||||
command_node.add_child(std::move(error));
|
||||
}
|
||||
}
|
||||
return command_node;
|
||||
}
|
||||
|
||||
void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid)
|
||||
{
|
||||
auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid));
|
||||
if (session_it != this->sessions.end())
|
||||
{
|
||||
this->sessions.erase(session_it);
|
||||
return ;
|
||||
}
|
||||
log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found");
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#ifndef ADHOC_COMMANDS_HANDLER_HPP
|
||||
# define ADHOC_COMMANDS_HANDLER_HPP
|
||||
|
||||
/**
|
||||
* Manage a list of available AdhocCommands and the list of ongoing
|
||||
* AdhocCommandSessions.
|
||||
*/
|
||||
|
||||
#include <xmpp/adhoc_command.hpp>
|
||||
#include <xmpp/xmpp_stanza.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class XmppComponent;
|
||||
|
||||
class AdhocCommandsHandler
|
||||
{
|
||||
public:
|
||||
explicit AdhocCommandsHandler(XmppComponent* xmpp_component);
|
||||
~AdhocCommandsHandler();
|
||||
/**
|
||||
* Returns the list of available commands.
|
||||
*/
|
||||
const std::map<const std::string, const AdhocCommand>& get_commands() const;
|
||||
/**
|
||||
* Find the requested command, create a new session or use an existing
|
||||
* one, and process the request (provide a new form, an error, or a
|
||||
* result).
|
||||
*
|
||||
* Returns a (moved) XmlNode that will be inserted in the iq response. It
|
||||
* should be a <command/> node containing one or more useful children. If
|
||||
* it contains an <error/> node, the iq response will have an error type.
|
||||
*
|
||||
* Takes a copy of the <command/> node so we can actually edit it and use
|
||||
* it as our return value.
|
||||
*/
|
||||
XmlNode handle_request(const std::string& executor_jid, XmlNode command_node);
|
||||
/**
|
||||
* Remove the session from the list. This is done to avoid filling the
|
||||
* memory with waiting session (for example due to a client that starts
|
||||
* multi-steps command but never finishes them).
|
||||
*/
|
||||
void remove_session(const std::string& session_id, const std::string& initiator_jid);
|
||||
private:
|
||||
/**
|
||||
* A pointer to the XmppComponent, to access to basically anything in the
|
||||
* gateway.
|
||||
*/
|
||||
XmppComponent* xmpp_component;
|
||||
/**
|
||||
* The list of all available commands.
|
||||
*/
|
||||
const std::map<const std::string, const AdhocCommand> commands;
|
||||
/**
|
||||
* The list of all currently on-going commands.
|
||||
*
|
||||
* Of the form: {{session_id, owner_jid}, session}.
|
||||
*/
|
||||
std::map<std::pair<const std::string, const std::string>, AdhocSession> sessions;
|
||||
|
||||
AdhocCommandsHandler(const AdhocCommandsHandler&) = delete;
|
||||
AdhocCommandsHandler(AdhocCommandsHandler&&) = delete;
|
||||
AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete;
|
||||
AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete;
|
||||
};
|
||||
|
||||
#endif // ADHOC_COMMANDS_HANDLER_HPP
|
||||
@@ -1,37 +0,0 @@
|
||||
#include <xmpp/adhoc_session.hpp>
|
||||
#include <xmpp/adhoc_command.hpp>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid):
|
||||
command(command),
|
||||
owner_jid(jid),
|
||||
current_step(0),
|
||||
terminated(false)
|
||||
{
|
||||
}
|
||||
|
||||
AdhocSession::~AdhocSession()
|
||||
{
|
||||
}
|
||||
|
||||
const AdhocStep& AdhocSession::get_next_step()
|
||||
{
|
||||
assert(this->current_step < this->command.callbacks.size());
|
||||
return this->command.callbacks[this->current_step++];
|
||||
}
|
||||
|
||||
size_t AdhocSession::remaining_steps() const
|
||||
{
|
||||
return this->command.callbacks.size() - this->current_step;
|
||||
}
|
||||
|
||||
bool AdhocSession::is_terminated() const
|
||||
{
|
||||
return this->terminated;
|
||||
}
|
||||
|
||||
void AdhocSession::terminate()
|
||||
{
|
||||
this->terminated = true;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#ifndef ADHOC_SESSION_HPP
|
||||
# define ADHOC_SESSION_HPP
|
||||
|
||||
#include <xmpp/xmpp_stanza.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
class XmppComponent;
|
||||
|
||||
class AdhocCommand;
|
||||
class AdhocSession;
|
||||
|
||||
/**
|
||||
* A function executed as an ad-hoc command step. It takes a <command/>
|
||||
* XmlNode and modifies it accordingly (inserting for example an <error/>
|
||||
* node, or a data form…).
|
||||
* TODO fix this:
|
||||
* It also must call one of step_passed(), cancel() etc on the AdhocSession object.
|
||||
*/
|
||||
typedef std::function<void(XmppComponent*, AdhocSession&, XmlNode&)> AdhocStep;
|
||||
|
||||
class AdhocSession
|
||||
{
|
||||
public:
|
||||
explicit AdhocSession(const AdhocCommand& command, const std::string& jid);
|
||||
~AdhocSession();
|
||||
/**
|
||||
* Return the function to be executed, found in our AdhocCommand, for the
|
||||
* current_step. And increment the current_step.
|
||||
*/
|
||||
const AdhocStep& get_next_step();
|
||||
/**
|
||||
* Return the number of remaining steps.
|
||||
*/
|
||||
size_t remaining_steps() const;
|
||||
/**
|
||||
* This may be modified by an AdhocStep, to indicate that this session
|
||||
* should no longer exist, because we encountered an error, and we can't
|
||||
* execute any more step of it.
|
||||
*/
|
||||
void terminate();
|
||||
bool is_terminated() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* A reference of the command concerned by this session. Used for example
|
||||
* to get the next step of that command, things like that.
|
||||
*/
|
||||
const AdhocCommand& command;
|
||||
/**
|
||||
* The full JID of the XMPP user that created this session by executing
|
||||
* the first step of a command. Only that JID must be allowed to access
|
||||
* this session.
|
||||
*/
|
||||
const std::string& owner_jid;
|
||||
/**
|
||||
* The current step we are at. It starts at zero. It is used to index the
|
||||
* associated AdhocCommand::callbacks vector.
|
||||
*/
|
||||
size_t current_step;
|
||||
bool terminated;
|
||||
|
||||
AdhocSession(const AdhocSession&) = delete;
|
||||
AdhocSession(AdhocSession&&) = delete;
|
||||
AdhocSession& operator=(const AdhocSession&) = delete;
|
||||
AdhocSession& operator=(AdhocSession&&) = delete;
|
||||
};
|
||||
|
||||
#endif // ADHOC_SESSION_HPP
|
||||
@@ -0,0 +1,568 @@
|
||||
#include <xmpp/biboumi_component.hpp>
|
||||
|
||||
#include <utils/timed_events.hpp>
|
||||
#include <utils/scopeguard.hpp>
|
||||
#include <utils/tolower.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <xmpp/adhoc_command.hpp>
|
||||
#include <bridge/list_element.hpp>
|
||||
#include <config/config.hpp>
|
||||
#include <xmpp/jid.hpp>
|
||||
#include <utils/sha1.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <louloulibs.h>
|
||||
|
||||
#include <uuid.h>
|
||||
|
||||
#ifdef SYSTEMD_FOUND
|
||||
# include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
static std::set<std::string> kickable_errors{
|
||||
"gone",
|
||||
"internal-server-error",
|
||||
"item-not-found",
|
||||
"jid-malformed",
|
||||
"recipient-unavailable",
|
||||
"redirect",
|
||||
"remote-server-not-found",
|
||||
"remote-server-timeout",
|
||||
"service-unavailable",
|
||||
"malformed-error"
|
||||
};
|
||||
|
||||
|
||||
BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
|
||||
XmppComponent(poller, hostname, secret)
|
||||
{
|
||||
this->stanza_handlers.emplace("presence",
|
||||
std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1));
|
||||
this->stanza_handlers.emplace("message",
|
||||
std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1));
|
||||
this->stanza_handlers.emplace("iq",
|
||||
std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1));
|
||||
|
||||
this->adhoc_commands_handler.get_commands()= {
|
||||
{"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
|
||||
{"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)},
|
||||
{"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)},
|
||||
{"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
|
||||
};
|
||||
}
|
||||
|
||||
void BiboumiComponent::shutdown()
|
||||
{
|
||||
for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
|
||||
{
|
||||
it->second->shutdown("Gateway shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
void BiboumiComponent::clean()
|
||||
{
|
||||
auto it = this->bridges.begin();
|
||||
while (it != this->bridges.end())
|
||||
{
|
||||
it->second->clean();
|
||||
if (it->second->active_clients() == 0)
|
||||
it = this->bridges.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void BiboumiComponent::handle_presence(const Stanza& stanza)
|
||||
{
|
||||
std::string from = stanza.get_tag("from");
|
||||
std::string id = stanza.get_tag("id");
|
||||
std::string to_str = stanza.get_tag("to");
|
||||
std::string type = stanza.get_tag("type");
|
||||
|
||||
// Check for mandatory tags
|
||||
if (from.empty())
|
||||
{
|
||||
log_warning("Received an invalid presence stanza: tag 'from' is missing.");
|
||||
return;
|
||||
}
|
||||
if (to_str.empty())
|
||||
{
|
||||
this->send_stanza_error("presence", from, this->served_hostname, id,
|
||||
"modify", "bad-request", "Missing 'to' tag");
|
||||
return;
|
||||
}
|
||||
|
||||
Bridge* bridge = this->get_user_bridge(from);
|
||||
Jid to(to_str);
|
||||
Iid iid(to.local);
|
||||
|
||||
// An error stanza is sent whenever we exit this function without
|
||||
// disabling this scopeguard. If error_type and error_name are not
|
||||
// changed, the error signaled is internal-server-error. Change their
|
||||
// value to signal an other kind of error. For example
|
||||
// feature-not-implemented, etc. Any non-error process should reach the
|
||||
// stanza_error.disable() call at the end of the function.
|
||||
std::string error_type("cancel");
|
||||
std::string error_name("internal-server-error");
|
||||
utils::ScopeGuard stanza_error([&](){
|
||||
this->send_stanza_error("presence", from, to_str, id,
|
||||
error_type, error_name, "");
|
||||
});
|
||||
|
||||
if (iid.is_channel && !iid.get_server().empty())
|
||||
{ // presence toward a MUC that corresponds to an irc channel, or a
|
||||
// dummy channel if iid.chan is empty
|
||||
if (type.empty())
|
||||
{
|
||||
const std::string own_nick = bridge->get_own_nick(iid);
|
||||
if (!own_nick.empty() && own_nick != to.resource)
|
||||
bridge->send_irc_nick_change(iid, to.resource);
|
||||
XmlNode* x = stanza.get_child("x", MUC_NS);
|
||||
XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
|
||||
bridge->join_irc_channel(iid, to.resource,
|
||||
password ? password->get_inner() : "");
|
||||
}
|
||||
else if (type == "unavailable")
|
||||
{
|
||||
XmlNode* status = stanza.get_child("status", COMPONENT_NS);
|
||||
bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// An user wants to join an invalid IRC channel, return a presence error to him
|
||||
if (type.empty())
|
||||
this->send_invalid_room_error(to.local, to.resource, from);
|
||||
}
|
||||
stanza_error.disable();
|
||||
}
|
||||
|
||||
void BiboumiComponent::handle_message(const Stanza& stanza)
|
||||
{
|
||||
std::string from = stanza.get_tag("from");
|
||||
std::string id = stanza.get_tag("id");
|
||||
std::string to_str = stanza.get_tag("to");
|
||||
std::string type = stanza.get_tag("type");
|
||||
|
||||
if (from.empty())
|
||||
return;
|
||||
if (type.empty())
|
||||
type = "normal";
|
||||
Bridge* bridge = this->get_user_bridge(from);
|
||||
Jid to(to_str);
|
||||
Iid iid(to.local);
|
||||
|
||||
std::string error_type("cancel");
|
||||
std::string error_name("internal-server-error");
|
||||
utils::ScopeGuard stanza_error([&](){
|
||||
this->send_stanza_error("message", from, to_str, id,
|
||||
error_type, error_name, "");
|
||||
});
|
||||
XmlNode* body = stanza.get_child("body", COMPONENT_NS);
|
||||
if (type == "groupchat" && iid.is_channel)
|
||||
{
|
||||
if (body && !body->get_inner().empty())
|
||||
{
|
||||
bridge->send_channel_message(iid, body->get_inner());
|
||||
}
|
||||
XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
|
||||
if (subject)
|
||||
bridge->set_channel_topic(iid, subject->get_inner());
|
||||
}
|
||||
else if (type == "error")
|
||||
{
|
||||
const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
|
||||
// Only a set of errors are considered “fatal”. If we encounter one of
|
||||
// them, we purge (we disconnect the user from all the IRC servers).
|
||||
// We consider this to be true, unless the error condition is
|
||||
// specified and is not in the kickable_errors set
|
||||
bool kickable_error = true;
|
||||
if (error && error->has_children())
|
||||
{
|
||||
const XmlNode* condition = error->get_last_child();
|
||||
if (kickable_errors.find(condition->get_name()) == kickable_errors.end())
|
||||
kickable_error = false;
|
||||
}
|
||||
if (kickable_error)
|
||||
bridge->shutdown("Error from remote client");
|
||||
}
|
||||
else if (type == "chat")
|
||||
{
|
||||
if (body && !body->get_inner().empty())
|
||||
{
|
||||
// a message for nick!server
|
||||
if (iid.is_user && !iid.get_local().empty())
|
||||
{
|
||||
bridge->send_private_message(iid, body->get_inner());
|
||||
bridge->remove_preferred_from_jid(iid.get_local());
|
||||
}
|
||||
else if (!iid.is_user && !to.resource.empty())
|
||||
{ // a message for chan%server@biboumi/Nick or
|
||||
// server@biboumi/Nick
|
||||
// Convert that into a message to nick!server
|
||||
Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server());
|
||||
bridge->send_private_message(user_iid, body->get_inner());
|
||||
bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (iid.is_user)
|
||||
this->send_invalid_user_error(to.local, from);
|
||||
stanza_error.disable();
|
||||
}
|
||||
|
||||
// We MUST return an iq, whatever happens, except if the type is
|
||||
// "result".
|
||||
// To do this, we use a scopeguard. If an exception is raised somewhere, an
|
||||
// iq of type error "internal-server-error" is sent. If we handle the
|
||||
// request properly (by calling a function that registers an iq to be sent
|
||||
// later, or that directly sends an iq), we disable the ScopeGuard. If we
|
||||
// reach the end of the function without having disabled the scopeguard, we
|
||||
// send a "feature-not-implemented" iq as a result. If an other kind of
|
||||
// error is found (for example the feature is implemented in biboumi, but
|
||||
// the request is missing some attribute) we can just change the values of
|
||||
// error_type and error_name and return from the function (without disabling
|
||||
// the scopeguard); an iq error will be sent
|
||||
void BiboumiComponent::handle_iq(const Stanza& stanza)
|
||||
{
|
||||
std::string id = stanza.get_tag("id");
|
||||
std::string from = stanza.get_tag("from");
|
||||
std::string to_str = stanza.get_tag("to");
|
||||
std::string type = stanza.get_tag("type");
|
||||
|
||||
if (from.empty())
|
||||
return;
|
||||
if (id.empty() || to_str.empty() || type.empty())
|
||||
{
|
||||
this->send_stanza_error("iq", from, this->served_hostname, id,
|
||||
"modify", "bad-request", "");
|
||||
return;
|
||||
}
|
||||
|
||||
Bridge* bridge = this->get_user_bridge(from);
|
||||
Jid to(to_str);
|
||||
|
||||
// These two values will be used in the error iq sent if we don't disable
|
||||
// the scopeguard.
|
||||
std::string error_type("cancel");
|
||||
std::string error_name("internal-server-error");
|
||||
utils::ScopeGuard stanza_error([&](){
|
||||
this->send_stanza_error("iq", from, to_str, id,
|
||||
error_type, error_name, "");
|
||||
});
|
||||
if (type == "set")
|
||||
{
|
||||
XmlNode* query;
|
||||
if ((query = stanza.get_child("query", MUC_ADMIN_NS)))
|
||||
{
|
||||
const XmlNode* child = query->get_child("item", MUC_ADMIN_NS);
|
||||
if (child)
|
||||
{
|
||||
std::string nick = child->get_tag("nick");
|
||||
std::string role = child->get_tag("role");
|
||||
std::string affiliation = child->get_tag("affiliation");
|
||||
if (!nick.empty())
|
||||
{
|
||||
Iid iid(to.local);
|
||||
if (role == "none")
|
||||
{ // This is a kick
|
||||
std::string reason;
|
||||
XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
|
||||
if (reason_el)
|
||||
reason = reason_el->get_inner();
|
||||
bridge->send_irc_kick(iid, nick, reason, id, from);
|
||||
}
|
||||
else
|
||||
bridge->forward_affiliation_role_change(iid, nick, affiliation, role);
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((query = stanza.get_child("command", ADHOC_NS)))
|
||||
{
|
||||
Stanza response("iq");
|
||||
response["to"] = from;
|
||||
response["from"] = this->served_hostname;
|
||||
response["id"] = id;
|
||||
XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query);
|
||||
if (inner_node.get_child("error", ADHOC_NS))
|
||||
response["type"] = "error";
|
||||
else
|
||||
response["type"] = "result";
|
||||
response.add_child(std::move(inner_node));
|
||||
response.close();
|
||||
this->send_stanza(response);
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
else if (type == "get")
|
||||
{
|
||||
XmlNode* query;
|
||||
if ((query = stanza.get_child("query", DISCO_INFO_NS)))
|
||||
{ // Disco info
|
||||
if (to_str == this->served_hostname)
|
||||
{
|
||||
const std::string node = query->get_tag("node");
|
||||
if (node.empty())
|
||||
{
|
||||
// On the gateway itself
|
||||
this->send_self_disco_info(id, from);
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((query = stanza.get_child("query", VERSION_NS)))
|
||||
{
|
||||
Iid iid(to.local);
|
||||
if (iid.is_user ||
|
||||
(iid.is_channel && !to.resource.empty()))
|
||||
{
|
||||
// Get the IRC user version
|
||||
std::string target;
|
||||
if (iid.is_user)
|
||||
target = iid.get_local();
|
||||
else
|
||||
target = to.resource;
|
||||
bridge->send_irc_version_request(iid.get_server(), target, id,
|
||||
from, to_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On the gateway itself or on a channel
|
||||
this->send_version(id, from, to_str);
|
||||
}
|
||||
stanza_error.disable();
|
||||
}
|
||||
else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
|
||||
{
|
||||
Iid iid(to.local);
|
||||
const std::string node = query->get_tag("node");
|
||||
if (node == ADHOC_NS)
|
||||
{
|
||||
this->send_adhoc_commands_list(id, from);
|
||||
stanza_error.disable();
|
||||
}
|
||||
else if (node.empty() && !iid.is_user && !iid.is_channel)
|
||||
{ // Disco on an IRC server: get the list of channels
|
||||
bridge->send_irc_channel_list_request(iid, id, from);
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
else if ((query = stanza.get_child("ping", PING_NS)))
|
||||
{
|
||||
Iid iid(to.local);
|
||||
if (iid.is_user)
|
||||
{ // Ping any user (no check on the nick done ourself)
|
||||
bridge->send_irc_user_ping_request(iid.get_server(),
|
||||
iid.get_local(), id, from, to_str);
|
||||
}
|
||||
else if (iid.is_channel && !to.resource.empty())
|
||||
{ // Ping a room participant (we check if the nick is in the room)
|
||||
bridge->send_irc_participant_ping_request(iid,
|
||||
to.resource, id, from, to_str);
|
||||
}
|
||||
else
|
||||
{ // Ping a channel, a server or the gateway itself
|
||||
bridge->on_gateway_ping(iid.get_server(),
|
||||
id, from, to_str);
|
||||
}
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
else if (type == "result")
|
||||
{
|
||||
stanza_error.disable();
|
||||
XmlNode* query;
|
||||
if ((query = stanza.get_child("query", VERSION_NS)))
|
||||
{
|
||||
XmlNode* name_node = query->get_child("name", VERSION_NS);
|
||||
XmlNode* version_node = query->get_child("version", VERSION_NS);
|
||||
XmlNode* os_node = query->get_child("os", VERSION_NS);
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::string os;
|
||||
if (name_node)
|
||||
name = name_node->get_inner() + " (through the biboumi gateway)";
|
||||
if (version_node)
|
||||
version = version_node->get_inner();
|
||||
if (os_node)
|
||||
os = os_node->get_inner();
|
||||
const Iid iid(to.local);
|
||||
bridge->send_xmpp_version_to_irc(iid, name, version, os);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto it = this->waiting_iq.find(id);
|
||||
if (it != this->waiting_iq.end())
|
||||
{
|
||||
it->second(bridge, stanza);
|
||||
this->waiting_iq.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
error_type = "cancel";
|
||||
error_name = "feature-not-implemented";
|
||||
}
|
||||
|
||||
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->bridges.at(user_jid).get();
|
||||
}
|
||||
catch (const std::out_of_range& exception)
|
||||
{
|
||||
this->bridges.emplace(user_jid, std::make_unique<Bridge>(user_jid, this, this->poller));
|
||||
return this->bridges.at(user_jid).get();
|
||||
}
|
||||
}
|
||||
|
||||
Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->bridges.at(user_jid).get();
|
||||
}
|
||||
catch (const std::out_of_range& exception)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Bridge*> BiboumiComponent::get_bridges() const
|
||||
{
|
||||
std::list<Bridge*> res;
|
||||
for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
|
||||
res.push_back(it->second.get());
|
||||
return res;
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["type"] = "result";
|
||||
iq["id"] = id;
|
||||
iq["to"] = jid_to;
|
||||
iq["from"] = this->served_hostname;
|
||||
XmlNode query("query");
|
||||
query["xmlns"] = DISCO_INFO_NS;
|
||||
XmlNode identity("identity");
|
||||
identity["category"] = "conference";
|
||||
identity["type"] = "irc";
|
||||
identity["name"] = "Biboumi XMPP-IRC gateway";
|
||||
identity.close();
|
||||
query.add_child(std::move(identity));
|
||||
for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
|
||||
{
|
||||
XmlNode feature("feature");
|
||||
feature["var"] = ns;
|
||||
feature.close();
|
||||
query.add_child(std::move(feature));
|
||||
}
|
||||
query.close();
|
||||
iq.add_child(std::move(query));
|
||||
iq.close();
|
||||
this->send_stanza(iq);
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["type"] = "result";
|
||||
iq["id"] = id;
|
||||
iq["to"] = requester_jid;
|
||||
iq["from"] = this->served_hostname;
|
||||
XmlNode query("query");
|
||||
query["xmlns"] = DISCO_ITEMS_NS;
|
||||
query["node"] = ADHOC_NS;
|
||||
for (const auto& kv: this->adhoc_commands_handler.get_commands())
|
||||
{
|
||||
XmlNode item("item");
|
||||
item["jid"] = this->served_hostname;
|
||||
item["node"] = kv.first;
|
||||
item["name"] = kv.second.name;
|
||||
item.close();
|
||||
query.add_child(std::move(item));
|
||||
}
|
||||
query.close();
|
||||
iq.add_child(std::move(query));
|
||||
iq.close();
|
||||
this->send_stanza(iq);
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_iq_version_request(const std::string& from,
|
||||
const std::string& jid_to)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["type"] = "get";
|
||||
iq["id"] = "version_"s + this->next_id();
|
||||
iq["from"] = from + "@" + this->served_hostname;
|
||||
iq["to"] = jid_to;
|
||||
XmlNode query("query");
|
||||
query["xmlns"] = VERSION_NS;
|
||||
query.close();
|
||||
iq.add_child(std::move(query));
|
||||
iq.close();
|
||||
this->send_stanza(iq);
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_ping_request(const std::string& from,
|
||||
const std::string& jid_to,
|
||||
const std::string& id)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["type"] = "get";
|
||||
iq["id"] = id;
|
||||
iq["from"] = from + "@" + this->served_hostname;
|
||||
iq["to"] = jid_to;
|
||||
XmlNode ping("ping");
|
||||
ping["xmlns"] = PING_NS;
|
||||
ping.close();
|
||||
iq.add_child(std::move(ping));
|
||||
iq.close();
|
||||
this->send_stanza(iq);
|
||||
|
||||
auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza)
|
||||
{
|
||||
Jid to(stanza.get_tag("to"));
|
||||
if (to.local != from)
|
||||
{
|
||||
log_error("Received a corresponding ping result, but the 'to' from "
|
||||
"the response mismatches the 'from' of the request");
|
||||
}
|
||||
else
|
||||
bridge->send_irc_ping_result(from, id);
|
||||
};
|
||||
this->waiting_iq[id] = result_cb;
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_iq_room_list_result(const std::string& id,
|
||||
const std::string to_jid,
|
||||
const std::string& from,
|
||||
const std::vector<ListElement>& rooms_list)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["from"] = from + "@" + this->served_hostname;
|
||||
iq["to"] = to_jid;
|
||||
iq["id"] = id;
|
||||
iq["type"] = "result";
|
||||
XmlNode query("query");
|
||||
query["xmlns"] = DISCO_ITEMS_NS;
|
||||
for (const auto& room: rooms_list)
|
||||
{
|
||||
XmlNode item("item");
|
||||
item["jid"] = room.channel + "%" + from + "@" + this->served_hostname;
|
||||
item.close();
|
||||
query.add_child(std::move(item));
|
||||
}
|
||||
query.close();
|
||||
iq.add_child(std::move(query));
|
||||
iq.close();
|
||||
this->send_stanza(iq);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#ifndef BIBOUMI_COMPONENT_INCLUDED
|
||||
# define BIBOUMI_COMPONENT_INCLUDED
|
||||
|
||||
#include <xmpp/xmpp_component.hpp>
|
||||
|
||||
#include <bridge/bridge.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class ListElement;
|
||||
|
||||
/**
|
||||
* A callback called when the waited iq result is received (it is matched
|
||||
* against the iq id)
|
||||
*/
|
||||
using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza& stanza)>;
|
||||
|
||||
/**
|
||||
* Interact with the Biboumi Bridge
|
||||
*/
|
||||
class BiboumiComponent: public XmppComponent
|
||||
{
|
||||
public:
|
||||
explicit BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
|
||||
~BiboumiComponent() = default;
|
||||
|
||||
/**
|
||||
* Returns the bridge for the given user. If it does not exist, return
|
||||
* nullptr.
|
||||
*/
|
||||
Bridge* find_user_bridge(const std::string& user_jid);
|
||||
/**
|
||||
* Return a list of all the managed bridges.
|
||||
*/
|
||||
std::list<Bridge*> get_bridges() const;
|
||||
|
||||
/**
|
||||
* Send a "close" message to all our connected peers. That message
|
||||
* depends on the protocol used (this may be a QUIT irc message, or a
|
||||
* <stream/>, etc). We may also directly close the connection, or we may
|
||||
* wait for the remote peer to acknowledge it before closing.
|
||||
*/
|
||||
void shutdown();
|
||||
/**
|
||||
* Run a check on all bridges, to remove all disconnected (socket is
|
||||
* closed, or no channel is joined) IrcClients. Some kind of garbage collector.
|
||||
*/
|
||||
void clean();
|
||||
/**
|
||||
* Send a result IQ with the gateway disco informations.
|
||||
*/
|
||||
void send_self_disco_info(const std::string& id, const std::string& jid_to);
|
||||
/**
|
||||
* Send the list of all available ad-hoc commands to that JID. The list is
|
||||
* different depending on what JID made the request.
|
||||
*/
|
||||
void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid);
|
||||
/**
|
||||
* Send an iq version request
|
||||
*/
|
||||
void send_iq_version_request(const std::string& from,
|
||||
const std::string& jid_to);
|
||||
/**
|
||||
* Send a ping request
|
||||
*/
|
||||
void send_ping_request(const std::string& from,
|
||||
const std::string& jid_to,
|
||||
const std::string& id);
|
||||
/**
|
||||
* Send the channels list in one big stanza
|
||||
*/
|
||||
void send_iq_room_list_result(const std::string& id, const std::string to_jid,
|
||||
const std::string& from,
|
||||
const std::vector<ListElement>& rooms_list);
|
||||
/**
|
||||
* Handle the various stanza types
|
||||
*/
|
||||
void handle_presence(const Stanza& stanza);
|
||||
void handle_message(const Stanza& stanza);
|
||||
void handle_iq(const Stanza& stanza);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return the bridge associated with the given full JID. Create a new one
|
||||
* if none already exist.
|
||||
*/
|
||||
Bridge* get_user_bridge(const std::string& user_jid);
|
||||
|
||||
/**
|
||||
* A map of id -> callback. When we want to wait for an iq result, we add
|
||||
* the callback to this map, with the iq id as the key. When an iq result
|
||||
* is received, we look for a corresponding callback in this map. If
|
||||
* found, we call it and remove it.
|
||||
*/
|
||||
std::map<std::string, iq_responder_callback_t> waiting_iq;
|
||||
|
||||
/**
|
||||
* One bridge for each user of the component. Indexed by the user's full
|
||||
* jid
|
||||
*/
|
||||
std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges;
|
||||
|
||||
BiboumiComponent(const BiboumiComponent&) = delete;
|
||||
BiboumiComponent(BiboumiComponent&&) = delete;
|
||||
BiboumiComponent& operator=(const BiboumiComponent&) = delete;
|
||||
BiboumiComponent& operator=(BiboumiComponent&&) = delete;
|
||||
};
|
||||
|
||||
#endif // BIBOUMI_COMPONENT_INCLUDED
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
#include <xmpp/jid.hpp>
|
||||
#include <config.h>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
#include <louloulibs.h>
|
||||
#ifdef LIBIDN_FOUND
|
||||
#include <stringprep.h>
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,303 +0,0 @@
|
||||
#ifndef XMPP_COMPONENT_INCLUDED
|
||||
# define XMPP_COMPONENT_INCLUDED
|
||||
|
||||
#include <xmpp/adhoc_commands_handler.hpp>
|
||||
#include <network/tcp_socket_handler.hpp>
|
||||
#include <xmpp/xmpp_parser.hpp>
|
||||
#include <bridge/bridge.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#define STREAM_NS "http://etherx.jabber.org/streams"
|
||||
#define COMPONENT_NS "jabber:component:accept"
|
||||
#define MUC_NS "http://jabber.org/protocol/muc"
|
||||
#define MUC_USER_NS MUC_NS"#user"
|
||||
#define MUC_ADMIN_NS MUC_NS"#admin"
|
||||
#define DISCO_NS "http://jabber.org/protocol/disco"
|
||||
#define DISCO_ITEMS_NS DISCO_NS"#items"
|
||||
#define DISCO_INFO_NS DISCO_NS"#info"
|
||||
#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im"
|
||||
#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas"
|
||||
#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams"
|
||||
#define VERSION_NS "jabber:iq:version"
|
||||
#define ADHOC_NS "http://jabber.org/protocol/commands"
|
||||
#define PING_NS "urn:xmpp:ping"
|
||||
|
||||
class ListElement;
|
||||
|
||||
/**
|
||||
* A callback called when the waited iq result is received (it is matched
|
||||
* against the iq id)
|
||||
*/
|
||||
using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza& stanza)>;
|
||||
|
||||
/**
|
||||
* An XMPP component, communicating with an XMPP server using the protocole
|
||||
* described in XEP-0114: Jabber Component Protocol
|
||||
*
|
||||
* TODO: implement XEP-0225: Component Connections
|
||||
*/
|
||||
class XmppComponent: public TCPSocketHandler
|
||||
{
|
||||
public:
|
||||
explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
|
||||
~XmppComponent();
|
||||
|
||||
void on_connection_failed(const std::string& reason) override final;
|
||||
void on_connected() override final;
|
||||
void on_connection_close(const std::string& error) override final;
|
||||
void parse_in_buffer(const size_t size) override final;
|
||||
|
||||
/**
|
||||
* Returns the bridge for the given user. If it does not exist, return
|
||||
* nullptr.
|
||||
*/
|
||||
Bridge* find_user_bridge(const std::string& user_jid);
|
||||
/**
|
||||
* Return a list of all the managed bridges.
|
||||
*/
|
||||
std::list<Bridge*> get_bridges() const;
|
||||
|
||||
/**
|
||||
* Returns a unique id, to be used in the 'id' element of our iq stanzas.
|
||||
*/
|
||||
static std::string next_id();
|
||||
/**
|
||||
* Send a "close" message to all our connected peers. That message
|
||||
* depends on the protocol used (this may be a QUIT irc message, or a
|
||||
* <stream/>, etc). We may also directly close the connection, or we may
|
||||
* wait for the remote peer to acknowledge it before closing.
|
||||
*/
|
||||
void shutdown();
|
||||
bool is_document_open() const;
|
||||
/**
|
||||
* Run a check on all bridges, to remove all disconnected (socket is
|
||||
* closed, or no channel is joined) IrcClients. Some kind of garbage collector.
|
||||
*/
|
||||
void clean();
|
||||
/**
|
||||
* Connect to the XMPP server.
|
||||
*/
|
||||
void start();
|
||||
/**
|
||||
* Reset the component so we can use the component on a new XMPP stream
|
||||
*/
|
||||
void reset();
|
||||
/**
|
||||
* Serialize the stanza and add it to the out_buf to be sent to the
|
||||
* server.
|
||||
*/
|
||||
void send_stanza(const Stanza& stanza);
|
||||
/**
|
||||
* Handle the opening of the remote stream
|
||||
*/
|
||||
void on_remote_stream_open(const XmlNode& node);
|
||||
/**
|
||||
* Handle the closing of the remote stream
|
||||
*/
|
||||
void on_remote_stream_close(const XmlNode& node);
|
||||
/**
|
||||
* Handle received stanzas
|
||||
*/
|
||||
void on_stanza(const Stanza& stanza);
|
||||
/**
|
||||
* Send an error stanza. Message being the name of the element inside the
|
||||
* stanza, and explanation being a short human-readable sentence
|
||||
* describing the error.
|
||||
*/
|
||||
void send_stream_error(const std::string& message, const std::string& explanation);
|
||||
/**
|
||||
* Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error
|
||||
*/
|
||||
void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
|
||||
const std::string& id, const std::string& error_type,
|
||||
const std::string& defined_condition, const std::string& text,
|
||||
const bool fulljid=true);
|
||||
/**
|
||||
* Send the closing signal for our document (not closing the connection though).
|
||||
*/
|
||||
void close_document();
|
||||
/**
|
||||
* Send a message from from@served_hostname, with the given body
|
||||
*
|
||||
* If fulljid is false, the provided 'from' doesn't contain the
|
||||
* server-part of the JID and must be added.
|
||||
*/
|
||||
void send_message(const std::string& from, Xmpp::body&& body,
|
||||
const std::string& to, const std::string& type,
|
||||
const bool fulljid=false);
|
||||
/**
|
||||
* Send a join from a new participant
|
||||
*/
|
||||
void send_user_join(const std::string& from,
|
||||
const std::string& nick,
|
||||
const std::string& realjid,
|
||||
const std::string& affiliation,
|
||||
const std::string& role,
|
||||
const std::string& to,
|
||||
const bool self);
|
||||
/**
|
||||
* Send an error to indicate that the user tried to join an invalid room
|
||||
*/
|
||||
void send_invalid_room_error(const std::string& muc_jid,
|
||||
const std::string& nick,
|
||||
const std::string& to);
|
||||
/**
|
||||
* Send an error to indicate that the user tried to send a message to an
|
||||
* invalid user.
|
||||
*/
|
||||
void send_invalid_user_error(const std::string& user_name,
|
||||
const std::string& to);
|
||||
/**
|
||||
* Send the MUC topic to the user
|
||||
*/
|
||||
void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to);
|
||||
/**
|
||||
* Send a (non-private) message to the MUC
|
||||
*/
|
||||
void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to);
|
||||
/**
|
||||
* Send an unavailable presence for this nick
|
||||
*/
|
||||
void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self);
|
||||
/**
|
||||
* Indicate that a participant changed his nick
|
||||
*/
|
||||
void send_nick_change(const std::string& muc_name,
|
||||
const std::string& old_nick,
|
||||
const std::string& new_nick,
|
||||
const std::string& affiliation,
|
||||
const std::string& role,
|
||||
const std::string& jid_to,
|
||||
const bool self);
|
||||
/**
|
||||
* An user is kicked from a room
|
||||
*/
|
||||
void kick_user(const std::string& muc_name,
|
||||
const std::string& target,
|
||||
const std::string& reason,
|
||||
const std::string& author,
|
||||
const std::string& jid_to);
|
||||
/**
|
||||
* Send a generic presence error
|
||||
*/
|
||||
void send_presence_error(const std::string& muc_name,
|
||||
const std::string& nickname,
|
||||
const std::string& jid_to,
|
||||
const std::string& type,
|
||||
const std::string& condition,
|
||||
const std::string& error_code,
|
||||
const std::string& text);
|
||||
/**
|
||||
* Send a presence from the MUC indicating a change in the role and/or
|
||||
* affiliation of a participant
|
||||
*/
|
||||
void send_affiliation_role_change(const std::string& muc_name,
|
||||
const std::string& target,
|
||||
const std::string& affiliation,
|
||||
const std::string& role,
|
||||
const std::string& jid_to);
|
||||
/**
|
||||
* Send a result IQ with the gateway disco informations.
|
||||
*/
|
||||
void send_self_disco_info(const std::string& id, const std::string& jid_to);
|
||||
/**
|
||||
* Send a result IQ with the given version, or the gateway version if the
|
||||
* passed string is empty.
|
||||
*/
|
||||
void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
|
||||
const std::string& version="");
|
||||
/**
|
||||
* Send the list of all available ad-hoc commands to that JID. The list is
|
||||
* different depending on what JID made the request.
|
||||
*/
|
||||
void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid);
|
||||
/**
|
||||
* Send an iq version request
|
||||
*/
|
||||
void send_iq_version_request(const std::string& from,
|
||||
const std::string& jid_to);
|
||||
/**
|
||||
* Send a ping request
|
||||
*/
|
||||
void send_ping_request(const std::string& from,
|
||||
const std::string& jid_to,
|
||||
const std::string& id);
|
||||
/**
|
||||
* Send an empty iq of type result
|
||||
*/
|
||||
void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from);
|
||||
/**
|
||||
* Send the channels list in one big stanza
|
||||
*/
|
||||
void send_iq_room_list_result(const std::string& id, const std::string to_jid,
|
||||
const std::string& from,
|
||||
const std::vector<ListElement>& rooms_list);
|
||||
/**
|
||||
* Handle the various stanza types
|
||||
*/
|
||||
void handle_handshake(const Stanza& stanza);
|
||||
void handle_presence(const Stanza& stanza);
|
||||
void handle_message(const Stanza& stanza);
|
||||
void handle_iq(const Stanza& stanza);
|
||||
void handle_error(const Stanza& stanza);
|
||||
|
||||
/**
|
||||
* Whether or not we ever succeeded our authentication to the XMPP server
|
||||
*/
|
||||
bool ever_auth;
|
||||
/**
|
||||
* Whether or not this is the first consecutive try on connecting to the
|
||||
* XMPP server. We use this to delay the connection attempt for a few
|
||||
* seconds, if it is not the first try.
|
||||
*/
|
||||
bool first_connection_try;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return the bridge associated with the given full JID. Create a new one
|
||||
* if none already exist.
|
||||
*/
|
||||
Bridge* get_user_bridge(const std::string& user_jid);
|
||||
/**
|
||||
* Return a buffer provided by the XML parser, to read data directly into
|
||||
* it, and avoiding some unnecessary copy.
|
||||
*/
|
||||
void* get_receive_buffer(const size_t size) const override final;
|
||||
XmppParser parser;
|
||||
std::string stream_id;
|
||||
std::string served_hostname;
|
||||
std::string secret;
|
||||
bool authenticated;
|
||||
/**
|
||||
* Whether or not OUR XMPP document is open
|
||||
*/
|
||||
bool doc_open;
|
||||
|
||||
std::unordered_map<std::string, std::function<void(const Stanza&)>> stanza_handlers;
|
||||
AdhocCommandsHandler adhoc_commands_handler;
|
||||
|
||||
/**
|
||||
* A map of id -> callback. When we want to wait for an iq result, we add
|
||||
* the callback to this map, with the iq id as the key. When an iq result
|
||||
* is received, we look for a corresponding callback in this map. If
|
||||
* found, we call it and remove it.
|
||||
*/
|
||||
std::map<std::string, iq_responder_callback_t> waiting_iq;
|
||||
|
||||
/**
|
||||
* One bridge for each user of the component. Indexed by the user's full
|
||||
* jid
|
||||
*/
|
||||
std::unordered_map<std::string, std::unique_ptr<Bridge>> bridges;
|
||||
|
||||
XmppComponent(const XmppComponent&) = delete;
|
||||
XmppComponent(XmppComponent&&) = delete;
|
||||
XmppComponent& operator=(const XmppComponent&) = delete;
|
||||
XmppComponent& operator=(XmppComponent&&) = delete;
|
||||
};
|
||||
|
||||
#endif // XMPP_COMPONENT_INCLUDED
|
||||
Reference in New Issue
Block a user