summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.rst40
-rw-r--r--CMakeLists.txt343
-rw-r--r--CONTRIBUTING.rst45
-rw-r--r--INSTALL.rst4
-rw-r--r--README.rst6
-rw-r--r--biboumi.h.cmake11
-rw-r--r--cmake/Modules/FindBOTAN.cmake45
-rw-r--r--cmake/Modules/FindGCRYPT.cmake41
-rw-r--r--cmake/Modules/FindICONV.cmake (renamed from louloulibs/cmake/Modules/FindICONV.cmake)4
-rw-r--r--cmake/Modules/FindLIBIDN.cmake (renamed from louloulibs/cmake/Modules/FindLIBIDN.cmake)11
-rw-r--r--cmake/Modules/FindLIBUUID.cmake (renamed from louloulibs/cmake/Modules/FindLIBUUID.cmake)9
-rw-r--r--cmake/Modules/FindLITESQL.cmake4
-rw-r--r--cmake/Modules/FindSYSTEMD.cmake (renamed from louloulibs/cmake/Modules/FindSYSTEMD.cmake)11
-rw-r--r--cmake/Modules/FindUDNS.cmake (renamed from louloulibs/cmake/Modules/FindUDNS.cmake)5
-rw-r--r--conf/irc.mozilla.org.policy.txt1
-rw-r--r--conf/irc.ppirc.net.policy.txt2
-rw-r--r--conf/policy.txt2
-rw-r--r--database/database.xml3
-rw-r--r--debian/changelog57
-rw-r--r--debian/control5
-rw-r--r--debian/control.in4
-rw-r--r--debian/copyright2
-rwxr-xr-xdebian/copyright-check27
-rw-r--r--debian/copyright_hints166
-rw-r--r--debian/patches/020161125~ad22be4.patch38
-rw-r--r--debian/patches/020161204~eb8f1cb.patch24
-rw-r--r--debian/patches/2001_cmake_ignore_git.patch16
-rw-r--r--debian/patches/series2
-rwxr-xr-xdebian/rules12
-rw-r--r--debian/watch1
-rw-r--r--doc/biboumi.1.rst145
-rw-r--r--docker/biboumi-test/alpine/Dockerfile10
-rw-r--r--docker/biboumi-test/alpine/Dockerfile.base51
-rw-r--r--docker/biboumi-test/archlinux/Dockerfile13
-rw-r--r--docker/biboumi-test/debian/Dockerfile.base1
-rw-r--r--docker/biboumi-test/fedora/Dockerfile.base2
-rw-r--r--docker/biboumi/Dockerfile36
-rw-r--r--docker/biboumi/README.md16
-rw-r--r--docker/biboumi/biboumi.cfg8
-rw-r--r--docker/biboumi/entrypoint.sh11
-rw-r--r--docker/packaging/debian/Dockerfile10
-rw-r--r--louloulibs/CMakeLists.txt181
-rw-r--r--louloulibs/cmake/Modules/FindBOTAN.cmake35
-rw-r--r--louloulibs/cmake/Modules/FindGCRYPT.cmake35
-rw-r--r--louloulibs/louloulibs.h.cmake11
-rw-r--r--packaging/biboumi.spec.cmake16
-rw-r--r--src/bridge/bridge.cpp153
-rw-r--r--src/bridge/bridge.hpp23
-rw-r--r--src/bridge/colors.cpp2
-rw-r--r--src/bridge/colors.hpp2
-rw-r--r--src/config/config.cpp (renamed from louloulibs/config/config.cpp)48
-rw-r--r--src/config/config.hpp (renamed from louloulibs/config/config.hpp)2
-rw-r--r--src/database/database.cpp16
-rw-r--r--src/database/database.hpp4
-rw-r--r--src/identd/identd_server.hpp2
-rw-r--r--src/identd/identd_socket.cpp2
-rw-r--r--src/identd/identd_socket.hpp3
-rw-r--r--src/irc/iid.cpp7
-rw-r--r--src/irc/iid.hpp2
-rw-r--r--src/irc/irc_channel.cpp24
-rw-r--r--src/irc/irc_channel.hpp5
-rw-r--r--src/irc/irc_client.cpp190
-rw-r--r--src/irc/irc_client.hpp12
-rw-r--r--src/irc/irc_message.cpp6
-rw-r--r--src/irc/irc_user.cpp2
-rw-r--r--src/irc/irc_user.hpp2
-rw-r--r--src/logger/logger.cpp (renamed from louloulibs/logger/logger.cpp)0
-rw-r--r--src/logger/logger.hpp (renamed from louloulibs/logger/logger.hpp)2
-rw-r--r--src/main.cpp9
-rw-r--r--src/network/credentials_manager.cpp (renamed from louloulibs/network/credentials_manager.cpp)10
-rw-r--r--src/network/credentials_manager.hpp (renamed from louloulibs/network/credentials_manager.hpp)4
-rw-r--r--src/network/dns_handler.cpp (renamed from louloulibs/network/dns_handler.cpp)4
-rw-r--r--src/network/dns_handler.hpp (renamed from louloulibs/network/dns_handler.hpp)4
-rw-r--r--src/network/dns_socket_handler.cpp (renamed from louloulibs/network/dns_socket_handler.cpp)4
-rw-r--r--src/network/dns_socket_handler.hpp (renamed from louloulibs/network/dns_socket_handler.hpp)4
-rw-r--r--src/network/poller.cpp (renamed from louloulibs/network/poller.cpp)12
-rw-r--r--src/network/poller.hpp (renamed from louloulibs/network/poller.hpp)2
-rw-r--r--src/network/resolver.cpp (renamed from louloulibs/network/resolver.cpp)34
-rw-r--r--src/network/resolver.hpp (renamed from louloulibs/network/resolver.hpp)6
-rw-r--r--src/network/socket_handler.hpp (renamed from louloulibs/network/socket_handler.hpp)4
-rw-r--r--src/network/tcp_client_socket_handler.cpp (renamed from louloulibs/network/tcp_client_socket_handler.cpp)10
-rw-r--r--src/network/tcp_client_socket_handler.hpp (renamed from louloulibs/network/tcp_client_socket_handler.hpp)2
-rw-r--r--src/network/tcp_server_socket.hpp (renamed from louloulibs/network/tcp_server_socket.hpp)3
-rw-r--r--src/network/tcp_socket_handler.cpp (renamed from louloulibs/network/tcp_socket_handler.cpp)30
-rw-r--r--src/network/tcp_socket_handler.hpp (renamed from louloulibs/network/tcp_socket_handler.hpp)21
-rw-r--r--src/network/tls_policy.cpp48
-rw-r--r--src/network/tls_policy.hpp28
-rw-r--r--src/utils/dirname.cpp16
-rw-r--r--src/utils/dirname.hpp6
-rw-r--r--src/utils/encoding.cpp (renamed from louloulibs/utils/encoding.cpp)2
-rw-r--r--src/utils/encoding.hpp (renamed from louloulibs/utils/encoding.hpp)2
-rw-r--r--src/utils/get_first_non_empty.cpp (renamed from louloulibs/utils/get_first_non_empty.cpp)0
-rw-r--r--src/utils/get_first_non_empty.hpp (renamed from louloulibs/utils/get_first_non_empty.hpp)0
-rw-r--r--src/utils/revstr.cpp (renamed from louloulibs/utils/revstr.cpp)0
-rw-r--r--src/utils/revstr.hpp (renamed from louloulibs/utils/revstr.hpp)0
-rw-r--r--src/utils/scopeguard.hpp (renamed from louloulibs/utils/scopeguard.hpp)0
-rw-r--r--src/utils/sha1.cpp (renamed from louloulibs/utils/sha1.cpp)10
-rw-r--r--src/utils/sha1.hpp (renamed from louloulibs/utils/sha1.hpp)0
-rw-r--r--src/utils/split.cpp (renamed from louloulibs/utils/split.cpp)0
-rw-r--r--src/utils/split.hpp (renamed from louloulibs/utils/split.hpp)0
-rw-r--r--src/utils/string.cpp (renamed from louloulibs/utils/string.cpp)0
-rw-r--r--src/utils/string.hpp (renamed from louloulibs/utils/string.hpp)2
-rw-r--r--src/utils/system.cpp (renamed from louloulibs/utils/system.cpp)2
-rw-r--r--src/utils/system.hpp (renamed from louloulibs/utils/system.hpp)0
-rw-r--r--src/utils/time.cpp (renamed from louloulibs/utils/time.cpp)22
-rw-r--r--src/utils/time.hpp (renamed from louloulibs/utils/time.hpp)0
-rw-r--r--src/utils/timed_events.cpp (renamed from louloulibs/utils/timed_events.cpp)23
-rw-r--r--src/utils/timed_events.hpp (renamed from louloulibs/utils/timed_events.hpp)9
-rw-r--r--src/utils/timed_events_manager.cpp (renamed from louloulibs/utils/timed_events_manager.cpp)14
-rw-r--r--src/utils/tolower.cpp (renamed from louloulibs/utils/tolower.cpp)0
-rw-r--r--src/utils/tolower.hpp (renamed from louloulibs/utils/tolower.hpp)0
-rw-r--r--src/utils/xdg.cpp (renamed from louloulibs/utils/xdg.cpp)2
-rw-r--r--src/utils/xdg.hpp (renamed from louloulibs/utils/xdg.hpp)2
-rw-r--r--src/xmpp/adhoc_command.cpp (renamed from louloulibs/xmpp/adhoc_command.cpp)5
-rw-r--r--src/xmpp/adhoc_command.hpp (renamed from louloulibs/xmpp/adhoc_command.hpp)2
-rw-r--r--src/xmpp/adhoc_commands_handler.cpp (renamed from louloulibs/xmpp/adhoc_commands_handler.cpp)0
-rw-r--r--src/xmpp/adhoc_commands_handler.hpp (renamed from louloulibs/xmpp/adhoc_commands_handler.hpp)0
-rw-r--r--src/xmpp/adhoc_session.cpp (renamed from louloulibs/xmpp/adhoc_session.cpp)2
-rw-r--r--src/xmpp/adhoc_session.hpp (renamed from louloulibs/xmpp/adhoc_session.hpp)0
-rw-r--r--src/xmpp/auth.cpp (renamed from louloulibs/xmpp/auth.cpp)0
-rw-r--r--src/xmpp/auth.hpp (renamed from louloulibs/xmpp/auth.hpp)0
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp124
-rw-r--r--src/xmpp/biboumi_adhoc_commands.hpp3
-rw-r--r--src/xmpp/biboumi_component.cpp137
-rw-r--r--src/xmpp/biboumi_component.hpp8
-rw-r--r--src/xmpp/body.hpp (renamed from louloulibs/xmpp/body.hpp)0
-rw-r--r--src/xmpp/jid.cpp (renamed from louloulibs/xmpp/jid.cpp)7
-rw-r--r--src/xmpp/jid.hpp (renamed from louloulibs/xmpp/jid.hpp)0
-rw-r--r--src/xmpp/xmpp_component.cpp (renamed from louloulibs/xmpp/xmpp_component.cpp)30
-rw-r--r--src/xmpp/xmpp_component.hpp (renamed from louloulibs/xmpp/xmpp_component.hpp)13
-rw-r--r--src/xmpp/xmpp_parser.cpp (renamed from louloulibs/xmpp/xmpp_parser.cpp)0
-rw-r--r--src/xmpp/xmpp_parser.hpp (renamed from louloulibs/xmpp/xmpp_parser.hpp)2
-rw-r--r--src/xmpp/xmpp_stanza.cpp (renamed from louloulibs/xmpp/xmpp_stanza.cpp)4
-rw-r--r--src/xmpp/xmpp_stanza.hpp (renamed from louloulibs/xmpp/xmpp_stanza.hpp)0
-rw-r--r--tests/end_to_end/__main__.py556
-rw-r--r--tests/end_to_end/ircd.conf5
-rw-r--r--tests/jid.cpp2
-rw-r--r--tests/network.cpp44
-rw-r--r--tests/utils.cpp15
-rw-r--r--tests/xmpp.cpp11
141 files changed, 2261 insertions, 1092 deletions
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index b408e6c..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/.pc
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 69676cb..5709483 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,9 +1,43 @@
-Version 5.0
-===========
+Version 5.0 - 2017-05-24
+========================
- - An identd server has been added
+ - An identd server has been added.
+ - Add a **persistent** option for channels. When a channel is configured
+ as persistent, when the user leaves the room, biboumi stays idle and keeps
+ saving the received messages in the archive, instead of leaving the channel
+ entirely. When the user re-joins the room later, biboumi sends the message
+ history to her/him. This feature can be used to make biboumi behave like
+ an IRC bouncer.
- Use the udns library instead of c-ares, for asynchronous DNS resolution.
It’s still fully optional.
+ - Update MAM implementation to version 6.0 (namespace mam:2)
+ - If the client doesn’t specify any limit in its MAM and channel list request,
+ the results returned by biboumi contain at most 100 messages, instead of
+ the potentially huge complete result.
+ - Multiline topics are now properly handled
+ - Configuration options can be overridden by values found in the process env.
+ - Botan’s TLS policies can be customized by the administrator, for each
+ IRC server, with simple text files.
+ - The IRC channel configuration form is now also available using the MUC
+ configuration, in addition to the ad-hoc command.
+ - Notices starting with [#channel] are considered as welcome messages coming
+ from that channel, instead of private messages.
+
+Version 4.3 - 2017-05-02
+========================
+
+ - Fix a segmentation fault that occured when trying to connect to an IRC
+ server without any port configured.
+
+Version 4.2 - 2017-04-26
+========================
+
+ - Fix a build issue when LiteSQL is absent from the system
+
+Version 4.1 - 2017-03-21
+========================
+
+ - Works with botan 2.x, as well as botan 1.11.x
Version 4.0 - 2016-11-09
========================
diff --git a/CMakeLists.txt b/CMakeLists.txt
index da9cf37..139f85a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,36 +4,35 @@ project(biboumi)
set(${PROJECT_NAME}_VERSION_MAJOR 5)
set(${PROJECT_NAME}_VERSION_MINOR 0)
-set(${PROJECT_NAME}_VERSION_SUFFIX "~dev")
+set(${PROJECT_NAME}_VERSION_SUFFIX "")
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
+ "Build type (Release/Debug/RelWithDebInfo/MinSizeRel)" FORCE)
+endif()
+
+#
+## Find optional instrumentation libraries that will be used in debug only
+#
find_library(LIBASAN NAMES asan libasan.so.3 libasan.so.2 libasan.so.1)
find_library(LIBUBSAN NAMES ubsan libubsan.so.0)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra")
+#
+## Set various debug flags (instrumentation libs, coverage, …)
+#
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra -Wconversion")
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage")
endif()
if(LIBASAN)
- message(STATUS "Libasan found.")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
-else()
- message(STATUS "Libasan NOT found.")
endif()
if(LIBUBSAN)
- message(STATUS "Libubsan found.")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined")
-else()
- message(STATUS "Libubsan NOT found.")
endif()
-
-#
-## Look for external libraries
#
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
-
-#
-## Get the software version
+## Set the software version, archive name, RPM name etc
#
set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR})
set(RPM_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR})
@@ -62,16 +61,8 @@ endif()
set(SOFTWARE_VERSION
${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX})
-include(CheckFunctionExists)
-check_function_exists(ppoll HAVE_PPOLL_FUNCTION)
-
-# To be able to include the config.h and other files generated by cmake
-include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/")
-include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")
-include_directories("${CMAKE_CURRENT_BINARY_DIR}/")
-
#
-## Documentation
+## The rule that generates the documentation
#
execute_process(COMMAND "date" "+%Y-%m-%d" OUTPUT_VARIABLE DOC_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
@@ -94,35 +85,57 @@ if (NOT PANDOC_EXECUTABLE)
endif()
mark_as_advanced(PANDOC_EXECUTABLE)
-# Look for litesql and enable the database if found
+#
+## Set this search path for cmake, to find our custom search modules
+#
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+find_package(ICONV REQUIRED)
+find_package(LIBUUID REQUIRED)
+find_package(EXPAT REQUIRED)
+
+#
+## Find all the libraries (optional or not)
+#
+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(NOT BOTAN_FOUND)
+ find_package(GCRYPT REQUIRED)
+endif()
+
+if(WITH_UDNS)
+ find_package(UDNS REQUIRED)
+elseif(NOT WITHOUT_UDNS)
+ find_package(UDNS)
+endif()
+
if(WITH_LITESQL)
find_package(LITESQL REQUIRED)
elseif(NOT WITHOUT_LITESQL)
find_package(LITESQL)
endif()
-if(LITESQL_FOUND)
- LITESQL_GENERATE_CPP("database/database.xml"
- "biboudb"
- LITESQL_GENERATED_SOURCES)
-
- add_library(database STATIC src/database/database.cpp
- ${LITESQL_GENERATED_SOURCES})
- target_link_libraries(database ${LITESQL_LIBRARIES} utils)
- if(BOTAN_FOUND)
- target_link_libraries(database ${BOTAN_LIBRARIES})
- endif()
- set(USE_DATABASE TRUE)
-endif()
-
-add_subdirectory("louloulibs")
-include_directories("louloulibs")
-
+#
+## Set all the include directories, depending on what libraries are used
+#
include_directories(${EXPAT_INCLUDE_DIRS})
include_directories(${ICONV_INCLUDE_DIRS})
include_directories(${LIBUUID_INCLUDE_DIRS})
-
-# If they are found in louloulibs CMakeLists.txt, we inherite these values
if(SYSTEMD_FOUND)
include_directories(${SYSTEMD_INCLUDE_DIRS})
endif()
@@ -133,90 +146,131 @@ if(UDNS_FOUND)
include_directories(${UDNS_INCLUDE_DIRS})
endif()
-#
-## utils
-#
-file(GLOB source_src_utils
- src/utils/*.[hc]pp)
-# Todo, switch to target_sources(utils) when we go cmake >=3.1 only
-add_library(src_utils STATIC ${source_src_utils})
-target_link_libraries(src_utils logger config)
-if(USE_DATABASE)
- target_link_libraries(src_utils database)
-endif()
+# To be able to include the config.h and other files generated by cmake
+include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/")
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/")
+include_directories("${CMAKE_CURRENT_BINARY_DIR}/")
#
-## irclib
+## Define all the modules
#
+
+file(GLOB source_utils
+ src/utils/*.[hc]pp)
+add_library(utils OBJECT ${source_utils})
+add_dependencies(utils litesql_generated_sources)
+
file(GLOB source_irc
- src/irc/*.[hc]pp)
-add_library(irc STATIC ${source_irc})
-target_link_libraries(irc network utils logger)
+ src/irc/*.[hc]pp)
+add_library(irc OBJECT ${source_irc})
+add_dependencies(irc litesql_generated_sources)
-#
-## xmpp
-#
file(GLOB source_xmpp
- src/xmpp/*.[hc]pp)
-add_library(xmpp STATIC ${source_xmpp})
-target_link_libraries(xmpp xmpplib bridge network utils src_utils logger)
-
-if(USE_DATABASE)
- target_link_libraries(xmpp database)
- target_link_libraries(irc database)
-endif()
+ src/xmpp/*.[hc]pp)
+add_library(xmpp OBJECT ${source_xmpp})
+add_dependencies(xmpp litesql_generated_sources)
-#
-## identd
-#
file(GLOB source_identd
- src/identd/*.[hc]pp)
-add_library(identd STATIC ${source_identd})
-target_link_libraries(identd bridge network utils src_utils logger)
+ src/identd/*.[hc]pp)
+add_library(identd OBJECT ${source_identd})
-#
-## bridge
-#
file(GLOB source_bridge
- src/bridge/*.[hc]pp)
-add_library(bridge STATIC ${source_bridge})
-target_link_libraries(bridge xmpp irc utils logger)
+ src/bridge/*.[hc]pp)
+add_library(bridge OBJECT ${source_bridge})
+add_dependencies(bridge litesql_generated_sources)
-#
-## Main executable
-#
-add_executable(${PROJECT_NAME} src/main.cpp)
-target_link_libraries(${PROJECT_NAME}
- xmpp
- irc
- bridge
- utils
- src_utils
- identd
- config)
-if(SYSTEMD_FOUND)
- target_link_libraries(xmpp ${SYSTEMD_LIBRARIES})
+file(GLOB source_config
+ src/config/*.[hc]pp)
+add_library(config OBJECT ${source_config})
+
+file(GLOB source_logger
+ src/logger/*.[hc]pp)
+add_library(logger OBJECT ${source_logger})
+
+file(GLOB source_network
+ src/network/*.[hc]pp)
+add_library(network OBJECT ${source_network})
+
+if(LITESQL_FOUND)
+ LITESQL_GENERATE_CPP("database/database.xml"
+ "biboudb"
+ LITESQL_GENERATED_SOURCES)
+ add_custom_target(litesql_generated_sources SOURCES ${LITESQL_GENERATED_SOURCES})
+
+ add_library(database OBJECT src/database/database.cpp ${LITESQL_GENERATED_SOURCES})
+ add_dependencies(database litesql_generated_sources)
+
+ include_directories(database ${LITESQL_INCLUDE_DIRS})
+ set(USE_DATABASE TRUE)
+else()
+ add_library(database OBJECT "")
+ add_custom_target(litesql_generated_sources)
endif()
#
-## Tests
+## Define the executables
#
+
+## main
+add_executable(${PROJECT_NAME} src/main.cpp
+ $<TARGET_OBJECTS:utils>
+ $<TARGET_OBJECTS:config>
+ $<TARGET_OBJECTS:logger>
+ $<TARGET_OBJECTS:network>
+ $<TARGET_OBJECTS:xmpp>
+ $<TARGET_OBJECTS:bridge>
+ $<TARGET_OBJECTS:irc>
+ $<TARGET_OBJECTS:identd>
+ $<TARGET_OBJECTS:database>)
+
+## test_suite
file(GLOB source_tests
tests/*.cpp)
-add_executable(test_suite EXCLUDE_FROM_ALL
- ${source_tests})
+add_executable(test_suite ${source_tests}
+ $<TARGET_OBJECTS:utils>
+ $<TARGET_OBJECTS:config>
+ $<TARGET_OBJECTS:logger>
+ $<TARGET_OBJECTS:network>
+ $<TARGET_OBJECTS:xmpp>
+ $<TARGET_OBJECTS:bridge>
+ $<TARGET_OBJECTS:irc>
+ $<TARGET_OBJECTS:identd>
+ $<TARGET_OBJECTS:database>)
+set_target_properties(test_suite PROPERTIES EXCLUDE_FROM_ALL TRUE)
+
+#
+## Link the executables with their libraries
+#
+target_link_libraries(${PROJECT_NAME}
+ ${ICONV_LIBRARIES}
+ ${LIBUUID_LIBRARIES}
+ ${EXPAT_LIBRARY})
target_link_libraries(test_suite
- xmpplib
- xmpp
- irc
- bridge
- utils
- config
- logger
- network)
+ ${ICONV_LIBRARIES}
+ ${LIBUUID_LIBRARIES}
+ ${EXPAT_LIBRARY})
+if(SYSTEMD_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${SYSTEMD_LIBRARIES})
+ target_link_libraries(test_suite ${SYSTEMD_LIBRARIES})
+endif()
+if(BOTAN_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${BOTAN_LIBRARIES})
+ target_link_libraries(test_suite ${BOTAN_LIBRARIES})
+elseif(GCRYPT_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${GCRYPT_LIBRARIES})
+ target_link_libraries(test_suite ${GCRYPT_LIBRARIES})
+endif()
+if(UDNS_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${UDNS_LIBRARIES})
+ target_link_libraries(test_suite ${UDNS_LIBRARIES})
+endif()
+if(LIBIDN_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${LIBIDN_LIBRARIES})
+ target_link_libraries(test_suite ${LIBIDN_LIBRARIES})
+endif()
if(USE_DATABASE)
- target_link_libraries(test_suite
- database)
+ target_link_libraries(${PROJECT_NAME} ${LITESQL_LIBRARIES})
+ target_link_libraries(test_suite ${LITESQL_LIBRARIES})
endif()
# Define a __FILENAME__ macro with the relative path (from the base project directory)
@@ -227,6 +281,9 @@ foreach(file ${source_all})
set_property(SOURCE ${file} APPEND PROPERTY COMPILE_DEFINITIONS __FILENAME__="${shorter_file}")
endforeach()
+#
+## Add a rule to download the catch unit test framework
+#
include(ExternalProject)
ExternalProject_Add(catch
GIT_REPOSITORY "https://lab.louiz.org/louiz/Catch.git"
@@ -244,27 +301,30 @@ if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp)
)
add_dependencies(test_suite catch)
endif()
+
+#
+## Add some custom rules to launch the tests
+#
add_custom_target(check COMMAND "test_suite"
DEPENDS test_suite biboumi)
+set_target_properties(check PROPERTIES EXCLUDE_FROM_ALL TRUE)
add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/"
DEPENDS biboumi)
+set_target_properties(e2e PROPERTIES EXCLUDE_FROM_ALL TRUE)
add_custom_target(e2e_valgrind COMMAND "E2E_BIBOUMI_SUPP_DIR=${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" "E2E_BIBOUMI_VALGRIND=1" "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/"
DEPENDS biboumi)
-
-
-#
-## Code coverage
-#
if(CMAKE_BUILD_TYPE MATCHES Debug)
include(CodeCoverage)
SETUP_TARGET_FOR_COVERAGE(coverage_check
- make
- coverage_test_suite
- check)
+ ./test_suite
+ coverage_test_suite)
+ add_dependencies(coverage_check test_suite)
+
SETUP_TARGET_FOR_COVERAGE(coverage_e2e
- make
+ python3
coverage_e2e
- e2e)
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/)
+ add_dependencies(coverage_e2e biboumi)
ADD_CUSTOM_TARGET(coverage
COMMAND ${LCOV_PATH} -a coverage_e2e.info -a coverage_test_suite.info -o coverage_total.info
@@ -273,8 +333,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
-
endif()
+add_custom_target(everything DEPENDS test_suite biboumi)
#
## Install target
@@ -282,7 +342,8 @@ endif()
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL COMPONENT documentation)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION lib/systemd/system COMPONENT init)
-install(FILES conf/biboumi.cfg DESTINATION /etc/biboumi COMPONENT configuration)
+file(GLOB policy_files conf/*policy.txt)
+install(FILES ${policy_files} DESTINATION /etc/biboumi COMPONENT configuration)
#
## Dist target
@@ -310,8 +371,9 @@ add_custom_target(rpm
COMMAND rpmbuild --define "_topdir `pwd`/rpmbuild/" --define "_sourcedir `pwd`" -ba biboumi.spec
)
-configure_file(biboumi.h.cmake src/biboumi.h)
-
+#
+## Set some variables that will be used in the cmake-generated files
+#
set(SYSTEMD_SERVICE_TYPE_DOCSTRING "The value used as the Type= in the systemd unit file.")
set(WATCHDOG_SEC_DOCSTRING "The value used as WatchdogSec= in the systemd unit file.")
if(SYSTEMD_FOUND)
@@ -329,7 +391,6 @@ set(SERVICE_GROUP_DOCSTRING "The value used as the Group= in the systemd unit fi
if(NOT DEFINED SERVICE_GROUP)
set(SERVICE_GROUP "nobody" CACHE STRING ${SERVICE_GROUP_DOCSTRING})
endif()
-configure_file(unit/biboumi.service.cmake biboumi.service)
# Force the format of the date output
set(ENV{LANG} "C")
@@ -337,4 +398,38 @@ execute_process(COMMAND "date" "+%a %b %d %Y" OUTPUT_VARIABLE RPM_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
unset(ENV{LANG})
+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()
+
+#
+## Check if we have std::get_time and put_time
+#
+include(CheckCXXSourceCompiles)
+
+check_cxx_source_compiles("
+ #include <iomanip>
+ int main()
+ { std::get_time(nullptr, \"\"); }"
+ HAS_GET_TIME)
+
+mark_as_advanced(HAS_GET_TIME)
+
+check_cxx_source_compiles("
+ #include <iomanip>
+ int main()
+ { std::put_time(nullptr, \"\"); }"
+ HAS_PUT_TIME)
+
+mark_as_advanced(HAS_PUT_TIME)
+
+configure_file(unit/biboumi.service.cmake biboumi.service)
configure_file(packaging/biboumi.spec.cmake biboumi.spec)
+configure_file(biboumi.h.cmake src/biboumi.h)
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index ed3915f..74459d1 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -9,12 +9,17 @@ but that’s mainly for the convenience of users.
Before doing anything, you can come on the `XMPP chatroom`_ to discuss your
changes, issues or ideas.
+
Bug reports, feature requests
-----------------------------
To open a bug report, or a feature request, please do so on `our gitlab’s
bug tracker`_.
+If the bug you’re reporting is about a bad behaviour of biboumi when some XMPP
+or IRC events occur, please try to reproduce the issue with a biboumi running
+in log_level=0, and include the relevant logs in your bug report.
+
If the issue you’re reporting may have security implications, please select
the “confidential” flag in your bug report.
@@ -34,6 +39,42 @@ It is also recommended to add some unit or end-to-end tests for the proposed
changes.
+Tests
+-----
+
+There are two test suites for biboumi:
+
+- unit tests that can be run simply using `make check`.
+ These tests use the Catch test framework, are written in pure C++
+ and they should always succeed, in all possible build configuration.
+
+- a more complex end-to-end test suite. This test suite is written in python3,
+ uses a specific IRC server (`charybdis`_), and only tests the most complete
+ biboumi configuration (when all dependencies are used). To run it, you need
+ to install various dependencies: refer to fedora’s `Dockerfile.base`_ and
+ `Dockerfile`_ to see how to install charybdis, slixmpp, botan, litesql, an
+ ssl certificate, etc.
+
+ Once all the dependencies are correctly installed, the tests are run with
+
+ `make e2e`
+
+ To run one or more specific tests, you can do something like this:
+
+ `make biboumi && python3 ../tests/end_to_end self_ping basic_handshake_success`
+
+ This will run two tests, self_ping and basic_handshake_success.
+
+ To write additional tests, you need to add a Scenario
+ into `the __main__.py file`_. If you have problem running this end-to-end
+ test suite, or if you struggle with this weird code (that would be
+ completely normal…), don’t hesitate to ask for help.
+
+
+All these tests automatically run with various configurations, on various
+platforms, using gitlab CI.
+
+
Coding style
------------
Please try to follow the existing style:
@@ -50,3 +91,7 @@ Please try to follow the existing style:
.. _gitlab merge request: https://lab.louiz.org/louiz/biboumi/merge_requests/new
.. _github pull request: https://github.com/louiz/biboumi/pulls
.. _XMPP chatroom: xmpp:biboumi@muc.poez.io
+.. _Dockerfile.base: docker/biboumi-test/fedora/Dockerfile.base
+.. _Dockerfile: docker/biboumi-test/fedora/Dockerfile
+.. _charybdis: https://github.com/charybdis-ircd/charybdis
+.. _the __main__.py file: tests/end_to_end/__main__.py \ No newline at end of file
diff --git a/INSTALL.rst b/INSTALL.rst
index 9815af9..6cd85d2 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -118,7 +118,9 @@ This command will configure the project to build a release, with TLS enabled
Build
-----
-Once you’ve configured everything using cmake, build the project
+Once you’ve configured everything using cmake, build the software:
+
+To build the biboumi binary:
make
diff --git a/README.rst b/README.rst
index 02cfa3d..5ee9846 100644
--- a/README.rst
+++ b/README.rst
@@ -4,13 +4,13 @@ Biboumi
.. image:: https://lab.louiz.org/louiz/biboumi/badges/master/build.svg
:target: https://lab.louiz.org/louiz/biboumi/pipelines
-.. image:: https://codecov.io/gh/louiz/biboumi/branch/master/graph/badge.svg
+.. image:: https://codecov.proxy.louiz.org/gh/louiz/biboumi/branch/master/graph/badge.svg
:target: https://codecov.io/gh/louiz/biboumi
-.. image:: https://scan.coverity.com/projects/3726/badge.svg
+.. image:: https://coverity.proxy.louiz.org/projects/3726/badge.svg
:target: https://scan.coverity.com/projects/louiz-biboumi
-.. image:: https://bestpractices.coreinfrastructure.org/projects/450/badge
+.. image:: https://coreinfrastructure.proxy.louiz.org/projects/450/badge
:target: https://bestpractices.coreinfrastructure.org/projects/450
Biboumi is an XMPP gateway that connects to IRC servers and translates
diff --git a/biboumi.h.cmake b/biboumi.h.cmake
index beb67d0..1ad9a40 100644
--- a/biboumi.h.cmake
+++ b/biboumi.h.cmake
@@ -1 +1,12 @@
#cmakedefine USE_DATABASE
+#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST
+#cmakedefine LIBIDN_FOUND
+#cmakedefine SYSTEMD_FOUND
+#cmakedefine POLLER ${POLLER}
+#cmakedefine BOTAN_FOUND
+#cmakedefine GCRYPT_FOUND
+#cmakedefine UDNS_FOUND
+#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}"
+#cmakedefine PROJECT_NAME "${PROJECT_NAME}"
+#cmakedefine HAS_GET_TIME
+#cmakedefine HAS_PUT_TIME
diff --git a/cmake/Modules/FindBOTAN.cmake b/cmake/Modules/FindBOTAN.cmake
new file mode 100644
index 0000000..9eb76c4
--- /dev/null
+++ b/cmake/Modules/FindBOTAN.cmake
@@ -0,0 +1,45 @@
+# - 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
+
+include(FindPkgConfig)
+
+if(NOT BOTAN_FOUND)
+ pkg_check_modules(BOTAN botan-2)
+ pkg_check_modules(BOTAN botan-1.11)
+endif()
+
+if(NOT BOTAN_FOUND)
+ find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h
+ PATH_SUFFIXES botan-2 botan-1.11
+ DOC "The botan include directory")
+
+ find_library(BOTAN_LIBRARIES NAMES botan botan-2 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} CACHE INTERNAL "")
+ set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(BOTAN_FOUND ${BOTAN_FOUND} CACHE INTERNAL "")
+ endif()
+endif()
+
+mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES)
diff --git a/cmake/Modules/FindGCRYPT.cmake b/cmake/Modules/FindGCRYPT.cmake
new file mode 100644
index 0000000..b73bfd0
--- /dev/null
+++ b/cmake/Modules/FindGCRYPT.cmake
@@ -0,0 +1,41 @@
+# - Find gcrypt
+# Find the gcrypt cryptographic library
+#
+# This module defines the following variables:
+# GCRYPT_FOUND - True if library and include directory are found
+# If set to TRUE, the following are also defined:
+# GCRYPT_INCLUDE_DIRS - The directory where to find the header file
+# GCRYPT_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.
+# GCRYPT_LIBRARY
+# GCRYPT_INCLUDE_DIR
+#
+# This file is in the public domain
+
+include(FindPkgConfig)
+pkg_check_modules(GCRYPT gcrypt)
+
+if(NOT GCRYPT_FOUND)
+ find_path(GCRYPT_INCLUDE_DIRS NAMES gcrypt.h
+ PATH_SUFFIXES gcrypt
+ DOC "The gcrypt include directory")
+
+ find_library(GCRYPT_LIBRARIES NAMES gcrypt
+ DOC "The gcrypt library")
+
+ # Use some standard module to handle the QUIETLY and REQUIRED arguments, and
+ # set GCRYPT_FOUND to TRUE if these two variables are set.
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(GCRYPT REQUIRED_VARS GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIRS)
+
+ if(GCRYPT_FOUND)
+ set(GCRYPT_LIBRARY ${GCRYPT_LIBRARIES} CACHE INTERNAL "")
+ set(GCRYPT_INCLUDE_DIR ${GCRYPT_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(GCRYPT_FOUND ${GCRYPT_FOUND} CACHE INTERNAL "")
+ endif()
+endif()
+
+mark_as_advanced(GCRYPT_INCLUDE_DIRS GCRYPT_LIBRARIES)
diff --git a/louloulibs/cmake/Modules/FindICONV.cmake b/cmake/Modules/FindICONV.cmake
index 7ca173f..fb78ac7 100644
--- a/louloulibs/cmake/Modules/FindICONV.cmake
+++ b/cmake/Modules/FindICONV.cmake
@@ -52,9 +52,9 @@ if(ICONV_FOUND)
return 0;}"
ICONV_SECOND_ARGUMENT_IS_CONST)
-# Compatibility for all the ways of writing these variables
+ # 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
+mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST)
diff --git a/louloulibs/cmake/Modules/FindLIBIDN.cmake b/cmake/Modules/FindLIBIDN.cmake
index 611a6a8..c769d06 100644
--- a/louloulibs/cmake/Modules/FindLIBIDN.cmake
+++ b/cmake/Modules/FindLIBIDN.cmake
@@ -16,7 +16,9 @@
# This file is in the public domain
include(FindPkgConfig)
-pkg_check_modules(LIBIDN libidn)
+if(NOT LIBIDN_FOUND)
+ pkg_check_modules(LIBIDN libidn)
+endif()
if(NOT LIBIDN_FOUND)
find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h
@@ -33,9 +35,10 @@ if(NOT LIBIDN_FOUND)
# Compatibility for all the ways of writing these variables
if(LIBIDN_FOUND)
- set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS})
- set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES})
+ set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES} CACHE INTERNAL "")
+ set(LIBIDN_FOUN ${LIBIDN_FOUND} CACHE INTERNAL "")
endif()
endif()
-mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file
+mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES)
diff --git a/louloulibs/cmake/Modules/FindLIBUUID.cmake b/cmake/Modules/FindLIBUUID.cmake
index f344249..67352ab 100644
--- a/louloulibs/cmake/Modules/FindLIBUUID.cmake
+++ b/cmake/Modules/FindLIBUUID.cmake
@@ -16,7 +16,9 @@
# This file is in the public domain
include(FindPkgConfig)
-pkg_check_modules(LIBUUID uuid)
+if(NOT LIBUUID_FOUND)
+ pkg_check_modules(LIBUUID uuid)
+endif()
if(NOT LIBUUID_FOUND)
find_path(LIBUUID_INCLUDE_DIRS NAMES uuid/uuid.h
@@ -33,8 +35,9 @@ if(NOT LIBUUID_FOUND)
# Compatibility for all the ways of writing these variables
if(LIBUUID_FOUND)
- set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS})
- set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES})
+ set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES} CACHE INTERNAL "")
+ set(LIBUUID_FOUND ${LIBUUID_FOUND} CACHE INTERNAL "")
endif()
endif()
diff --git a/cmake/Modules/FindLITESQL.cmake b/cmake/Modules/FindLITESQL.cmake
index 91155bb..2d3b073 100644
--- a/cmake/Modules/FindLITESQL.cmake
+++ b/cmake/Modules/FindLITESQL.cmake
@@ -64,8 +64,8 @@ function(LITESQL_GENERATE_CPP
set(${OUTPUT_SOURCES})
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.cpp"
- "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.hpp"
- COMMAND ${LITESQLGEN_EXECUTABLE}
+ "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.hpp"
+ COMMAND ${LITESQLGEN_EXECUTABLE}
ARGS -t c++ --output-dir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE}
DEPENDS ${SOURCE_FILE}
COMMENT "Running litesql-gen on ${SOURCE_FILE}"
diff --git a/louloulibs/cmake/Modules/FindSYSTEMD.cmake b/cmake/Modules/FindSYSTEMD.cmake
index c7decde..2d3f063 100644
--- a/louloulibs/cmake/Modules/FindSYSTEMD.cmake
+++ b/cmake/Modules/FindSYSTEMD.cmake
@@ -16,7 +16,9 @@
# This file is in the public domain
include(FindPkgConfig)
-pkg_check_modules(SYSTEMD libsystemd)
+if(NOT SYSTEMD_FOUND)
+ pkg_check_modules(SYSTEMD libsystemd)
+endif()
if(NOT SYSTEMD_FOUND)
find_path(SYSTEMD_INCLUDE_DIRS NAMES systemd/sd-daemon.h
@@ -31,9 +33,10 @@ if(NOT SYSTEMD_FOUND)
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})
+ set(SYSTEMD_LIBRARY ${SYSTEMD_LIBRARIES} CACHE INTERNAL "")
+ set(SYSTEMD_INCLUDE_DIR ${SYSTEMD_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(SYSTEMD_FOUND ${SYSTEMD_FOUND} CACHE INTERNAL "")
endif()
endif()
-mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) \ No newline at end of file
+mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES)
diff --git a/louloulibs/cmake/Modules/FindUDNS.cmake b/cmake/Modules/FindUDNS.cmake
index 1d32cd3..9576b2a 100644
--- a/louloulibs/cmake/Modules/FindUDNS.cmake
+++ b/cmake/Modules/FindUDNS.cmake
@@ -29,8 +29,9 @@ if(NOT UDNS_FOUND)
# Compatibility for all the ways of writing these variables
if(UDNS_FOUND)
- set(UDNS_INCLUDE_DIR ${UDNS_INCLUDE_DIRS})
- set(UDNS_LIBRARY ${UDNS_LIBRARIES})
+ set(UDNS_INCLUDE_DIR ${UDNS_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(UDNS_LIBRARY ${UDNS_LIBRARIES} CACHE INTERNAL "")
+ set(UDNS_FOUND ${UDNS_FOUND} CACHE INTERNAL "")
endif()
endif()
diff --git a/conf/irc.mozilla.org.policy.txt b/conf/irc.mozilla.org.policy.txt
new file mode 100644
index 0000000..f099e61
--- /dev/null
+++ b/conf/irc.mozilla.org.policy.txt
@@ -0,0 +1 @@
+minimum_dh_group_size = 1024
diff --git a/conf/irc.ppirc.net.policy.txt b/conf/irc.ppirc.net.policy.txt
new file mode 100644
index 0000000..557d129
--- /dev/null
+++ b/conf/irc.ppirc.net.policy.txt
@@ -0,0 +1,2 @@
+key_exchange_methods = RSA
+minimum_rsa_bits = 1024
diff --git a/conf/policy.txt b/conf/policy.txt
new file mode 100644
index 0000000..8edc09f
--- /dev/null
+++ b/conf/policy.txt
@@ -0,0 +1,2 @@
+require_cert_revocation_info = false
+use_ecc_point_compression = true
diff --git a/database/database.xml b/database/database.xml
index 7dc70e1..e641fdf 100644
--- a/database/database.xml
+++ b/database/database.xml
@@ -24,7 +24,6 @@
<field name="realname" type="string" length="1024" default=""/>
<field name="verifyCert" type="boolean" default="true"/>
<field name="trustedFingerprint" type="string"/>
- <field name="lingerTime" type="integer" default="0"/>
<field name="encodingOut" type="string" default="ISO-8859-1"/>
<field name="encodingIn" type="string" default="ISO-8859-1"/>
@@ -46,6 +45,8 @@
<field name="maxHistoryLength" type="integer" default="20"/>
+ <field name="persistent" type="boolean" default="false"/>
+
<index unique="true">
<indexfield name="owner"/>
<indexfield name="server"/>
diff --git a/debian/changelog b/debian/changelog
index bc485af..608f2a2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,60 @@
+biboumi (5.0-1) unstable; urgency=medium
+
+ [ upstream ]
+ * New release.
+ + Add identd server.
+ + Add persistent option for channels, to behave like an IRC bouncer.
+ + Use udns library (not c-ares) for asynchronous DNS resolution.
+ + Update MAM implementation to version 6.0 (namespace mam:2).
+ + Limit MAM to 100 messages per channel by default.
+ + Properly handle multiline topics.
+ + Support overriding configuration options by environment values.
+ + Support customizing Botan TLS policies per IRC server.
+ + IRC channel config form now available via MUC config.
+ + Notices starting with "#channel" now treated as welcome messages.
+
+ [ Jonas Smedegaard ]
+ * Declare compliance with Debian Policy 4.0.0.
+ * Modernize Vcs-Browser field:
+ + Use git (not cgit) in path.
+ * Update package relations:
+ + Build-depend on libudns-dev (not libc-ares-dev).
+ + Build-depend on libgcrypt20-dev.
+ + Stop build-depend on libbotan1.10-dev (unused: Too old).
+ * Drop patch 2002: Fixed upstream.
+ * Unfuzz patch 2001.
+
+ -- Jonas Smedegaard <dr@jones.dk> Sat, 24 Jun 2017 10:15:23 +0200
+
+biboumi (4.3-1) unstable; urgency=medium
+
+ [ upstream ]
+ * New release(s).
+ + Works with botan 2.x (not only botan 1.11.x).
+ + Fix build without LiteSQL available.
+ + Fix segmentation fault connecting to IRC server on undefined port.
+
+ [ Jonas Smedegaard ]
+ * Update watch file:
+ + Fix mangle upstream filename (lacked versioning, confusing uscan).
+ * Modernize cdbs:
+ + Do copyright-check in maintainer script (not during build).
+ + Stop build-depend on licensecheck.
+ * Drop patch cherry-picked upstream and now applied.
+ * Drop patch cherry-picked upstream but now mysteriously gone from
+ upstream master branch.
+ * Update copyright info:
+ + Extend copyright for my parts to cover current year.
+
+ -- Jonas Smedegaard <dr@jones.dk> Sun, 21 May 2017 12:03:04 +0200
+
+biboumi (4.0-2) unstable; urgency=medium
+
+ * Fix set proper group (and explicitly set user too, while at it).
+ Closes: Bug#854252. Thanks to Jonas Wielicki and Florent Le Coz.
+
+ -- Jonas Smedegaard <dr@jones.dk> Sat, 04 Mar 2017 20:37:49 +0100
+
biboumi (4.0-1) unstable; urgency=medium
[ Vasudev Kamath & Jonas Smedegaard ]
diff --git a/debian/control b/debian/control
index 8a61261..0404a78 100644
--- a/debian/control
+++ b/debian/control
@@ -4,7 +4,6 @@ Priority: optional
Build-Depends: cdbs,
debhelper,
dh-buildinfo,
- licensecheck,
cmake,
catch,
uuid-dev,
@@ -17,9 +16,9 @@ Build-Depends: cdbs,
Maintainer: Debian VoIP Team <pkg-voip-maintainers@lists.alioth.debian.org>
Uploaders: Jonas Smedegaard <dr@jones.dk>,
Vasudev Kamath <vasudev@copyninja.info>
-Standards-Version: 3.9.8
+Standards-Version: 4.0.0
Vcs-Git: https://anonscm.debian.org/git/pkg-voip/biboumi.git
-Vcs-Browser: https://anonscm.debian.org/cgit/pkg-voip/biboumi.git
+Vcs-Browser: https://anonscm.debian.org/git/pkg-voip/biboumi.git
Homepage: https://lab.louiz.org/louiz/biboumi/
Package: biboumi
diff --git a/debian/control.in b/debian/control.in
index 9cbaed8..11feb97 100644
--- a/debian/control.in
+++ b/debian/control.in
@@ -5,9 +5,9 @@ Build-Depends: @cdbs@
Maintainer: Debian VoIP Team <pkg-voip-maintainers@lists.alioth.debian.org>
Uploaders: Jonas Smedegaard <dr@jones.dk>,
Vasudev Kamath <vasudev@copyninja.info>
-Standards-Version: 3.9.8
+Standards-Version: 4.0.0
Vcs-Git: https://anonscm.debian.org/git/pkg-voip/biboumi.git
-Vcs-Browser: https://anonscm.debian.org/cgit/pkg-voip/biboumi.git
+Vcs-Browser: https://anonscm.debian.org/git/pkg-voip/biboumi.git
Homepage: https://lab.louiz.org/louiz/biboumi/
Package: biboumi
diff --git a/debian/copyright b/debian/copyright
index 7882205..36c542a 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -28,7 +28,7 @@ License: public-domain
Files: debian/*
Copyright: 2016, Vasudev Kamath <vasudev@copyninja.info>
- 2016, Jonas Smedegaard <dr@jones.dk>
+ 2016-2017, Jonas Smedegaard <dr@jones.dk>
License-Grant:
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
diff --git a/debian/copyright-check b/debian/copyright-check
new file mode 100755
index 0000000..1f715d9
--- /dev/null
+++ b/debian/copyright-check
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright © 2016-2017 Jonas Smedegaard <dr@jones.dk>
+# Description: helper script to update copyright_hints
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -eu
+
+# extract metadata from binary media files
+#export DEB_COPYRIGHT_EXTRACT_EXTS=png
+
+make -f /usr/share/cdbs/1/rules/utils.mk pre-build || true
+make -f /usr/share/cdbs/1/rules/utils.mk clean DEB_COPYRIGHT_CHECK_STRICT=1
+
+# unconditionally merge changes - safe to do with git-tracked package
+[ ! -f debian/copyright_newhints ] || mv -f debian/copyright_newhints debian/copyright_hints
diff --git a/debian/copyright_hints b/debian/copyright_hints
index 9e2ff85..ada5b91 100644
--- a/debian/copyright_hints
+++ b/debian/copyright_hints
@@ -11,14 +11,14 @@ Files: CHANGELOG.rst
README.rst
biboumi.h.cmake
conf/biboumi.cfg
+ conf/irc.mozilla.org.policy.txt
+ conf/irc.ppirc.net.policy.txt
+ conf/policy.txt
database/database.xml
debian/compat
debian/control
debian/control.in
- debian/files
debian/gbp.conf
- debian/patches/020161125~ad22be4.patch
- debian/patches/020161204~eb8f1cb.patch
debian/patches/2001_cmake_ignore_git.patch
debian/patches/2002_fix_default_locale.patch
debian/patches/README
@@ -28,66 +28,17 @@ Files: CHANGELOG.rst
debian/watch
doc/biboumi.1.rst
doc/example.conf
+ docker/biboumi-test/alpine/Dockerfile
+ docker/biboumi-test/alpine/Dockerfile.base
+ docker/biboumi-test/archlinux/Dockerfile
docker/biboumi-test/debian/Dockerfile
+ docker/biboumi-test/debian/Dockerfile.base
docker/biboumi-test/fedora/Dockerfile
- louloulibs/CMakeLists.txt
- louloulibs/config/config.cpp
- louloulibs/config/config.hpp
- louloulibs/logger/logger.cpp
- louloulibs/logger/logger.hpp
- louloulibs/louloulibs.h.cmake
- louloulibs/network/credentials_manager.cpp
- louloulibs/network/credentials_manager.hpp
- louloulibs/network/dns_handler.cpp
- louloulibs/network/dns_handler.hpp
- louloulibs/network/dns_socket_handler.cpp
- louloulibs/network/dns_socket_handler.hpp
- louloulibs/network/poller.cpp
- louloulibs/network/poller.hpp
- louloulibs/network/resolver.cpp
- louloulibs/network/resolver.hpp
- louloulibs/network/socket_handler.hpp
- louloulibs/network/tcp_socket_handler.cpp
- louloulibs/network/tcp_socket_handler.hpp
- louloulibs/utils/encoding.cpp
- louloulibs/utils/encoding.hpp
- louloulibs/utils/get_first_non_empty.cpp
- louloulibs/utils/get_first_non_empty.hpp
- louloulibs/utils/revstr.cpp
- louloulibs/utils/revstr.hpp
- louloulibs/utils/scopeguard.hpp
- louloulibs/utils/sha1.cpp
- louloulibs/utils/sha1.hpp
- louloulibs/utils/split.cpp
- louloulibs/utils/split.hpp
- louloulibs/utils/string.cpp
- louloulibs/utils/string.hpp
- louloulibs/utils/time.cpp
- louloulibs/utils/time.hpp
- louloulibs/utils/timed_events.cpp
- louloulibs/utils/timed_events.hpp
- louloulibs/utils/timed_events_manager.cpp
- louloulibs/utils/tolower.cpp
- louloulibs/utils/tolower.hpp
- louloulibs/utils/xdg.cpp
- louloulibs/utils/xdg.hpp
- louloulibs/xmpp/adhoc_command.cpp
- louloulibs/xmpp/adhoc_command.hpp
- louloulibs/xmpp/adhoc_commands_handler.cpp
- louloulibs/xmpp/adhoc_commands_handler.hpp
- louloulibs/xmpp/adhoc_session.cpp
- louloulibs/xmpp/adhoc_session.hpp
- louloulibs/xmpp/auth.cpp
- louloulibs/xmpp/auth.hpp
- louloulibs/xmpp/body.hpp
- louloulibs/xmpp/jid.cpp
- louloulibs/xmpp/jid.hpp
- louloulibs/xmpp/xmpp_component.cpp
- louloulibs/xmpp/xmpp_component.hpp
- louloulibs/xmpp/xmpp_parser.cpp
- louloulibs/xmpp/xmpp_parser.hpp
- louloulibs/xmpp/xmpp_stanza.cpp
- louloulibs/xmpp/xmpp_stanza.hpp
+ docker/biboumi-test/fedora/Dockerfile.base
+ docker/biboumi/Dockerfile
+ docker/biboumi/README.md
+ docker/biboumi/biboumi.cfg
+ docker/packaging/debian/Dockerfile
packaging/biboumi.spec.cmake
src/bridge/bridge.cpp
src/bridge/bridge.hpp
@@ -95,8 +46,13 @@ Files: CHANGELOG.rst
src/bridge/colors.hpp
src/bridge/list_element.hpp
src/bridge/result_set_management.hpp
+ src/config/config.cpp
+ src/config/config.hpp
src/database/database.cpp
src/database/database.hpp
+ src/identd/identd_server.hpp
+ src/identd/identd_socket.cpp
+ src/identd/identd_socket.hpp
src/irc/iid.cpp
src/irc/iid.hpp
src/irc/irc_channel.cpp
@@ -107,14 +63,77 @@ Files: CHANGELOG.rst
src/irc/irc_message.hpp
src/irc/irc_user.cpp
src/irc/irc_user.hpp
+ src/logger/logger.cpp
+ src/logger/logger.hpp
src/main.cpp
+ src/network/credentials_manager.cpp
+ src/network/credentials_manager.hpp
+ src/network/dns_handler.cpp
+ src/network/dns_handler.hpp
+ src/network/dns_socket_handler.cpp
+ src/network/dns_socket_handler.hpp
+ src/network/poller.cpp
+ src/network/poller.hpp
+ src/network/resolver.cpp
+ src/network/resolver.hpp
+ src/network/socket_handler.hpp
+ src/network/tcp_client_socket_handler.cpp
+ src/network/tcp_client_socket_handler.hpp
+ src/network/tcp_server_socket.hpp
+ src/network/tcp_socket_handler.cpp
+ src/network/tcp_socket_handler.hpp
+ src/network/tls_policy.cpp
+ src/network/tls_policy.hpp
+ src/utils/dirname.cpp
+ src/utils/dirname.hpp
src/utils/empty_if_fixed_server.hpp
+ src/utils/encoding.cpp
+ src/utils/encoding.hpp
+ src/utils/get_first_non_empty.cpp
+ src/utils/get_first_non_empty.hpp
src/utils/reload.cpp
src/utils/reload.hpp
+ src/utils/revstr.cpp
+ src/utils/revstr.hpp
+ src/utils/scopeguard.hpp
+ src/utils/sha1.cpp
+ src/utils/sha1.hpp
+ src/utils/split.cpp
+ src/utils/split.hpp
+ src/utils/string.cpp
+ src/utils/string.hpp
+ src/utils/system.cpp
+ src/utils/system.hpp
+ src/utils/time.cpp
+ src/utils/time.hpp
+ src/utils/timed_events.cpp
+ src/utils/timed_events.hpp
+ src/utils/timed_events_manager.cpp
+ src/utils/tolower.cpp
+ src/utils/tolower.hpp
+ src/utils/xdg.cpp
+ src/utils/xdg.hpp
+ src/xmpp/adhoc_command.cpp
+ src/xmpp/adhoc_command.hpp
+ src/xmpp/adhoc_commands_handler.cpp
+ src/xmpp/adhoc_commands_handler.hpp
+ src/xmpp/adhoc_session.cpp
+ src/xmpp/adhoc_session.hpp
+ src/xmpp/auth.cpp
+ src/xmpp/auth.hpp
src/xmpp/biboumi_adhoc_commands.cpp
src/xmpp/biboumi_adhoc_commands.hpp
src/xmpp/biboumi_component.cpp
src/xmpp/biboumi_component.hpp
+ src/xmpp/body.hpp
+ src/xmpp/jid.cpp
+ src/xmpp/jid.hpp
+ src/xmpp/xmpp_component.cpp
+ src/xmpp/xmpp_component.hpp
+ src/xmpp/xmpp_parser.cpp
+ src/xmpp/xmpp_parser.hpp
+ src/xmpp/xmpp_stanza.cpp
+ src/xmpp/xmpp_stanza.hpp
tests/colors.cpp
tests/config.cpp
tests/database.cpp
@@ -125,6 +144,7 @@ Files: CHANGELOG.rst
tests/io_tester.hpp
tests/jid.cpp
tests/logger.cpp
+ tests/network.cpp
tests/test.cpp
tests/timed_events.cpp
tests/utils.cpp
@@ -135,13 +155,14 @@ Copyright: NONE
License: UNKNOWN
FIXME
-Files: cmake/Modules/FindLITESQL.cmake
- louloulibs/cmake/Modules/FindBOTAN.cmake
- louloulibs/cmake/Modules/FindCARES.cmake
- louloulibs/cmake/Modules/FindICONV.cmake
- louloulibs/cmake/Modules/FindLIBIDN.cmake
- louloulibs/cmake/Modules/FindLIBUUID.cmake
- louloulibs/cmake/Modules/FindSYSTEMD.cmake
+Files: cmake/Modules/FindBOTAN.cmake
+ cmake/Modules/FindGCRYPT.cmake
+ cmake/Modules/FindICONV.cmake
+ cmake/Modules/FindLIBIDN.cmake
+ cmake/Modules/FindLIBUUID.cmake
+ cmake/Modules/FindLITESQL.cmake
+ cmake/Modules/FindSYSTEMD.cmake
+ cmake/Modules/FindUDNS.cmake
Copyright: NONE
License: public-domain
FIXME
@@ -151,9 +172,14 @@ Copyright: 2012-2015, Lars Bilke
License: BSD-3-clause
FIXME
+Files: debian/copyright-check
+Copyright: 2016-2017, Jonas Smedegaard <dr@jones.dk>
+License: GPL-3+
+ FIXME
+
Files: debian/rules
-Copyright: 2016, Jonas Smedegaard <dr@jones.dk>
- 2016, Vasudev Kamath <vasudev@copyninja.info>
+Copyright: 2016, Vasudev Kamath <vasudev@copyninja.info>
+ 2016-2017, Jonas Smedegaard <dr@jones.dk>
License: GPL-3+
FIXME
diff --git a/debian/patches/020161125~ad22be4.patch b/debian/patches/020161125~ad22be4.patch
deleted file mode 100644
index 46013bc..0000000
--- a/debian/patches/020161125~ad22be4.patch
+++ /dev/null
@@ -1,38 +0,0 @@
-Description: Do not fail to build if litesql is not there
-Origin: upstream, https://lab.louiz.org/louiz/biboumi/commit/ad22be4
-Author: louiz’ <louiz@louiz.org>
-Forwarded: yes
-Last-Update: 2016-12-21
-
---- a/src/main.cpp
-+++ b/src/main.cpp
-@@ -12,7 +12,9 @@
-
- #include <atomic>
- #include <signal.h>
--#include <litesql.hpp>
-+#ifdef USE_DATABASE
-+# include <litesql.hpp>
-+#endif
-
- // A flag set by the SIGINT signal handler.
- static std::atomic<bool> stop(false);
-@@ -83,11 +85,14 @@
- if (hostname.empty())
- return config_help("hostname");
-
-+
-+#ifdef USE_DATABASE
- try {
-- open_database();
-- } catch (const litesql::DatabaseError&) {
-- return 1;
-- }
-+ open_database();
-+ } catch (const litesql::DatabaseError&) {
-+ return 1;
-+ }
-+#endif
-
- // Block the signals we want to manage. They will be unblocked only during
- // the epoll_pwait or ppoll calls. This avoids some race conditions,
diff --git a/debian/patches/020161204~eb8f1cb.patch b/debian/patches/020161204~eb8f1cb.patch
deleted file mode 100644
index e303cbc..0000000
--- a/debian/patches/020161204~eb8f1cb.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-Description: Avoid a potential nullptr dereference
-Origin: upstream, https://lab.louiz.org/louiz/biboumi/commit/eb8f1cb
-Author: louiz’ <louiz@louiz.org>
-Forwarded: yes
-Last-Update: 2016-12-21
-
---- a/src/xmpp/biboumi_component.cpp
-+++ b/src/xmpp/biboumi_component.cpp
-@@ -570,13 +570,11 @@
- Jid to(stanza.get_tag("to"));
-
- const XmlNode* query = stanza.get_child("query", MAM_NS);
-- std::string query_id;
-- if (query)
-- query_id = query->get_tag("queryid");
-
- Iid iid(to.local, {'#', '&'});
-- if (iid.type == Iid::Type::Channel && to.resource.empty())
-+ if (query && iid.type == Iid::Type::Channel && to.resource.empty())
- {
-+ const std::string query_id = query->get_tag("queryid");
- std::string start;
- std::string end;
- const XmlNode* x = query->get_child("x", DATAFORM_NS);
diff --git a/debian/patches/2001_cmake_ignore_git.patch b/debian/patches/2001_cmake_ignore_git.patch
index 4334d0f..eb2be01 100644
--- a/debian/patches/2001_cmake_ignore_git.patch
+++ b/debian/patches/2001_cmake_ignore_git.patch
@@ -4,10 +4,12 @@ Last-Update: 2016-12-21
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -198,23 +198,6 @@
- database)
- endif()
+@@ -282,27 +282,6 @@
+ endforeach()
+ #
+-## Add a rule to download the catch unit test framework
+-#
-include(ExternalProject)
-ExternalProject_Add(catch
- GIT_REPOSITORY "https://lab.louiz.org/louiz/Catch.git"
@@ -25,10 +27,12 @@ Last-Update: 2016-12-21
- )
- add_dependencies(test_suite catch)
-endif()
+-
+-#
+ ## Add some custom rules to launch the tests
+ #
add_custom_target(check COMMAND "test_suite"
- DEPENDS test_suite biboumi)
- add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/"
-@@ -263,8 +246,7 @@
+@@ -362,8 +341,7 @@
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(dist
diff --git a/debian/patches/series b/debian/patches/series
index 66f0cf4..9da3ec6 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,3 +1 @@
-020161125~ad22be4.patch
-020161204~eb8f1cb.patch
2001_cmake_ignore_git.patch
diff --git a/debian/rules b/debian/rules
index 8229cce..03709e6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,6 +1,6 @@
#!/usr/bin/make -f
-# Copyright 2016 Jonas Smedegaard <dr@jones.dk>
+# Copyright 2016-2017 Jonas Smedegaard <dr@jones.dk>
# Copyright 2016 Vasudev Kamath <vasudev@copyninja.info>
# Description: Main Debian packaging script for biboumi
#
@@ -18,16 +18,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
include /usr/share/cdbs/1/rules/debhelper.mk
-include /usr/share/cdbs/1/rules/utils.mk
-
include /usr/share/cdbs/1/class/cmake.mk
pkg = $(DEB_SOURCE_PACKAGE)
-dev-deps = expat1 idn11 systemd udns0 gcrypt20
+# TODO: Build-dpend on libbotan1.11-dev or libbotan2.0 when available
+dev-deps = expat1 idn11 systemd udns gcrypt20
CDBS_BUILD_DEPENDS += , cmake, catch, uuid-dev, pandoc
CDBS_BUILD_DEPENDS += , $(patsubst %,$(comma) lib%-dev,$(dev-deps))
+DEB_CMAKE_EXTRA_FLAGS = \
+ -DSERVICE_USER=nobody -DSERVICE_GROUP=nogroup \
+ -DBOTAN_LIBRARIES="$(shell pkg-config botan-1.10 --libs-only-L)" \
+ -DBOTAN_INCLUDE_DIRS="$(shell pkg-config botan-1.10 --cflags-only-I)"
+
DEB_MAKE_CHECK_TARGET = check
DEB_INSTALL_CHANGELOGS_ALL += CHANGELOG.rst
diff --git a/debian/watch b/debian/watch
index 3fdb5ee..12cf86c 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,4 +1,5 @@
# run "uscan --report" to check or "gpb import-orig --uscan" to update
version=4
+opts="filenamemangle=s%(?:.*?)?archive\.tar\.bz2\?ref=(\d[\d.]*)%@PACKAGE@-$1.tar.bz2%" \
https://lab.louiz.org/louiz/biboumi/tags \
.*/archive@ARCHIVE_EXT@\?ref=@ANY_VERSION@
diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst
index 98b941e..5396015 100644
--- a/doc/biboumi.1.rst
+++ b/doc/biboumi.1.rst
@@ -35,12 +35,19 @@ details on its content.
Configuration
=============
-The configuration file uses a simple format of the form
-``option=value``. Here is a description of each possible option:
+The configuration file uses a simple format of the form ``option=value``.
-The configuration can be re-read at runtime (you can for example change the
-log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2
-(see kill(1)) to the process.
+The values from the configuration file can be overridden by environment
+variables, with the name all in upper case and prefixed with "BIBOUMI_".
+For example, if the environment contains “BIBOUMI_PASSWORD=blah", this will
+override the value of the “password” option in the configuration file.
+
+Sending SIGUSR1 or SIGUSR2 (see kill(1)) to the process will force it to
+re-read the configuration and make it close and re-open the log files. You
+can use this to change any configuration option at runtime, or do a log
+rotation.
+
+Here is a description of every possible option:
hostname
--------
@@ -74,9 +81,8 @@ admin
-----
The bare JID of the gateway administrator. This JID will have more
-privileges than other standard users (the admin thus needs to check their
-privileges), for example some administration ad-hoc commands will only be
-available to that JID.
+privileges than other standard users, for example some administration
+ad-hoc commands will only be available to that JID.
fixed_irc_server
----------------
@@ -155,8 +161,56 @@ to IRC servers.
identd_port
-----------
-The TCP port on which to listen for identd queries. The default is the standard value: 113.
+The TCP port on which to listen for identd queries. The default is the
+standard value: 113. To be able to listen on this privileged port, biboumi
+needs to have certain capabilities: on linux, using systemd, this can be
+achieved by adding `AmbientCapabilities=CAP_NET_BIND_SERVICE` to the unit
+file. On other systems, other solutions exist, like the portacl module on
+FreeBSD.
+
+If biboumi’s identd server is properly started, it will receive queries from
+the IRC servers asking for the “identity” of each IRC connection made to it.
+Biboumi will answer with a hash of the JID that made the connection. This is
+useful for the IRC server to be able to distinguish the different users, and
+be able to deal with the absuses without having to simply ban the IP. Without
+this identd server, moderation is a lot harder, because all the different
+users of a single biboumi instance all share the same IP, and they can’t be
+distinguished by the IRC servers.
+
+policy_directory
+----------------
+
+A directory that should contain the policy files, used to customize
+Botan’s behaviour when negociating the TLS connections with the IRC
+servers. If not specified, the directory is the one where biboumi’s
+configuration file is located: for example if biboumi reads its
+configuration from /etc/biboumi/biboumi.cfg, the policy_directory value
+will be /etc/biboumi.
+
+
+TLS configuration
+=================
+
+Various settings of the TLS connections can be customized using policy
+files. The files should be located in the directory specified by the
+configuration option `policy_directory`_. When attempting to connect to
+an IRC server using TLS, biboumi will use Botan’s default TLS policy, and
+then will try to load some policy files to override the values found in
+these files. For example, if policy_directory is /etc/biboumi, when
+trying to connect to irc.example.com, biboumi will try to read
+/etc/biboumi/policy.txt, use the values found to override the default
+values, then it will try to read /etc/biboumi/irc.example.com.policy.txt
+and re-override the policy with the values found in this file.
+
+The policy.txt file applies to all the connections, and
+irc.example.policy.txt will only apply (in addition to policy.txt) when
+connecting to that specific server.
+To see the list of possible options to configure, refer to `Botan’s TLS
+documentation <https://botan.randombit.net/manual/tls.html#tls-policies>`_.
+
+By default, biboumi provides a few policy files, to work around some
+issues found with a few well-known IRC servers.
Usage
=====
@@ -286,7 +340,7 @@ biboumi provides a virtual channel on the jid
channel ``#foo`` on the server ``irc.example.com``, but you need to authenticate
to a bot of the server before you’re allowed to join it, you can first join
the room ``%irc.example.com@biboumi.example.com`` (this will effectively
-connect you to the IRC server without joining any room), then send your
+connect you to the IRC server without joining any channel), then send your
authentication message to the user ``bot%irc.example.com@biboumi.example.com``
and finally join the room ``#foo%irc.example.com@biboumi.example.com``.
@@ -368,6 +422,13 @@ Notices
Notices are received exactly like private messages. It is not possible to
send a notice.
+Topic
+-----
+
+The topic can be set and retrieved seemlessly. The unique difference is that
+if an XMPP user tries to set a multiline topic, every line return (\\n) will
+be replaced by a space, because the IRC server wouldn’t accept it.
+
Invitations
-----------
@@ -491,11 +552,59 @@ On the gateway itself (e.g on the JID biboumi.example.com):
with an XMPP message. The administrator can disconnect any user, while
the other users can only disconnect themselves.
+- configure: Lets each user configure some options that applies globally.
+ The provided configuration form contains these fields:
+ * Record History: whether or not history messages should be saved in
+ the database.
+ * Max history length: The maximum number of lines in the history
+ that the server is allowed to send when joining a channel.
+
On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- configure: Lets each user configure some options that applies to the
- concerned IRC server.
+ concerned IRC server. The provided configuration form contains these
+ fields:
+ * Realname: The customized “real name” as it will appear on the
+ user’s whois. This option is not available if biboumi is configured
+ with realname_customization to false.
+ * Username: The “user” part in your `user@host`. This option is not
+ available if biboumi is configured with realname_customization to
+ false.
+ * In encoding: The incoming encoding. Any received message that is not
+ proper UTF-8 will be converted will be converted from the configured
+ In encoding into UTF-8. If the conversion fails at some point, some
+ characters will be replaced by the placeholders.
+ * Out encoding: Currently ignored.
+ * After-connection IRC command: A raw IRC command that will be sent to
+ the server immediately after the connection has been successful. It
+ can for example be used to identify yourself using NickServ, with a
+ command like this: `PRIVMSG NickServ :identify PASSWORD`.
+ * Ports: The list of TCP ports to use when connecting to this IRC server.
+ This list will be tried in sequence, until the connection succeeds for
+ one of them. The connection made on these ports will not use TLS, the
+ communication will be insecure. The default list contains 6697 and 6670.
+ * TLS ports: A second list of ports to try when connecting to the IRC
+ server. The only difference is that TLS will be used if the connection
+ is established on one of these ports. All the ports in this list will
+ be tried before using the other plain-text ports list. To entirely
+ disable any non-TLS connection, just remove all the values from the
+ “normal” ports list. The default list contains 6697.
+ * Verify certificate: If set to true (the default value), when connecting
+ on a TLS port, the connection will be aborted if the certificate is
+ not valid (for example if it’s not signed by a known authority, or if
+ the domain name doesn’t match, etc). Set it to false if you want to
+ connect on a server with a self-signed certificate.
+ * SHA-1 fingerprint of the TLS certificate to trust: if you know the hash
+ of the certificate that the server is supposed to use, and you only want
+ to accept this one, set its SHA-1 hash in this field.
+ * Server password: A password that will be sent just after the connection,
+ in a PASS command. This is usually used in private servers, where you’re
+ only allowed to connect if you have the password. Note that, although
+ this is NOT a password that will be sent to NickServ (or some author
+ authentication service), some server (notably Freenode) use it as if it
+ was sent to NickServ to identify your nickname.
+
- get-irc-connection-info: Returns some information about the IRC server,
for the executing user. It lets the user know if they are connected to
this server, from what port, with or without TLS, and it gives the list
@@ -510,7 +619,18 @@ On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com)
specific channel, defaults to the value configured at the IRC server
level. For example the encoding can be specified for both the channel
and the server. If an encoding is not specified for a channel, the
- encoding configured in the server applies.
+ encoding configured in the server applies. The provided configuration
+ form contains these fields:
+ * In encoding: see the option with the same name in the server configuration
+ form.
+ * Out encoding: Currently ignored.
+ * Persistent: If set to true, biboumi will stay in this channel even when
+ all the XMPP resources have left the room. I.e. it will not send a PART
+ command, and will stay idle in the channel until the connection is
+ forcibly closed. If a resource comes back in the room again, and if
+ the archiving of messages is enabled for this room, the client will
+ receive the messages that where sent in this channel. This option can be
+ used to make biboumi act as an IRC bouncer.
Raw IRC messages
----------------
@@ -556,3 +676,4 @@ protection against flood or any sort of abuse that your users may cause on
the IRC servers. Some XMPP server however offer the possibility to restrict
what JID can access a gateway. Use that feature if you wish to grant access
to your biboumi instance only to a list of trusted users.
+
diff --git a/docker/biboumi-test/alpine/Dockerfile b/docker/biboumi-test/alpine/Dockerfile
new file mode 100644
index 0000000..ab288b6
--- /dev/null
+++ b/docker/biboumi-test/alpine/Dockerfile
@@ -0,0 +1,10 @@
+# This Dockerfile creates a docker image suitable to run biboumi’s build and
+# tests. For example, it can be used on with gitlab-ci.
+
+FROM docker.louiz.org/biboumi-test-alpine-base
+
+# Install litesql
+RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && rm -rf /litesql && ldconfig || true
+
+WORKDIR /home/tester
+USER tester
diff --git a/docker/biboumi-test/alpine/Dockerfile.base b/docker/biboumi-test/alpine/Dockerfile.base
new file mode 100644
index 0000000..dffa1d1
--- /dev/null
+++ b/docker/biboumi-test/alpine/Dockerfile.base
@@ -0,0 +1,51 @@
+# This Dockerfile creates a docker image suitable to run biboumi’s build and
+# tests. For example, it can be used on with gitlab-ci.
+
+FROM docker.io/alpine:latest
+
+ENV LC_ALL C.UTF-8
+
+# Needed to build biboumi
+RUN apk add --no-cache g++\
+ clang\
+ valgrind\
+ udns-dev\
+ c-ares-dev\
+ sqlite-dev\
+ libuuid\
+ util-linux-dev\
+ libgcrypt-dev\
+ cmake\
+ make\
+ expat-dev\
+ libidn-dev\
+ git\
+ py3-lxml\
+ libtool\
+ py3-pip\
+ python2\
+ python3-dev\
+ automake\
+ autoconf\
+ flex\
+ bison\
+ libltdl\
+ openssl\
+ libressl-dev\
+ zlib-dev\
+ curl
+
+# Install botan
+RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
+
+# Install slixmpp, for e2e tests
+RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
+RUN adduser tester -D -h /home/tester
+
+# Install charybdis, for e2e tests
+RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install && rm -rf /charybdis
+
+RUN chown -R tester:tester /home/tester/ircd
+
+RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
diff --git a/docker/biboumi-test/archlinux/Dockerfile b/docker/biboumi-test/archlinux/Dockerfile
new file mode 100644
index 0000000..20f0343
--- /dev/null
+++ b/docker/biboumi-test/archlinux/Dockerfile
@@ -0,0 +1,13 @@
+FROM docker.io/base/archlinux:latest
+
+RUN pacman -Syuuuu --noconfirm
+
+RUN pacman -Syu --noconfirm cmake base-devel git clang-tools-extra
+
+RUN useradd -m -G wheel -s /bin/bash builder
+
+RUN sed -i '/^# %wheel ALL=(ALL) NOPASSWD: ALL/s/^# //' /etc/sudoers
+
+WORKDIR /home/builder
+
+USER builder
diff --git a/docker/biboumi-test/debian/Dockerfile.base b/docker/biboumi-test/debian/Dockerfile.base
index e609feb..f5d061b 100644
--- a/docker/biboumi-test/debian/Dockerfile.base
+++ b/docker/biboumi-test/debian/Dockerfile.base
@@ -12,6 +12,7 @@ RUN apt install -y g++\
clang\
valgrind\
libudns-dev\
+ libc-ares-dev\
libsqlite3-dev\
libuuid1\
libgcrypt20-dev\
diff --git a/docker/biboumi-test/fedora/Dockerfile.base b/docker/biboumi-test/fedora/Dockerfile.base
index dd536e5..20984a2 100644
--- a/docker/biboumi-test/fedora/Dockerfile.base
+++ b/docker/biboumi-test/fedora/Dockerfile.base
@@ -10,6 +10,7 @@ RUN dnf --refresh install -y\
clang\
valgrind\
udns-devel\
+ c-ares-devel\
sqlite-devel\
libuuid-devel\
libgcrypt-devel\
@@ -56,4 +57,3 @@ RUN chown -R tester:tester /home/tester/ircd
RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
COPY coverity /home/tester/coverity
-COPY sonar-scanner-2.8 /home/tester/sonar-scanner
diff --git a/docker/biboumi/Dockerfile b/docker/biboumi/Dockerfile
index 721d106..d27421b 100644
--- a/docker/biboumi/Dockerfile
+++ b/docker/biboumi/Dockerfile
@@ -1,26 +1,25 @@
# This Dockerfile creates a docker image running biboumi
-FROM docker.io/fedora:latest
+FROM docker.io/alpine:latest
-RUN dnf --refresh install -y\
- gcc-c++\
+RUN apk add --no-cache\
+ g++\
cmake\
make\
- udns-devel\
- sqlite-devel\
- libuuid-devel\
- expat-devel\
- libidn-devel\
- systemd-devel\
+ udns-dev\
+ sqlite-dev\
+ libuuid\
+ util-linux-dev\
+ expat-dev\
+ libidn-dev\
git\
- python\
- && dnf clean all
+ python2
# Install botan
-RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && ldconfig && rm -rf /botan
+RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
# Install litesql
-RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && ldconfig && rm -rf /litesql
+RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && rm -rf /litesql
# Install biboumi
RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./biboumi/build &&\
@@ -29,10 +28,9 @@ RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./bibou
-DWITH_BOTAN=1\
-DWITH_LITESQL=1\
-DWITH_LIBIDN=1\
- -DWITH_SYSTEMD=1\
&& make -j8 && make install && rm -rf /biboumi
-RUN useradd biboumi
+RUN adduser biboumi -D -h /home/biboumi
RUN mkdir /var/lib/biboumi
RUN chown -R biboumi:biboumi /var/lib/biboumi
@@ -40,9 +38,7 @@ RUN chown -R biboumi:biboumi /var/lib/biboumi
COPY ./biboumi.cfg /etc/biboumi/biboumi.cfg
RUN chown -R biboumi:biboumi /etc/biboumi
-COPY ./entrypoint.sh /entrypoint.sh
-RUN chmod 755 /entrypoint.sh
-
-ENTRYPOINT ["/entrypoint.sh"]
-
+WORKDIR /home/biboumi
USER biboumi
+
+CMD ["/usr/bin/biboumi", "/etc/biboumi/biboumi.cfg"]
diff --git a/docker/biboumi/README.md b/docker/biboumi/README.md
index e69c1b0..4b9e1e5 100644
--- a/docker/biboumi/README.md
+++ b/docker/biboumi/README.md
@@ -32,14 +32,12 @@ docker run --network=host \
Variables
---------
-The configuration file inside the image is a template that is completed when the container is started, using the following environment variables:
+The configuration file inside the image contains only a few default values. To be able to run, biboumi needs additional configuration. Additional options can be passed using environment variables. Any configuration option can be customized this way (see biboumi’s doc), but the main are listed here for convenience:
* BIBOUMI_HOSTNAME: Sets the value of the *hostname* option.
-* BIBOUMI_SECRET: Sets the value of the *password* option.
+* BIBOUMI_PASSWORD: Sets the value of the *password* option.
* BIBOUMI_ADMIN: Sets the value of the *admin* option.
-* BIBOUMI_XMPP_SERVER_IP: Sets the value of the *xmpp_server_ip* option. The default is **xmpp**.
-
-All these variables are optional, but biboumi will probably fail to start if the hostname and secret are missing.
+* BIBOUMI_XMPP_SERVER_IP: Sets the value of the *xmpp_server_ip* option. The default value is **xmpp**.
You can also directly provide your own configuration file by mounting it inside the container using the -v option:
@@ -49,6 +47,8 @@ docker run --link prosody:xmpp \
biboumi
```
+If both a custom configuration file and custom environment variables are passed to the container, the environment variables will take precedence.
+
Linking with the XMPP server
----------------------------
@@ -60,3 +60,9 @@ Volumes
-------
The database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**.
+
+Note: Due to a limitation in Docker, to be able to read and write into this database, make sure this mounted directory is owned by UID and GID 1001:1001, on the host.
+
+```
+chown -R 1001:1001 database/
+```
diff --git a/docker/biboumi/biboumi.cfg b/docker/biboumi/biboumi.cfg
index cc5df61..98c5a9f 100644
--- a/docker/biboumi/biboumi.cfg
+++ b/docker/biboumi/biboumi.cfg
@@ -1,6 +1,6 @@
-xmpp_server_ip=BIBOUMI_XMPP_SERVER_IP
+xmpp_server_ip=127.0.0.1
port=5347
db_name=/var/lib/biboumi/biboumi.sqlite
-hostname=BIBOUMI_HOSTNAME
-password=BIBOUMI_PASSWORD
-admin=BIBOUMI_ADMIN
+hostname=xmpp
+password=
+admin=
diff --git a/docker/biboumi/entrypoint.sh b/docker/biboumi/entrypoint.sh
deleted file mode 100644
index eda53a4..0000000
--- a/docker/biboumi/entrypoint.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-sed -i s/BIBOUMI_XMPP_SERVER_IP/${BIBOUMI_XMPP_SERVER_IP:-xmpp}/ /etc/biboumi/biboumi.cfg
-sed -i s/BIBOUMI_HOSTNAME/${BIBOUMI_HOSTNAME:-biboumi.localhost}/ /etc/biboumi/biboumi.cfg
-sed -i s/BIBOUMI_ADMIN/${BIBOUMI_ADMIN:-}/ /etc/biboumi/biboumi.cfg
-sed -i s/BIBOUMI_PASSWORD/${BIBOUMI_PASSWORD:-missing_password}/ /etc/biboumi/biboumi.cfg
-
-echo "Running biboumi with the following conf: "
-cat /etc/biboumi/biboumi.cfg
-
-/usr/bin/biboumi /etc/biboumi/biboumi.cfg
diff --git a/docker/packaging/debian/Dockerfile b/docker/packaging/debian/Dockerfile
new file mode 100644
index 0000000..f9f4d84
--- /dev/null
+++ b/docker/packaging/debian/Dockerfile
@@ -0,0 +1,10 @@
+# This Dockerfile creates a docker image suitable to build a debian package
+
+FROM docker.io/debian:sid
+
+RUN apt update
+
+# Needed to build biboumi
+RUN apt install -y \
+ git \
+ devscripts
diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt
deleted file mode 100644
index 2268571..0000000
--- a/louloulibs/CMakeLists.txt
+++ /dev/null
@@ -1,181 +0,0 @@
-cmake_minimum_required(VERSION 2.6)
-
-set(${PROJECT_NAME}_VERSION_MAJOR 1)
-set(${PROJECT_NAME}_VERSION_MINOR 0)
-set(${PROJECT_NAME}_VERSION_SUFFIX "~dev")
-
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra")
-
-#
-## Look for external libraries
-#
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_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(NOT BOTAN_FOUND)
- find_package(GCRYPT REQUIRED)
-endif()
-
-if(WITH_UDNS)
- find_package(UDNS REQUIRED)
-elseif(NOT WITHOUT_UDNS)
- find_package(UDNS)
-endif()
-
-# To be able to include the config.h file generated by cmake
-include_directories("${CMAKE_CURRENT_BINARY_DIR}")
-include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
-include_directories(${EXPAT_INCLUDE_DIRS})
-include_directories(${ICONV_INCLUDE_DIRS})
-include_directories(${LIBUUID_INCLUDE_DIRS})
-
-set(EXPAT_INCLUDE_DIRS ${EXPAT_INCLUDE_DIRS} PARENT_SCOPE)
-set(ICONV_INCLUDE_DIRS ${ICONV_INCLUDE_DIRS} PARENT_SCOPE)
-set(LIBUUID_INCLUDE_DIRS ${LIBUUID_INCLUDE_DIRS} PARENT_SCOPE)
-
-if(LIBIDN_FOUND)
- include_directories(${LIBIDN_INCLUDE_DIRS})
- set(LIBDIN_FOUND ${LIBDIN_FOUND} PARENT_SCOPE)
- set(LIBDIN_INCLUDE_DIRS ${LIBDIN_INCLUDE_DIRS} PARENT_SCOPE)
-endif()
-
-if(SYSTEMD_FOUND)
- include_directories(${SYSTEMD_INCLUDE_DIRS})
- set(SYSTEMD_FOUND ${SYSTEMD_FOUND} PARENT_SCOPE)
- set(SYSTEMD_INCLUDE_DIRS ${SYSTEMD_INCLUDE_DIRS} PARENT_SCOPE)
-endif()
-
-if(BOTAN_FOUND)
- include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS})
- set(BOTAN_FOUND ${BOTAN_FOUND} PARENT_SCOPE)
- set(BOTAN_INCLUDE_DIRS ${BOTAN_INCLUDE_DIRS} PARENT_SCOPE)
-endif()
-if(GCRYPT_FOUND)
- include_directories(SYSTEM ${GCRYPT_INCLUDE_DIRS})
- set(GCRYPT_FOUND ${GCRYPT_FOUND} PARENT_SCOPE)
- set(GCRYPT_INCLUDE_DIRS ${GCRYPT_INCLUDE_DIRS} PARENT_SCOPE)
-endif()
-
-if(UDNS_FOUND)
- include_directories(${UDNS_INCLUDE_DIRS})
- set(UDNS_FOUND ${UDNS_FOUND} PARENT_SCOPE)
- set(UDNS_INCLUDE_DIRS ${UDNS_INCLUDE_DIRS} PARENT_SCOPE)
-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()
-
-#
-## utils
-#
-file(GLOB source_utils
- utils/*.[hc]pp)
-add_library(utils STATIC ${source_utils})
-target_link_libraries(utils ${ICONV_LIBRARIES})
-
-#
-## config
-#
-file(GLOB source_config
- config/*.[hc]pp)
-add_library(config STATIC ${source_config})
-
-#
-## logger
-#
-file(GLOB source_logger
- logger/*.[hc]pp)
-add_library(logger STATIC ${source_logger})
-target_link_libraries(logger config)
-
-#
-## network
-#
-file(GLOB source_network
- network/*.[hc]pp)
-add_library(network STATIC ${source_network})
-target_link_libraries(network logger)
-if(BOTAN_FOUND)
- target_link_libraries(network ${BOTAN_LIBRARIES})
-elseif(GCRYPT_FOUND)
- target_link_libraries(network ${GCRYPT_LIBRARIES})
-endif()
-if(UDNS_FOUND)
- target_link_libraries(network ${UDNS_LIBRARIES})
-endif()
-
-#
-## xmpplib
-#
-file(GLOB source_xmpplib
- xmpp/*.[hc]pp)
-add_library(xmpplib STATIC ${source_xmpplib})
-target_link_libraries(xmpplib network utils logger
- ${EXPAT_LIBRARIES}
- ${LIBUUID_LIBRARIES})
-if(LIBIDN_FOUND)
- target_link_libraries(xmpplib ${LIBIDN_LIBRARIES})
-endif()
-if(SYSTEMD_FOUND)
- target_link_libraries(xmpplib ${SYSTEMD_LIBRARIES})
-endif()
-
-# Define a __FILENAME__ macro with the relative path (from the base project directory)
-# of each source file
-file(GLOB_RECURSE source_all *.[hc]pp)
-foreach(file ${source_all})
- file(RELATIVE_PATH shorter_file ${CMAKE_CURRENT_SOURCE_DIR} ${file})
- set_property(SOURCE ${file} APPEND PROPERTY COMPILE_DEFINITIONS __FILENAME__="${shorter_file}")
-endforeach()
-
-#
-## Check if we have std::get_time
-#
-include(CheckCXXSourceCompiles)
-
-check_cxx_source_compiles("
- #include <iomanip>
- int main()
- { std::get_time(nullptr, \"\"); }"
- HAS_GET_TIME)
-
-mark_as_advanced(HAS_GET_TIME)
-
-check_cxx_source_compiles("
- #include <iomanip>
- int main()
- { std::put_time(nullptr, \"\"); }"
- HAS_PUT_TIME)
-
-mark_as_advanced(HAS_PUT_TIME)
-
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/louloulibs.h.cmake ${CMAKE_BINARY_DIR}/src/louloulibs.h)
diff --git a/louloulibs/cmake/Modules/FindBOTAN.cmake b/louloulibs/cmake/Modules/FindBOTAN.cmake
deleted file mode 100644
index 26069f4..0000000
--- a/louloulibs/cmake/Modules/FindBOTAN.cmake
+++ /dev/null
@@ -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-2 botan-1.11
- DOC "The botan include directory")
-
-find_library(BOTAN_LIBRARIES NAMES botan botan-2 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)
diff --git a/louloulibs/cmake/Modules/FindGCRYPT.cmake b/louloulibs/cmake/Modules/FindGCRYPT.cmake
deleted file mode 100644
index bb1bc67..0000000
--- a/louloulibs/cmake/Modules/FindGCRYPT.cmake
+++ /dev/null
@@ -1,35 +0,0 @@
-# - Find gcrypt
-# Find the gcrypt cryptographic library
-#
-# This module defines the following variables:
-# GCRYPT_FOUND - True if library and include directory are found
-# If set to TRUE, the following are also defined:
-# GCRYPT_INCLUDE_DIRS - The directory where to find the header file
-# GCRYPT_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.
-# GCRYPT_LIBRARY
-# GCRYPT_INCLUDE_DIR
-#
-# This file is in the public domain
-
-find_path(GCRYPT_INCLUDE_DIRS NAMES gcrypt.h
- PATH_SUFFIXES gcrypt
- DOC "The gcrypt include directory")
-
-find_library(GCRYPT_LIBRARIES NAMES gcrypt
- DOC "The gcrypt library")
-
-# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
-# set GCRYPT_FOUND to TRUE if these two variables are set.
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(GCRYPT REQUIRED_VARS GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIRS)
-
-if(GCRYPT_FOUND)
- set(GCRYPT_LIBRARY ${GCRYPT_LIBRARIES})
- set(GCRYPT_INCLUDE_DIR ${GCRYPT_INCLUDE_DIRS})
-endif()
-
-mark_as_advanced(GCRYPT_INCLUDE_DIRS GCRYPT_LIBRARIES)
diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake
deleted file mode 100644
index 5777d92..0000000
--- a/louloulibs/louloulibs.h.cmake
+++ /dev/null
@@ -1,11 +0,0 @@
-#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST
-#cmakedefine LIBIDN_FOUND
-#cmakedefine SYSTEMD_FOUND
-#cmakedefine POLLER ${POLLER}
-#cmakedefine BOTAN_FOUND
-#cmakedefine GCRYPT_FOUND
-#cmakedefine UDNS_FOUND
-#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}"
-#cmakedefine PROJECT_NAME "${PROJECT_NAME}"
-#cmakedefine HAS_GET_TIME
-#cmakedefine HAS_PUT_TIME
diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake
index 55d7631..dccd2f9 100644
--- a/packaging/biboumi.spec.cmake
+++ b/packaging/biboumi.spec.cmake
@@ -55,12 +55,22 @@ make check %{?_smp_mflags}
%{_mandir}/man1/%{name}.1*
%doc README.rst COPYING doc/biboumi.1.rst
%{_unitdir}/%{name}.service
-%config(noreplace) %{biboumi_confdir}/biboumi.cfg
+%config(noreplace) %{biboumi_confdir}/*policy.txt
%changelog
-* ${RPM_DATE} Le Coz Florent <louiz@louiz.org> - ${RPM_VERSION}-1
-- Build latest git revision
+* Wed May 24 Le Coz Florent <louiz@louiz.org> - 5.0-1
+- Update to version 5.0
+
+* Wed May 2 2017 Le Coz Florent <louiz@louiz.org> - 4.3-1
+- Fix a segmentation fault that occured when trying to connect
+ to an IRC server without any port configured.
+
+* Wed Apr 26 2017 Le Coz Florent <louiz@louiz.org> - 4.2-1
+- Fix a build issue when LiteSQL is absent from the system
+
+* Tue Mar 21 2017 Le Coz Florent <louiz@louiz.org> - 4.1-1
+- Update to 4.1 sources: compatibility with botan 2.0
* Wed Nov 9 2016 Le Coz Florent <louiz@louiz.org> - 4.0-1
- Update to 4.0 sources
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index 573e8d7..4a41b50 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -1,4 +1,5 @@
#include <bridge/bridge.hpp>
+#include <utility>
#include <xmpp/biboumi_component.hpp>
#include <network/poller.hpp>
#include <utils/empty_if_fixed_server.hpp>
@@ -11,7 +12,6 @@
#include <database/database.hpp>
#include "result_set_management.hpp"
#include <algorithm>
-#include <utils/timed_events.hpp>
using namespace std::string_literals;
@@ -25,12 +25,14 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
return options.encodingIn.value();
#else
+ (void)bridge;
+ (void)iid;
return {"ISO-8859-1"};
#endif
}
-Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller):
- user_jid(user_jid),
+Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
+ user_jid(std::move(user_jid)),
xmpp(xmpp),
poller(poller)
{
@@ -59,10 +61,10 @@ static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(c
void Bridge::shutdown(const std::string& exit_message)
{
- for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it)
+ for (auto& pair: this->irc_clients)
{
- it->second->send_quit_command(exit_message);
- it->second->leave_dummy_channel(exit_message);
+ pair.second->send_quit_command(exit_message);
+ pair.second->leave_dummy_channel(exit_message, {});
}
}
@@ -168,8 +170,7 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
const std::string& resource)
{
- const auto hostname = iid.get_server();
- this->cancel_linger_timer(hostname);
+ const auto& hostname = iid.get_server();
IrcClient* irc = this->make_irc_client(hostname, nickname);
this->add_resource_to_server(hostname, resource);
auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
@@ -253,15 +254,16 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
else
irc->send_channel_message(iid.get_local(), line);
+ std::string uuid;
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(line);
if (this->record_history)
- Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
+ uuid = Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
std::get<0>(xmpp_body), irc->get_own_nick());
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
- this->make_xmpp_body(line), this->user_jid + "/" + resource);
+ this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
+ this->user_jid + "/" + resource, uuid);
}
}
@@ -422,33 +424,47 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
if (!this->is_resource_in_chan(key, resource))
return ;
+ IrcChannel* channel = irc->get_channel(iid.get_local());
+
const auto resources = this->number_of_resources_in_chan(key);
if (resources == 1)
{
// Do not send a PART message if we actually are not in that channel
// or if we already sent a PART but we are just waiting for the
// acknowledgment from the server
- IrcChannel* channel = irc->get_channel(iid.get_local());
- if (channel->joined && !channel->parting)
- irc->send_part_command(iid.get_local(), status_message);
+ bool persistent = false;
+#ifdef USE_DATABASE
+ const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
+ iid.get_server(), iid.get_local());
+ persistent = coptions.persistent.value();
+#endif
+ if (channel->joined && !channel->parting && !persistent)
+ {
+ const auto& chan_name = iid.get_local();
+ if (chan_name.empty())
+ irc->leave_dummy_channel(status_message, resource);
+ else
+ irc->send_part_command(iid.get_local(), status_message);
+ }
+ else if (channel->joined)
+ {
+ this->send_muc_leave(iid, channel->get_self()->nick, "", true, resource);
+ }
// Since there are no resources left in that channel, we don't
// want to receive private messages using this room's JID
this->remove_all_preferred_from_jid_of_room(iid.get_local());
}
else
{
- IrcChannel* chan = irc->get_channel(iid.get_local());
- if (chan)
- {
- auto nick = chan->get_self()->nick;
- this->remove_resource_from_chan(key, resource);
- this->send_muc_leave(std::move(iid), std::move(nick),
- "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.",
- true, resource);
- if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
- this->remove_resource_from_server(iid.get_server(), resource);
- }
+ if (channel && channel->joined)
+ this->send_muc_leave(iid, channel->get_self()->nick,
+ "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.",
+ true, resource);
+ this->remove_resource_from_chan(key, resource);
+ if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
+ this->remove_resource_from_server(iid.get_server(), resource);
}
+
}
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
@@ -464,7 +480,7 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, c
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
ResultSetInfo rs_info)
{
- auto& list = channel_list_cache[iid.get_server()];
+ auto& list = this->channel_list_cache[iid.get_server()];
// We fetch the list from the IRC server only if we have a complete
// cached list that needs to be invalidated (that is, when the request
@@ -487,7 +503,7 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq
if (irc_hostname != iid.get_server())
return false;
- auto& list = channel_list_cache[iid.get_server()];
+ auto& list = this->channel_list_cache[iid.get_server()];
if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
|| message.command == "ERR_NOSUCHSERVER")
@@ -545,7 +561,6 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq
{
auto& list = channel_list_cache[iid.get_server()];
const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
- log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res);
return res;
}
else if (message.command == "323" || message.command == "RPL_LISTEND")
@@ -565,7 +580,7 @@ bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const R
const std::string& id, const std::string& to_jid, const std::string& from)
{
auto begin = channel_list.channels.begin();
- auto end = channel_list.channels.begin();
+ auto end = channel_list.channels.end();
if (channel_list.complete)
{
begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
@@ -667,9 +682,12 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std:
this->add_waiting_irc(std::move(cb));
}
-void Bridge::set_channel_topic(const Iid& iid, const std::string& subject)
+void Bridge::set_channel_topic(const Iid& iid, std::string subject)
{
IrcClient* irc = this->get_irc_client(iid.get_server());
+ std::string::size_type pos{0};
+ while ((pos = subject.find('\n', pos)) != std::string::npos)
+ subject[pos] = ' ';
irc->send_topic_command(iid.get_local(), subject);
}
@@ -727,9 +745,10 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string
const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid)
{
+ Jid from(to_jid);
IrcClient* irc = this->get_irc_client(iid.get_server());
IrcChannel* chan = irc->get_channel(iid.get_local());
- if (!chan->joined)
+ if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource))
{
this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
"", true);
@@ -823,8 +842,8 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
{
- this->xmpp.send_muc_message(std::to_string(iid), nick,
- this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource);
+ this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding),
+ this->user_jid + "/" + resource, {});
}
}
@@ -855,19 +874,25 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick,
this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
}
-void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self,
+void Bridge::send_muc_leave(const Iid& iid, const std::string& nick,
+ const std::string& message, const bool self,
const std::string& resource)
{
if (!resource.empty())
- this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message),
+ this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message),
this->user_jid + "/" + resource, self);
else
- for (const auto& res: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message),
- this->user_jid + "/" + res, self);
+ {
+ for (const auto &res: this->resources_in_chan[iid.to_tuple()])
+ this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message),
+ this->user_jid + "/" + res, self);
+ if (self)
+ this->remove_all_resources_from_chan(iid.to_tuple());
+
+ }
IrcClient* irc = this->find_irc_client(iid.get_server());
- if (irc && irc->number_of_joined_channels() == 0)
- this->quit_or_start_linger_timer(iid.get_server());
+ if (self && irc && irc->number_of_joined_channels() == 0)
+ irc->send_quit_command("");
}
void Bridge::send_nick_change(Iid&& iid,
@@ -965,18 +990,22 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c
this->send_room_history(hostname, chan_name, resource);
}
-void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource)
+void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource)
{
#ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value());
+ chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines)
{
const auto seconds = line.date.value().timeStamp();
- this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(),
- line.body.value(),
+ this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(),
this->user_jid + "/" + resource, seconds);
}
+#else
+ (void)hostname;
+ (void)chan_name;
+ (void)resource;
#endif
}
@@ -1022,7 +1051,7 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string&
{
const auto resources = this->resources_in_server[hostname];
if (resources.begin() != resources.end())
- this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname),
+ this->xmpp.send_iq_version_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname),
this->user_jid + "/" + *resources.begin());
}
@@ -1035,7 +1064,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string&
// Forward to the first resource (arbitrary, based on the “order” of the std::set) only
const auto resources = this->resources_in_server[hostname];
if (resources.begin() != resources.end())
- this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname),
+ this->xmpp.send_ping_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname),
this->user_jid + "/" + *resources.begin(), utils::revstr(id));
}
@@ -1137,6 +1166,11 @@ bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::s
return false;
}
+void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel)
+{
+ this->resources_in_chan.erase(channel);
+}
+
void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
{
auto it = this->resources_in_server.find(irc_hostname);
@@ -1166,9 +1200,9 @@ bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, cons
return false;
}
-std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel_key) const
+std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel) const
{
- auto it = this->resources_in_chan.find(channel_key);
+ auto it = this->resources_in_chan.find(channel);
if (it == this->resources_in_chan.end())
return 0;
return it->second.size();
@@ -1214,28 +1248,3 @@ void Bridge::set_record_history(const bool val)
this->record_history = val;
}
#endif
-
-void Bridge::quit_or_start_linger_timer(const std::string& irc_hostname)
-{
-#ifdef USE_DATABASE
- auto options = Database::get_irc_server_options(this->get_bare_jid(),
- irc_hostname);
- const auto timeout = std::chrono::seconds(options.lingerTime.value());
-#else
- const auto timeout = 0s;
-#endif
-
- const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid();
- TimedEvent event(std::chrono::steady_clock::now() + timeout, [this, irc_hostname]() {
- IrcClient* irc = this->find_irc_client(irc_hostname);
- if (irc)
- irc->send_quit_command("");
- }, event_name);
- TimedEventsManager::instance().add_event(std::move(event));
-}
-
-void Bridge::cancel_linger_timer(const std::string& irc_hostname)
-{
- const auto event_name = "IRCLINGER:" + irc_hostname + ".." + this->get_bare_jid();
- TimedEventsManager::instance().cancel(event_name);
-}
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index b165650..033291c 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -38,7 +38,7 @@ using irc_responder_callback_t = std::function<bool(const std::string& irc_hostn
class Bridge
{
public:
- explicit Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller);
+ explicit Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller);
~Bridge() = default;
Bridge(const Bridge&) = delete;
@@ -83,7 +83,7 @@ public:
void send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource);
void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
const std::string& iq_id, const std::string& to_jid);
- void set_channel_topic(const Iid& iid, const std::string& subject);
+ void set_channel_topic(const Iid& iid, std::string subject);
void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version,
const std::string& os);
void send_irc_ping_result(const Iid& iid, const std::string& id);
@@ -157,7 +157,7 @@ public:
* Send the MUC history to the user
*/
void send_room_history(const std::string& hostname, const std::string& chan_name);
- void send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource);
+ void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource);
/**
* Send a MUC message from some participant
*/
@@ -169,7 +169,7 @@ public:
/**
* Send an unavailable presence from this participant
*/
- void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource="");
+ void send_muc_leave(const Iid& iid, const std::string& nick, const std::string& message, const bool self, const std::string& resource = "");
/**
* Send presences to indicate that an user old_nick (ourself if self ==
* true) changed his nick to new_nick. The user_mode is needed because
@@ -236,12 +236,6 @@ public:
#ifdef USE_DATABASE
void set_record_history(const bool val);
#endif
- /**
- * Start a timer that will send a QUIT command after the
- * configured linger time is expired.
- */
- void quit_or_start_linger_timer(const std::string& irc_hostname);
- void cancel_linger_timer(const std::string& irc_hostname);
private:
/**
@@ -309,10 +303,11 @@ private:
/**
* Manage which resource is in which channel
*/
- void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource);
- void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource);
- bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const;
- std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const;
+ void add_resource_to_chan(const ChannelKey& channel, const std::string& resource);
+ void remove_resource_from_chan(const ChannelKey& channel, const std::string& resource);
+ bool is_resource_in_chan(const ChannelKey& channel, const std::string& resource) const;
+ void remove_all_resources_from_chan(const ChannelKey& channel);
+ std::size_t number_of_resources_in_chan(const ChannelKey& channel) const;
void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource);
void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource);
diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp
index 66f51ee..7662425 100644
--- a/src/bridge/colors.cpp
+++ b/src/bridge/colors.cpp
@@ -4,7 +4,7 @@
#include <algorithm>
#include <iostream>
-#include <string.h>
+#include <cstring>
using namespace std::string_literals;
diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp
index e2c8a87..dceed74 100644
--- a/src/bridge/colors.hpp
+++ b/src/bridge/colors.hpp
@@ -51,6 +51,6 @@ static const char irc_format_char[] = {
* Returns the body cleaned from any IRC formatting (but without any xhtml),
* and the body as XHTML-IM
*/
-Xmpp::body irc_format_to_xhtmlim(const std::string& str);
+Xmpp::body irc_format_to_xhtmlim(const std::string& s);
diff --git a/louloulibs/config/config.cpp b/src/config/config.cpp
index 24a1c87..0f3d639 100644
--- a/louloulibs/config/config.cpp
+++ b/src/config/config.cpp
@@ -1,10 +1,15 @@
#include <config/config.hpp>
+#include <utils/tolower.hpp>
#include <iostream>
#include <cstring>
#include <cstdlib>
+using namespace std::string_literals;
+
+extern char** environ;
+
std::string Config::filename{};
std::map<std::string, std::string> Config::values{};
std::vector<t_config_changed_callback> Config::callbacks{};
@@ -37,7 +42,7 @@ void Config::set(const std::string& option, const std::string& value, bool save)
}
}
-void Config::connect(t_config_changed_callback callback)
+void Config::connect(const t_config_changed_callback& callback)
{
Config::callbacks.push_back(callback);
}
@@ -71,21 +76,40 @@ bool Config::read_conf(const std::string& name)
Config::clear();
+ auto parse_line = [](const std::string& line, const bool env)
+ {
+ static const auto env_option_prefix = "BIBOUMI_"s;
+
+ if (line == "" || line[0] == '#')
+ return;
+ size_t pos = line.find('=');
+ if (pos == std::string::npos)
+ return;
+ std::string option = line.substr(0, pos);
+ std::string value = line.substr(pos+1);
+ if (env)
+ {
+ auto a = option.substr(0, env_option_prefix.size());
+ if (a == env_option_prefix)
+ option = utils::tolower(option.substr(env_option_prefix.size()));
+ else
+ return;
+ }
+ Config::values[option] = value;
+ };
+
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);
- Config::values[option] = value;
+ parse_line(line, false);
+ }
+
+ char** env_line = environ;
+ while (*env_line)
+ {
+ parse_line(*env_line, true);
+ env_line++;
}
return true;
}
diff --git a/louloulibs/config/config.hpp b/src/config/config.hpp
index 4e01281..2ba38cc 100644
--- a/louloulibs/config/config.hpp
+++ b/src/config/config.hpp
@@ -54,7 +54,7 @@ public:
* configuration change occurs (when set() is called, or when the initial
* conf is read)
*/
- static void connect(t_config_changed_callback);
+ static void connect(const t_config_changed_callback&);
/**
* Destroy the instance, forcing it to be recreated (with potentially
* different parameters) the next time it’s needed.
diff --git a/src/database/database.cpp b/src/database/database.cpp
index cb0c78f..9f310da 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -20,7 +20,7 @@ void Database::open(const std::string& filename, const std::string& db_type)
"database="s + filename);
if (new_db->needsUpgrade())
new_db->upgrade();
- Database::db.reset(new_db.release());
+ Database::db = std::move(new_db);
} catch (const litesql::DatabaseError& e) {
log_error("Failed to open database ", filename, ". ", e.what());
throw;
@@ -119,14 +119,16 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_d
return coptions;
}
-void Database::store_muc_message(const std::string& owner, const Iid& iid,
- Database::time_point date,
- const std::string& body,
- const std::string& nick)
+std::string Database::store_muc_message(const std::string& owner, const Iid& iid,
+ Database::time_point date,
+ const std::string& body,
+ const std::string& nick)
{
db::MucLogLine line(*Database::db);
- line.uuid = Database::gen_uuid();
+ auto uuid = Database::gen_uuid();
+
+ line.uuid = uuid;
line.owner = owner;
line.ircChanName = iid.get_local();
line.ircServerName = iid.get_server();
@@ -135,6 +137,8 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid,
line.nick = nick;
line.update();
+
+ return uuid;
}
std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
diff --git a/src/database/database.hpp b/src/database/database.hpp
index 6823574..b08a175 100644
--- a/src/database/database.hpp
+++ b/src/database/database.hpp
@@ -49,8 +49,8 @@ public:
const std::string& server,
const std::string& channel);
static std::vector<db::MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
- int limit=-1, const std::string& before="", const std::string& after="");
- static void store_muc_message(const std::string& owner, const Iid& iid,
+ int limit=-1, const std::string& start="", const std::string& end="");
+ static std::string store_muc_message(const std::string& owner, const Iid& iid,
time_point date, const std::string& body, const std::string& nick);
static void close();
diff --git a/src/identd/identd_server.hpp b/src/identd/identd_server.hpp
index 5f74976..b1c8ec8 100644
--- a/src/identd/identd_server.hpp
+++ b/src/identd/identd_server.hpp
@@ -10,7 +10,7 @@ class BiboumiComponent;
class IdentdServer: public TcpSocketServer<IdentdSocket>
{
public:
- IdentdServer(const BiboumiComponent& biboumi_component, std::shared_ptr<Poller> poller, const uint16_t port):
+ IdentdServer(const BiboumiComponent& biboumi_component, std::shared_ptr<Poller>& poller, const uint16_t port):
TcpSocketServer<IdentdSocket>(poller, port),
biboumi_component(biboumi_component)
{}
diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp
index a94f172..b85257c 100644
--- a/src/identd/identd_socket.cpp
+++ b/src/identd/identd_socket.cpp
@@ -8,7 +8,7 @@
#include <logger/logger.hpp>
-IdentdSocket::IdentdSocket(std::shared_ptr<Poller> poller, const socket_t socket, TcpSocketServer<IdentdSocket>& server):
+IdentdSocket::IdentdSocket(std::shared_ptr<Poller>& poller, const socket_t socket, TcpSocketServer<IdentdSocket>& server):
TCPSocketHandler(poller),
server(dynamic_cast<IdentdServer&>(server))
{
diff --git a/src/identd/identd_socket.hpp b/src/identd/identd_socket.hpp
index 1c2bd27..a386d80 100644
--- a/src/identd/identd_socket.hpp
+++ b/src/identd/identd_socket.hpp
@@ -2,7 +2,6 @@
#include <network/socket_handler.hpp>
-#include <cassert>
#include <network/tcp_socket_handler.hpp>
#include <logger/logger.hpp>
@@ -17,7 +16,7 @@ class TcpSocketServer;
class IdentdSocket: public TCPSocketHandler
{
public:
- IdentdSocket(std::shared_ptr<Poller> poller, const socket_t socket, TcpSocketServer<IdentdSocket>& server);
+ IdentdSocket(std::shared_ptr<Poller>& poller, const socket_t socket, TcpSocketServer<IdentdSocket>& server);
~IdentdSocket() = default;
std::string generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote);
diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp
index 6b07793..a63a1c3 100644
--- a/src/irc/iid.cpp
+++ b/src/irc/iid.cpp
@@ -1,3 +1,4 @@
+#include <utility>
#include <utils/tolower.hpp>
#include <config/config.hpp>
#include <bridge/bridge.hpp>
@@ -7,10 +8,10 @@
constexpr char Iid::separator[];
-Iid::Iid(const std::string& local, const std::string& server, Iid::Type type):
+Iid::Iid(std::string local, std::string server, Iid::Type type):
type(type),
- local(local),
- server(server)
+ local(std::move(local)),
+ server(std::move(server))
{
}
diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp
index 81cf3ca..89f4797 100644
--- a/src/irc/iid.hpp
+++ b/src/irc/iid.hpp
@@ -59,7 +59,7 @@ public:
Iid(const std::string& iid, const std::set<char>& chantypes);
Iid(const std::string& iid, const std::initializer_list<char>& chantypes);
Iid(const std::string& iid, const Bridge* bridge);
- Iid(const std::string& local, const std::string& server, Type type);
+ Iid(std::string local, std::string server, Type type);
Iid() = default;
Iid(const Iid&) = default;
diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp
index 40d7f54..53043c7 100644
--- a/src/irc/irc_channel.cpp
+++ b/src/irc/irc_channel.cpp
@@ -1,21 +1,25 @@
#include <irc/irc_channel.hpp>
#include <algorithm>
-void IrcChannel::set_self(const std::string& name)
+void IrcChannel::set_self(IrcUser* user)
{
- this->self = std::make_unique<IrcUser>(name);
+ this->self = user;
}
IrcUser* IrcChannel::add_user(const std::string& name,
const std::map<char, char>& prefix_to_mode)
{
- this->users.emplace_back(std::make_unique<IrcUser>(name, prefix_to_mode));
+ auto new_user = std::make_unique<IrcUser>(name, prefix_to_mode);
+ auto old_user = this->find_user(new_user->nick);
+ if (old_user)
+ return old_user;
+ this->users.emplace_back(std::move(new_user));
return this->users.back().get();
}
IrcUser* IrcChannel::get_self() const
{
- return this->self.get();
+ return this->self;
}
IrcUser* IrcChannel::find_user(const std::string& name) const
@@ -32,19 +36,27 @@ IrcUser* IrcChannel::find_user(const std::string& name) const
void IrcChannel::remove_user(const IrcUser* user)
{
const auto nick = user->nick;
+ const bool is_self = (user == this->self);
const auto it = std::find_if(this->users.begin(), this->users.end(),
[nick](const std::unique_ptr<IrcUser>& u)
{
return nick == u->nick;
});
if (it != this->users.end())
- this->users.erase(it);
+ {
+ this->users.erase(it);
+ if (is_self)
+ {
+ this->self = nullptr;
+ this->joined = false;
+ }
+ }
}
void IrcChannel::remove_all_users()
{
this->users.clear();
- this->self.reset();
+ this->self = nullptr;
}
DummyIrcChannel::DummyIrcChannel():
diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp
index 7c269b9..8f85edb 100644
--- a/src/irc/irc_channel.hpp
+++ b/src/irc/irc_channel.hpp
@@ -27,7 +27,7 @@ public:
bool parting{false};
std::string topic{};
std::string topic_author{};
- void set_self(const std::string& name);
+ void set_self(IrcUser* user);
IrcUser* get_self() const;
IrcUser* add_user(const std::string& name,
const std::map<char, char>& prefix_to_mode);
@@ -38,7 +38,8 @@ public:
{ return this->users; }
protected:
- std::unique_ptr<IrcUser> self{};
+ // Pointer to one IrcUser stored in users
+ IrcUser* self{nullptr};
std::vector<std::unique_ptr<IrcUser>> users{};
};
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index 6813bba..1d74098 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -1,3 +1,4 @@
+#include <utility>
#include <utils/timed_events.hpp>
#include <database/database.hpp>
#include <irc/irc_message.hpp>
@@ -21,7 +22,6 @@
#include <string>
#include "biboumi.h"
-#include "louloulibs.h"
using namespace std::string_literals;
using namespace std::chrono_literals;
@@ -62,6 +62,8 @@ static const std::unordered_map<std::string,
{"333", {&IrcClient::on_topic_who_time_received, {4, 0}}},
{"RPL_TOPICWHOTIME", {&IrcClient::on_topic_who_time_received, {4, 0}}},
{"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
+ {"367", {&IrcClient::on_banlist, {3, 0}}},
+ {"368", {&IrcClient::on_banlist_end, {3, 0}}},
{"396", {&IrcClient::on_own_host_received, {2, 0}}},
{"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
{"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
@@ -128,16 +130,16 @@ static const std::unordered_map<std::string,
{"502", {&IrcClient::on_generic_error, {2, 0}}},
};
-IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
- const std::string& nickname, const std::string& username,
- const std::string& realname, const std::string& user_hostname,
+IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
+ std::string nickname, std::string username,
+ std::string realname, std::string user_hostname,
Bridge& bridge):
TCPClientSocketHandler(poller),
- hostname(hostname),
- user_hostname(user_hostname),
- username(username),
- realname(realname),
- current_nick(nickname),
+ hostname(std::move(hostname)),
+ user_hostname(std::move(user_hostname)),
+ username(std::move(username)),
+ realname(std::move(realname)),
+ current_nick(std::move(nickname)),
bridge(bridge),
welcomed(false),
chanmodes({"", "", "", ""}),
@@ -183,6 +185,11 @@ void IrcClient::start()
{
if (this->is_connecting() || this->is_connected())
return;
+ if (this->ports_to_try.empty())
+ {
+ this->bridge.send_xmpp_message(this->hostname, "", "Can not connect to IRC server: no port specified.");
+ return;
+ }
std::string port;
bool tls;
std::tie(port, tls) = this->ports_to_try.top();
@@ -382,10 +389,10 @@ void IrcClient::send_message(IrcMessage&& message)
std::string res;
if (!message.prefix.empty())
res += ":" + std::move(message.prefix) + " ";
- res += std::move(message.command);
+ res += message.command;
for (const std::string& arg: message.arguments)
{
- if (arg.find(" ") != std::string::npos ||
+ if (arg.find(' ') != std::string::npos ||
(!arg.empty() && arg[0] == ':'))
{
res += " :" + arg;
@@ -502,15 +509,7 @@ void IrcClient::send_private_message(const std::string& username, const std::str
void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
{
- IrcChannel* channel = this->get_channel(chan_name);
- if (channel->joined == true)
- {
- if (chan_name.empty())
- this->leave_dummy_channel(status_message);
- else
- this->send_message(IrcMessage("PART", {chan_name, status_message}));
- channel->parting = true;
- }
+ this->send_message(IrcMessage("PART", {chan_name, status_message}));
}
void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments)
@@ -547,9 +546,18 @@ void IrcClient::forward_server_message(const IrcMessage& message)
void IrcClient::on_notice(const IrcMessage& message)
{
std::string from = message.prefix;
- const std::string to = message.arguments[0];
+ std::string to = message.arguments[0];
const std::string body = message.arguments[1];
+ // Handle notices starting with [#channame] as if they were sent to that channel
+ if (body.size() > 3 && body[0] == '[')
+ {
+ const auto chan_prefix = body[1];
+ auto end = body.find(']');
+ if (this->chantypes.find(chan_prefix) != this->chantypes.end() && end != std::string::npos)
+ to = body.substr(1, end - 1);
+ }
+
if (!body.empty() && body[0] == '\01' && body[body.size() - 1] == '\01')
// Do not forward the notice to the user if it's a CTCP command
return ;
@@ -636,15 +644,18 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message)
std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
for (const std::string& nick: nicks)
{
- const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
- if (user->nick != channel->get_self()->nick)
+ // Just create this dummy user to parse and get its modes
+ IrcUser tmp_user{nick, this->prefix_to_mode};
+ // Does this concern ourself
+ if (channel->get_self() && channel->find_user(tmp_user.nick) == channel->get_self())
{
- this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
+ // We now know our own modes, that’s all.
+ channel->get_self()->modes = tmp_user.modes;
}
else
- {
- // we now know the modes of self, so copy the modes into self
- channel->get_self()->modes = user->modes;
+ { // Otherwise this is a new user
+ const IrcUser *user = channel->add_user(nick, this->prefix_to_mode);
+ this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
}
}
}
@@ -658,13 +669,11 @@ void IrcClient::on_channel_join(const IrcMessage& message)
else
channel = this->get_channel(chan_name);
const std::string nick = message.prefix;
+ IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
if (channel->joined == false)
- channel->set_self(nick);
+ channel->set_self(user);
else
- {
- const IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
- this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
- }
+ this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
}
void IrcClient::on_channel_message(const IrcMessage& message)
@@ -777,6 +786,43 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message)
this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
}
+void IrcClient::on_banlist(const IrcMessage& message)
+{
+ const std::string chan_name = utils::tolower(message.arguments[1]);
+ IrcChannel* channel = this->get_channel(chan_name);
+ if (channel->joined)
+ {
+ Iid iid;
+ iid.set_local(chan_name);
+ iid.set_server(this->hostname);
+ iid.type = Iid::Type::Channel;
+ std::string body{message.arguments[2] + " banned"};
+ if (message.arguments.size() >= 4)
+ {
+ IrcUser by(message.arguments[3], this->prefix_to_mode);
+ body += " by " + by.nick;
+ }
+ if (message.arguments.size() >= 5)
+ body += " on " + message.arguments[4];
+
+ this->bridge.send_message(iid, "", body, true);
+ }
+}
+
+void IrcClient::on_banlist_end(const IrcMessage& message)
+{
+ const std::string chan_name = utils::tolower(message.arguments[1]);
+ IrcChannel* channel = this->get_channel(chan_name);
+ if (channel->joined)
+ {
+ Iid iid;
+ iid.set_local(chan_name);
+ iid.set_server(this->hostname);
+ iid.type = Iid::Type::Channel;
+ this->bridge.send_message(iid, "", message.arguments[2], true);
+ }
+}
+
void IrcClient::on_own_host_received(const IrcMessage& message)
{
this->own_host = message.arguments[1];
@@ -800,10 +846,10 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message)
{
const std::string nickname = message.arguments[1];
this->on_generic_error(message);
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
this->bridge.send_nickname_conflict_error(iid, nickname);
@@ -817,10 +863,10 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
if (message.arguments.size() >= 3)
txt = message.arguments[2];
this->on_generic_error(message);
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
this->bridge.send_presence_error(iid, nickname,
@@ -854,8 +900,47 @@ void IrcClient::on_welcome_message(const IrcMessage& message)
// Install a repeated events to regularly send a PING
TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
"PING"s + this->hostname + this->bridge.get_jid()));
+ std::string channels{};
+ std::string channels_with_key{};
+ std::string keys{};
+
for (const auto& tuple: this->channels_to_join)
- this->send_join_command(std::get<0>(tuple), std::get<1>(tuple));
+ {
+ const auto& chan = std::get<0>(tuple);
+ const auto& key = std::get<1>(tuple);
+ if (chan.empty())
+ continue;
+ if (!key.empty())
+ {
+ if (keys.size() + channels_with_key.size() >= 300)
+ { // Arbitrary size, to make sure we never send more than 512
+ this->send_join_command(channels_with_key, keys);
+ channels_with_key.clear();
+ keys.clear();
+ }
+ if (!keys.empty())
+ keys += ",";
+ keys += key;
+ if (!channels_with_key.empty())
+ channels_with_key += ",";
+ channels_with_key += chan;
+ }
+ else
+ {
+ if (channels.size() >= 300)
+ { // Arbitrary size, to make sure we never send more than 512
+ this->send_join_command(channels, {});
+ channels.clear();
+ }
+ if (!channels.empty())
+ channels += ",";
+ channels += chan;
+ }
+ }
+ if (!channels.empty())
+ this->send_join_command(channels, {});
+ if (!channels_with_key.empty())
+ this->send_join_command(channels_with_key, keys);
this->channels_to_join.clear();
// Indicate that the dummy channel is joined as well, if needed
if (this->dummy_channel.joining)
@@ -884,20 +969,19 @@ void IrcClient::on_part(const IrcMessage& message)
if (user)
{
std::string nick = user->nick;
+ bool self = channel->get_self() && channel->get_self()->nick == nick;
channel->remove_user(user);
Iid iid;
iid.set_local(chan_name);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- bool self = channel->get_self()->nick == nick;
if (self)
{
- channel->joined = false;
this->channels.erase(utils::tolower(chan_name));
// channel pointer is now invalid
channel = nullptr;
}
- this->bridge.send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self);
+ this->bridge.send_muc_leave(iid, std::move(nick), txt, self);
}
}
@@ -905,17 +989,17 @@ void IrcClient::on_error(const IrcMessage& message)
{
const std::string leave_message = message.arguments[0];
// The user is out of all the channels
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
Iid iid;
- iid.set_local(it->first);
+ iid.set_local(pair.first);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- IrcChannel* channel = it->second.get();
+ IrcChannel* channel = pair.second.get();
if (!channel->joined)
continue;
std::string own_nick = channel->get_self()->nick;
- this->bridge.send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
+ this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true);
}
this->channels.clear();
this->send_gateway_message("ERROR: "s + leave_message);
@@ -926,10 +1010,10 @@ void IrcClient::on_quit(const IrcMessage& message)
std::string txt;
if (message.arguments.size() >= 1)
txt = message.arguments[0];
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
- const std::string chan_name = it->first;
- IrcChannel* channel = it->second.get();
+ const std::string& chan_name = pair.first;
+ IrcChannel* channel = pair.second.get();
const IrcUser* user = channel->find_user(message.prefix);
if (user)
{
@@ -939,7 +1023,7 @@ void IrcClient::on_quit(const IrcMessage& message)
iid.set_local(chan_name);
iid.set_server(this->hostname);
iid.type = Iid::Type::Channel;
- this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false);
+ this->bridge.send_muc_leave(iid, std::move(nick), txt, false);
}
}
}
@@ -975,9 +1059,9 @@ void IrcClient::on_nick(const IrcMessage& message)
{
change_nick_func("", &this->get_dummy_channel());
}
- for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
+ for (const auto& pair: this->channels)
{
- change_nick_func(it->first, it->second.get());
+ change_nick_func(pair.first, pair.second.get());
}
}
@@ -1088,7 +1172,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
{
// That mode can also be of type B if it is present in the
// prefix_to_mode map
- for (const std::pair<char, char>& pair: this->prefix_to_mode)
+ for (const auto& pair: this->prefix_to_mode)
if (pair.second == c)
{
type = 1;
@@ -1161,14 +1245,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel()
return this->dummy_channel;
}
-void IrcClient::leave_dummy_channel(const std::string& exit_message)
+void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource)
{
if (!this->dummy_channel.joined)
return;
this->dummy_channel.joined = false;
this->dummy_channel.joining = false;
this->dummy_channel.remove_all_users();
- this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true);
+ this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, resource);
}
#ifdef BOTAN_FOUND
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index 4b942ad..aec6cd9 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -26,9 +26,9 @@ class Bridge;
class IrcClient: public TCPClientSocketHandler
{
public:
- explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
- const std::string& nickname, const std::string& username,
- const std::string& realname, const std::string& user_hostname,
+ explicit IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
+ std::string nickname, std::string username,
+ std::string realname, std::string user_hostname,
Bridge& bridge);
~IrcClient();
@@ -52,7 +52,7 @@ public:
/**
* Close the connection, remove us from the poller
*/
- void on_connection_close(const std::string& error) override final;
+ void on_connection_close(const std::string& error_msg) override final;
/**
* Parse the data we have received so far and try to get one or more
* complete messages from it.
@@ -222,6 +222,8 @@ public:
* received etc), send the self presence and topic to the XMPP user.
*/
void on_channel_completely_joined(const IrcMessage& message);
+ void on_banlist(const IrcMessage& message);
+ void on_banlist_end(const IrcMessage& message);
/**
* Save our own host, as reported by the server
*/
@@ -283,7 +285,7 @@ public:
* Leave the dummy channel: forward a message to the user to indicate that
* he left it, and mark it as not joined.
*/
- void leave_dummy_channel(const std::string& exit_message);
+ void leave_dummy_channel(const std::string& exit_message, const std::string& resource);
const std::string& get_hostname() const { return this->hostname; }
std::string get_nick() const { return this->current_nick; }
diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp
index 966a47c..14fdb0e 100644
--- a/src/irc/irc_message.cpp
+++ b/src/irc/irc_message.cpp
@@ -8,12 +8,12 @@ IrcMessage::IrcMessage(std::string&& line)
// optional prefix
if (line[0] == ':')
{
- pos = line.find(" ");
+ pos = line.find(' ');
this->prefix = line.substr(1, pos - 1);
line = line.substr(pos + 1, std::string::npos);
}
// command
- pos = line.find(" ");
+ pos = line.find(' ');
this->command = line.substr(0, pos);
line = line.substr(pos + 1, std::string::npos);
// arguments
@@ -24,7 +24,7 @@ IrcMessage::IrcMessage(std::string&& line)
this->arguments.emplace_back(line.substr(1, std::string::npos));
break ;
}
- pos = line.find(" ");
+ pos = line.find(' ');
this->arguments.emplace_back(line.substr(0, pos));
line = line.substr(pos + 1, std::string::npos);
} while (pos != std::string::npos);
diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp
index 9fa3612..139015e 100644
--- a/src/irc/irc_user.cpp
+++ b/src/irc/irc_user.cpp
@@ -21,7 +21,7 @@ IrcUser::IrcUser(const std::string& name,
name_begin++;
}
- const std::string::size_type sep = name.find("!", name_begin);
+ const std::string::size_type sep = name.find('!', name_begin);
if (sep == std::string::npos)
this->nick = name.substr(name_begin);
else
diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp
index c84030e..a4291d4 100644
--- a/src/irc/irc_user.hpp
+++ b/src/irc/irc_user.hpp
@@ -23,7 +23,7 @@ public:
void add_mode(const char mode);
void remove_mode(const char mode);
- char get_most_significant_mode(const std::vector<char>& sorted_user_modes) const;
+ char get_most_significant_mode(const std::vector<char>& modes) const;
std::string nick;
std::string host;
diff --git a/louloulibs/logger/logger.cpp b/src/logger/logger.cpp
index 92a3d9b..92a3d9b 100644
--- a/louloulibs/logger/logger.cpp
+++ b/src/logger/logger.cpp
diff --git a/louloulibs/logger/logger.hpp b/src/logger/logger.hpp
index b3284a6..ff6a82b 100644
--- a/louloulibs/logger/logger.hpp
+++ b/src/logger/logger.hpp
@@ -17,7 +17,7 @@
#define warning_lvl 2
#define error_lvl 3
-#include "louloulibs.h"
+#include "biboumi.h"
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#else
diff --git a/src/main.cpp b/src/main.cpp
index 76ab5d9..1a9b065 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -11,7 +11,7 @@
#endif
#include <atomic>
-#include <signal.h>
+#include <csignal>
#ifdef USE_DATABASE
# include <litesql.hpp>
#endif
@@ -99,7 +99,7 @@ int main(int ac, char** av)
// Block the signals we want to manage. They will be unblocked only during
// the epoll_pwait or ppoll calls. This avoids some race conditions,
// explained in man 2 pselect on linux
- sigset_t mask;
+ sigset_t mask{};
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
@@ -179,19 +179,20 @@ int main(int ac, char** av)
{
if (xmpp_component->ever_auth)
{
+ static const std::string reconnect_name{"XMPP reconnection"};
if (xmpp_component->first_connection_try == true)
{ // immediately re-try to connect
xmpp_component->reset();
xmpp_component->start();
}
- else
+ else if (!TimedEventsManager::instance().find_event(reconnect_name))
{ // Re-connecting failed, we now try only each few seconds
auto reconnect_later = [xmpp_component]()
{
xmpp_component->reset();
xmpp_component->start();
};
- TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, "XMPP reconnection");
+ TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, reconnect_name);
TimedEventsManager::instance().add_event(std::move(event));
}
}
diff --git a/louloulibs/network/credentials_manager.cpp b/src/network/credentials_manager.cpp
index 289307b..f93a366 100644
--- a/louloulibs/network/credentials_manager.cpp
+++ b/src/network/credentials_manager.cpp
@@ -1,4 +1,4 @@
-#include "louloulibs.h"
+#include "biboumi.h"
#ifdef BOTAN_FOUND
#include <network/tcp_socket_handler.hpp>
@@ -7,10 +7,6 @@
#include <botan/tls_exceptn.h>
#include <config/config.hpp>
-#ifdef USE_DATABASE
-# include <database/database.hpp>
-#endif
-
/**
* TODO find a standard way to find that out.
*/
@@ -44,7 +40,7 @@ const std::string& BasicCredentialsManager::get_trusted_fingerprint() const
void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs,
const std::string& hostname, const std::string& trusted_fingerprint,
- std::exception_ptr exc)
+ const std::exception_ptr& exc)
{
if (!trusted_fingerprint.empty() && !certs.empty() &&
@@ -98,7 +94,7 @@ bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector<std::s
// because the certificate is signed by an issuer that was ignored.
try {
Botan::X509_Certificate cert(bundle);
- BasicCredentialsManager::certificate_store.add_certificate(std::move(cert));
+ BasicCredentialsManager::certificate_store.add_certificate(cert);
} catch (const Botan::Decoding_Error& error) {
continue;
}
diff --git a/louloulibs/network/credentials_manager.hpp b/src/network/credentials_manager.hpp
index 29ee024..e7c247d 100644
--- a/louloulibs/network/credentials_manager.hpp
+++ b/src/network/credentials_manager.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "louloulibs.h"
+#include "biboumi.h"
#ifdef BOTAN_FOUND
@@ -19,7 +19,7 @@ class TCPSocketHandler;
*/
void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs,
const std::string& hostname, const std::string& trusted_fingerprint,
- std::exception_ptr exc);
+ const std::exception_ptr& exc);
class BasicCredentialsManager: public Botan::Credentials_Manager
{
diff --git a/louloulibs/network/dns_handler.cpp b/src/network/dns_handler.cpp
index fbd2763..7f0c96a 100644
--- a/louloulibs/network/dns_handler.cpp
+++ b/src/network/dns_handler.cpp
@@ -1,4 +1,4 @@
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef UDNS_FOUND
#include <network/dns_socket_handler.hpp>
@@ -17,7 +17,7 @@ using namespace std::string_literals;
std::unique_ptr<DNSSocketHandler> DNSHandler::socket_handler{};
-DNSHandler::DNSHandler(std::shared_ptr<Poller> poller)
+DNSHandler::DNSHandler(std::shared_ptr<Poller>& poller)
{
dns_init(nullptr, 0);
const auto socket = dns_open(nullptr);
diff --git a/louloulibs/network/dns_handler.hpp b/src/network/dns_handler.hpp
index 78ffe4d..c694452 100644
--- a/louloulibs/network/dns_handler.hpp
+++ b/src/network/dns_handler.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef UDNS_FOUND
class Poller;
@@ -14,7 +14,7 @@ class Poller;
class DNSHandler
{
public:
- explicit DNSHandler(std::shared_ptr<Poller> poller);
+ explicit DNSHandler(std::shared_ptr<Poller>& poller);
~DNSHandler() = default;
DNSHandler(const DNSHandler&) = delete;
diff --git a/louloulibs/network/dns_socket_handler.cpp b/src/network/dns_socket_handler.cpp
index ad744a9..5c286c4 100644
--- a/louloulibs/network/dns_socket_handler.cpp
+++ b/src/network/dns_socket_handler.cpp
@@ -1,4 +1,4 @@
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef UDNS_FOUND
#include <network/dns_socket_handler.hpp>
@@ -7,7 +7,7 @@
#include <udns.h>
-DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller> poller,
+DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller>& poller,
const socket_t socket):
SocketHandler(poller, socket)
{
diff --git a/louloulibs/network/dns_socket_handler.hpp b/src/network/dns_socket_handler.hpp
index e12f145..6e83e87 100644
--- a/louloulibs/network/dns_socket_handler.hpp
+++ b/src/network/dns_socket_handler.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef UDNS_FOUND
#include <network/socket_handler.hpp>
@@ -12,7 +12,7 @@
class DNSSocketHandler: public SocketHandler
{
public:
- explicit DNSSocketHandler(std::shared_ptr<Poller> poller, const socket_t socket);
+ explicit DNSSocketHandler(std::shared_ptr<Poller>& poller, const socket_t socket);
~DNSSocketHandler();
DNSSocketHandler(const DNSSocketHandler&) = delete;
DNSSocketHandler(DNSSocketHandler&&) = delete;
diff --git a/louloulibs/network/poller.cpp b/src/network/poller.cpp
index 9f5bcfb..0f02cc5 100644
--- a/louloulibs/network/poller.cpp
+++ b/src/network/poller.cpp
@@ -2,8 +2,8 @@
#include <logger/logger.hpp>
#include <utils/timed_events.hpp>
-#include <assert.h>
-#include <errno.h>
+#include <cassert>
+#include <cerrno>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
@@ -198,9 +198,13 @@ int Poller::poll(const std::chrono::milliseconds& timeout)
static const size_t max_events = 12;
struct epoll_event revents[max_events];
// Unblock all signals, only during the epoll_pwait call
- sigset_t empty_signal_set;
+ sigset_t empty_signal_set{};
sigemptyset(&empty_signal_set);
- const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(),
+
+ int real_timeout = std::numeric_limits<int>::max();
+ if (timeout.count() < real_timeout) // Just avoid any potential int overflow
+ real_timeout = static_cast<int>(timeout.count());
+ const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, real_timeout,
&empty_signal_set);
if (nb_events == -1)
{
diff --git a/louloulibs/network/poller.hpp b/src/network/poller.hpp
index e39e438..3cc2710 100644
--- a/louloulibs/network/poller.hpp
+++ b/src/network/poller.hpp
@@ -10,7 +10,7 @@
#define POLL 1
#define EPOLL 2
#define KQUEUE 3
-#include <louloulibs.h>
+#include <biboumi.h>
#ifndef POLLER
#define POLLER POLL
#endif
diff --git a/louloulibs/network/resolver.cpp b/src/network/resolver.cpp
index 0655b1b..ef54ba2 100644
--- a/louloulibs/network/resolver.cpp
+++ b/src/network/resolver.cpp
@@ -1,7 +1,7 @@
#include <network/dns_handler.hpp>
#include <utils/timed_events.hpp>
#include <network/resolver.hpp>
-#include <string.h>
+#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#ifdef UDNS_FOUND
@@ -41,8 +41,8 @@ Resolver::Resolver():
void Resolver::resolve(const std::string& hostname, const std::string& port,
SuccessCallbackType success_cb, ErrorCallbackType error_cb)
{
- this->error_cb = error_cb;
- this->success_cb = success_cb;
+ this->error_cb = std::move(error_cb);
+ this->success_cb = std::move(success_cb);
#ifdef UDNS_FOUND
this->port = port;
#endif
@@ -52,8 +52,7 @@ void Resolver::resolve(const std::string& hostname, const std::string& port,
int Resolver::call_getaddrinfo(const char *name, const char* port, int flags)
{
- struct addrinfo hints;
- memset(&hints, 0, sizeof(struct addrinfo));
+ struct addrinfo hints{};
hints.ai_flags = flags;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
@@ -111,14 +110,18 @@ void Resolver::start_resolving(const std::string& hostname, const std::string& p
// And finally, we try a DNS resolution
auto hostname6_resolved = [](dns_ctx*, dns_rr_a6* result, void* data)
{
- Resolver* resolver = static_cast<Resolver*>(data);
+ auto resolver = static_cast<Resolver*>(data);
resolver->on_hostname6_resolved(result);
+ resolver->after_resolved();
+ std::free(result);
};
auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data)
{
- Resolver* resolver = static_cast<Resolver*>(data);
+ auto resolver = static_cast<Resolver*>(data);
resolver->on_hostname4_resolved(result);
+ resolver->after_resolved();
+ std::free(result);
};
DNSHandler::watch();
@@ -173,9 +176,6 @@ std::vector<std::string> Resolver::look_in_etc_hosts(const std::string &hostname
void Resolver::on_hostname4_resolved(dns_rr_a4 *result)
{
- if (dns_active(nullptr) == 0)
- DNSHandler::unwatch();
-
this->resolved4 = true;
const auto status = dns_status(nullptr);
@@ -196,29 +196,29 @@ void Resolver::on_hostname4_resolved(dns_rr_a4 *result)
if (error != end(dns_error_messages))
this->error_msg = error->second;
}
-
- if (this->resolved6 && this->resolved4)
- this->on_resolved();
}
void Resolver::on_hostname6_resolved(dns_rr_a6 *result)
{
- if (dns_active(nullptr) == 0)
- DNSHandler::unwatch();
-
this->resolved6 = true;
- char buf[INET6_ADDRSTRLEN];
const auto status = dns_status(nullptr);
if (status >= 0 && result)
{
+ char buf[INET6_ADDRSTRLEN];
for (auto i = 0; i < result->dnsa6_nrr; ++i)
{
inet_ntop(AF_INET6, &result->dnsa6_addr[i], buf, sizeof(buf));
this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST);
}
}
+}
+
+void Resolver::after_resolved()
+{
+ if (dns_active(nullptr) == 0)
+ DNSHandler::unwatch();
if (this->resolved6 && this->resolved4)
this->on_resolved();
diff --git a/louloulibs/network/resolver.hpp b/src/network/resolver.hpp
index 800c7ec..f65ff86 100644
--- a/louloulibs/network/resolver.hpp
+++ b/src/network/resolver.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "louloulibs.h"
+#include "biboumi.h"
#include <functional>
#include <vector>
@@ -89,6 +89,10 @@ private:
#ifdef UDNS_FOUND
void on_hostname4_resolved(dns_rr_a4 *result);
void on_hostname6_resolved(dns_rr_a6 *result);
+ /**
+ * Called after one record (4 or 6) has been resolved.
+ */
+ void after_resolved();
void start_timer();
diff --git a/louloulibs/network/socket_handler.hpp b/src/network/socket_handler.hpp
index 607a106..181a6c0 100644
--- a/louloulibs/network/socket_handler.hpp
+++ b/src/network/socket_handler.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include <louloulibs.h>
+#include <biboumi.h>
#include <memory>
class Poller;
@@ -10,7 +10,7 @@ using socket_t = int;
class SocketHandler
{
public:
- explicit SocketHandler(std::shared_ptr<Poller> poller, const socket_t socket):
+ explicit SocketHandler(std::shared_ptr<Poller>& poller, const socket_t socket):
poller(poller),
socket(socket)
{}
diff --git a/louloulibs/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp
index 530c3d9..35f2446 100644
--- a/louloulibs/network/tcp_client_socket_handler.cpp
+++ b/src/network/tcp_client_socket_handler.cpp
@@ -11,7 +11,7 @@
using namespace std::string_literals;
-TCPClientSocketHandler::TCPClientSocketHandler(std::shared_ptr<Poller> poller):
+TCPClientSocketHandler::TCPClientSocketHandler(std::shared_ptr<Poller>& poller):
TCPSocketHandler(poller),
hostname_resolution_failed(false),
connected(false),
@@ -35,7 +35,7 @@ void TCPClientSocketHandler::init_socket(const struct addrinfo* rp)
// Convert the address from string format to a sockaddr that can be
// used in bind()
struct addrinfo* result;
- struct addrinfo hints;
+ struct addrinfo hints{};
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
@@ -161,14 +161,14 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri
this->local_port = static_cast<uint16_t>(-1);
if (rp->ai_family == AF_INET6)
{
- struct sockaddr_in6 a;
+ struct sockaddr_in6 a{};
socklen_t l = sizeof(a);
if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1)
this->local_port = ntohs(a.sin6_port);
}
else if (rp->ai_family == AF_INET)
{
- struct sockaddr_in a;
+ struct sockaddr_in a{};
socklen_t l = sizeof(a);
if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1)
this->local_port = ntohs(a.sin_port);
@@ -256,6 +256,6 @@ std::string TCPClientSocketHandler::get_port() const
bool TCPClientSocketHandler::match_port_pairt(const uint16_t local, const uint16_t remote) const
{
- const uint16_t remote_port = static_cast<uint16_t>(std::stoi(this->port));
+ const auto remote_port = static_cast<uint16_t>(std::stoi(this->port));
return this->is_connected() && local == this->local_port && remote == remote_port;
}
diff --git a/louloulibs/network/tcp_client_socket_handler.hpp b/src/network/tcp_client_socket_handler.hpp
index 75e1364..74caca9 100644
--- a/louloulibs/network/tcp_client_socket_handler.hpp
+++ b/src/network/tcp_client_socket_handler.hpp
@@ -5,7 +5,7 @@
class TCPClientSocketHandler: public TCPSocketHandler
{
public:
- TCPClientSocketHandler(std::shared_ptr<Poller> poller);
+ TCPClientSocketHandler(std::shared_ptr<Poller>& poller);
~TCPClientSocketHandler();
/**
* Connect to the remote server, and call on_connected() if this
diff --git a/louloulibs/network/tcp_server_socket.hpp b/src/network/tcp_server_socket.hpp
index 7ea49ab..652b773 100644
--- a/louloulibs/network/tcp_server_socket.hpp
+++ b/src/network/tcp_server_socket.hpp
@@ -12,13 +12,12 @@
#include <netinet/ip.h>
#include <cstring>
-#include <cassert>
template <typename RemoteSocketType>
class TcpSocketServer: public SocketHandler
{
public:
- TcpSocketServer(std::shared_ptr<Poller> poller, const uint16_t port):
+ TcpSocketServer(std::shared_ptr<Poller>& poller, const uint16_t port):
SocketHandler(poller, -1)
{
if ((this->socket = ::socket(AF_INET6, SOCK_STREAM, 0)) == -1)
diff --git a/louloulibs/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp
index 6aef2b1..1049375 100644
--- a/louloulibs/network/tcp_socket_handler.cpp
+++ b/src/network/tcp_socket_handler.cpp
@@ -8,12 +8,14 @@
#include <sys/types.h>
#include <stdexcept>
#include <unistd.h>
-#include <errno.h>
+#include <cerrno>
#include <cstring>
#ifdef BOTAN_FOUND
# include <botan/hex.h>
# include <botan/tls_exceptn.h>
+# include <config/config.hpp>
+# include <utils/dirname.hpp>
namespace
{
@@ -22,11 +24,6 @@ namespace
static Botan::AutoSeeded_RNG rng{};
return rng;
}
- BiboumiTLSPolicy& get_policy()
- {
- static BiboumiTLSPolicy policy{};
- return policy;
- }
Botan::TLS::Session_Manager_In_Memory& get_session_manager()
{
static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()};
@@ -42,9 +39,8 @@ namespace
using namespace std::string_literals;
using namespace std::chrono_literals;
-namespace ph = std::placeholders;
-TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
+TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller>& poller):
SocketHandler(poller, -1),
use_tls(false)
#ifdef BOTAN_FOUND
@@ -126,9 +122,9 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size)
void TCPSocketHandler::on_send()
{
struct iovec msg_iov[UIO_FASTIOV] = {};
- struct msghdr msg{nullptr, 0,
- msg_iov,
- 0, nullptr, 0, 0};
+ struct msghdr msg{};
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = 0;
for (const std::string& s: this->out_buf)
{
// unconsting the content of s is ok, sendmsg will never modify it
@@ -231,9 +227,15 @@ void TCPSocketHandler::consume_in_buffer(const std::size_t size)
}
#ifdef BOTAN_FOUND
-void TCPSocketHandler::start_tls(const std::string& address, const std::string& port)
+void TCPSocketHandler::start_tls(const std::string& address, const std::string& port_string)
{
- Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port));
+ auto port = std::min(std::stoul(port_string), static_cast<unsigned long>(std::numeric_limits<uint16_t>::max()));
+ Botan::TLS::Server_Information server_info(address, "irc", static_cast<uint16_t>(port));
+ auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename()));
+ if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/')
+ policy_directory += '/';
+ this->policy.load(policy_directory + "policy.txt");
+ this->policy.load(policy_directory + address + ".policy.txt");
this->tls = std::make_unique<Botan::TLS::Client>(
# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32)
*this,
@@ -243,7 +245,7 @@ void TCPSocketHandler::start_tls(const std::string& address, const std::string&
[this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); },
[this](const Botan::TLS::Session& session) { return this->tls_session_established(session); },
# endif
- get_session_manager(), this->credential_manager, get_policy(),
+ get_session_manager(), this->credential_manager, this->policy,
get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version());
}
diff --git a/louloulibs/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp
index 600405d..f68698e 100644
--- a/louloulibs/network/tcp_socket_handler.hpp
+++ b/src/network/tcp_socket_handler.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "louloulibs.h"
+#include "biboumi.h"
#include <network/socket_handler.hpp>
#include <network/resolver.hpp>
@@ -23,21 +23,7 @@
# include <botan/types.h>
# include <botan/botan.h>
# include <botan/tls_session_manager.h>
-
-class BiboumiTLSPolicy: public Botan::TLS::Policy
-{
-public:
-# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,33)
- bool use_ecc_point_compression() const override
- {
- return true;
- }
- bool require_cert_revocation_info() const override
- {
- return false;
- }
-# endif
-};
+# include <network/tls_policy.hpp>
# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32)
# define BOTAN_TLS_CALLBACKS_OVERRIDE override final
@@ -60,7 +46,7 @@ class TCPSocketHandler: public SocketHandler
protected:
~TCPSocketHandler();
public:
- explicit TCPSocketHandler(std::shared_ptr<Poller> poller);
+ explicit TCPSocketHandler(std::shared_ptr<Poller>& poller);
TCPSocketHandler(const TCPSocketHandler&) = delete;
TCPSocketHandler(TCPSocketHandler&&) = delete;
TCPSocketHandler& operator=(const TCPSocketHandler&) = delete;
@@ -230,6 +216,7 @@ protected:
protected:
BasicCredentialsManager credential_manager;
private:
+ BiboumiTLSPolicy policy;
/**
* We use a unique_ptr because we may not want to create the object at
* all. The Botan::TLS::Client object generates a handshake message and
diff --git a/src/network/tls_policy.cpp b/src/network/tls_policy.cpp
new file mode 100644
index 0000000..5439397
--- /dev/null
+++ b/src/network/tls_policy.cpp
@@ -0,0 +1,48 @@
+#include "biboumi.h"
+
+#ifdef BOTAN_FOUND
+
+#include <fstream>
+
+#include <utils/tolower.hpp>
+
+#include <network/tls_policy.hpp>
+#include <logger/logger.hpp>
+
+bool BiboumiTLSPolicy::load(const std::string& filename)
+{
+ std::ifstream is(filename.data());
+ if (is)
+ {
+ try {
+ this->load(is);
+ log_info("Successfully loaded policy file: ", filename);
+ return true;
+ } catch (const Botan::Exception& e) {
+ log_error("Failed to parse policy_file ", filename, ": ", e.what());
+ return false;
+ }
+ }
+ log_info("Could not open policy file: ", filename);
+ return false;
+}
+
+void BiboumiTLSPolicy::load(std::istream& is)
+{
+ const auto dict = Botan::read_cfg(is);
+ for (const auto& pair: dict)
+ {
+ // Workaround for options that are not overridden in Botan::TLS::Text_Policy
+ if (pair.first == "require_cert_revocation_info")
+ this->req_cert_revocation_info = !(pair.second == "0" || utils::tolower(pair.second) == "false");
+ else
+ this->set(pair.first, pair.second);
+ }
+}
+
+bool BiboumiTLSPolicy::require_cert_revocation_info() const
+{
+ return this->req_cert_revocation_info;
+}
+
+#endif
diff --git a/src/network/tls_policy.hpp b/src/network/tls_policy.hpp
new file mode 100644
index 0000000..29fd2b3
--- /dev/null
+++ b/src/network/tls_policy.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "biboumi.h"
+
+#ifdef BOTAN_FOUND
+
+#include <botan/tls_policy.h>
+
+class BiboumiTLSPolicy: public Botan::TLS::Text_Policy
+{
+public:
+ BiboumiTLSPolicy():
+ Botan::TLS::Text_Policy({})
+ {}
+ bool load(const std::string& filename);
+ void load(std::istream& iss);
+
+ BiboumiTLSPolicy(const BiboumiTLSPolicy &) = delete;
+ BiboumiTLSPolicy(BiboumiTLSPolicy &&) = delete;
+ BiboumiTLSPolicy &operator=(const BiboumiTLSPolicy &) = delete;
+ BiboumiTLSPolicy &operator=(BiboumiTLSPolicy &&) = delete;
+
+ bool require_cert_revocation_info() const override;
+protected:
+ bool req_cert_revocation_info{true};
+};
+
+#endif
diff --git a/src/utils/dirname.cpp b/src/utils/dirname.cpp
new file mode 100644
index 0000000..71c9c38
--- /dev/null
+++ b/src/utils/dirname.cpp
@@ -0,0 +1,16 @@
+#include <utils/dirname.hpp>
+
+namespace utils
+{
+ std::string dirname(const std::string filename)
+ {
+ if (filename.empty())
+ return "./";
+ if (filename == ".." || filename == ".")
+ return filename;
+ auto pos = filename.rfind('/');
+ if (pos == std::string::npos)
+ return "./";
+ return filename.substr(0, pos + 1);
+ }
+}
diff --git a/src/utils/dirname.hpp b/src/utils/dirname.hpp
new file mode 100644
index 0000000..c1df81b
--- /dev/null
+++ b/src/utils/dirname.hpp
@@ -0,0 +1,6 @@
+#include <string>
+
+namespace utils
+{
+std::string dirname(const std::string filename);
+}
diff --git a/louloulibs/utils/encoding.cpp b/src/utils/encoding.cpp
index aa91dac..cff0039 100644
--- a/louloulibs/utils/encoding.cpp
+++ b/src/utils/encoding.cpp
@@ -4,7 +4,7 @@
#include <stdexcept>
-#include <assert.h>
+#include <cassert>
#include <string.h>
#include <iconv.h>
#include <cerrno>
diff --git a/louloulibs/utils/encoding.hpp b/src/utils/encoding.hpp
index 586edd8..b707a0c 100644
--- a/louloulibs/utils/encoding.hpp
+++ b/src/utils/encoding.hpp
@@ -28,7 +28,7 @@ namespace utils
* 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);
+ std::string convert_to_utf8(const std::string& str, const char* charset);
}
namespace xep0106
diff --git a/louloulibs/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp
index 5b3bedb..5b3bedb 100644
--- a/louloulibs/utils/get_first_non_empty.cpp
+++ b/src/utils/get_first_non_empty.cpp
diff --git a/louloulibs/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp
index a38f5fb..a38f5fb 100644
--- a/louloulibs/utils/get_first_non_empty.hpp
+++ b/src/utils/get_first_non_empty.hpp
diff --git a/louloulibs/utils/revstr.cpp b/src/utils/revstr.cpp
index 87fd801..87fd801 100644
--- a/louloulibs/utils/revstr.cpp
+++ b/src/utils/revstr.cpp
diff --git a/louloulibs/utils/revstr.hpp b/src/utils/revstr.hpp
index 8e521ea..8e521ea 100644
--- a/louloulibs/utils/revstr.hpp
+++ b/src/utils/revstr.hpp
diff --git a/louloulibs/utils/scopeguard.hpp b/src/utils/scopeguard.hpp
index e697fc3..e697fc3 100644
--- a/louloulibs/utils/scopeguard.hpp
+++ b/src/utils/scopeguard.hpp
diff --git a/louloulibs/utils/sha1.cpp b/src/utils/sha1.cpp
index 71ad18d..2e6efc2 100644
--- a/louloulibs/utils/sha1.cpp
+++ b/src/utils/sha1.cpp
@@ -1,10 +1,12 @@
#include <utils/sha1.hpp>
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef BOTAN_FOUND
+# include <botan/version.h>
# include <botan/hash.h>
# include <botan/hex.h>
+# include <botan/exceptn.h>
#endif
#ifdef GCRYPT_FOUND
# include <gcrypt.h>
@@ -16,7 +18,13 @@
std::string sha1(const std::string& input)
{
#ifdef BOTAN_FOUND
+# if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34)
+ auto sha1 = Botan::HashFunction::create("SHA-1");
+ if (!sha1)
+ throw Botan::Algorithm_Not_Found("SHA-1");
+# else
auto sha1 = Botan::HashFunction::create_or_throw("SHA-1");
+# endif
sha1->update(input);
return Botan::hex_encode(sha1->final(), false);
#endif
diff --git a/louloulibs/utils/sha1.hpp b/src/utils/sha1.hpp
index 6c551ac..6c551ac 100644
--- a/louloulibs/utils/sha1.hpp
+++ b/src/utils/sha1.hpp
diff --git a/louloulibs/utils/split.cpp b/src/utils/split.cpp
index 80f8dae..80f8dae 100644
--- a/louloulibs/utils/split.cpp
+++ b/src/utils/split.cpp
diff --git a/louloulibs/utils/split.hpp b/src/utils/split.hpp
index 3755ef8..3755ef8 100644
--- a/louloulibs/utils/split.hpp
+++ b/src/utils/split.hpp
diff --git a/louloulibs/utils/string.cpp b/src/utils/string.cpp
index 635e71a..635e71a 100644
--- a/louloulibs/utils/string.cpp
+++ b/src/utils/string.cpp
diff --git a/louloulibs/utils/string.hpp b/src/utils/string.hpp
index 84ba101..071ce2c 100644
--- a/louloulibs/utils/string.hpp
+++ b/src/utils/string.hpp
@@ -6,5 +6,3 @@
bool to_bool(const std::string& val);
std::vector<std::string> cut(const std::string& val, const std::size_t size);
-
-
diff --git a/louloulibs/utils/system.cpp b/src/utils/system.cpp
index c0bee11..d821dec 100644
--- a/louloulibs/utils/system.cpp
+++ b/src/utils/system.cpp
@@ -9,7 +9,7 @@ namespace utils
{
std::string get_system_name()
{
- struct utsname uts;
+ struct utsname uts{};
const int res = ::uname(&uts);
if (res == -1)
{
diff --git a/louloulibs/utils/system.hpp b/src/utils/system.hpp
index 7ea1677..7ea1677 100644
--- a/louloulibs/utils/system.hpp
+++ b/src/utils/system.hpp
diff --git a/louloulibs/utils/time.cpp b/src/utils/time.cpp
index e9f3943..bc2c18d 100644
--- a/louloulibs/utils/time.cpp
+++ b/src/utils/time.cpp
@@ -1,11 +1,11 @@
#include <utils/time.hpp>
-#include <time.h>
+#include <ctime>
#include <sstream>
#include <iomanip>
#include <locale>
-#include "louloulibs.h"
+#include "biboumi.h"
namespace utils
{
@@ -26,8 +26,8 @@ std::time_t parse_datetime(const std::string& stamp)
std::istringstream ss(stamp);
ss.imbue(std::locale("C"));
- std::string timezone;
- ss >> std::get_time(&t, format) >> timezone;
+ std::string remainings;
+ ss >> std::get_time(&t, format) >> remainings;
if (ss.fail())
return -1;
#else
@@ -36,12 +36,22 @@ std::time_t parse_datetime(const std::string& stamp)
if (!strptime(stamp.data(), format, &t)) {
return -1;
}
- const std::string timezone(stamp.data() + stamp_size_without_tz);
+ const std::string remainings(stamp.data() + stamp_size_without_tz);
#endif
- if (timezone.empty())
+ if (remainings.empty())
return -1;
+ std::string timezone;
+ // Skip optional fractions of seconds
+ if (remainings[0] == '.')
+ {
+ const auto pos = remainings.find_first_not_of(".0123456789");
+ timezone = remainings.substr(pos);
+ }
+ else
+ timezone = std::move(remainings);
+
if (timezone.compare(0, 1, "Z") != 0)
{
std::stringstream tz_ss;
diff --git a/louloulibs/utils/time.hpp b/src/utils/time.hpp
index c71cd9c..c71cd9c 100644
--- a/louloulibs/utils/time.hpp
+++ b/src/utils/time.hpp
diff --git a/louloulibs/utils/timed_events.cpp b/src/utils/timed_events.cpp
index 68d009c..26ded82 100644
--- a/louloulibs/utils/timed_events.cpp
+++ b/src/utils/timed_events.cpp
@@ -1,22 +1,23 @@
+#include <utility>
#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),
+ std::function<void()> callback, std::string name):
+ time_point(time_point),
+ callback(std::move(callback)),
repeat(false),
repeat_delay(0),
- name(name)
+ name(std::move(name))
{
}
TimedEvent::TimedEvent(std::chrono::milliseconds&& duration,
- std::function<void()> callback, const std::string& name):
+ std::function<void()> callback, std::string name):
time_point(std::chrono::steady_clock::now() + duration),
- callback(callback),
+ callback(std::move(callback)),
repeat(true),
- repeat_delay(std::move(duration)),
- name(name)
+ repeat_delay(duration),
+ name(std::move(name))
{
}
@@ -32,10 +33,8 @@ bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_poin
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);
+ auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(this->time_point - std::chrono::steady_clock::now());
+ return std::max(diff, 0ms);
}
void TimedEvent::execute() const
diff --git a/louloulibs/utils/timed_events.hpp b/src/utils/timed_events.hpp
index 6e28206..fa0fc50 100644
--- a/louloulibs/utils/timed_events.hpp
+++ b/src/utils/timed_events.hpp
@@ -25,9 +25,9 @@ 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="");
+ std::function<void()> callback, std::string name="");
explicit TimedEvent(std::chrono::milliseconds&& duration,
- std::function<void()> callback, const std::string& name="");
+ std::function<void()> callback, std::string name="");
explicit TimedEvent(TimedEvent&&) = default;
TimedEvent& operator=(TimedEvent&&) = default;
@@ -125,6 +125,11 @@ public:
* Return the number of managed events.
*/
std::size_t size() const;
+ /**
+ * Return a pointer to the first event with the given name. If none
+ * is found, returns nullptr.
+ */
+ const TimedEvent* find_event(const std::string& name) const;
private:
std::vector<TimedEvent> events;
diff --git a/louloulibs/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp
index 67d61fe..75e6338 100644
--- a/louloulibs/utils/timed_events_manager.cpp
+++ b/src/utils/timed_events_manager.cpp
@@ -1,5 +1,7 @@
#include <utils/timed_events.hpp>
+#include <algorithm>
+
TimedEventsManager& TimedEventsManager::instance()
{
static TimedEventsManager inst;
@@ -67,7 +69,19 @@ std::size_t TimedEventsManager::cancel(const std::string& name)
return res;
}
+
+
std::size_t TimedEventsManager::size() const
{
return this->events.size();
}
+
+const TimedEvent* TimedEventsManager::find_event(const std::string& name) const
+{
+ const auto it = std::find_if(this->events.begin(), this->events.end(), [&name](const TimedEvent& o) {
+ return o.get_name() == name;
+ });
+ if (it == this->events.end())
+ return nullptr;
+ return &*it;
+}
diff --git a/louloulibs/utils/tolower.cpp b/src/utils/tolower.cpp
index 3e518bd..3e518bd 100644
--- a/louloulibs/utils/tolower.cpp
+++ b/src/utils/tolower.cpp
diff --git a/louloulibs/utils/tolower.hpp b/src/utils/tolower.hpp
index 650e05d..650e05d 100644
--- a/louloulibs/utils/tolower.hpp
+++ b/src/utils/tolower.hpp
diff --git a/louloulibs/utils/xdg.cpp b/src/utils/xdg.cpp
index 48212a1..b0fa7be 100644
--- a/louloulibs/utils/xdg.cpp
+++ b/src/utils/xdg.cpp
@@ -1,7 +1,7 @@
#include <utils/xdg.hpp>
#include <cstdlib>
-#include "louloulibs.h"
+#include "biboumi.h"
std::string xdg_path(const std::string& filename, const char* env_var)
{
diff --git a/louloulibs/utils/xdg.hpp b/src/utils/xdg.hpp
index 56e11da..7be6922 100644
--- a/louloulibs/utils/xdg.hpp
+++ b/src/utils/xdg.hpp
@@ -10,5 +10,3 @@
*/
std::string xdg_config_path(const std::string& filename);
std::string xdg_data_path(const std::string& filename);
-
-
diff --git a/louloulibs/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp
index 825cc92..e02bf35 100644
--- a/louloulibs/xmpp/adhoc_command.cpp
+++ b/src/xmpp/adhoc_command.cpp
@@ -1,11 +1,12 @@
+#include <utility>
#include <xmpp/adhoc_command.hpp>
#include <xmpp/xmpp_component.hpp>
#include <utils/reload.hpp>
using namespace std::string_literals;
-AdhocCommand::AdhocCommand(std::vector<AdhocStep>&& callbacks, const std::string& name, const bool admin_only):
- name(name),
+AdhocCommand::AdhocCommand(std::vector<AdhocStep>&& callbacks, std::string name, const bool admin_only):
+ name(std::move(name)),
callbacks(std::move(callbacks)),
admin_only(admin_only)
{
diff --git a/louloulibs/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp
index 7c4de47..c00d9e6 100644
--- a/louloulibs/xmpp/adhoc_command.hpp
+++ b/src/xmpp/adhoc_command.hpp
@@ -17,7 +17,7 @@ class AdhocCommand
{
friend class AdhocSession;
public:
- AdhocCommand(std::vector<AdhocStep>&& callback, const std::string& name, const bool admin_only);
+ AdhocCommand(std::vector<AdhocStep>&& callbacks, std::string name, const bool admin_only);
~AdhocCommand() = default;
AdhocCommand(const AdhocCommand&) = default;
AdhocCommand(AdhocCommand&&) = default;
diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp
index 040d0ff..040d0ff 100644
--- a/louloulibs/xmpp/adhoc_commands_handler.cpp
+++ b/src/xmpp/adhoc_commands_handler.cpp
diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp
index e37d913..e37d913 100644
--- a/louloulibs/xmpp/adhoc_commands_handler.hpp
+++ b/src/xmpp/adhoc_commands_handler.hpp
diff --git a/louloulibs/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp
index dda4bea..e2d6c0e 100644
--- a/louloulibs/xmpp/adhoc_session.cpp
+++ b/src/xmpp/adhoc_session.cpp
@@ -1,7 +1,7 @@
#include <xmpp/adhoc_session.hpp>
#include <xmpp/adhoc_command.hpp>
-#include <assert.h>
+#include <cassert>
AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner_jid,
const std::string& to_jid):
diff --git a/louloulibs/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp
index 0de8d13..0de8d13 100644
--- a/louloulibs/xmpp/adhoc_session.hpp
+++ b/src/xmpp/adhoc_session.hpp
diff --git a/louloulibs/xmpp/auth.cpp b/src/xmpp/auth.cpp
index 8a34a4e..8a34a4e 100644
--- a/louloulibs/xmpp/auth.cpp
+++ b/src/xmpp/auth.cpp
diff --git a/louloulibs/xmpp/auth.hpp b/src/xmpp/auth.hpp
index 34a2116..34a2116 100644
--- a/louloulibs/xmpp/auth.hpp
+++ b/src/xmpp/auth.hpp
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index a83af80..ab28cfd 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -24,7 +24,7 @@ using namespace std::string_literals;
void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node)
{
- auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
XmlSubNode x(command_node, "jabber:x:data:x");
x["type"] = "form";
@@ -55,7 +55,7 @@ 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);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
// Find out if the jids, and the quit message are provided in the form.
std::string quit_message;
@@ -151,7 +151,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)
{
- BiboumiComponent& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
const XmlNode* x = command_node.get_child("x", "jabber:x:data");
if (x)
@@ -256,7 +256,8 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode pass(x, "field");
pass["var"] = "pass";
pass["type"] = "text-private";
- pass["label"] = "Server password (to be used in a PASS command when connecting)";
+ pass["label"] = "Server password";
+ pass["desc"] = "Will be used in a PASS command when connecting";
if (!options.pass.value().empty())
{
XmlSubNode pass_value(pass, "value");
@@ -318,16 +319,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value());
}
-
- XmlSubNode linger_time(x, "field");
- linger_time["var"] = "linger_time";
- linger_time["type"] = "text-single";
- linger_time["desc"] = "The number of seconds to wait before sending a QUIT command, after the last channel on that server has been left.";
- linger_time["label"] = "Linger time";
- {
- XmlSubNode linger_time_value(linger_time, "value");
- linger_time_value.set_inner(std::to_string(options.lingerTime.value()));
- }
}
void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
@@ -407,10 +398,6 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
value && !value->get_inner().empty())
options.encodingIn = value->get_inner();
- else if (field->get_tag("var") == "linger_time" &&
- value && !value->get_inner().empty())
- options.lingerTime = value->get_inner();
-
}
options.update();
@@ -431,11 +418,18 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
{
const Jid owner(session.get_owner_jid());
const Jid target(session.get_target_jid());
+
+ insert_irc_channel_configuration_form(command_node, owner, target);
+}
+
+void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, const Jid& target)
+{
const Iid iid(target.local, {});
- auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain,
+
+ auto options = Database::get_irc_channel_options_with_server_default(requester.local + "@" + requester.domain,
iid.get_server(), iid.get_local());
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(node, "jabber:x:data:x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server());
@@ -463,43 +457,75 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value());
}
+
+ XmlSubNode persistent(x, "field");
+ persistent["var"] = "persistent";
+ persistent["type"] = "boolean";
+ persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command.";
+ persistent["label"] = "Persistent";
+ {
+ XmlSubNode value(persistent, "value");
+ value.set_name("value");
+ if (options.persistent.value())
+ value.set_inner("true");
+ else
+ value.set_inner("false");
+ }
}
void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
{
- const XmlNode* x = command_node.get_child("x", "jabber:x:data");
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+
+ if (handle_irc_channel_configuration_form(command_node, owner, target))
+ {
+ command_node.delete_all_children();
+ XmlSubNode note(command_node, "note");
+ note["type"] = "info";
+ note.set_inner("Configuration successfully applied.");
+ }
+ else
+ {
+ XmlSubNode error(command_node, ADHOC_NS":error");
+ error["type"] = "modify";
+ XmlSubNode condition(error, STANZA_NS":bad-request");
+ session.terminate();
+ }
+}
+
+bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target)
+{
+ const XmlNode* x = node.get_child("x", "jabber:x:data");
if (x)
{
- const Jid owner(session.get_owner_jid());
- const Jid target(session.get_target_jid());
- const Iid iid(target.local, {});
- auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain,
- iid.get_server(), iid.get_local());
- for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
+ if (x->get_tag("type") == "submit")
{
- const XmlNode* value = field->get_child("value", "jabber:x:data");
+ const Iid iid(target.local, {});
+ auto options = Database::get_irc_channel_options(requester.local + "@" + requester.domain,
+ iid.get_server(), iid.get_local());
+ for (const XmlNode *field: x->get_children("field", "jabber:x:data"))
+ {
+ const XmlNode *value = field->get_child("value", "jabber:x:data");
- if (field->get_tag("var") == "encoding_out" &&
- value && !value->get_inner().empty())
- options.encodingOut = value->get_inner();
+ if (field->get_tag("var") == "encoding_out" &&
+ value && !value->get_inner().empty())
+ options.encodingOut = value->get_inner();
- else if (field->get_tag("var") == "encoding_in" &&
- value && !value->get_inner().empty())
- options.encodingIn = value->get_inner();
- }
+ else if (field->get_tag("var") == "encoding_in" &&
+ value && !value->get_inner().empty())
+ options.encodingIn = value->get_inner();
- options.update();
+ else if (field->get_tag("var") == "persistent" &&
+ value)
+ options.persistent = to_bool(value->get_inner());
+ }
- command_node.delete_all_children();
- XmlSubNode note(command_node, "note");
- note["type"] = "info";
- note.set_inner("Configuration successfully applied.");
- return;
+ options.update();
+ }
+ return true;
}
- XmlSubNode error(command_node, ADHOC_NS":error");
- error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
- session.terminate();
+ return false;
}
#endif // USE_DATABASE
@@ -514,7 +540,7 @@ void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession&
}
else
{ // Send a form to select the user to disconnect
- auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
XmlSubNode x(command_node, "jabber:x:data:x");
x["type"] = "form";
@@ -559,7 +585,7 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession&
// Send a data form to let the user choose which server to disconnect the
// user from
command_node.delete_all_children();
- auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
XmlSubNode x(command_node, "jabber:x:data:x");
x["type"] = "form";
@@ -624,7 +650,7 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession&
}
}
- auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect);
auto& clients = bridge->get_irc_clients();
@@ -652,7 +678,7 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession&
void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node)
{
- BiboumiComponent& biboumi_component = static_cast<BiboumiComponent&>(component);
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(component);
const Jid owner(session.get_owner_jid());
const Jid target(session.get_target_jid());
diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp
index b5fce61..7d29cc2 100644
--- a/src/xmpp/biboumi_adhoc_commands.hpp
+++ b/src/xmpp/biboumi_adhoc_commands.hpp
@@ -4,6 +4,7 @@
#include <xmpp/adhoc_command.hpp>
#include <xmpp/adhoc_session.hpp>
#include <xmpp/xmpp_stanza.hpp>
+#include <xmpp/jid.hpp>
class XmppComponent;
@@ -17,7 +18,9 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, const Jid& target);
void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target);
void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index 2783b93..ca3a887 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -16,7 +16,6 @@
#include <cstdlib>
-#include <louloulibs.h>
#include <biboumi.h>
#include <uuid/uuid.h>
@@ -44,7 +43,7 @@ static std::set<std::string> kickable_errors{
};
-BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
+BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller>& poller, const std::string& hostname, const std::string& secret):
XmppComponent(poller, hostname, secret),
irc_server_adhoc_commands_handler(*this),
irc_channel_adhoc_commands_handler(*this)
@@ -84,10 +83,8 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::st
void BiboumiComponent::shutdown()
{
- for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
- {
- it->second->shutdown("Gateway shutdown");
- }
+ for (auto& pair: this->bridges)
+ pair.second->shutdown("Gateway shutdown");
}
void BiboumiComponent::clean()
@@ -395,6 +392,11 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
if (this->handle_mam_request(stanza))
stanza_error.disable();
}
+ else if ((query = stanza.get_child("query", MUC_OWNER_NS)))
+ {
+ if (this->handle_room_configuration_form(*query, from, to, id))
+ stanza_error.disable();
+ }
#endif
}
else if (type == "get")
@@ -423,7 +425,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
}
else if (iid.type == Iid::Type::Channel)
{
- if (node == MUC_TRAFFIC_NS)
+ if (node.empty())
+ {
+ this->send_irc_channel_disco_info(id, from, to_str);
+ stanza_error.disable();
+ }
+ else if (node == MUC_TRAFFIC_NS)
{
this->send_irc_channel_muc_traffic_info(id, from, to_str);
stanza_error.disable();
@@ -501,6 +508,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
rs_info.max = std::atoi(max->get_inner().data());
}
+ if (rs_info.max == -1)
+ rs_info.max = 100;
bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info));
stanza_error.disable();
}
@@ -525,6 +534,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
}
stanza_error.disable();
}
+#ifdef USE_DATABASE
+ else if ((query = stanza.get_child("query", MUC_OWNER_NS)))
+ {
+ if (this->handle_room_configuration_form_request(from, to, id))
+ stanza_error.disable();
+ }
+#endif
}
else if (type == "result")
{
@@ -560,6 +576,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
else if (type == "error")
{
stanza_error.disable();
+ const auto it = this->waiting_iq.find(id);
+ if (it != this->waiting_iq.end())
+ {
+ it->second(bridge, stanza);
+ this->waiting_iq.erase(it);
+ }
}
}
catch (const IRCNotConnected& ex)
@@ -611,7 +633,21 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
}
}
}
- const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end);
+ const XmlNode* set = query->get_child("set", RSM_NS);
+ int limit = -1;
+ if (set)
+ {
+ const XmlNode* max = set->get_child("max", RSM_NS);
+ if (max)
+ limit = std::atoi(max->get_inner().data());
+ }
+ // If the archive is really big, and the client didn’t specify any
+ // limit, we avoid flooding it: we set an arbitrary max limit.
+ if (limit == -1 && start.empty() && end.empty())
+ {
+ limit = 100;
+ }
+ const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end);
for (const db::MucLogLine& line: lines)
{
if (!line.nick.value().empty())
@@ -655,6 +691,50 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con
this->send_stanza(message);
}
+bool BiboumiComponent::handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id)
+{
+ Iid iid(to.local, {'#', '&'});
+
+ if (iid.type != Iid::Type::Channel)
+ return false;
+
+ Stanza iq("iq");
+ {
+ iq["from"] = to.full();
+ iq["to"] = from;
+ iq["id"] = id;
+ iq["type"] = "result";
+ XmlSubNode query(iq, "query");
+ query["xmlns"] = MUC_OWNER_NS;
+ Jid requester(from);
+ insert_irc_channel_configuration_form(query, requester, to);
+ }
+ this->send_stanza(iq);
+ return true;
+}
+
+bool BiboumiComponent::handle_room_configuration_form(const XmlNode& query, const std::string &from, const Jid &to, const std::string &id)
+{
+ Iid iid(to.local, {'#', '&'});
+
+ if (iid.type != Iid::Type::Channel)
+ return false;
+
+ Jid requester(from);
+ if (!handle_irc_channel_configuration_form(query, requester, to))
+ return false;
+
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["from"] = to.full();
+ iq["to"] = from;
+ iq["id"] = id;
+
+ this->send_stanza(iq);
+
+ return true;
+}
+
#endif
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
@@ -686,8 +766,8 @@ Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid)
std::vector<Bridge*> BiboumiComponent::get_bridges() const
{
std::vector<Bridge*> res;
- for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it)
- res.push_back(it->second.get());
+ for (const auto& bridge: this->bridges)
+ res.push_back(bridge.second.get());
return res;
}
@@ -729,7 +809,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s
identity["category"] = "conference";
identity["type"] = "irc";
identity["name"] = "IRC server "s + from.local + " over Biboumi";
- for (const char *ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS})
+ for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
{
XmlSubNode feature(query, "feature");
feature["var"] = ns;
@@ -738,7 +818,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s
this->send_stanza(iq);
}
-void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from)
+void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
{
Stanza iq("iq");
{
@@ -756,6 +836,31 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, c
this->send_stanza(iq);
}
+void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
+{
+ Jid from(jid_from);
+ Iid iid(from.local, {});
+ Stanza iq("iq");
+ {
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["to"] = jid_to;
+ iq["from"] = jid_from;
+ XmlSubNode query(iq, "query");
+ query["xmlns"] = DISCO_INFO_NS;
+ XmlSubNode identity(query, "identity");
+ identity["category"] = "conference";
+ identity["type"] = "irc";
+ identity["name"] = "IRC channel "s + iid.get_local() + " from server " + iid.get_server() + " over biboumi";
+ for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
+ {
+ XmlSubNode feature(query, "feature");
+ feature["var"] = ns;
+ }
+ }
+ this->send_stanza(iq);
+}
+
void BiboumiComponent::send_ping_request(const std::string& from,
const std::string& jid_to,
const std::string& id)
@@ -778,8 +883,14 @@ void BiboumiComponent::send_ping_request(const std::string& from,
{
log_error("Received a corresponding ping result, but the 'to' from "
"the response mismatches the 'from' of the request");
+ return;
}
- else
+ const std::string type = stanza.get_tag("type");
+ const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
+ // Check if what we receive is considered a valid response. And yes, those errors are valid responses
+ if (type == "result" ||
+ (type == "error" && error && (error->get_child("feature-not-implemented", STANZA_NS) ||
+ error->get_child("service-unavailable", STANZA_NS))))
bridge->send_irc_ping_result({from, bridge}, id);
};
this->waiting_iq[id] = result_cb;
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
index aa0c3db..ac9bde4 100644
--- a/src/xmpp/biboumi_component.hpp
+++ b/src/xmpp/biboumi_component.hpp
@@ -2,6 +2,7 @@
#include <xmpp/xmpp_component.hpp>
+#include <xmpp/jid.hpp>
#include <bridge/bridge.hpp>
@@ -27,7 +28,7 @@ using iq_responder_callback_t = std::function<void(Bridge* bridge, const Stanza&
class BiboumiComponent: public XmppComponent
{
public:
- explicit BiboumiComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
+ explicit BiboumiComponent(std::shared_ptr<Poller>& poller, const std::string& hostname, const std::string& secret);
~BiboumiComponent() = default;
BiboumiComponent(const BiboumiComponent&) = delete;
@@ -69,7 +70,8 @@ public:
* Sends the allowed namespaces in MUC message, according to
* http://xmpp.org/extensions/xep-0045.html#impl-service-traffic
*/
- void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from);
+ void send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from);
+ void send_irc_channel_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from);
/**
* Send a ping request
*/
@@ -96,6 +98,8 @@ public:
bool handle_mam_request(const Stanza& stanza);
void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
const std::string& queryid);
+ bool handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id);
+ bool handle_room_configuration_form(const XmlNode& query, const std::string& from, const Jid& to, const std::string& id);
#endif
/**
diff --git a/louloulibs/xmpp/body.hpp b/src/xmpp/body.hpp
index 068d1a4..068d1a4 100644
--- a/louloulibs/xmpp/body.hpp
+++ b/src/xmpp/body.hpp
diff --git a/louloulibs/xmpp/jid.cpp b/src/xmpp/jid.cpp
index 46e01ea..19d1b55 100644
--- a/louloulibs/xmpp/jid.cpp
+++ b/src/xmpp/jid.cpp
@@ -3,7 +3,7 @@
#include <cstring>
#include <map>
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef LIBIDN_FOUND
#include <stringprep.h>
#include <sys/types.h>
@@ -53,7 +53,7 @@ std::string jidprep(const std::string& original)
char local[max_jid_part_len] = {};
memcpy(local, jid.local.data(), std::min(max_jid_part_len, jid.local.size()));
- Stringprep_rc rc = static_cast<Stringprep_rc>(::stringprep(local, max_jid_part_len,
+ auto rc = static_cast<Stringprep_rc>(::stringprep(local, max_jid_part_len,
static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_nodeprep));
if (rc != STRINGPREP_OK)
{
@@ -68,8 +68,7 @@ std::string jidprep(const std::string& original)
// Using getaddrinfo, check if the domain part is a valid IPv4 (then use
// it as is), or IPv6 (surround it with []), or a domain name (run
// nameprep)
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
+ struct addrinfo hints{};
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
diff --git a/louloulibs/xmpp/jid.hpp b/src/xmpp/jid.hpp
index 85e835c..85e835c 100644
--- a/louloulibs/xmpp/jid.hpp
+++ b/src/xmpp/jid.hpp
diff --git a/louloulibs/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp
index e40b1e4..b138ed9 100644
--- a/louloulibs/xmpp/xmpp_component.cpp
+++ b/src/xmpp/xmpp_component.cpp
@@ -19,7 +19,7 @@
#include <cstdlib>
#include <set>
-#include <louloulibs.h>
+#include <biboumi.h>
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif
@@ -39,14 +39,14 @@ static std::set<std::string> kickable_errors{
"malformed-error"
};
-XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
+XmppComponent::XmppComponent(std::shared_ptr<Poller>& poller, std::string hostname, std::string secret):
TCPClientSocketHandler(poller),
ever_auth(false),
first_connection_try(true),
- secret(secret),
+ secret(std::move(secret)),
authenticated(false),
doc_open(false),
- served_hostname(hostname),
+ served_hostname(std::move(hostname)),
stanza_handlers{},
adhoc_commands_handler(*this)
{
@@ -112,17 +112,20 @@ void XmppComponent::on_connection_close(const std::string& error)
void XmppComponent::parse_in_buffer(const size_t size)
{
+ // in_buf.size, or size, cannot be bigger than our read-size (4096) so it’s safe
+ // to cast.
+
if (!this->in_buf.empty())
{ // This may happen if the parser could not allocate enough space for
// us. We try to feed it the data that was read into our in_buf
// instead. If this fails again we are in trouble.
- this->parser.feed(this->in_buf.data(), this->in_buf.size(), false);
+ this->parser.feed(this->in_buf.data(), static_cast<int>(this->in_buf.size()), false);
this->in_buf.clear();
}
else
{ // Just tell the parser to parse the data that was placed into the
// buffer it provided to us with GetBuffer
- this->parser.parse(size, false);
+ this->parser.parse(static_cast<int>(size), false);
}
}
@@ -380,7 +383,7 @@ void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, cons
this->send_stanza(message);
}
-void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to)
+void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to, std::string uuid)
{
Stanza message("message");
message["to"] = jid_to;
@@ -402,6 +405,15 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str
// Pass the ownership of the pointer to this xmlnode
html.add_child(std::move(std::get<1>(xmpp_body)));
}
+
+ if (!uuid.empty())
+ {
+ XmlSubNode stanza_id(message, "stanza-id");
+ stanza_id["xmlns"] = STABLE_ID_NS;
+ stanza_id["by"] = muc_name + "@" + this->served_hostname;
+ stanza_id["id"] = std::move(uuid);
+ }
+
this->send_stanza(message);
}
@@ -429,14 +441,14 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std:
this->send_stanza(message);
}
-void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
+void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
{
Stanza presence("presence");
{
presence["to"] = jid_to;
presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
presence["type"] = "unavailable";
- const std::string message_str = std::get<0>(message);
+ const std::string& message_str = std::get<0>(message);
XmlSubNode x(presence, "x");
x["xmlns"] = MUC_USER_NS;
if (self)
diff --git a/louloulibs/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp
index a9bac0f..ebe3ec8 100644
--- a/louloulibs/xmpp/xmpp_component.hpp
+++ b/src/xmpp/xmpp_component.hpp
@@ -17,6 +17,7 @@
#define MUC_NS "http://jabber.org/protocol/muc"
#define MUC_USER_NS MUC_NS"#user"
#define MUC_ADMIN_NS MUC_NS"#admin"
+#define MUC_OWNER_NS MUC_NS"#owner"
#define DISCO_NS "http://jabber.org/protocol/disco"
#define DISCO_ITEMS_NS DISCO_NS"#items"
#define DISCO_INFO_NS DISCO_NS"#info"
@@ -27,12 +28,13 @@
#define ADHOC_NS "http://jabber.org/protocol/commands"
#define PING_NS "urn:xmpp:ping"
#define DELAY_NS "urn:xmpp:delay"
-#define MAM_NS "urn:xmpp:mam:1"
+#define MAM_NS "urn:xmpp:mam:2"
#define FORWARD_NS "urn:xmpp:forward:0"
#define CLIENT_NS "jabber:client"
#define DATAFORM_NS "jabber:x:data"
#define RSM_NS "http://jabber.org/protocol/rsm"
#define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic"
+#define STABLE_ID_NS "urn:xmpp:sid:0"
/**
* An XMPP component, communicating with an XMPP server using the protocole
@@ -43,7 +45,7 @@
class XmppComponent: public TCPClientSocketHandler
{
public:
- explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
+ explicit XmppComponent(std::shared_ptr<Poller>& poller, std::string hostname, std::string secret);
virtual ~XmppComponent() = default;
XmppComponent(const XmppComponent&) = delete;
@@ -91,7 +93,7 @@ public:
* stanza, and explanation being a short human-readable sentence
* describing the error.
*/
- void send_stream_error(const std::string& message, const std::string& explanation);
+ void send_stream_error(const std::string& name, const std::string& explanation);
/**
* Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error
*/
@@ -134,7 +136,8 @@ public:
/**
* Send a (non-private) message to the MUC
*/
- void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to);
+ void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to,
+ std::string uuid);
/**
* Send a message, with a <delay/> element, part of a MUC history
*/
@@ -143,7 +146,7 @@ public:
/**
* Send an unavailable presence for this nick
*/
- void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self);
+ void send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self);
/**
* Indicate that a participant changed his nick
*/
diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp
index 0488be9..0488be9 100644
--- a/louloulibs/xmpp/xmpp_parser.cpp
+++ b/src/xmpp/xmpp_parser.cpp
diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp
index 9d67228..ec42f9a 100644
--- a/louloulibs/xmpp/xmpp_parser.hpp
+++ b/src/xmpp/xmpp_parser.hpp
@@ -106,7 +106,7 @@ private:
/**
* Expat structure.
*/
- XML_Parser parser;
+ XML_Parser parser{};
/**
* The current depth in the XML document
*/
diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp
index ac6ce9b..435f333 100644
--- a/louloulibs/xmpp/xmpp_stanza.cpp
+++ b/src/xmpp/xmpp_stanza.cpp
@@ -7,7 +7,7 @@
#include <iostream>
#include <sstream>
-#include <string.h>
+#include <cstring>
std::string xml_escape(const std::string& data)
{
@@ -52,7 +52,7 @@ XmlNode::XmlNode(const std::string& name, XmlNode* parent):
parent(parent)
{
// split the namespace and the name
- auto n = name.rfind(":");
+ auto n = name.rfind(':');
if (n == std::string::npos)
this->name = name;
else
diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp
index f4b3948..f4b3948 100644
--- a/louloulibs/xmpp/xmpp_stanza.hpp
+++ b/src/xmpp/xmpp_stanza.hpp
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py
index c298793..f9e04a8 100644
--- a/tests/end_to_end/__main__.py
+++ b/tests/end_to_end/__main__.py
@@ -1,13 +1,14 @@
#!/usr/bin/env python3
import collections
+import lxml.etree
import datetime
import slixmpp
import asyncio
import logging
import signal
import atexit
-import lxml.etree
+import time
import sys
import io
import os
@@ -118,6 +119,7 @@ def match(stanza, xpath):
tree = lxml.etree.parse(io.StringIO(str(stanza)))
matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions',
'muc_user': 'http://jabber.org/protocol/muc#user',
+ 'muc_owner': 'http://jabber.org/protocol/muc#owner',
'muc': 'http://jabber.org/protocol/muc',
'disco_info': 'http://jabber.org/protocol/disco#info',
'muc_traffic': 'http://jabber.org/protocol/muc#traffic',
@@ -125,22 +127,29 @@ def match(stanza, xpath):
'commands': 'http://jabber.org/protocol/commands',
'dataform': 'jabber:x:data',
'version': 'jabber:iq:version',
- 'mam': 'urn:xmpp:mam:1',
+ 'mam': 'urn:xmpp:mam:2',
'delay': 'urn:xmpp:delay',
'forward': 'urn:xmpp:forward:0',
'client': 'jabber:client',
'rsm': 'http://jabber.org/protocol/rsm',
'carbon': 'urn:xmpp:carbons:2',
'hints': 'urn:xmpp:hints',
- 'stanza': 'urn:ietf:params:xml:ns:xmpp-stanzas'})
+ 'stanza': 'urn:ietf:params:xml:ns:xmpp-stanzas',
+ 'stable_id': 'urn:xmpp:sid:0'})
return matched
def check_xpath(xpaths, xmpp, after, stanza):
for xpath in xpaths:
+ expected = True
+ real_xpath = xpath
+ # We can check that a stanza DOESN’T match, by adding a ! before it.
+ if xpath.startswith('!'):
+ expected = False
+ xpath = xpath[1:]
matched = match(stanza, xpath)
- if not matched:
- raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath))
+ if (expected and not matched) or (not expected and matched):
+ raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, real_xpath))
if after:
if isinstance(after, collections.Iterable):
for af in after:
@@ -266,14 +275,13 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None):
else:
print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths)))
-def save_datetime(xmpp, biboumi):
- xmpp.saved_values["saved_datetime"] = datetime.datetime.now()
- asyncio.get_event_loop().call_soon(xmpp.run_scenario)
+def save_current_timestamp_plus_delta(key, delta, message, xmpp):
+ now_plus_delta = datetime.datetime.utcnow() + delta
+ xmpp.saved_values[key] = now_plus_delta.strftime("%FT%T.967Z")
+ print(xmpp.saved_values[key])
-def expect_now_is_after(timedelta, xmpp, biboumi):
- time_passed = datetime.datetime.now() - xmpp.saved_values["saved_datetime"]
- if time_passed < timedelta:
- raise StanzaError("Not enough time has passed: %s instead of %s" % (time_passed, timedelta))
+def sleep_for(duration, xmpp, biboumi):
+ time.sleep(duration)
asyncio.get_event_loop().call_soon(xmpp.run_scenario)
# list_of_xpaths: [(xpath, xpath), (xpath, xpath), (xpath)]
@@ -477,22 +485,41 @@ def connection_end_sequence(irc_host, jid):
xpath_re % r'^User mode for \w+ is \[\+Z?i\]$'),
)
+def connection_middle_sequence(irc_host, jid):
+ xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]"
+ irc_host = 'irc.localhost'
+ return (
+ partial(expect_stanza, xpath_re % (r'^%s: \*\*\* You are exempt from flood limits$' % irc_host)),
+ )
+
def connection_sequence(irc_host, jid):
- return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid)
+ return connection_begin_sequence(irc_host, jid) +\
+ connection_middle_sequence(irc_host, jid) +\
+ connection_end_sequence(irc_host, jid)
def connection_tls_sequence(irc_host, jid):
- return connection_tls_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid)
+ return connection_tls_begin_sequence(irc_host, jid) + \
+ connection_middle_sequence(irc_host, jid) +\
+ connection_end_sequence(irc_host, jid)
def extract_attribute(xpath, name, stanza):
matched = match(stanza, xpath)
return matched[0].get(name)
+def chan_name_from_jid(jid):
+ return jid[1:jid.find('%')]
+
+
+def extract_text(xpath, stanza):
+ matched = match(stanza, xpath)
+ return matched[0].text
def save_value(name, func, stanza, xmpp):
xmpp.saved_values[name] = func(stanza)
+
if __name__ == '__main__':
atexit.register(asyncio.get_event_loop().close)
@@ -519,11 +546,11 @@ if __name__ == '__main__':
partial(expect_stanza,
"/message/body[text()='Connecting to doesnotexist:6697 (encrypted)']"),
partial(expect_stanza,
- "/message/body[text()='Connection failed: Domain name not found']"),
+ "/message/body[re:test(text(), 'Connection failed: (Domain name not found|Name or service not known)')]"),
partial(expect_stanza,
("/presence[@from='#foo%doesnotexist@{biboumi_host}/{nick_one}']/muc:x",
"/presence/error[@type='cancel']/stanza:item-not-found",
- "/presence/error[@type='cancel']/stanza:text[text()='Domain name not found']")),
+ "/presence/error[@type='cancel']/stanza:text[re:test(text(), '(Domain name not found|Name or service not known)')]")),
]),
Scenario("simple_channel_join",
[
@@ -539,12 +566,50 @@ if __name__ == '__main__':
),
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
]),
+ Scenario("multiple_channels_join",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#baz%{irc_server_one}/{nick_one}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"),
+
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ partial(expect_stanza,
+ "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ partial(expect_stanza,
+ "/message/body[text()='Mode #baz [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#baz%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#baz%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+ ]),
Scenario("virtual_channel",
[
handshake_sequence(),
partial(send_stanza,
"<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"),
connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+
partial(expect_stanza,
("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",
"/presence/muc_user:x/muc_user:status[@code='110']")
@@ -578,6 +643,8 @@ if __name__ == '__main__':
partial(send_stanza,
"<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"),
connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+
partial(expect_stanza,
("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",
"/presence/muc_user:x/muc_user:status[@code='110']")
@@ -706,6 +773,23 @@ if __name__ == '__main__':
),
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"),
]),
+ Scenario("multiline_topic",
+ [
+ handshake_sequence(),
+ # User joins
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ # User tries to set a multiline topic
+ partial(send_stanza,
+ "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>FIRST LINE\nSECOND LINE.</subject></message>"),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='FIRST LINE SECOND LINE.']"),
+ ]),
Scenario("channel_basic_join_on_fixed_irc_server",
[
handshake_sequence(),
@@ -782,11 +866,34 @@ if __name__ == '__main__':
"/iq/commands:command/commands:actions/commands:next",
),
after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid"))
-
),
partial(send_stanza, "<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='name'><value>COUCOU</value></field></x></command></iq>"),
partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']")
]),
+ Scenario("execute_incomplete_hello_adhoc_command",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"),
+ partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']",
+ "/iq/commands:command/commands:actions/commands:next",
+ ),
+ after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid"))
+ ),
+ partial(send_stanza, "<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'></x></command></iq>"),
+ partial(expect_stanza, "/iq[@type='error']")
+ ]),
+ Scenario("execute_ping_adhoc_command",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='set' id='ping-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='ping' action='execute' /></iq>"),
+ partial(expect_stanza, "/iq[@type='result']/commands:command[@node='ping'][@status='completed']/commands:note[@type='info'][text()='Pong']")
+ ]),
+ Scenario("execute_reload_adhoc_command",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='set' id='ping-command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='reload' action='execute' /></iq>"),
+ partial(expect_stanza, "/iq[@type='result']/commands:command[@node='reload'][@status='completed']/commands:note[@type='info'][text()='Configuration reloaded.']")
+ ]),
Scenario("execute_forbidden_adhoc_command",
[
handshake_sequence(),
@@ -1007,6 +1114,19 @@ if __name__ == '__main__':
),
partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"),
]),
+ Scenario("notices",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza, "/message"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message"),
+
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>NOTICE {nick_one} :[#foo] Hello in a notice.</body></message>"),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='[notice] [#foo] Hello in a notice.']"),
+ ]),
Scenario("channel_messages",
[
handshake_sequence(),
@@ -1100,6 +1220,72 @@ if __name__ == '__main__':
),
partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
]),
+ Scenario("self_ping_with_error",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ # Send a ping to ourself
+ partial(send_stanza,
+ "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
+ # We receive our own ping request,
+ partial(expect_stanza,
+ "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"),
+ # Respond to the request with an error
+ partial(send_stanza,
+ "<iq from='{jid_one}/{resource_one}' id='gnip_tsrif' to='{lower_nick_one}%{irc_server_one}' type='error'><error type='cancel'><feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"),
+ partial(expect_stanza,
+ "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"),
+
+ # Send a ping to ourself
+ partial(send_stanza,
+ "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
+ # We receive our own ping request,
+ partial(expect_stanza,
+ "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"),
+ # Respond to the request with an error
+ partial(send_stanza,
+ "<iq from='{jid_one}/{resource_one}' id='gnip_tsrif' to='{lower_nick_one}%{irc_server_one}' type='error'><error type='cancel'><service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"),
+ partial(expect_stanza,
+ "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"),
+ ]),
+ Scenario("self_ping_not_in_muc",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ # Send a ping to ourself, in a muc where we’re not
+ partial(send_stanza,
+ "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#nil%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
+ # Immediately receive an error
+ partial(expect_stanza,
+ "/iq[@from='#nil%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_one}'][@id='first_ping']/error/stanza:not-allowed"),
+
+ # Send a ping to ourself, in a muc where we are, but not this resource
+ partial(send_stanza,
+ "<iq type='get' from='{jid_one}/{resource_two}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
+ # Immediately receive an error
+ partial(expect_stanza,
+ "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_two}'][@id='first_ping']/error/stanza:not-allowed"),
+ ]),
Scenario("self_ping_on_real_channel",
[
handshake_sequence(),
@@ -1159,6 +1345,31 @@ if __name__ == '__main__':
"/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"),
]),
+ Scenario("self_ping_fixed_server", [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"),
+
+ # Send a ping to ourself
+ partial(send_stanza,
+ "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo@{biboumi_host}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
+ # We receive our own ping request,
+ partial(expect_stanza,
+ "/iq[@from='{lower_nick_one}@{biboumi_host}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"),
+ # Respond to the request
+ partial(send_stanza,
+ "<iq type='result' to='{lower_nick_one}@{biboumi_host}' id='gnip_tsrif' from='{jid_one}/{resource_one}'/>"),
+ partial(expect_stanza,
+ "/iq[@from='#foo@{biboumi_host}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"),
+ ], conf="fixed_server"),
Scenario("simple_kick",
[
handshake_sequence(),
@@ -1483,13 +1694,16 @@ if __name__ == '__main__':
# Send two channel messages
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
- partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"),
+ partial(expect_stanza,
+ ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
+ "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",)
+ ),
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"),
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"),
# Retrieve the complete archive
- partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:1' queryid='qid1' /></iq>"),
+ partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"),
partial(expect_stanza,
("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
@@ -1505,9 +1719,9 @@ if __name__ == '__main__':
# Retrieve an empty archive by specifying an early “end” date
partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'>
- <query xmlns='urn:xmpp:mam:1' queryid='qid2'>
+ <query xmlns='urn:xmpp:mam:2' queryid='qid2'>
<x xmlns='jabber:x:data' type='submit'>
- <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:1</value></field>
+ <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field>
<field var='end'><value>2000-06-07T00:00:00Z</value></field>
</x>
</query></iq>"""),
@@ -1518,16 +1732,87 @@ if __name__ == '__main__':
# Retrieve an empty archive by specifying a late “start” date
# (note that this test will break in ~1000 years)
partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'>
- <query xmlns='urn:xmpp:mam:1' queryid='qid3'>
+ <query xmlns='urn:xmpp:mam:2' queryid='qid3'>
<x xmlns='jabber:x:data' type='submit'>
- <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:1</value></field>
+ <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field>
<field var='start'><value>3016-06-07T00:00:00Z</value></field>
</x>
</query></iq>"""),
partial(expect_stanza,
"/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
+
+ # Retrieve a limited archive
+ partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4'><set xmlns='http://jabber.org/protocol/rsm'><max>1</max></set></query></iq>"),
+
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay",
+ "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']")
+ ),
+
+ partial(expect_stanza,
+ "/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
+
]),
+ Scenario("mam_with_timestamps",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+
+ # Send two channel messages
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
+ partial(expect_stanza,
+ ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
+ "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",)
+ ),
+
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"),
+ # Record the current time
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']",
+ after = partial(save_current_timestamp_plus_delta, "first_timestamp", datetime.timedelta(seconds=1))),
+
+ # Wait two seconds before sending two new messages
+ partial(sleep_for, 2),
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"),
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"),
+ partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 3']"),
+ partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 4']",
+ after = partial(save_current_timestamp_plus_delta, "second_timestamp", datetime.timedelta(seconds=1))),
+
+ # Retrieve the archive, after our saved datetime
+ partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id8'>
+ <query xmlns='urn:xmpp:mam:2' queryid='qid16'>
+ <x type='submit' xmlns='jabber:x:data'>
+ <field var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field>
+ <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field>
+ <field var='end' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{second_timestamp}</value></field>
+ </x>
+ </query>
+ </iq>"""),
+
+
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay",
+ "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']")
+ ),
+
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay",
+ "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']")
+ ),
+
+ partial(expect_stanza,
+ "/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
+ ]),
Scenario("mam_on_fixed_server",
[
handshake_sequence(),
@@ -1550,7 +1835,7 @@ if __name__ == '__main__':
partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"),
# Retrieve the complete archive
- partial(send_stanza, "<iq to='#foo@{biboumi_host}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:1' queryid='qid1' /></iq>"),
+ partial(send_stanza, "<iq to='#foo@{biboumi_host}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"),
partial(expect_stanza,
("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
@@ -1561,6 +1846,51 @@ if __name__ == '__main__':
"/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']")
),
], conf="fixed_server"),
+ Scenario("default_mam_limit",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",
+ after = partial(save_value, "counter", lambda x: 0)),
+ ] + [
+ partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>{counter}</body></message>"),
+ partial(expect_stanza,
+ "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='{counter}']",
+ after = partial(save_value, "counter", lambda stanza: str(1 + int(extract_text("/message/body", stanza))))
+ ),
+ ] * 150 + [
+ # Retrieve the archive, without any restriction
+ partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"),
+ # Since we should only receive the last 100 messages from the archive,
+ # it should start with message "50"
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
+ "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='50']")
+ ),
+ ] + [
+ # followed by 98 more messages
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
+ "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body")
+ ),
+ ] * 98 + [
+ # and finally the message "149"
+ partial(expect_stanza,
+ ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay",
+ "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']")
+ ),
+ partial(expect_stanza,
+ "/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
+
+ ]),
Scenario("channel_history_on_fixed_server",
[
handshake_sequence(),
@@ -1689,21 +2019,27 @@ if __name__ == '__main__':
partial(expect_stanza, "/presence"),
partial(expect_stanza, "/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
+ # Ask for 0 item
partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>0</max></set></query></iq>"),
+
+ # Get 0 item
partial(expect_stanza, (
"/iq[@type='result']/disco_items:query",
)),
+ # Ask for 2 (of 3) items We don’t have the count,
+ # because biboumi doesn’t have the complete list when
+ # it sends us the 2 items
partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>2</max></set></query></iq>"),
partial(expect_stanza, (
"/iq[@type='result']/disco_items:query",
"/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']",
"/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']",
"/iq/disco_items:query/rsm:set/rsm:first[text()='#bar%{irc_server_one}'][@index='0']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:count[text()='3']"
+ "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']"
)),
+ # Ask for 12 (of 3) items. We get the whole list, and thus we have the count included.
partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>12</max></set></query></iq>"),
partial(expect_stanza, (
"/iq[@type='result']/disco_items:query",
@@ -1715,6 +2051,10 @@ if __name__ == '__main__':
"/iq/disco_items:query/rsm:set/rsm:count[text()='3']"
)),
+ # Ask for 1 item, AFTER the first item (so,
+ # the second). Since we don’t invalidate the cache
+ # with this request, we should have the count
+ # included.
partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#bar%{irc_server_one}</after><max>1</max></set></query></iq>"),
partial(expect_stanza, (
"/iq[@type='result']/disco_items:query",
@@ -1724,14 +2064,52 @@ if __name__ == '__main__':
"/iq/disco_items:query/rsm:set/rsm:count[text()='3']"
)),
- partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#bar%{irc_server_one}</after><max>1</max></set></query></iq>"),
+ # Ask for 1 item, AFTER the second item (so,
+ # the third).
+ partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#coucou%{irc_server_one}</after><max>1</max></set></query></iq>"),
partial(expect_stanza, (
"/iq[@type='result']/disco_items:query",
- "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:first[text()='#coucou%{irc_server_one}'][@index='1']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']",
+ "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']",
+ "/iq/disco_items:query/rsm:set/rsm:first[text()='#foo%{irc_server_one}'][@index='2']",
+ "/iq/disco_items:query/rsm:set/rsm:last[text()='#foo%{irc_server_one}']",
"/iq/disco_items:query/rsm:set/rsm:count[text()='3']"
- ))
+ )),
+
+ # Ask for 1 item, AFTER the third item (so,
+ # the fourth). Since it doesn't exist, we get 0 item
+ partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#foo%{irc_server_one}</after><max>1</max></set></query></iq>"),
+ partial(expect_stanza, (
+ "/iq[@type='result']/disco_items:query",
+ "/iq/disco_items:query/rsm:set/rsm:count[text()='3']"
+ )),
+ ]),
+ Scenario("default_channel_list_limit",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza, "/message"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message",
+ after = partial(save_value, "counter", lambda x: 0)),
+ ] + [
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' />"),
+ partial(expect_stanza, "/message"),
+ partial(expect_stanza, "/presence",
+ after = partial(save_value, "counter", lambda stanza: str(1 + int(chan_name_from_jid(extract_attribute("/presence", "from", stanza)))))),
+ partial(expect_stanza, "/message")
+ ] * 110 + [
+ partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"),
+ # charybdis sends the list in alphabetic order, so #foo is the last, and #99 is after #120
+ partial(expect_stanza, ("/iq/disco_items:query/disco_items:item[@jid='#0%{irc_server_one}']",
+ "/iq/disco_items:query/disco_items:item[@jid='#1%{irc_server_one}']",
+ "/iq/disco_items:query/disco_items:item[@jid='#109%{irc_server_one}']",
+ "/iq/disco_items:query/disco_items:item[@jid='#9%{irc_server_one}']",
+ "!/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']",
+ "!/iq/disco_items:query/disco_items:item[@jid='#99%{irc_server_one}']",
+ "!/iq/disco_items:query/disco_items:item[@jid='#90%{irc_server_one}']")),
]),
Scenario("complete_channel_list_with_pages_of_3",
[
@@ -1866,6 +2244,38 @@ if __name__ == '__main__':
"<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info' node='http://jabber.org/protocol/muc#traffic'/></iq>"),
partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"),
]),
+ Scenario("muc_disco_info",
+ [
+ handshake_sequence(),
+
+ partial(send_stanza,
+ "<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"),
+ partial(expect_stanza,
+ ("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query",
+ "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='IRC channel #foo from server {irc_host_one} over biboumi']",
+ "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
+ "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']",
+ "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
+ )),
+ ]),
+ Scenario("fixed_muc_disco_info",
+ [
+ handshake_sequence(),
+
+ partial(send_stanza,
+ "<iq from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"),
+ partial(expect_stanza,
+ ("/iq[@from='#foo@{biboumi_host}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query",
+ "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='IRC channel #foo from server {irc_host_one} over biboumi']",
+ "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
+ "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']",
+ "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
+ )),
+ ], conf='fixed_server'),
Scenario("raw_message",
[
handshake_sequence(),
@@ -1887,7 +2297,7 @@ if __name__ == '__main__':
"/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
"/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']",
"/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']",
- "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:1']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']",
"/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
)),
]),
@@ -1915,6 +2325,8 @@ if __name__ == '__main__':
partial(send_stanza,
"<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"),
connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+
partial(expect_stanza,
("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",
"/presence/muc_user:x/muc_user:status[@code='110']")
@@ -2041,7 +2453,6 @@ if __name__ == '__main__':
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']/dataform:value[text()='realname']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']",
- "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='linger_time']/dataform:value[text()='0']",
"/iq/commands:command/commands:actions/commands:next",
),
after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))
@@ -2080,6 +2491,26 @@ if __name__ == '__main__':
partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"),
partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"),
]),
+ Scenario("irc_channel_configure_xep0045",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='get' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"),
+ partial(expect_stanza, ("/iq[@type='result']/muc_owner:query",
+ "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']",
+ "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']",
+ ),
+ ),
+ partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>"
+ "<query xmlns='http://jabber.org/protocol/muc#owner'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='ports' />"
+ "<field var='encoding_out'><value>UTF-8</value></field>"
+ "<field var='encoding_in'><value>latin-1</value></field>"
+ "</x></query></iq>"),
+ partial(expect_stanza, "/iq[@type='result']"),
+ partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'> <x xmlns='jabber:x:data' type='cancel'/></query></iq>"),
+ partial(expect_stanza, "/iq[@type='result']"),
+ ]),
Scenario("irc_channel_configure_fixed",
[
handshake_sequence(),
@@ -2111,42 +2542,6 @@ if __name__ == '__main__':
partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"),
partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"),
], conf='fixed_server'),
- Scenario("irc_server_linger_time",
- [
- handshake_sequence(),
- # Set a custom value for the linger_time option, using the ad-hoc command
- partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"),
- partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
- "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']",
- "/iq/commands:command/commands:actions/commands:next",
- ),
- after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))
- ),
- partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>"
- "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>"
- "<x xmlns='jabber:x:data' type='submit'>"
- "<field var='linger_time'><value>3</value></field>"
- "</x></command></iq>"),
- partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
-
- partial(send_stanza,
- "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
- connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
- partial(expect_stanza,
- "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
- partial(expect_stanza,
- ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
- "/presence/muc_user:x/muc_user:status[@code='110']")
- ),
- partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
-
- partial(save_datetime),
- partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
- partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"),
- partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"),
- partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"),
- partial(expect_now_is_after, datetime.timedelta(seconds=3)),
- ]),
Scenario("irc_tls_connection",
[
handshake_sequence(),
@@ -2236,11 +2631,25 @@ if __name__ == '__main__':
handshake_sequence(),
partial(send_stanza, "<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' />"),
partial(expect_stanza, "/presence[@to='{jid_one}'][@from='{biboumi_host}'][@type='subscribed']")
- ], conf='fixed_server')
-
+ ], conf='fixed_server'),
+ Scenario("leave_unjoined_chan",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza, "/message"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message"),
+
+ partial(send_stanza, "<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ connection_begin_sequence("irc.localhost", '{jid_two}/{resource_two}'),
+
+ partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use.']"),
+ partial(expect_stanza, "/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"),
+ partial(send_stanza, "<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />")
+ ])
)
-
failures = 0
scenar_list = sys.argv[1:]
@@ -2257,7 +2666,8 @@ if __name__ == '__main__':
if b"now running in foreground mode" in res:
break
print("irc server started.")
- print("Running %s checks for biboumi." % (len(scenarios)))
+ checks = len([s for s in scenarios if s.name in scenar_list]) if scenar_list else len(scenarios)
+ print("Running %s checks for biboumi." % checks)
for s in scenarios:
if scenar_list and s.name not in scenar_list:
diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf
index ec55884..cdb06d5 100644
--- a/tests/end_to_end/ircd.conf
+++ b/tests/end_to_end/ircd.conf
@@ -221,6 +221,7 @@ auth {
auth {
user = "*@*";
class = "users";
+ flags = flood_exempt;
};
/* privset {} blocks MUST be specified before anything that uses them. That
@@ -352,8 +353,8 @@ channel {
use_knock = yes;
knock_delay = 5 minutes;
knock_delay_channel = 1 minute;
- max_chans_per_user = 15;
- max_chans_per_user_large = 60;
+ max_chans_per_user = 140;
+ max_chans_per_user_large = 200;
max_bans = 100;
max_bans_large = 500;
default_split_user_count = 0;
diff --git a/tests/jid.cpp b/tests/jid.cpp
index 089f797..480827b 100644
--- a/tests/jid.cpp
+++ b/tests/jid.cpp
@@ -1,7 +1,7 @@
#include "catch.hpp"
#include <xmpp/jid.hpp>
-#include <louloulibs.h>
+#include <biboumi.h>
TEST_CASE("Jid")
{
diff --git a/tests/network.cpp b/tests/network.cpp
new file mode 100644
index 0000000..33cf023
--- /dev/null
+++ b/tests/network.cpp
@@ -0,0 +1,44 @@
+#include "catch.hpp"
+#include <network/tls_policy.hpp>
+
+#ifdef BOTAN_FOUND
+TEST_CASE("tls_policy")
+{
+ BiboumiTLSPolicy policy;
+ const auto default_minimum_signature_strength = policy.minimum_signature_strength();
+ const auto default_session_ticket_lifetime = policy.session_ticket_lifetime();
+ const auto default_minimum_rsa_bits = policy.minimum_rsa_bits();
+
+ policy.load("does not exist");
+ WHEN("we fail to load the file")
+ {
+ THEN("all values are the default ones")
+ {
+ CHECK(policy.minimum_signature_strength() == default_minimum_signature_strength);
+ CHECK(policy.minimum_rsa_bits() == default_minimum_rsa_bits);
+ }
+ AND_WHEN("we load a valid first file")
+ {
+ std::istringstream iss("minimum_signature_strength = 128\nminimum_rsa_bits=12\n");
+ policy.load(iss);
+ THEN("the specified values are updated, and the rest is still the default")
+ {
+ CHECK(policy.minimum_signature_strength() == 128);
+ CHECK(policy.minimum_rsa_bits() == 12);
+ CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime);
+ }
+ AND_WHEN("we load a second file")
+ {
+ std::istringstream iss("minimum_signature_strength = 15");
+ policy.load(iss);
+ THEN("the specified values are updated, and the rest is untouched")
+ {
+ CHECK(policy.minimum_signature_strength() == 15);
+ CHECK(policy.minimum_rsa_bits() == 12);
+ CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime);
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/tests/utils.cpp b/tests/utils.cpp
index b8a3e75..c5ef7e7 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -10,6 +10,7 @@
#include <utils/time.hpp>
#include <utils/system.hpp>
#include <utils/scopeguard.hpp>
+#include <utils/dirname.hpp>
using namespace std::string_literals;
@@ -157,4 +158,16 @@ TEST_CASE("system_name")
{
CHECK(utils::get_system_name() != "Unknown");
CHECK(!utils::get_system_name().empty());
-} \ No newline at end of file
+}
+
+TEST_CASE("dirname")
+{
+ CHECK(utils::dirname("/") == "/");
+ CHECK(utils::dirname("coucou.txt") == "./");
+ CHECK(utils::dirname("../coucou.txt") == "../");
+ CHECK(utils::dirname("/etc/biboumi/coucou.txt") == "/etc/biboumi/");
+ CHECK(utils::dirname("..") == "..");
+ CHECK(utils::dirname("../") == "../");
+ CHECK(utils::dirname(".") == ".");
+ CHECK(utils::dirname("./") == "./");
+}
diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp
index 42b7c08..14c51da 100644
--- a/tests/xmpp.cpp
+++ b/tests/xmpp.cpp
@@ -30,20 +30,21 @@ TEST_CASE("Test basic XML parsing")
// And do the same checks on moved-constructed stanza
Stanza moved(std::move(copy));
});
- xml.feed(doc.data(), doc.size(), true);
+ CHECK(doc.size() <= std::numeric_limits<int>::max());
+ xml.feed(doc.data(), static_cast<int>(doc.size()), true);
const std::string doc2 = "<stream xmlns='s'><stanza>coucou\r\n\a</stanza></stream>";
xml.add_stanza_callback([](const Stanza& stanza)
{
CHECK(stanza.get_inner() == "coucou\r\n");
});
-
- xml.feed(doc2.data(), doc.size(), true);
+ CHECK(doc.size() <= std::numeric_limits<int>::max());
+ xml.feed(doc2.data(), static_cast<int>(doc.size()), true);
}
TEST_CASE("XML escape")
{
- const std::string unescaped = "'coucou'<cc>/&\"gaga\"";
+ const std::string unescaped = R"('coucou'<cc>/&"gaga")";
CHECK(xml_escape(unescaped) == "&apos;coucou&apos;&lt;cc&gt;/&amp;&quot;gaga&quot;");
}
@@ -68,4 +69,4 @@ TEST_CASE("substanzas")
CHECK(b.has_children());
}
CHECK(a.has_children());
-} \ No newline at end of file
+}