summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml63
-rw-r--r--CMakeLists.txt4
-rw-r--r--INSTALL.rst4
-rw-r--r--README.rst3
-rw-r--r--docker/biboumi-test/debian/Dockerfile.base1
-rw-r--r--docker/biboumi-test/fedora/Dockerfile.base1
-rw-r--r--docker/biboumi/Dockerfile48
-rw-r--r--docker/biboumi/README.md62
-rw-r--r--docker/biboumi/biboumi.cfg6
-rw-r--r--docker/biboumi/entrypoint.sh11
-rw-r--r--louloulibs/CMakeLists.txt11
-rw-r--r--louloulibs/cmake/Modules/FindGCRYPT.cmake35
-rw-r--r--louloulibs/louloulibs.h.cmake2
-rw-r--r--louloulibs/network/tcp_client_socket_handler.cpp6
-rw-r--r--louloulibs/utils/encoding.cpp4
-rw-r--r--louloulibs/utils/scopeguard.hpp6
-rw-r--r--louloulibs/utils/sha1.cpp151
-rw-r--r--louloulibs/utils/sha1.hpp34
-rw-r--r--louloulibs/utils/system.cpp21
-rw-r--r--louloulibs/utils/system.hpp8
-rw-r--r--louloulibs/utils/time.cpp2
-rw-r--r--louloulibs/xmpp/auth.cpp15
-rw-r--r--louloulibs/xmpp/jid.cpp76
-rw-r--r--louloulibs/xmpp/xmpp_component.cpp3
-rw-r--r--src/identd/identd_socket.cpp9
-rw-r--r--src/main.cpp7
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp16
-rw-r--r--src/xmpp/biboumi_component.cpp11
-rw-r--r--src/xmpp/biboumi_component.hpp2
-rw-r--r--tests/end_to_end/__main__.py234
-rw-r--r--tests/jid.cpp20
-rw-r--r--tests/utils.cpp18
32 files changed, 626 insertions, 268 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d03a47f..c616f89 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,8 @@
+stages:
+ - build
+ - test
+ - packaging
+
before_script:
- uname -a
- locale
@@ -64,24 +69,6 @@ build:6:
UDNS: "-DWITHOUT_UDNS=1"
<<: *basic_build
-build:rpm:
- stage: build
- only:
- - master@louiz/biboumi
- tags:
- - docker
- image: docker.louiz.org/biboumi-test-fedora:latest
- script:
- - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
- - make rpm -j$(nproc || echo 1)
- artifacts:
- paths:
- - build/rpmbuild/RPMS
- - build/rpmbuild/SRPMS
- when: always
- name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID
-
-
.template:basic_test: &basic_test
stage: test
tags:
@@ -140,16 +127,42 @@ test:coverity:
- tar czvf biboumi_coverity.tgz cov-int
- curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi
-test:sonar-qube:
- stage: test
+packaging:rpm:
+ stage: packaging
only:
- master@louiz/biboumi
tags:
- docker
image: docker.louiz.org/biboumi-test-fedora:latest
- allow_failure: true
script:
- - cmake ..
- - ~/sonar-scanner/bin/build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir ./bw-outputs make biboumi test_suite
- - cd ..
- - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN -Dsonar.language=cpp -Dsonar.cfamily.build-wrapper-output=build/bw-outputs -Dsonar.sourceEncoding=UTF-8 -Dsonar.sources=src/,louloulibs/,tests/ -Dsonar.projectKey=biboumi -Dsonar.projectName=Biboumi -Dsonar.projectVersion=3.0
+ - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
+ - make rpm -j$(nproc || echo 1)
+ artifacts:
+ paths:
+ - build/rpmbuild/RPMS
+ - build/rpmbuild/SRPMS
+ when: always
+ name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID
+
+packaging:deb:
+ stage: packaging
+ only:
+ - master@louiz/biboumi
+ - debian@louiz/biboumi
+ tags:
+ - docker
+ image: docker.louiz.org/packaging-debian:latest
+ before_script: []
+ script:
+ - git checkout debian
+ - git merge --no-commit --no-ff master
+ - mk-build-deps
+ - apt update -y
+ - apt install -y ./biboumi-build-deps*.deb
+ - debuild -b -us -uc
+ - mv ../*.deb .
+ - mv ../*.build .
+ - mv ../*.buildinfo .
+ artifacts:
+ untracked: true
+ name: $CI_PROJECT_NAME-deb-$CI_BUILD_ID
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d934ef..da9cf37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -331,8 +331,8 @@ if(NOT DEFINED SERVICE_GROUP)
endif()
configure_file(unit/biboumi.service.cmake biboumi.service)
-# The date MUST be in english format
-set(ENV{LANG} "en_US.utf-8")
+# Force the format of the date output
+set(ENV{LANG} "C")
execute_process(COMMAND "date" "+%a %b %d %Y" OUTPUT_VARIABLE RPM_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
unset(ENV{LANG})
diff --git a/INSTALL.rst b/INSTALL.rst
index fa88ffb..9815af9 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -45,6 +45,9 @@ libbotan_ 1.11 or 2.0 (optional)
Provides TLS support. Without it, IRC connections are all made in
plain-text mode.
+gcrypt_ (mandatory only if botan is absent)
+ Provides the SHA-1 hash function, for the case where Botan is absent.
+
litesql_ (optional)
Provides a way to store various options in a (sqlite3) database. Each user
of the gateway can store their own values (for example their prefered port,
@@ -158,3 +161,4 @@ to use biboumi.
.. _litesql: http://git.louiz.org/litesql
.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/
.. _biboumi.1.rst: doc/biboumi.1.rst
+.. _gcrypt: https://www.gnu.org/software/libgcrypt/
diff --git a/README.rst b/README.rst
index 636c717..02cfa3d 100644
--- a/README.rst
+++ b/README.rst
@@ -7,9 +7,6 @@ Biboumi
.. image:: https://codecov.io/gh/louiz/biboumi/branch/master/graph/badge.svg
:target: https://codecov.io/gh/louiz/biboumi
-.. image:: https://sonarqube.com/api/badges/gate?key=biboumi
- :target: https://sonarqube.com/component_issues/index?id=biboumi
-
.. image:: https://scan.coverity.com/projects/3726/badge.svg
:target: https://scan.coverity.com/projects/louiz-biboumi
diff --git a/docker/biboumi-test/debian/Dockerfile.base b/docker/biboumi-test/debian/Dockerfile.base
index 125c048..e609feb 100644
--- a/docker/biboumi-test/debian/Dockerfile.base
+++ b/docker/biboumi-test/debian/Dockerfile.base
@@ -14,6 +14,7 @@ RUN apt install -y g++\
libudns-dev\
libsqlite3-dev\
libuuid1\
+ libgcrypt20-dev\
cmake\
make\
libexpat1-dev\
diff --git a/docker/biboumi-test/fedora/Dockerfile.base b/docker/biboumi-test/fedora/Dockerfile.base
index 0fd3095..dd536e5 100644
--- a/docker/biboumi-test/fedora/Dockerfile.base
+++ b/docker/biboumi-test/fedora/Dockerfile.base
@@ -12,6 +12,7 @@ RUN dnf --refresh install -y\
udns-devel\
sqlite-devel\
libuuid-devel\
+ libgcrypt-devel\
cmake\
make\
expat-devel\
diff --git a/docker/biboumi/Dockerfile b/docker/biboumi/Dockerfile
new file mode 100644
index 0000000..721d106
--- /dev/null
+++ b/docker/biboumi/Dockerfile
@@ -0,0 +1,48 @@
+# This Dockerfile creates a docker image running biboumi
+
+FROM docker.io/fedora:latest
+
+RUN dnf --refresh install -y\
+ gcc-c++\
+ cmake\
+ make\
+ udns-devel\
+ sqlite-devel\
+ libuuid-devel\
+ expat-devel\
+ libidn-devel\
+ systemd-devel\
+ git\
+ python\
+ && dnf clean all
+
+# 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
+
+# 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
+
+# Install biboumi
+RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./biboumi/build &&\
+ cmake .. -DCMAKE_INSTALL_PREFIX=/usr\
+ -DCMAKE_BUILD_TYPE=Release\
+ -DWITH_BOTAN=1\
+ -DWITH_LITESQL=1\
+ -DWITH_LIBIDN=1\
+ -DWITH_SYSTEMD=1\
+ && make -j8 && make install && rm -rf /biboumi
+
+RUN useradd biboumi
+
+RUN mkdir /var/lib/biboumi
+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"]
+
+USER biboumi
diff --git a/docker/biboumi/README.md b/docker/biboumi/README.md
new file mode 100644
index 0000000..e69c1b0
--- /dev/null
+++ b/docker/biboumi/README.md
@@ -0,0 +1,62 @@
+Biboumi Docker Image
+====================
+
+Running
+-------
+
+This image does not embed any XMPP server. You need to have a running XMPP server first: as an other docker image, or running on the host machine.
+
+Assuming you have a running [prosody](https://hub.docker.com/r/prosody/prosody/) container already running and [properly configured](https://prosody.im/doc/components#adding_an_external_component) you can use the following command to start your biboumi container.
+
+```
+docker run --link prosody:xmpp \
+ -v $PWD/database:/var/lib/biboumi \
+ -e BIBOUMI_PASSWORD=P4SSW0RD \
+ -e BIBOUMI_HOSTNAME=irc.example.com \
+ -e BIBOUMI_ADMIN=blabla \
+ biboumi
+```
+
+If instead you already have an XMPP server running on the host machine, you can start the biboumi container like this:
+
+```
+docker run --network=host \
+ -v $PWD/database:/var/lib/biboumi \
+ -e BIBOUMI_PASSWORD=P4SSW0RD \
+ -e BIBOUMI_HOSTNAME=irc.example.com \
+ -e BIBOUMI_ADMIN=blabla \
+ -e BIBOUMI_XMPP_SERVER_IP=127.0.0.1 \
+ biboumi
+```
+
+Variables
+---------
+
+The configuration file inside the image is a template that is completed when the container is started, using the following environment variables:
+
+* BIBOUMI_HOSTNAME: Sets the value of the *hostname* option.
+* BIBOUMI_SECRET: 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.
+
+You can also directly provide your own configuration file by mounting it inside the container using the -v option:
+
+```
+docker run --link prosody:xmpp \
+ -v $PWD/biboumi.cfg:/etc/biboumi/biboumi.cfg \
+ biboumi
+```
+
+Linking with the XMPP server
+----------------------------
+
+You can use the --link option to connect to any server running in a docker container, but it needs to be called *xmpp*, or the custom value set for the **BIBOUMI_XMPP_SERVER_IP** option. For example, if you are using a container named ejabberd, you would use the option *--link ejabberd:xmpp*.
+
+If you want to connect to the XMPP server running on the host machine, use the **--network=host** option.
+
+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**.
diff --git a/docker/biboumi/biboumi.cfg b/docker/biboumi/biboumi.cfg
new file mode 100644
index 0000000..cc5df61
--- /dev/null
+++ b/docker/biboumi/biboumi.cfg
@@ -0,0 +1,6 @@
+xmpp_server_ip=BIBOUMI_XMPP_SERVER_IP
+port=5347
+db_name=/var/lib/biboumi/biboumi.sqlite
+hostname=BIBOUMI_HOSTNAME
+password=BIBOUMI_PASSWORD
+admin=BIBOUMI_ADMIN
diff --git a/docker/biboumi/entrypoint.sh b/docker/biboumi/entrypoint.sh
new file mode 100644
index 0000000..eda53a4
--- /dev/null
+++ b/docker/biboumi/entrypoint.sh
@@ -0,0 +1,11 @@
+#!/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/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt
index f672833..2268571 100644
--- a/louloulibs/CMakeLists.txt
+++ b/louloulibs/CMakeLists.txt
@@ -33,6 +33,10 @@ 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)
@@ -67,6 +71,11 @@ if(BOTAN_FOUND)
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})
@@ -117,6 +126,8 @@ 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})
diff --git a/louloulibs/cmake/Modules/FindGCRYPT.cmake b/louloulibs/cmake/Modules/FindGCRYPT.cmake
new file mode 100644
index 0000000..bb1bc67
--- /dev/null
+++ b/louloulibs/cmake/Modules/FindGCRYPT.cmake
@@ -0,0 +1,35 @@
+# - 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
index ebb9b9a..5777d92 100644
--- a/louloulibs/louloulibs.h.cmake
+++ b/louloulibs/louloulibs.h.cmake
@@ -1,9 +1,9 @@
-#define SYSTEM_NAME "${CMAKE_SYSTEM}"
#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}"
diff --git a/louloulibs/network/tcp_client_socket_handler.cpp b/louloulibs/network/tcp_client_socket_handler.cpp
index 4e6445c..530c3d9 100644
--- a/louloulibs/network/tcp_client_socket_handler.cpp
+++ b/louloulibs/network/tcp_client_socket_handler.cpp
@@ -35,7 +35,11 @@ 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;
- int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result);
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+ int err = ::getaddrinfo(this->bind_addr.data(), nullptr, &hints, &result);
if (err != 0 || !result)
log_error("Failed to bind socket to ", this->bind_addr, ": ",
gai_strerror(err));
diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp
index 087095f..aa91dac 100644
--- a/louloulibs/utils/encoding.cpp
+++ b/louloulibs/utils/encoding.cpp
@@ -152,7 +152,7 @@ namespace utils
throw std::runtime_error("Cannot convert into UTF-8");
// Make sure cd is always closed when we leave this function
- const auto sg = utils::make_scope_guard([&cd](auto&&){ iconv_close(cd); });
+ const auto sg = utils::make_scope_guard([&cd](){ iconv_close(cd); });
size_t inbytesleft = str.size();
@@ -169,7 +169,7 @@ namespace utils
char* outbuf_ptr = outbuf;
// Make sure outbuf is always deleted when we leave this function
- const auto sg2 = utils::make_scope_guard([outbuf](auto&&){ delete[] outbuf; });
+ const auto sg2 = utils::make_scope_guard([outbuf](){ delete[] outbuf; });
bool done = false;
while (done == false)
diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp
index cd0e89e..e697fc3 100644
--- a/louloulibs/utils/scopeguard.hpp
+++ b/louloulibs/utils/scopeguard.hpp
@@ -87,9 +87,11 @@ private:
};
template<typename F>
-auto make_scope_guard(F&& f)
+auto make_scope_guard(F f)
{
- return std::unique_ptr<void, std::decay_t<F>>{(void*)1, std::forward<F>(f)};
+ static struct Empty {} empty;
+ auto deleter = [f = std::move(f)](Empty*) { f(); };
+ return std::unique_ptr<Empty, decltype(deleter)>{&empty, std::move(deleter)};
}
}
diff --git a/louloulibs/utils/sha1.cpp b/louloulibs/utils/sha1.cpp
index f75bc2a..71ad18d 100644
--- a/louloulibs/utils/sha1.cpp
+++ b/louloulibs/utils/sha1.cpp
@@ -1,121 +1,32 @@
-/* This code is public-domain - it is based on libcrypt
- * placed in the public domain by Wei Dai and other contributors.
- */
-
-#include "sha1.hpp"
-
-#define SHA1_K0 0x5a827999
-#define SHA1_K20 0x6ed9eba1
-#define SHA1_K40 0x8f1bbcdc
-#define SHA1_K60 0xca62c1d6
-
-const uint8_t sha1InitState[] = {
- 0x01,0x23,0x45,0x67, // H0
- 0x89,0xab,0xcd,0xef, // H1
- 0xfe,0xdc,0xba,0x98, // H2
- 0x76,0x54,0x32,0x10, // H3
- 0xf0,0xe1,0xd2,0xc3 // H4
-};
-
-void sha1_init(sha1nfo *s) {
- memcpy(s->state.b,sha1InitState,HASH_LENGTH);
- s->byteCount = 0;
- s->bufferOffset = 0;
-}
-
-uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
- return ((number << bits) | (number >> (32-bits)));
-}
-
-void sha1_hashBlock(sha1nfo *s) {
- uint8_t i;
- uint32_t a,b,c,d,e,t;
-
- a=s->state.w[0];
- b=s->state.w[1];
- c=s->state.w[2];
- d=s->state.w[3];
- e=s->state.w[4];
- for (i=0; i<80; i++) {
- if (i>=16) {
- t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15];
- s->buffer.w[i&15] = sha1_rol32(t,1);
- }
- if (i<20) {
- t = (d ^ (b & (c ^ d))) + SHA1_K0;
- } else if (i<40) {
- t = (b ^ c ^ d) + SHA1_K20;
- } else if (i<60) {
- t = ((b & c) | (d & (b | c))) + SHA1_K40;
- } else {
- t = (b ^ c ^ d) + SHA1_K60;
- }
- t+=sha1_rol32(a,5) + e + s->buffer.w[i&15];
- e=d;
- d=c;
- c=sha1_rol32(b,30);
- b=a;
- a=t;
- }
- s->state.w[0] += a;
- s->state.w[1] += b;
- s->state.w[2] += c;
- s->state.w[3] += d;
- s->state.w[4] += e;
-}
-
-void sha1_addUncounted(sha1nfo *s, uint8_t data) {
- s->buffer.b[s->bufferOffset ^ 3] = data;
- s->bufferOffset++;
- if (s->bufferOffset == BLOCK_LENGTH) {
- sha1_hashBlock(s);
- s->bufferOffset = 0;
- }
-}
-
-void sha1_writebyte(sha1nfo *s, uint8_t data) {
- ++s->byteCount;
- sha1_addUncounted(s, data);
-}
-
-void sha1_write(sha1nfo *s, const char *data, size_t len) {
- for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
-}
-
-void sha1_pad(sha1nfo *s) {
- // Implement SHA-1 padding (fips180-2 §5.1.1)
-
- // Pad with 0x80 followed by 0x00 until the end of the block
- sha1_addUncounted(s, 0x80);
- while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
-
- // Append length in the last 8 bytes
- sha1_addUncounted(s, 0); // We're only using 32 bit lengths
- sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
- sha1_addUncounted(s, 0); // So zero pad the top bits
- sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
- sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
- sha1_addUncounted(s, s->byteCount >> 13); // byte.
- sha1_addUncounted(s, s->byteCount >> 5);
- sha1_addUncounted(s, s->byteCount << 3);
-}
-
-uint8_t* sha1_result(sha1nfo *s) {
- int i;
- // Pad to complete the last block
- sha1_pad(s);
-
- // Swap byte order back
- for (i=0; i<5; i++) {
- uint32_t a,b;
- a=s->state.w[i];
- b=a<<24;
- b|=(a<<8) & 0x00ff0000;
- b|=(a>>8) & 0x0000ff00;
- b|=a>>24;
- s->state.w[i]=b;
- }
-
- // Return pointer to hash (20 characters)
- return s->state.b;
+#include <utils/sha1.hpp>
+
+#include <louloulibs.h>
+
+#ifdef BOTAN_FOUND
+# include <botan/hash.h>
+# include <botan/hex.h>
+#endif
+#ifdef GCRYPT_FOUND
+# include <gcrypt.h>
+# include <vector>
+# include <iomanip>
+# include <sstream>
+#endif
+
+std::string sha1(const std::string& input)
+{
+#ifdef BOTAN_FOUND
+ auto sha1 = Botan::HashFunction::create_or_throw("SHA-1");
+ sha1->update(input);
+ return Botan::hex_encode(sha1->final(), false);
+#endif
+#ifdef GCRYPT_FOUND
+ const auto hash_length = gcry_md_get_algo_dlen(GCRY_MD_SHA1);
+ std::vector<uint8_t> output(hash_length, {});
+ gcry_md_hash_buffer(GCRY_MD_SHA1, output.data(), input.data(), input.size());
+ std::ostringstream digest;
+ for (std::size_t i = 0; i < hash_length; i++)
+ digest << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(output[i]);
+ return digest.str();
+#endif
}
diff --git a/louloulibs/utils/sha1.hpp b/louloulibs/utils/sha1.hpp
index d436782..6c551ac 100644
--- a/louloulibs/utils/sha1.hpp
+++ b/louloulibs/utils/sha1.hpp
@@ -1,33 +1,5 @@
-/* This code is public-domain - it is based on libcrypt
- * placed in the public domain by Wei Dai and other contributors.
- */
+#pragma once
-#include <stdint.h>
-#include <string.h>
+#include <string>
-#define HASH_LENGTH 20
-#define BLOCK_LENGTH 64
-
-union _buffer {
- uint8_t b[BLOCK_LENGTH];
- uint32_t w[BLOCK_LENGTH/4];
-};
-
-union _state {
- uint8_t b[HASH_LENGTH];
- uint32_t w[HASH_LENGTH/4];
-};
-
-typedef struct sha1nfo {
- union _buffer buffer;
- uint8_t bufferOffset;
- union _state state;
- uint32_t byteCount;
- uint8_t keyBuffer[BLOCK_LENGTH];
- uint8_t innerHash[HASH_LENGTH];
-} sha1nfo;
-
-void sha1_init(sha1nfo *s);
-void sha1_writebyte(sha1nfo *s, uint8_t data);
-void sha1_write(sha1nfo *s, const char *data, size_t len);
-uint8_t* sha1_result(sha1nfo *s);
+std::string sha1(const std::string& input);
diff --git a/louloulibs/utils/system.cpp b/louloulibs/utils/system.cpp
new file mode 100644
index 0000000..c0bee11
--- /dev/null
+++ b/louloulibs/utils/system.cpp
@@ -0,0 +1,21 @@
+#include <logger/logger.hpp>
+#include <utils/system.hpp>
+#include <sys/utsname.h>
+#include <cstring>
+
+using namespace std::string_literals;
+
+namespace utils
+{
+std::string get_system_name()
+{
+ struct utsname uts;
+ const int res = ::uname(&uts);
+ if (res == -1)
+ {
+ log_error("uname failed: ", std::strerror(errno));
+ return "Unknown";
+ }
+ return uts.sysname + " "s + uts.release;
+}
+} \ No newline at end of file
diff --git a/louloulibs/utils/system.hpp b/louloulibs/utils/system.hpp
new file mode 100644
index 0000000..7ea1677
--- /dev/null
+++ b/louloulibs/utils/system.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <string>
+
+namespace utils
+{
+std::string get_system_name();
+} \ No newline at end of file
diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp
index e3c49ed..e9f3943 100644
--- a/louloulibs/utils/time.cpp
+++ b/louloulibs/utils/time.cpp
@@ -24,7 +24,7 @@ std::time_t parse_datetime(const std::string& stamp)
std::tm t = {};
#ifdef HAS_GET_TIME
std::istringstream ss(stamp);
- ss.imbue(std::locale("en_US.UTF-8"));
+ ss.imbue(std::locale("C"));
std::string timezone;
ss >> std::get_time(&t, format) >> timezone;
diff --git a/louloulibs/xmpp/auth.cpp b/louloulibs/xmpp/auth.cpp
index c20f95d..8a34a4e 100644
--- a/louloulibs/xmpp/auth.cpp
+++ b/louloulibs/xmpp/auth.cpp
@@ -2,20 +2,7 @@
#include <utils/sha1.hpp>
-#include <iomanip>
-#include <sstream>
-
std::string get_handshake_digest(const std::string& stream_id, const std::string& secret)
{
- sha1nfo sha1;
- sha1_init(&sha1);
- sha1_write(&sha1, stream_id.data(), stream_id.size());
- sha1_write(&sha1, secret.data(), secret.size());
- const uint8_t* result = sha1_result(&sha1);
-
- std::ostringstream digest;
- for (int i = 0; i < HASH_LENGTH; i++)
- digest << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(result[i]);
-
- return digest.str();
+ return sha1(stream_id + secret);
}
diff --git a/louloulibs/xmpp/jid.cpp b/louloulibs/xmpp/jid.cpp
index 7b62f3e..46e01ea 100644
--- a/louloulibs/xmpp/jid.cpp
+++ b/louloulibs/xmpp/jid.cpp
@@ -6,6 +6,11 @@
#include <louloulibs.h>
#ifdef LIBIDN_FOUND
#include <stringprep.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netdb.h>
+ #include <utils/scopeguard.hpp>
+ #include <set>
#endif
#include <logger/logger.hpp>
@@ -58,19 +63,68 @@ std::string jidprep(const std::string& original)
char domain[max_jid_part_len] = {};
memcpy(domain, jid.domain.data(), std::min(max_jid_part_len, jid.domain.size()));
- rc = static_cast<Stringprep_rc>(::stringprep(domain, max_jid_part_len,
- static_cast<Stringprep_profile_flags>(0), stringprep_nameprep));
- if (rc != STRINGPREP_OK)
+
{
- log_error(error_msg + stringprep_strerror(rc));
- return "";
+ // 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));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+
+ struct addrinfo* addr_res = nullptr;
+ const auto ret = ::getaddrinfo(domain, nullptr, &hints, &addr_res);
+ auto addrinfo_deleter = utils::make_scope_guard([addr_res] { if (addr_res) freeaddrinfo(addr_res); });
+ if (ret || !addr_res || (addr_res->ai_family != AF_INET && addr_res->ai_family != AF_INET6))
+ { // Not an IP, run nameprep on it
+ rc = static_cast<Stringprep_rc>(::stringprep(domain, max_jid_part_len,
+ static_cast<Stringprep_profile_flags>(0), stringprep_nameprep));
+ if (rc != STRINGPREP_OK)
+ {
+ log_error(error_msg + stringprep_strerror(rc));
+ return "";
+ }
+
+ // Make sure it contains only allowed characters
+ using std::begin;
+ using std::end;
+ char* domain_end = domain + ::strlen(domain);
+ std::replace_if(std::begin(domain), domain + ::strlen(domain),
+ [](const char c) -> bool
+ {
+ return !((c >= 'a' && c <= 'z') || c == '-' ||
+ (c >= '0' && c <= '9') || c == '.');
+ }, '-');
+ // Make sure there are no doubled - or .
+ std::set<char> special_chars{'-', '.'};
+ domain_end = std::unique(begin(domain), domain + ::strlen(domain), [&special_chars](const char& a, const char& b) -> bool
+ {
+ return special_chars.count(a) && special_chars.count(b);
+ });
+ // remove leading and trailing -. if any
+ if (domain_end != domain && special_chars.count(*(domain_end - 1)))
+ --domain_end;
+ if (domain_end != domain && special_chars.count(domain[0]))
+ {
+ std::memmove(domain, domain + 1, domain_end - domain + 1);
+ --domain_end;
+ }
+ // And if the final result is an empty string, return a dummy hostname
+ if (domain_end == domain)
+ ::strcpy(domain, "empty");
+ else
+ *domain_end = '\0';
+ }
+ else if (addr_res->ai_family == AF_INET6)
+ { // IPv6, surround it with []. The length is always enough:
+ // the longest possible IPv6 is way shorter than max_jid_part_len
+ ::memmove(domain + 1, domain, jid.domain.size());
+ domain[0] = '[';
+ domain[jid.domain.size() + 1] = ']';
+ }
}
- std::replace_if(std::begin(domain), domain + ::strlen(domain),
- [](const char c) -> bool
- {
- return !((c >= 'a' && c <= 'z') || c == '-' ||
- (c >= '0' && c <= '9') || c == '.');
- }, '-');
+
// If there is no resource, stop here
if (jid.resource.empty())
diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp
index e1b6131..e40b1e4 100644
--- a/louloulibs/xmpp/xmpp_component.cpp
+++ b/louloulibs/xmpp/xmpp_component.cpp
@@ -5,6 +5,7 @@
#include <xmpp/xmpp_component.hpp>
#include <config/config.hpp>
+#include <utils/system.hpp>
#include <utils/time.hpp>
#include <xmpp/auth.hpp>
#include <xmpp/jid.hpp>
@@ -585,7 +586,7 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t
}
{
XmlSubNode os(query, "os");
- os.set_inner(SYSTEM_NAME);
+ os.set_inner(utils::get_system_name());
}
}
else
diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp
index 46553ca..a94f172 100644
--- a/src/identd/identd_socket.cpp
+++ b/src/identd/identd_socket.cpp
@@ -38,14 +38,7 @@ void IdentdSocket::parse_in_buffer(const std::size_t)
static std::string hash_jid(const std::string& jid)
{
- sha1nfo sha1;
- sha1_init(&sha1);
- sha1_write(&sha1, jid.data(), jid.size());
- const uint8_t* res = sha1_result(&sha1);
- std::ostringstream result;
- for (int i = 0; i < HASH_LENGTH; i++)
- result << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(res[i]);
- return result.str();
+ return sha1(jid);
}
std::string IdentdSocket::generate_answer(const BiboumiComponent& biboumi, uint16_t local, uint16_t remote)
diff --git a/src/main.cpp b/src/main.cpp
index bc8e779..76ab5d9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -196,7 +196,12 @@ int main(int ac, char** av)
}
}
else
- identd.shutdown();
+ {
+#ifdef UDNS_FOUND
+ dns_handler.destroy();
+#endif
+ identd.shutdown();
+ }
}
// If the only existing connection is the one to the XMPP component:
// close the XMPP stream.
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index ccb3517..a83af80 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -206,8 +206,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode instructions(x, "instructions");
instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain);
- XmlNode required("required");
-
XmlSubNode ports(x, "field");
ports["var"] = "ports";
ports["type"] = "text-multi";
@@ -219,7 +217,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode ports_value(ports, "value");
ports_value.set_inner(val);
}
- ports.add_child(required);
#ifdef BOTAN_FOUND
XmlSubNode tls_ports(x, "field");
@@ -233,7 +230,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode tls_ports_value(tls_ports, "value");
tls_ports_value.set_inner(val);
}
- tls_ports.add_child(required);
XmlSubNode verify_cert(x, "field");
verify_cert["var"] = "verify_cert";
@@ -255,7 +251,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode fingerprint_value(fingerprint, "value");
fingerprint_value.set_inner(options.trustedFingerprint.value());
}
- fingerprint.add_child(required);
#endif
XmlSubNode pass(x, "field");
@@ -267,7 +262,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode pass_value(pass, "value");
pass_value.set_inner(options.pass.value());
}
- pass.add_child(required);
XmlSubNode after_cnt_cmd(x, "field");
after_cnt_cmd["var"] = "after_connect_command";
@@ -279,7 +273,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value");
after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value());
}
- after_cnt_cmd.add_child(required);
if (Config::get("realname_customization", "true") == "true")
{
@@ -292,7 +285,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode username_value(username, "value");
username_value.set_inner(options.username.value());
}
- username.add_child(required);
XmlSubNode realname(x, "field");
realname["var"] = "realname";
@@ -303,7 +295,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode realname_value(realname, "value");
realname_value.set_inner(options.realname.value());
}
- realname.add_child(required);
}
XmlSubNode encoding_out(x, "field");
@@ -316,7 +307,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode encoding_out_value(encoding_out, "value");
encoding_out_value.set_inner(options.encodingOut.value());
}
- encoding_out.add_child(required);
XmlSubNode encoding_in(x, "field");
encoding_in["var"] = "encoding_in";
@@ -328,7 +318,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value());
}
- encoding_in.add_child(required);
XmlSubNode linger_time(x, "field");
linger_time["var"] = "linger_time";
@@ -339,7 +328,6 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
XmlSubNode linger_time_value(linger_time, "value");
linger_time_value.set_inner(std::to_string(options.lingerTime.value()));
}
- encoding_in.add_child(required);
}
void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
@@ -454,8 +442,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
XmlSubNode instructions(x, "instructions");
instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local());
- XmlNode required("required");
-
XmlSubNode encoding_out(x, "field");
encoding_out["var"] = "encoding_out";
encoding_out["type"] = "text-single";
@@ -466,7 +452,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
XmlSubNode encoding_out_value(encoding_out, "value");
encoding_out_value.set_inner(options.encodingOut.value());
}
- encoding_out.add_child(required);
XmlSubNode encoding_in(x, "field");
encoding_in["var"] = "encoding_in";
@@ -478,7 +463,6 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value());
}
- encoding_in.add_child(required);
}
void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index bd6975e..2783b93 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -179,10 +179,11 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
}
catch (const IRCNotConnected& ex)
{
- this->send_stanza_error("presence", from_str, to_str, id,
- "cancel", "remote-server-not-found",
- "Not connected to IRC server "s + ex.hostname,
- true);
+ if (type != "unavailable")
+ this->send_stanza_error("presence", from_str, to_str, id,
+ "cancel", "remote-server-not-found",
+ "Not connected to IRC server "s + ex.hostname,
+ true);
}
stanza_error.disable();
}
@@ -737,7 +738,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_from, const std::string& jid_to)
+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");
{
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
index 7cafdec..aa0c3db 100644
--- a/src/xmpp/biboumi_component.hpp
+++ b/src/xmpp/biboumi_component.hpp
@@ -69,7 +69,7 @@ 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_from, const std::string& jid_to);
+ void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_to, const std::string& jid_from);
/**
* Send a ping request
*/
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py
index 20730a7..c298793 100644
--- a/tests/end_to_end/__main__.py
+++ b/tests/end_to_end/__main__.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import collections
+import datetime
import slixmpp
import asyncio
import logging
@@ -95,7 +96,11 @@ class XMPPComponent(slixmpp.BaseXMPP):
def run_scenario(self):
if self.scenario.steps:
step = self.scenario.steps.pop(0)
- step(self, self.biboumi)
+ try:
+ step(self, self.biboumi)
+ except Exception as e:
+ self.error(e)
+ self.run_scenario()
else:
if self.biboumi:
self.biboumi.stop()
@@ -261,6 +266,16 @@ 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 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))
+ asyncio.get_event_loop().call_soon(xmpp.run_scenario)
+
# list_of_xpaths: [(xpath, xpath), (xpath, xpath), (xpath)]
def expect_unordered(list_of_xpaths, xmpp, biboumi):
formatted_list_of_xpaths = []
@@ -342,7 +357,8 @@ password=coucou
db_name=e2e_test.sqlite
port=8811
admin=admin@example.com
-identd_port=1113""",
+identd_port=1113
+outgoing_bind=127.0.0.1""",
'fixed_server':
"""hostname=biboumi.localhost
@@ -356,7 +372,9 @@ identd_port=1113
common_replacements = {
'irc_server_one': 'irc.localhost@biboumi.localhost',
+ 'irc_server_two': 'localhost@biboumi.localhost',
'irc_host_one': 'irc.localhost',
+ 'irc_host_two': 'localhost',
'biboumi_host': 'biboumi.localhost',
'resource_one': 'resource1',
'resource_two': 'resource2',
@@ -378,8 +396,8 @@ def handshake_sequence():
def connection_begin_sequence(irc_host, jid):
jid = jid.format_map(common_replacements)
- xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']"
- xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]"
+ xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']"
+ xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]"
return (
partial(expect_stanza,
xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)),
@@ -395,21 +413,22 @@ def connection_begin_sequence(irc_host, jid):
xpath % 'Connected to IRC server.'),
# These five messages can be receive in any order
partial(expect_stanza,
- xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
partial(expect_stanza,
- xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
partial(expect_stanza,
- xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
partial(expect_stanza,
- xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
partial(expect_stanza,
- xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
)
def connection_tls_begin_sequence(irc_host, jid):
jid = jid.format_map(common_replacements)
- xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']"
- xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]"
+ xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']"
+ xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]"
+ irc_host = 'irc.localhost'
return (
partial(expect_stanza,
xpath % ('Connecting to %s:7778 (encrypted)' % irc_host)),
@@ -430,8 +449,9 @@ def connection_tls_begin_sequence(irc_host, jid):
def connection_end_sequence(irc_host, jid):
jid = jid.format_map(common_replacements)
- xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']"
- xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]"
+ xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']"
+ 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: Your host is .*$' % irc_host)),
@@ -473,7 +493,6 @@ def extract_attribute(xpath, name, stanza):
def save_value(name, func, stanza, xmpp):
xmpp.saved_values[name] = func(stanza)
-
if __name__ == '__main__':
atexit.register(asyncio.get_event_loop().close)
@@ -537,6 +556,22 @@ if __name__ == '__main__':
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.']"),
]),
+ Scenario("not_connected_error",
+ [
+ handshake_sequence(),
+ partial(send_stanza,
+ "<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ 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())]"),
+ ]),
Scenario("irc_server_disconnection",
[
handshake_sequence(),
@@ -714,8 +749,6 @@ if __name__ == '__main__':
partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']",
"/iq/disco_items:query/disco_items:item[5]")),
], conf='fixed_server'),
-
-
Scenario("list_adhoc_irc",
[
handshake_sequence(),
@@ -782,6 +815,75 @@ if __name__ == '__main__':
# Note, charybdis ignores our QUIT message, so we can't test it
partial(expect_stanza, "/presence[@type='unavailable'][@to='{jid_admin}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"),
]),
+ Scenario("execute_admin_disconnect_from_server_adhoc_command",
+ [
+ handshake_sequence(),
+
+ # Admin connects to first server
+ partial(send_stanza, "<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
+ connection_sequence("irc.localhost", '{jid_admin}/{resource_one}'),
+ partial(expect_stanza, "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message"),
+
+ # Non-Admin connects to first server
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ 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"),
+ partial(expect_stanza, "/message"),
+
+ # Non-admin connects to second server
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#bon%{irc_server_two}/{nick_three}' />"),
+ connection_sequence("localhost", '{jid_one}/{resource_one}'),
+ partial(expect_stanza, "/message/body[text()='Mode #bon [+nt] by {irc_host_one}']"),
+ partial(expect_stanza, "/presence"),
+ partial(expect_stanza, "/message"),
+
+ # Execute as admin
+ partial(send_stanza, "<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"),
+ partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_one}']/dataform:value[text()='{jid_one}']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_admin}']/dataform:value[text()='{jid_admin}']",
+ "/iq/commands:command/commands:actions/commands:next",
+ ),
+ after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid"))
+ ),
+ partial(send_stanza, "<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='jid'><value>{jid_one}</value></field><field var='quit-message'><value>e2e test one</value></field></x></command></iq>"),
+
+ partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='localhost']/dataform:value[text()='localhost']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']",
+ "/iq/commands:command/commands:actions/commands:next",
+ ),
+ after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid"))
+ ),
+ partial(send_stanza, "<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"),
+ partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#bon%{irc_server_two}/{nick_three}']",),
+ ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one} was disconnected from 1 IRC server.']",),
+ ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",),
+ ]),
+
+
+ # Execute as non-admin (this skips the first step)
+ partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"),
+
+ partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']",
+ "/iq/commands:command/commands:actions/commands:next",
+ ),
+ after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid"))
+ ),
+ partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>irc.localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"),
+ partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",),
+ ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one}/{resource_one} was disconnected from 1 IRC server.']",),
+ ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",),
+ ]),
+ ]),
Scenario("multisessionnick",
[
handshake_sequence(),
@@ -1762,7 +1864,7 @@ if __name__ == '__main__':
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' node='http://jabber.org/protocol/muc#traffic'/></iq>"),
- partial(expect_stanza, "/iq[@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"),
+ 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("raw_message",
[
@@ -1947,6 +2049,104 @@ if __name__ == '__main__':
partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{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",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo%{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:field[@type='text-single'][@var='encoding_in']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']",
+ ),
+ 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='#foo%{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='ports' />"
+ "<field var='encoding_out'><value>UTF-8</value></field>"
+ "<field var='encoding_in'><value>latin-1</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, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{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 channel #foo on server irc.localhost']",
+ "/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/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='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_fixed",
+ [
+ handshake_sequence(),
+ partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><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:field[@type='text-single'][@var='encoding_in']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']",
+ ),
+ 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='#foo@{biboumi_host}'>"
+ "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>"
+ "<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></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, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><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 channel #foo on server irc.localhost']",
+ "/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/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='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(),
diff --git a/tests/jid.cpp b/tests/jid.cpp
index 9015afd..089f797 100644
--- a/tests/jid.cpp
+++ b/tests/jid.cpp
@@ -15,7 +15,10 @@ TEST_CASE("Jid")
CHECK(jid2.local == "");
CHECK(jid2.domain == "ツ.coucou");
CHECK(jid2.resource == "coucou@coucou/coucou");
+}
+TEST_CASE("jidprep")
+{
// Jidprep
const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™");
const std::string correctjid = jidprep(badjid);
@@ -26,13 +29,18 @@ TEST_CASE("Jid")
CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM");
CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM");
- const std::string badjid2("Zigougou@poez.io");
- const std::string correctjid2 = jidprep(badjid2);
- CHECK(correctjid2 == "zigougou@poez.io");
+ CHECK(jidprep("Zigougou@poez.io") == "zigougou@poez.io");
+
+ CHECK(jidprep("~Bisous@88.123.43.45") == "~bisous@88.123.43.45");
+
+ CHECK(jidprep("~Bisous@::ffff:42.156.139.46") == "~bisous@[::ffff:42.156.139.46]");
+
+ CHECK(jidprep("louiz!6bf74289@2001:bc8:38e7::") == "louiz!6bf74289@[2001:bc8:38e7::]");
- const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip");
- const std::string fixed_crappy = jidprep(crappy);
- CHECK(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip");
+ CHECK(jidprep("louiz@+:::::----coucou.com78--.") == "louiz@coucou.com78");
+ CHECK(jidprep("louiz@coucou.com78--.") == "louiz@coucou.com78");
+ CHECK(jidprep("louiz@+:::::----coucou.com78") == "louiz@coucou.com78");
+ CHECK(jidprep("louiz@:::::") == "louiz@empty");
#else // Without libidn, jidprep always returns an empty string
CHECK(jidprep(badjid) == "");
#endif
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 48951da..b8a3e75 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -8,6 +8,8 @@
#include <utils/empty_if_fixed_server.hpp>
#include <utils/get_first_non_empty.hpp>
#include <utils/time.hpp>
+#include <utils/system.hpp>
+#include <utils/scopeguard.hpp>
using namespace std::string_literals;
@@ -140,3 +142,19 @@ TEST_CASE("parse_datetime")
CHECK(utils::parse_datetime("1970-01-02T00:00:12*00:00") == -1);
CHECK(utils::parse_datetime("1970-01-02T00:00:12+0000") == -1);
}
+
+TEST_CASE("scope_guard")
+{
+ bool res = false;
+ {
+ auto guard = utils::make_scope_guard([&res](){ res = true; });
+ CHECK(!res);
+ }
+ CHECK(res);
+}
+
+TEST_CASE("system_name")
+{
+ CHECK(utils::get_system_name() != "Unknown");
+ CHECK(!utils::get_system_name().empty());
+} \ No newline at end of file