~singpolyma/biboumi

d600a2843f1dbe3b1ba2dead9a020cc73d7d10ae — Florent Le Coz 9 years ago 1028d4d
Remove all the libs that are now in louloulibs
51 files changed, 713 insertions(+), 4916 deletions(-)

M CMakeLists.txt
D cmake/Modules/FindBOTAN.cmake
D cmake/Modules/FindCARES.cmake
D cmake/Modules/FindICONV.cmake
D cmake/Modules/FindLIBIDN.cmake
D cmake/Modules/FindLIBUUID.cmake
D cmake/Modules/FindSYSTEMD.cmake
M src/bridge/bridge.cpp
M src/bridge/bridge.hpp
D src/config.h.cmake
D src/config/config.cpp
D src/config/config.hpp
M src/irc/irc_client.cpp
D src/logger/logger.cpp
D src/logger/logger.hpp
M src/main.cpp
D src/network/dns_handler.cpp
D src/network/dns_handler.hpp
D src/network/dns_socket_handler.cpp
D src/network/dns_socket_handler.hpp
D src/network/poller.cpp
D src/network/poller.hpp
D src/network/socket_handler.hpp
D src/network/tcp_socket_handler.cpp
D src/network/tcp_socket_handler.hpp
D src/utils/encoding.cpp
D src/utils/encoding.hpp
D src/utils/reload.cpp
D src/utils/reload.hpp
D src/utils/revstr.cpp
D src/utils/revstr.hpp
D src/utils/scopeguard.hpp
D src/utils/sha1.cpp
D src/utils/sha1.hpp
D src/utils/split.cpp
D src/utils/split.hpp
D src/utils/timed_events.cpp
D src/utils/timed_events.hpp
D src/utils/timed_events_manager.cpp
D src/utils/tolower.cpp
D src/utils/tolower.hpp
M src/xmpp/adhoc_command.cpp
D src/xmpp/adhoc_commands_handler.cpp
D src/xmpp/adhoc_commands_handler.hpp
D src/xmpp/adhoc_session.cpp
D src/xmpp/adhoc_session.hpp
A src/xmpp/biboumi_component.cpp
A src/xmpp/biboumi_component.hpp
M src/xmpp/jid.cpp
D src/xmpp/xmpp_component.cpp
D src/xmpp/xmpp_component.hpp
M CMakeLists.txt => CMakeLists.txt +18 -105
@@ 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)
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")

#
## logger
#
file(GLOB source_logger
  src/logger/*.[hc]pp)
add_library(logger STATIC ${source_logger})
target_link_libraries(logger config)
include_directories(${EXPAT_INCLUDE_DIRS})
include_directories(${ICONV_INCLUDE_DIRS})
include_directories(${LIBUUID_INCLUDE_DIRS})

#
## 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})
endif()
# 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
#

D cmake/Modules/FindBOTAN.cmake => cmake/Modules/FindBOTAN.cmake +0 -35
@@ 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)

D cmake/Modules/FindCARES.cmake => cmake/Modules/FindCARES.cmake +0 -37
@@ 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)

D cmake/Modules/FindICONV.cmake => cmake/Modules/FindICONV.cmake +0 -60
@@ 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)
\ No newline at end of file

D cmake/Modules/FindLIBIDN.cmake => cmake/Modules/FindLIBIDN.cmake +0 -41
@@ 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)
\ No newline at end of file

D cmake/Modules/FindLIBUUID.cmake => cmake/Modules/FindLIBUUID.cmake +0 -41
@@ 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)

D cmake/Modules/FindSYSTEMD.cmake => cmake/Modules/FindSYSTEMD.cmake +0 -39
@@ 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)
\ No newline at end of file

M src/bridge/bridge.cpp => src/bridge/bridge.cpp +2 -2
@@ 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)

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +3 -3
@@ 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.

D src/config.h.cmake => src/config.h.cmake +0 -8
@@ 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}"

D src/config/config.cpp => src/config/config.cpp +0 -122
@@ 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();
}

D src/config/config.hpp => src/config/config.hpp +0 -103
@@ 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

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +1 -1
@@ 14,7 14,7 @@
#include <chrono>
#include <string>

#include "config.h"
#include "louloulibs.h"

using namespace std::string_literals;
using namespace std::chrono_literals;

D src/logger/logger.cpp => src/logger/logger.cpp +0 -38
@@ 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;
}

D src/logger/logger.hpp => src/logger/logger.hpp +0 -81
@@ 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

M src/main.cpp => src/main.cpp +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);


D src/network/dns_handler.cpp => src/network/dns_handler.cpp +0 -112
@@ 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 */

D src/network/dns_handler.hpp => src/network/dns_handler.hpp +0 -62
@@ 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 */

D src/network/dns_socket_handler.cpp => src/network/dns_socket_handler.cpp +0 -45
@@ 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 */

D src/network/dns_socket_handler.hpp => src/network/dns_socket_handler.hpp +0 -46
@@ 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

D src/network/poller.cpp => src/network/poller.cpp +0 -203
@@ 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();
}

D src/network/poller.hpp => src/network/poller.hpp +0 -95
@@ 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

D src/network/socket_handler.hpp => src/network/socket_handler.hpp +0 -45
@@ 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

D src/network/tcp_socket_handler.cpp => src/network/tcp_socket_handler.cpp +0 -572
@@ 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

D src/network/tcp_socket_handler.hpp => src/network/tcp_socket_handler.hpp +0 -297
@@ 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

D src/utils/encoding.cpp => src/utils/encoding.cpp +0 -221
@@ 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;
  }

}


D src/utils/encoding.hpp => src/utils/encoding.hpp +0 -29
@@ 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

D src/utils/reload.cpp => src/utils/reload.cpp +0 -13
@@ 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.");
}

D src/utils/reload.hpp => src/utils/reload.hpp +0 -10
@@ 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 */

D src/utils/revstr.cpp => src/utils/revstr.cpp +0 -9
@@ 1,9 0,0 @@
#include <utils/revstr.hpp>

namespace utils
{
  std::string revstr(const std::string& original)
  {
    return {original.rbegin(), original.rend()};
  }
}

D src/utils/revstr.hpp => src/utils/revstr.hpp +0 -11
@@ 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

D src/utils/scopeguard.hpp => src/utils/scopeguard.hpp +0 -89
@@ 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

D src/utils/sha1.cpp => src/utils/sha1.cpp +0 -154
@@ 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);
}

D src/utils/sha1.hpp => src/utils/sha1.hpp +0 -35
@@ 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);

D src/utils/split.cpp => src/utils/split.cpp +0 -18
@@ 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;
  }
}

D src/utils/split.hpp => src/utils/split.hpp +0 -13
@@ 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

D src/utils/timed_events.cpp => src/utils/timed_events.cpp +0 -62
@@ 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;
}

D src/utils/timed_events.hpp => src/utils/timed_events.hpp +0 -132
@@ 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

D src/utils/timed_events_manager.cpp => src/utils/timed_events_manager.cpp +0 -81
@@ 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();
}

D src/utils/tolower.cpp => src/utils/tolower.cpp +0 -13
@@ 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;
  }
}

D src/utils/tolower.hpp => src/utils/tolower.hpp +0 -11
@@ 1,11 0,0 @@
#ifndef TOLOWER_INCLUDED
# define TOLOWER_INCLUDED

#include <string>

namespace utils
{
  std::string tolower(const std::string& original);
}

#endif // SPLIT_INCLUDED

M src/xmpp/adhoc_command.cpp => src/xmpp/adhoc_command.cpp +7 -3
@@ 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);

D src/xmpp/adhoc_commands_handler.cpp => src/xmpp/adhoc_commands_handler.cpp +0 -143
@@ 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");
}

D src/xmpp/adhoc_commands_handler.hpp => src/xmpp/adhoc_commands_handler.hpp +0 -69
@@ 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

D src/xmpp/adhoc_session.cpp => src/xmpp/adhoc_session.cpp +0 -37
@@ 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;
}

D src/xmpp/adhoc_session.hpp => src/xmpp/adhoc_session.hpp +0 -70
@@ 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

A src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +568 -0
@@ 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);
}

A src/xmpp/biboumi_component.hpp => src/xmpp/biboumi_component.hpp +111 -0
@@ 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

M src/xmpp/jid.cpp => src/xmpp/jid.cpp +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

D src/xmpp/xmpp_component.cpp => src/xmpp/xmpp_component.cpp +0 -1194
@@ 1,1194 0,0 @@
#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>

#include <xmpp/xmpp_component.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 <config.h>

#include <uuid.h>

#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif

using namespace std::string_literals;

static std::set<s