summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml36
-rw-r--r--CHANGELOG.rst35
-rw-r--r--CMakeLists.txt2
-rw-r--r--conf/irc.ppirc.net.policy.txt2
-rw-r--r--doc/admin.rst4
-rw-r--r--doc/conf.py4
-rw-r--r--doc/synopsis.rst17
-rw-r--r--doc/user.rst30
-rw-r--r--docker/biboumi-test/alpine/Dockerfile56
-rw-r--r--docker/biboumi-test/debian/Dockerfile62
-rw-r--r--docker/biboumi-test/fedora/Dockerfile62
-rw-r--r--docker/biboumi/alpine/Dockerfile15
-rw-r--r--docker/packaging/archlinux/Dockerfile2
-rw-r--r--docker/test/alpine/Dockerfile35
-rw-r--r--docker/test/debian/Dockerfile38
-rw-r--r--docker/test/fedora/Dockerfile41
-rw-r--r--packaging/biboumi.spec.cmake17
-rw-r--r--src/biboumi.h.cmake3
-rw-r--r--src/bridge/bridge.cpp79
-rw-r--r--src/bridge/bridge.hpp29
-rw-r--r--src/database/database.hpp4
-rw-r--r--src/identd/identd_server.hpp1
-rw-r--r--src/identd/identd_socket.cpp8
-rw-r--r--src/irc/capability.hpp9
-rw-r--r--src/irc/irc_client.cpp154
-rw-r--r--src/irc/irc_client.hpp30
-rw-r--r--src/irc/irc_message.cpp39
-rw-r--r--src/irc/irc_message.hpp4
-rw-r--r--src/irc/sasl.hpp9
-rw-r--r--src/main.cpp24
-rw-r--r--src/network/credentials_manager.cpp2
-rw-r--r--src/network/dns_handler.cpp1
-rw-r--r--src/network/tcp_client_socket_handler.cpp6
-rw-r--r--src/network/tcp_client_socket_handler.hpp2
-rw-r--r--src/network/tcp_socket_handler.cpp6
-rw-r--r--src/network/tcp_socket_handler.hpp2
-rw-r--r--src/utils/base64.cpp16
-rw-r--r--src/utils/base64.hpp14
-rw-r--r--src/utils/empty_if_fixed_server.hpp12
-rw-r--r--src/utils/get_first_non_empty.hpp4
-rw-r--r--src/utils/optional_bool.hpp1
-rw-r--r--src/xmpp/adhoc_command.cpp6
-rw-r--r--src/xmpp/adhoc_commands_handler.cpp12
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp52
-rw-r--r--src/xmpp/biboumi_component.cpp98
-rw-r--r--src/xmpp/xmpp_component.cpp8
-rw-r--r--src/xmpp/xmpp_component.hpp4
-rw-r--r--src/xmpp/xmpp_parser.cpp2
-rw-r--r--src/xmpp/xmpp_parser.hpp4
-rw-r--r--src/xmpp/xmpp_stanza.cpp14
-rw-r--r--src/xmpp/xmpp_stanza.hpp6
-rw-r--r--tests/end_to_end/__main__.py43
-rw-r--r--tests/end_to_end/functions.py11
-rw-r--r--tests/end_to_end/ircd.conf511
-rw-r--r--tests/end_to_end/ircd.yaml751
-rw-r--r--tests/end_to_end/scenarios/channel_custom_topic.py2
-rw-r--r--tests/end_to_end/scenarios/channel_force_join.py20
-rw-r--r--tests/end_to_end/scenarios/channel_history.py2
-rw-r--r--tests/end_to_end/scenarios/channel_history_on_fixed_server.py2
-rw-r--r--tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py3
-rw-r--r--tests/end_to_end/scenarios/channel_join_with_different_nick.py9
-rw-r--r--tests/end_to_end/scenarios/channel_join_with_password.py4
-rw-r--r--tests/end_to_end/scenarios/channel_join_with_two_users.py2
-rw-r--r--tests/end_to_end/scenarios/channel_list_escaping.py3
-rw-r--r--tests/end_to_end/scenarios/channel_list_with_rsm.py50
-rw-r--r--tests/end_to_end/scenarios/channel_messages.py29
-rw-r--r--tests/end_to_end/scenarios/client_error.py2
-rw-r--r--tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py69
-rw-r--r--tests/end_to_end/scenarios/default_channel_list_limit.py14
-rw-r--r--tests/end_to_end/scenarios/default_mam_limit.py3
-rw-r--r--tests/end_to_end/scenarios/encoded_channel_join.py3
-rw-r--r--tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py6
-rw-r--r--tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py2
-rw-r--r--tests/end_to_end/scenarios/fixed_irc_server_subscription.py2
-rw-r--r--tests/end_to_end/scenarios/get_irc_connection_info.py6
-rw-r--r--tests/end_to_end/scenarios/get_irc_connection_info_fixed.py3
-rw-r--r--tests/end_to_end/scenarios/invite_other.py6
-rw-r--r--tests/end_to_end/scenarios/irc_server_connection.py2
-rw-r--r--tests/end_to_end/scenarios/irc_server_connection_failure.py2
-rw-r--r--tests/end_to_end/scenarios/irc_server_presence_in_roster.py3
-rw-r--r--tests/end_to_end/scenarios/irc_tls_connection.py3
-rw-r--r--tests/end_to_end/scenarios/join_history_limit.py11
-rw-r--r--tests/end_to_end/scenarios/leave_unjoined_chan.py10
-rw-r--r--tests/end_to_end/scenarios/mode_change.py10
-rw-r--r--tests/end_to_end/scenarios/muc_disco_info.py4
-rw-r--r--tests/end_to_end/scenarios/multiline_message.py2
-rw-r--r--tests/end_to_end/scenarios/multiple_channels_join.py8
-rw-r--r--tests/end_to_end/scenarios/multisession_kick.py18
-rw-r--r--tests/end_to_end/scenarios/multisessionnick.py40
-rw-r--r--tests/end_to_end/scenarios/nick_change.py33
-rw-r--r--tests/end_to_end/scenarios/nick_change_in_join.py5
-rw-r--r--tests/end_to_end/scenarios/not_connected_error.py2
-rw-r--r--tests/end_to_end/scenarios/persistent_channel.py4
-rw-r--r--tests/end_to_end/scenarios/raw_message.py3
-rw-r--r--tests/end_to_end/scenarios/raw_message_fixed_irc_server.py3
-rw-r--r--tests/end_to_end/scenarios/raw_names_command.py13
-rw-r--r--tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py8
-rw-r--r--tests/end_to_end/scenarios/sasl.py99
-rw-r--r--tests/end_to_end/scenarios/self_invite.py7
-rw-r--r--tests/end_to_end/scenarios/self_ping_on_real_channel.py2
-rw-r--r--tests/end_to_end/scenarios/self_version.py2
-rw-r--r--tests/end_to_end/scenarios/simple_channel_join.py6
-rw-r--r--tests/end_to_end/scenarios/simple_channel_join_fixed.py3
-rw-r--r--tests/end_to_end/scenarios/simple_kick.py5
-rw-r--r--tests/end_to_end/scenarios/stable_id.py32
-rw-r--r--tests/end_to_end/sequences.py57
-rw-r--r--tests/irc.cpp43
-rw-r--r--tests/utils.cpp3
-rw-r--r--tests/xmpp.cpp2
-rw-r--r--unit/biboumi.service.cmake1
110 files changed, 1853 insertions, 1274 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 73bc720..db98e8a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -36,7 +36,7 @@ variables:
extends: .sources_changed
stage: build
tags:
- - docker
+ - container
script:
- "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL}"
- mkdir build/
@@ -136,8 +136,11 @@ build:no_db_botan:
- make check_junit || true; make check;
- make e2e
artifacts:
- reports:
- junit: ["build/check_result.xml"]
+ expire_in: 2 weeks
+ paths:
+ - build/
+ name: $CI_PROJECT_NAME-test-$CI_JOB_ID
+ when: always
test:debian:
extends: .basic_test
@@ -157,15 +160,6 @@ test:fedora:
dependencies:
- build:fedora
needs: ["build:fedora"]
- artifacts:
- expire_in: 2 weeks
- paths:
- - build/coverage_test_suite/
- - build/coverage_e2e/
- - build/coverage_total/
- - build/coverage_e2e.info
- when: always
- name: $CI_PROJECT_NAME-test-$CI_JOB_ID
test:no_udns:
extends: .basic_test
@@ -180,7 +174,6 @@ test:alpine:
dependencies:
- build:alpine
needs: ["build:alpine"]
- image: docker.louiz.org/louiz/biboumi/test-alpine:latest
test:freebsd:
extends: .basic_test
@@ -202,9 +195,9 @@ packaging:rpm:
- master@louiz/biboumi
tags:
- docker
- allow_failure: true
image: docker.louiz.org/louiz/biboumi/test-fedora:latest
script:
+ - mkdir -p build/
- cd build/
- make rpm -j$(nproc || echo 1)
artifacts:
@@ -220,10 +213,8 @@ packaging:archlinux:
stage: packaging
only:
- master@louiz/biboumi
- - triggers
tags:
- docker
- allow_failure: true
image: docker.louiz.org/louiz/biboumi/packaging-archlinux:latest
before_script: []
script:
@@ -246,13 +237,16 @@ packaging:archlinux:
- "doc/**/*"
# The jobs with the secure tag need to access directories where important
-# files are stored: the latest doc, etc.'
+# files are stored: the latest doc, etc.
# Other jobs can not access these, otherwise anybody doing a merge request
# could delete the official doc
.deploy:doc:
extends: .doc_changed
stage: deploy
- image: docker.louiz.org/louiz/biboumi/doc-builder
+ image: docker.louiz.org/louiz/biboumi/test-fedora:latest
+ tags:
+ - www
+ - secure
script:
- cd doc/
- make html
@@ -264,9 +258,6 @@ deploy:doc:latest:
extends: .deploy:doc
only:
- master@louiz/biboumi
- tags:
- - www
- - secure
environment:
name: doc.latest
url: https://doc.biboumi.louiz.org
@@ -277,9 +268,6 @@ deploy:doc:tag:
extends: .deploy:doc
only:
- tags
- tags:
- - www
- - secure
environment:
name: doc.$CI_COMMIT_TAG
url: https://doc.biboumi.louiz.org/$CI_COMMIT_TAG/
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 172bb7c..10b6eb4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,5 +1,22 @@
-Version 9.0
-===========
+Version 10.0
+============
+
+For users
+---------
+- Direct messages are always sent to the user's bare JID. It’s now the job
+ of the XMPP server to forward these messages to all online resources.
+- Private messages are now always received from the server-wide JID. You
+ can still use the in-room JID (#chan%irc@biboumi/NickName) to send a
+ private message but the response you will receive will come from
+ nickname%irc@biboumi.
+
+For admins
+----------
+- Command line option --test-config (or -t) has been added. When used,
+ biboumi will just exit without any error if the configuration is correct
+
+Version 9.0 - 2020-09-22
+========================
For users
---------
@@ -14,6 +31,9 @@ For users
- Support for XEP-0410 Self-Ping Optimization. This will prevent clients
which use self-ping from dropping out of the MUC if another client with
bad connectivity is also joined from the same account.
+- SASL support has been added. A new field in the Configure ad-hoc command
+ lets you set a password that will be used to authenticate to the nick
+ service. This replaces the cumbersome and imperfect NickServ method.
For admins
----------
@@ -31,6 +51,17 @@ For developers
A tutorial and a documentation have been written. It should now be easy
to write a test that demonstrates a bug or a missing feature.
+Version 8.5 - 2020-05-09
+========================
+
+- Fix a build failure with GCC 10
+
+Version 8.4 - 2020-02-25
+========================
+
+- Fix a possible crash that could be caused by a very well timed identd
+ query
+
Version 8.3 - 2018-06-01
========================
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ac86555..f07b97f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
project(biboumi)
-set(${PROJECT_NAME}_VERSION_MAJOR 9)
+set(${PROJECT_NAME}_VERSION_MAJOR 10)
set(${PROJECT_NAME}_VERSION_MINOR 0)
set(${PROJECT_NAME}_VERSION_SUFFIX "~dev")
diff --git a/conf/irc.ppirc.net.policy.txt b/conf/irc.ppirc.net.policy.txt
deleted file mode 100644
index 43a7d80..0000000
--- a/conf/irc.ppirc.net.policy.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-key_exchange_methods = RSA
-signature_methods = IMPLICIT
diff --git a/doc/admin.rst b/doc/admin.rst
index a5850a7..ec80112 100644
--- a/doc/admin.rst
+++ b/doc/admin.rst
@@ -205,7 +205,7 @@ ca_file
~~~~~~~
Specifies which file should be used as the list of trusted CA when
-negociating a TLS session. By default this value is unset and biboumi
+negotiating a TLS session. By default this value is unset and biboumi
tries a list of well-known paths.
outgoing_bind
@@ -242,7 +242,7 @@ policy_directory
~~~~~~~~~~~~~~~~
A directory that should contain the policy files, used to customize
-Botan’s behaviour when negociating the TLS connections with the IRC
+Botan’s behaviour when negotiating 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
diff --git a/doc/conf.py b/doc/conf.py
index c607fc5..504c15b 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -24,9 +24,9 @@ copyright = '2018, Florent Le Coz'
author = 'Florent Le Coz'
# The short X.Y version
-version = '8.3'
+version = '8.4'
# The full version, including alpha/beta/rc tags
-release = '8.3'
+release = '8.4'
# -- General configuration ---------------------------------------------------
diff --git a/doc/synopsis.rst b/doc/synopsis.rst
index 5b93a92..2b28c74 100644
--- a/doc/synopsis.rst
+++ b/doc/synopsis.rst
@@ -1,4 +1,19 @@
Synopsis
========
-biboumi [*config_filename*]
+biboumi [-ht] [*config_filename*]
+
+Command-Line Options
+========
+
+-h, \\-\\-help
+~~~~~~~~
+
+Display a help message and exit.
+
+-t, \\-\\-test-config
+~~~~~~~~
+
+Do not run, just test the configuration file syntax. Exit with a 0
+status if the configuration is valid, exits with a non-zero status
+otherwise.
diff --git a/doc/user.rst b/doc/user.rst
index 505e3b9..add3480 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -205,6 +205,27 @@ whole server by mistake. If you want to have a different nickname in the
channel you’re going to join, you need to do it explicitly with the NICK
command before joining the channel.
+Authentication
+--------------
+
+There are multiple different ways to authenticate to an IRC service. The
+most commonly used is to send some command with your password to some
+special user on the server, often called NickServ. This can be done
+manually by talking to this user in private and sending the appropriate
+messages, or this can be done automatically using the `After-connection
+IRC commands`_. The biggest issue with this method is that you need to
+first be connected and logged (nick and username selected) to the server
+before you can start this authentication method, and this often creates a
+race condition if you need to be authenticated before joining a channel.
+
+A new method has been introduced to improve this: SASL authentication. You
+just need to configure your password into the “Sasl password” field of the
+IRC server, and biboumi will automatically authenticate when you connect
+to that server. If the authentication fails, the connection to the server
+is aborted. To fix this, check the error message and fix your nick and/or
+password, or remove your password entirely (empty that field) if you don’t
+want to use SASL at all.
+
Private messages
----------------
@@ -439,6 +460,10 @@ server. The provided configuration form contains these fields:
- **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.
+- **SASL Password**: The password to authenticate with your nickname, on
+ that server. Authentication will be tried with the nick that is used when
+ connecting to the server. This is the Nickname_ field if it is set, otherwise
+ it’s simply the nickname specified in the first room you join.
- **Nickname**: A nickname that will be used instead of the nickname provided
in the initial presence sent to join a channel. This can be used if the
user always wants to have the same nickname on a given server, and not
@@ -448,9 +473,10 @@ server. The provided configuration form contains these fields:
- **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
+ this is NOT a password that will be sent to NickServ (or some other
authentication service), some server (notably Freenode) use it as if it
- was sent to NickServ to identify your nickname.
+ was sent to NickServ to identify your nickname. See SASL password if you
+ need to authenticate.
- **Throttle limit**: specifies a number of messages that can be sent
without a limit, before the throttling takes place. When messages
are throttled, only one command per second is sent to the server.
diff --git a/docker/biboumi-test/alpine/Dockerfile b/docker/biboumi-test/alpine/Dockerfile
deleted file mode 100644
index 9d59c32..0000000
--- a/docker/biboumi-test/alpine/Dockerfile
+++ /dev/null
@@ -1,56 +0,0 @@
-# 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\
- 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\
- libffi-dev\
- flex\
- bison\
- libltdl\
- openssl\
- libressl-dev\
- zlib-dev\
- curl\
- postgresql-dev
-
-# 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
-
-USER tester
-RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
-
-WORKDIR /home/tester
-
diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile
deleted file mode 100644
index 1c6437c..0000000
--- a/docker/biboumi-test/debian/Dockerfile
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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/debian:buster
-
-ENV LC_ALL C.UTF-8
-
-RUN apt update
-
-# Needed to build biboumi
-RUN apt install -y g++\
- clang\
- valgrind\
- libudns-dev\
- libc-ares-dev\
- libsqlite3-dev\
- libuuid1\
- libgcrypt20-dev\
- cmake\
- make\
- libexpat1-dev\
- libidn11-dev\
- uuid-dev\
- libsystemd-dev\
- python3-sphinx\
- libasan5\
- libubsan0\
- git\
- python3-lxml\
- lcov\
- libtool\
- python3-pip\
- python3-dev\
- automake\
- autoconf\
- flex\
- bison\
- libltdl-dev\
- openssl\
- zlib1g-dev\
- libssl-dev\
- curl\
- libpq-dev
-
-# 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 useradd tester -m
-
-# 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
-
-USER tester
-
-RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
-
-WORKDIR /home/tester
diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile
deleted file mode 100644
index 61fa3be..0000000
--- a/docker/biboumi-test/fedora/Dockerfile
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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/fedora:latest
-
-ENV LC_ALL C.UTF-8
-
-RUN dnf --refresh install -y\
- gcc-c++\
- clang\
- valgrind\
- udns-devel\
- c-ares-devel\
- sqlite-devel\
- libuuid-devel\
- libgcrypt-devel\
- cmake\
- make\
- expat-devel\
- libidn-devel\
- uuid-devel\
- systemd-devel\
- python3-sphinx\
- libasan\
- libubsan\
- git\
- fedora-packager\
- python3-lxml\
- lcov\
- rpmdevtools\
- python3-devel\
- automake\
- autoconf\
- flex\
- flex-devel\
- bison\
- libtool-ltdl-devel\
- libtool\
- openssl-devel\
- which\
- java-1.8.0-openjdk\
- postgresql-devel\
- botan2-devel\
- && dnf clean all
-
-# Install slixmpp, for e2e tests
-RUN git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
-
-RUN useradd 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 --with-included-boost && make -j8 && make install && rm -rf /charybdis
-
-RUN chown -R tester:tester /home/tester/ircd
-
-USER tester
-
-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
-
-WORKDIR /home/tester
diff --git a/docker/biboumi/alpine/Dockerfile b/docker/biboumi/alpine/Dockerfile
index 89c7223..9ceb1e2 100644
--- a/docker/biboumi/alpine/Dockerfile
+++ b/docker/biboumi/alpine/Dockerfile
@@ -8,15 +8,10 @@
FROM docker.io/alpine:latest as builder
RUN apk add --no-cache --virtual .build cmake expat-dev g++ git libidn-dev \
- make postgresql-dev python2 sqlite-dev udns-dev util-linux-dev
+ make postgresql-dev python2 sqlite-dev udns-dev util-linux-dev botan-dev
-RUN git clone https://github.com/randombit/botan.git && \
- cd botan && \
- ./configure.py --prefix=/usr && \
- make -j8 && \
- make install
-RUN git clone git://git.louiz.org/biboumi && \
+RUN git clone https://lab.louiz.org/louiz/biboumi && \
mkdir ./biboumi/build && \
cd ./biboumi/build && \
cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
@@ -33,11 +28,7 @@ RUN git clone git://git.louiz.org/biboumi && \
FROM docker.io/alpine:latest
RUN apk add --no-cache libidn libpq libstdc++ libuuid postgresql-libs \
- sqlite-libs udns expat ca-certificates
-
-COPY --from=builder /usr/bin/botan /usr/bin/botan
-COPY --from=builder /usr/lib/libbotan* /usr/lib/
-COPY --from=builder /usr/lib/pkgconfig/botan-2.pc /usr/lib/pkgconfig/botan-2.pc
+ sqlite-libs udns expat ca-certificates botan
COPY --from=builder /etc/biboumi /etc/biboumi
COPY --from=builder /usr/bin/biboumi /usr/bin/biboumi
diff --git a/docker/packaging/archlinux/Dockerfile b/docker/packaging/archlinux/Dockerfile
index 20f0343..5bad3c4 100644
--- a/docker/packaging/archlinux/Dockerfile
+++ b/docker/packaging/archlinux/Dockerfile
@@ -1,4 +1,4 @@
-FROM docker.io/base/archlinux:latest
+FROM docker.io/archlinux:latest
RUN pacman -Syuuuu --noconfirm
diff --git a/docker/test/alpine/Dockerfile b/docker/test/alpine/Dockerfile
new file mode 100644
index 0000000..60f7499
--- /dev/null
+++ b/docker/test/alpine/Dockerfile
@@ -0,0 +1,35 @@
+# 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 \
+git \
+make \
+cmake \
+g++ \
+libuuid \
+udns-dev \
+expat-dev \
+libidn-dev \
+sqlite-dev \
+botan-dev \
+util-linux-dev \
+libgcrypt-dev \
+postgresql-dev \
+valgrind \
+py3-pip \
+py3-lxml \
+python3-dev \
+libffi-dev \
+go \
+wget
+
+# Install oragono, for e2e tests
+RUN wget "https://github.com/oragono/oragono/archive/v2.0.0.tar.gz" && tar xvf "v2.0.0.tar.gz" && cd "oragono-2.0.0" && make && cp ~/go/bin/oragono /usr/local/bin
+
+# 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 https://lab.louiz.org/poezio/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
diff --git a/docker/test/debian/Dockerfile b/docker/test/debian/Dockerfile
new file mode 100644
index 0000000..d1254c5
--- /dev/null
+++ b/docker/test/debian/Dockerfile
@@ -0,0 +1,38 @@
+# 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/debian:buster
+
+ENV LC_ALL C.UTF-8
+
+RUN apt update
+
+# Needed to build biboumi
+RUN apt install -y --no-install-recommends \
+git \
+make \
+cmake \
+g++ \
+libuuid1 \
+libudns-dev \
+libexpat1-dev \
+libidn11-dev \
+libsqlite3-dev \
+libbotan-2-dev \
+libsystemd-dev \
+uuid-dev \
+libgcrypt20-dev \
+libpq-dev \
+valgrind \
+libasan5 \
+libubsan0 \
+python3-pip \
+python3-lxml \
+python3-dev \
+wget
+
+RUN wget "https://github.com/oragono/oragono/releases/download/v2.0.0/oragono-2.0.0-linux-x64.tar.gz" && tar xvf oragono-2.0.0-linux-x64.tar.gz && cp oragono-2.0.0-linux-x64/oragono /usr/local/bin
+
+# 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 https://lab.louiz.org/poezio/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
diff --git a/docker/test/fedora/Dockerfile b/docker/test/fedora/Dockerfile
new file mode 100644
index 0000000..cd41741
--- /dev/null
+++ b/docker/test/fedora/Dockerfile
@@ -0,0 +1,41 @@
+# 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/fedora:32
+
+ENV LC_ALL C.UTF-8
+
+RUN dnf --refresh install -y \
+git \
+make \
+cmake \
+gcc-c++ \
+uuid-devel \
+udns-devel \
+expat-devel \
+libidn-devel \
+sqlite-devel \
+botan2-devel \
+systemd-devel \
+libuuid-devel \
+libgcrypt-devel \
+postgresql-devel \
+lcov \
+libasan \
+libubsan \
+valgrind \
+python3-pip \
+python3-lxml \
+python3-devel \
+python3-sphinx \
+python-sphinx_rtd_theme \
+wget \
+fedora-packager \
+rpmdevtools \
+&& dnf clean all
+
+# Install slixmpp, for e2e tests
+RUN git clone https://lab.louiz.org/poezio/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
+# Install oragono, for e2e tests
+RUN wget "https://github.com/oragono/oragono/releases/download/v2.0.0/oragono-2.0.0-linux-x64.tar.gz" && tar xvf oragono-2.0.0-linux-x64.tar.gz && cp oragono-2.0.0-linux-x64/oragono /usr/local/bin
diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake
index 4114b7e..1352505 100644
--- a/packaging/biboumi.spec.cmake
+++ b/packaging/biboumi.spec.cmake
@@ -64,12 +64,19 @@ make check %{?_smp_mflags}
%changelog
-* ${RPM_DATE} Le Coz Florent <louiz@louiz.org> - ${RPM_VERSION}-1
-- Build latest git revision
-- Build against botan2
-- Build with sphinx instead of pandoc
+-* ${RPM_DATE} Le Coz Florent <louiz@louiz.org> - ${RPM_VERSION}-1
+-- Build latest git revision
-* Fri Jun 1 2018 Le Coz Florent <louiz@louiz.org> - 8.3-1
+* Tue Sep 22 2020 Le Coz Florent <louiz@louiz.org> - 9.0-1
+ Update to version 9.0
+
+* Sat May 9 2020 Le Coz Florent <louiz@louiz.org> - 8.5-1
+ Update to version 8.5
+
+* Tue Feb 25 2020 Le Coz Florent <louiz@louiz.org> - 8.4-1
+ Update to version 8.4
+
+* Wed Jun 1 2018 Le Coz Florent <louiz@louiz.org> - 8.3-1
Update to version 8.3
* Fri May 25 2018 Le Coz Florent <louiz@louiz.org> - 8.2-1
diff --git a/src/biboumi.h.cmake b/src/biboumi.h.cmake
index fa99cd4..29a0510 100644
--- a/src/biboumi.h.cmake
+++ b/src/biboumi.h.cmake
@@ -14,3 +14,6 @@
#cmakedefine HAS_PUT_TIME
#cmakedefine DEBUG_SQL_QUERIES
+#if defined(USE_DATABASE) && defined(BOTAN_FOUND)
+# define WITH_SASL
+#endif
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index 71c0ea4..e7f334f 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -170,8 +170,7 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const
bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
const std::string& password,
const std::string& resource,
- HistoryLimit history_limit,
- const bool force_join)
+ HistoryLimit history_limit)
{
const auto& hostname = iid.get_server();
#ifdef USE_DATABASE
@@ -185,18 +184,18 @@ bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
if (!res_in_chan)
this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource);
- if (irc->is_channel_joined(iid.get_local()) == false)
+ if (!irc->is_channel_joined(iid.get_local()))
{
irc->send_join_command(iid.get_local(), password);
return true;
- } else if (!res_in_chan || force_join) {
- // See https://github.com/xsf/xeps/pull/499 for the force_join argument
+ } else {
+ // See https://github.com/xsf/xeps/pull/499
this->generate_channel_join_for_resource(iid, resource);
}
return false;
}
-void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::string id)
+void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::string id, std::vector<XmlNode> nodes_to_reflect)
{
if (iid.get_server().empty())
{
@@ -234,15 +233,21 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::
if (!first || id.empty())
id = utils::gen_uuid();
- MessageCallback mirror_to_all_resources = [this, iid, uuid, id](const IrcClient* irc, const IrcMessage& message) {
+ MessageCallback mirror_to_all_resources = [this, iid, uuid, id, nodes_to_reflect](const IrcClient* irc, const IrcMessage& message) {
std::string line = message.arguments[1];
// “temporary” workaround for \01ACTION…\01 -> /me messages
if ((line.size() > strlen("\01ACTION\01")) &&
(line.substr(0, 7) == "\01ACTION") && line[line.size() - 1] == '\01')
line = "/me " + line.substr(8, line.size() - 9);
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, uuid, id);
+ {
+ auto stanza = this->xmpp.make_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
+ this->user_jid + "/"
+ + resource, uuid, id);
+ for (const auto& node: nodes_to_reflect)
+ stanza.add_child(node);
+ this->xmpp.send_stanza(stanza);
+ }
};
if (line.substr(0, 5) == "/mode")
@@ -448,9 +453,6 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
}
if (persistent)
this->remove_resource_from_chan(key, 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
{
@@ -859,27 +861,18 @@ 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, uuid, utils::gen_uuid());
+ auto stanza = this->xmpp.make_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding),
+ this->user_jid + "/"
+ + resource, uuid, utils::gen_uuid());
+ this->xmpp.send_stanza(stanza);
}
}
else
{
- const auto it = this->preferred_user_from.find(iid.get_local());
- if (it != this->preferred_user_from.end())
- {
- const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
- for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}])
- this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding),
- this->user_jid + "/"
- + resource, "chat", true, true, true);
- }
- else
- {
- for (const auto& resource: this->resources_in_server[iid.get_server()])
- this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding),
- this->user_jid + "/" + resource, "chat", false, true);
- }
+ this->xmpp.send_message(std::to_string(iid),
+ this->make_xmpp_body(body, encoding),
+ this->user_jid,
+ "chat", false, false);
}
}
@@ -1136,34 +1129,6 @@ void Bridge::on_irc_client_disconnected(const std::string& hostname)
this->xmpp.on_irc_client_disconnected(hostname, this->user_jid);
}
-void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)
-{
- auto it = this->preferred_user_from.find(nick);
- if (it == this->preferred_user_from.end())
- this->preferred_user_from.emplace(nick, full_jid);
- else
- this->preferred_user_from[nick] = full_jid;
-}
-
-void Bridge::remove_preferred_from_jid(const std::string& nick)
-{
- auto it = this->preferred_user_from.find(nick);
- if (it != this->preferred_user_from.end())
- this->preferred_user_from.erase(it);
-}
-
-void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_name)
-{
- for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();)
- {
- Iid iid(Jid(it->second).local, {});
- if (iid.get_local() == channel_name)
- it = this->preferred_user_from.erase(it);
- else
- ++it;
- }
-}
-
void Bridge::add_waiting_irc(irc_responder_callback_t&& callback)
{
this->waiting_irc.emplace_back(std::move(callback));
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index fa2a31f..a7aef3d 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -72,16 +72,14 @@ public:
**/
/**
- * Try to join an irc_channel, does nothing and return true if the channel
- * was already joined.
+ * Try to join an irc_channel.
*/
bool join_irc_channel(const Iid& iid, std::string nickname,
const std::string& password,
const std::string& resource,
- HistoryLimit history_limit,
- const bool force_join);
+ HistoryLimit history_limit);
- void send_channel_message(const Iid& iid, const std::string& body, std::string id);
+ void send_channel_message(const Iid& iid, const std::string& body, std::string id, std::vector<XmlNode> nodes_to_reflect);
void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");
void send_raw_message(const std::string& hostname, const std::string& body);
void leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource);
@@ -219,19 +217,6 @@ public:
*/
size_t active_clients() const;
/**
- * Add (or replace the existing) <nick, jid> into the preferred_user_from map
- */
- void set_preferred_from_jid(const std::string& nick, const std::string& full_jid);
- /**
- * Remove the preferred jid for the given IRC nick
- */
- void remove_preferred_from_jid(const std::string& nick);
- /**
- * Given a channel_name, remove all preferred from_jid that come
- * from this chan.
- */
- void remove_all_preferred_from_jid_of_room(const std::string& channel_name);
- /**
* Add a callback to the waiting list of irc callbacks.
*/
void add_waiting_irc(irc_responder_callback_t&& callback);
@@ -286,14 +271,6 @@ private:
*/
std::shared_ptr<Poller> poller;
/**
- * A map of <nick, full_jid>. For example if this map contains <"toto",
- * "#somechan%server@biboumi/ToTo">, whenever a private message is
- * received from the user "toto", instead of forwarding it to XMPP with
- * from='toto!server@biboumi', we use instead
- * from='#somechan%server@biboumi/ToTo'
- */
- std::unordered_map<std::string, std::string> preferred_user_from;
- /**
* A list of callbacks that are waiting for some IrcMessage to trigger a
* response. We add callbacks in this list whenever we received an IQ
* request and we need a response from IRC to be able to provide the
diff --git a/src/database/database.hpp b/src/database/database.hpp
index a53f87b..de1df49 100644
--- a/src/database/database.hpp
+++ b/src/database/database.hpp
@@ -43,6 +43,8 @@ class Database
struct Nick: Column<std::string> { static constexpr auto name = "nick_"; };
+ struct SaslPassword: Column<std::string> { static constexpr auto name = "saslpassword_"; };
+
struct Pass: Column<std::string> { static constexpr auto name = "pass_"; };
struct Ports: Column<std::string> { static constexpr auto name = "ports_";
@@ -95,7 +97,7 @@ class Database
using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory, GlobalPersistent>;
using GlobalOptions = GlobalOptionsTable::RowType;
- using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength, Address, Nick, ThrottleLimit>;
+ using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength, Address, Nick, SaslPassword, ThrottleLimit>;
using IrcServerOptions = IrcServerOptionsTable::RowType;
using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent, RecordHistoryOptional>;
diff --git a/src/identd/identd_server.hpp b/src/identd/identd_server.hpp
index b1c8ec8..55fe225 100644
--- a/src/identd/identd_server.hpp
+++ b/src/identd/identd_server.hpp
@@ -24,6 +24,7 @@ class IdentdServer: public TcpSocketServer<IdentdSocket>
if (this->poller->is_managing_socket(this->socket))
this->poller->remove_socket_handler(this->socket);
::close(this->socket);
+ this->sockets.clear();
}
void clean()
{
diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp
index 92cd80b..7688bbe 100644
--- a/src/identd/identd_socket.cpp
+++ b/src/identd/identd_socket.cpp
@@ -25,10 +25,12 @@ void IdentdSocket::parse_in_buffer(const std::size_t)
std::istringstream line(this->in_buf.substr(0, line_end));
this->consume_in_buffer(line_end + 1);
- uint16_t local_port;
- uint16_t remote_port;
+ uint16_t local_port{};
+ uint16_t remote_port{};
char sep;
line >> local_port >> sep >> remote_port;
+ if (line.fail()) // Data did not match the expected format, ignore the line entirely
+ continue;
const auto& xmpp = this->server.get_biboumi_component();
auto response = this->generate_answer(xmpp, local_port, remote_port);
@@ -47,7 +49,7 @@ std::string IdentdSocket::generate_answer(const BiboumiComponent& biboumi, uint1
{
for (const auto& pair: bridge->get_irc_clients())
{
- if (pair.second->match_port_pairt(local, remote))
+ if (pair.second->match_port_pair(local, remote))
{
std::ostringstream os;
os << local << " , " << remote << " : USERID : OTHER : " << hash_jid(bridge->get_bare_jid()) << "\r\n";
diff --git a/src/irc/capability.hpp b/src/irc/capability.hpp
new file mode 100644
index 0000000..55ccb0c
--- /dev/null
+++ b/src/irc/capability.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <functional>
+
+struct Capability
+{
+ std::function<void()> on_ack;
+ std::function<void()> on_nack;
+};
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index de38d42..8a64727 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -5,6 +5,7 @@
#include <irc/irc_client.hpp>
#include <bridge/bridge.hpp>
#include <irc/irc_user.hpp>
+#include <utils/base64.hpp>
#include <logger/logger.hpp>
#include <config/config.hpp>
@@ -81,7 +82,18 @@ static const std::unordered_map<std::string,
{"PONG", {&IrcClient::on_pong, {0, 0}}},
{"KICK", {&IrcClient::on_kick, {3, 0}}},
{"INVITE", {&IrcClient::on_invite, {2, 0}}},
-
+ {"CAP", {&IrcClient::on_cap, {3, 0}}},
+#ifdef WITH_SASL
+ {"AUTHENTICATE", {&IrcClient::on_authenticate, {1, 0}}},
+ {"900", {&IrcClient::on_sasl_login, {3, 0}}},
+ {"902", {&IrcClient::on_sasl_failure, {2, 0}}},
+ {"903", {&IrcClient::on_sasl_success, {0, 0}}},
+ {"904", {&IrcClient::on_sasl_failure, {2, 0}}},
+ {"905", {&IrcClient::on_sasl_failure, {2, 0}}},
+ {"906", {&IrcClient::on_sasl_failure, {2, 0}}},
+ {"907", {&IrcClient::on_sasl_failure, {2, 0}}},
+ {"908", {&IrcClient::on_sasl_failure, {2, 0}}},
+#endif
{"401", {&IrcClient::on_generic_error, {2, 0}}},
{"402", {&IrcClient::on_generic_error, {2, 0}}},
{"403", {&IrcClient::on_generic_error, {2, 0}}},
@@ -232,6 +244,7 @@ void IrcClient::on_connection_failed(const std::string& reason)
"cancel", "item-not-found",
"", reason);
}
+ this->channels_to_join.clear();
}
else // try the next port
this->start();
@@ -272,18 +285,45 @@ void IrcClient::on_connected()
}
}
- this->send_message({"CAP", {"REQ", "multi-prefix"}});
- this->send_message({"CAP", {"END"}});
+ this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
+
+ this->capabilities["multi-prefix"] = {[]{}, []{}};
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- if (!options.col<Database::Pass>().empty())
+
+ const auto& server_password = options.col<Database::Pass>();
+
+ if (!server_password.empty())
this->send_pass_command(options.col<Database::Pass>());
#endif
- this->send_nick_command(this->current_nick);
+#ifdef WITH_SASL
+ const auto& sasl_password = options.col<Database::SaslPassword>();
+ if (!sasl_password.empty())
+ {
+ this->capabilities["sasl"] = {
+ [this]
+ {
+ this->send_message({"AUTHENTICATE", {"PLAIN"}});
+ log_warning("negotiating SASL now...");
+ },
+ []
+ {
+ log_warning("SASL not supported by the server, disconnecting.");
+ }
+ };
+ this->sasl_state = SaslState::needed;
+ }
+#endif
+ {
+ for (const auto &pair : this->capabilities)
+ this->send_message({ "CAP", {"REQ", pair.first}});
+ }
+
+ this->send_nick_command(this->current_nick);
#ifdef USE_DATABASE
if (Config::get("realname_customization", "true") == "true")
{
@@ -294,13 +334,8 @@ void IrcClient::on_connected()
this->send_user_command(username, realname);
}
else
- this->send_user_command(this->username, this->realname);
-#else
- this->send_user_command(this->username, this->realname);
#endif
- this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
- this->send_pending_data();
- this->bridge.on_irc_client_connected(this->get_hostname());
+ this->send_user_command(this->username, this->realname);
}
void IrcClient::on_connection_close(const std::string& error_msg)
@@ -371,12 +406,14 @@ void IrcClient::parse_in_buffer(const size_t)
{
const auto& limits = it->second.second;
// Check that the Message is well formed before actually calling
- // the callback. limits.first is the min number of arguments,
- // second is the max
- if (message.arguments.size() < limits.first ||
- (limits.second > 0 && message.arguments.size() > limits.second))
+ // the callback.
+ const auto args_size = message.arguments.size();
+ const auto min = limits.first;
+ const auto max = limits.second;
+ if (args_size < min ||
+ (max > 0 && args_size > max))
log_warning("Invalid number of arguments for IRC command “", message.command,
- "”: ", message.arguments.size());
+ "”: ", args_size);
else
{
const auto& cb = it->second.first;
@@ -1266,7 +1303,7 @@ void IrcClient::on_unknown_message(const IrcMessage& message)
return ;
std::string from = message.prefix;
std::stringstream ss;
- for (auto it = message.arguments.begin() + 1; it != message.arguments.end(); ++it)
+ for (auto it = std::next(message.arguments.begin()); it != message.arguments.end(); ++it)
{
ss << *it;
if (it + 1 != message.arguments.end())
@@ -1299,3 +1336,86 @@ long int IrcClient::get_throttle_limit() const
return 10;
#endif
}
+
+void IrcClient::on_cap(const IrcMessage &message)
+{
+ const auto& sub_command = message.arguments[1];
+ const auto& caps = utils::split(message.arguments[2], ' ', false);
+ for (const auto& cap: caps)
+ {
+ auto it = this->capabilities.find(cap);
+ if (it == this->capabilities.end())
+ {
+ log_warning("Received a CAP message for something we didn’t ask, or that we already handled: [", cap, "]");
+ return;
+ }
+ Capability& capability = it->second;
+ if (sub_command == "ACK")
+ capability.on_ack();
+ else if (sub_command == "NACK")
+ capability.on_nack();
+ this->capabilities.erase(it);
+ }
+ if (this->capabilities.empty())
+ this->cap_end();
+}
+
+#ifdef WITH_SASL
+void IrcClient::on_authenticate(const IrcMessage &)
+{
+ if (this->sasl_state == SaslState::unneeded)
+ {
+ log_warning("Received an AUTHENTICATE command but we don’t intend to authenticate…");
+ return;
+ }
+
+ auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
+ this->get_hostname());
+ const auto& nick = !options.col<Database::Nick>().empty() ? options.col<Database::Nick>() : this->get_own_nick();
+ const auto auth_string = '\0' + nick + '\0' + options.col<Database::SaslPassword>();
+ const auto base64_auth_string = base64::encode(auth_string);
+ this->send_message({"AUTHENTICATE", {base64_auth_string}});
+}
+
+void IrcClient::on_sasl_success(const IrcMessage &)
+{
+ this->sasl_state = SaslState::success;
+ this->cap_end();
+}
+
+void IrcClient::on_sasl_failure(const IrcMessage& message)
+{
+ this->sasl_state = SaslState::failure;
+ const auto reason = message.arguments[1];
+ // Send an error message for all room that the user wanted to join
+ for (const auto& tuple: this->channels_to_join)
+ {
+ Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
+ this->bridge.send_presence_error(iid, this->current_nick,
+ "cancel", "item-not-found",
+ "", reason);
+ }
+ this->channels_to_join.clear();
+ this->send_quit_command(reason);
+}
+
+void IrcClient::on_sasl_login(const IrcMessage &message)
+{
+ const auto& login = message.arguments[2];
+ std::string text = "Your are now logged in as " + login;
+ if (message.arguments.size() > 3)
+ text = message.arguments[3];
+ this->bridge.send_xmpp_message(this->hostname, message.prefix, text);
+}
+#endif
+
+void IrcClient::cap_end()
+{
+#ifdef WITH_SASL
+ // If we are currently authenticating through sasl, finish that before sending CAP END
+ if (this->sasl_state == SaslState::needed)
+ return;
+#endif
+ this->send_message({"CAP", {"END"}});
+ this->bridge.on_irc_client_connected(this->get_hostname());
+}
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index cfb3d21..3423228 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -1,8 +1,14 @@
#pragma once
-
#include <irc/irc_message.hpp>
#include <irc/irc_channel.hpp>
+#include <irc/capability.hpp>
+
+#include "biboumi.h"
+
+#ifdef WITH_SASL
+# include <irc/sasl.hpp>
+#endif
#include <irc/iid.hpp>
#include <bridge/history_limit.hpp>
@@ -232,6 +238,20 @@ public:
*/
void on_invited(const IrcMessage& message);
/**
+ * The IRC server sends a CAP message, as part of capabilities negotiation. It could be a ACK,
+ * NACK, or something else
+ */
+ void on_cap(const IrcMessage& message);
+private:
+ void cap_end();
+public:
+#ifdef WITH_SASL
+ void on_authenticate(const IrcMessage& message);
+ void on_sasl_login(const IrcMessage& message);
+ void on_sasl_success(const IrcMessage& message);
+ void on_sasl_failure(const IrcMessage& message);
+#endif
+ /**
* The channel has been completely joined (self presence, topic, all names
* received etc), send the self presence and topic to the XMPP user.
*/
@@ -359,6 +379,14 @@ private:
* has been established, we are authentified and we have a nick)
*/
bool welcomed;
+#ifdef WITH_SASL
+ /**
+ * Whether or not we are trying to authenticate using sasl. If this is true we need to wait for a
+ * successful auth
+ */
+ SaslState sasl_state{SaslState::unneeded};
+#endif
+ std::map<std::string, Capability> capabilities;
/**
* See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.3
* We store the possible chanmodes in this object.
diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp
index 14fdb0e..62fe9a7 100644
--- a/src/irc/irc_message.cpp
+++ b/src/irc/irc_message.cpp
@@ -1,33 +1,30 @@
#include <irc/irc_message.hpp>
#include <iostream>
-IrcMessage::IrcMessage(std::string&& line)
+IrcMessage::IrcMessage(std::stringstream ss)
{
- std::string::size_type pos;
-
- // optional prefix
- if (line[0] == ':')
+ if (ss.peek() == ':')
{
- pos = line.find(' ');
- this->prefix = line.substr(1, pos - 1);
- line = line.substr(pos + 1, std::string::npos);
+ ss.ignore();
+ ss >> this->prefix;
}
- // command
- pos = line.find(' ');
- this->command = line.substr(0, pos);
- line = line.substr(pos + 1, std::string::npos);
- // arguments
- do
+ ss >> this->command;
+ while (ss >> std::ws)
{
- if (line[0] == ':')
+ std::string arg;
+ if (ss.peek() == ':')
+ {
+ ss.ignore();
+ std::getline(ss, arg);
+ }
+ else
{
- this->arguments.emplace_back(line.substr(1, std::string::npos));
- break ;
+ ss >> arg;
+ if (arg.empty())
+ break;
}
- pos = line.find(' ');
- this->arguments.emplace_back(line.substr(0, pos));
- line = line.substr(pos + 1, std::string::npos);
- } while (pos != std::string::npos);
+ this->arguments.push_back(std::move(arg));
+ }
}
IrcMessage::IrcMessage(std::string&& prefix,
diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp
index 269a12a..5475fd2 100644
--- a/src/irc/irc_message.hpp
+++ b/src/irc/irc_message.hpp
@@ -4,11 +4,13 @@
#include <vector>
#include <string>
#include <ostream>
+#include <sstream>
class IrcMessage
{
public:
- IrcMessage(std::string&& line);
+ IrcMessage(std::stringstream ss);
+ IrcMessage(std::string str): IrcMessage{std::stringstream{str}} {}
IrcMessage(std::string&& prefix, std::string&& command, std::vector<std::string>&& args);
IrcMessage(std::string&& command, std::vector<std::string>&& args);
~IrcMessage() = default;
diff --git a/src/irc/sasl.hpp b/src/irc/sasl.hpp
new file mode 100644
index 0000000..775bc3f
--- /dev/null
+++ b/src/irc/sasl.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+enum class SaslState
+{
+ unneeded,
+ needed,
+ failure,
+ success,
+};
diff --git a/src/main.cpp b/src/main.cpp
index 2448197..20bf9fd 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -38,7 +38,7 @@ int config_help(const std::string& missing_option)
int display_help()
{
- std::cout << "Usage: biboumi [configuration_file]" << std::endl;
+ std::cout << "Usage: biboumi [-ht] [configuration_file]" << std::endl;
return 0;
}
@@ -194,13 +194,19 @@ static int main_loop(std::string hostname, std::string password)
int main(int ac, char** av)
{
+ std::string conf_filename{};
+ bool test_conf = false;
if (ac > 1)
{
- const std::string arg = av[1];
- if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-')
+ for (int i = 1; i < ac; i++)
{
- if (arg == "--help")
+ const std::string arg = av[i];
+ if ((arg == "-h") || (arg == "--help"))
return display_help();
+ else if ((arg == "-t") || (arg == "--test-config"))
+ test_conf = true;
+ else if (i + 1 == ac)
+ conf_filename = arg;
else
{
std::cerr << "Unknow command line option: " << arg
@@ -209,8 +215,8 @@ int main(int ac, char** av)
}
}
}
- const std::string conf_filename =
- ac > 1 ? av[1]: xdg_config_path("biboumi.cfg");
+ if (conf_filename.empty())
+ conf_filename = xdg_config_path("biboumi.cfg");
std::cout << "Using configuration file: " << conf_filename << std::endl;
if (!Config::read_conf(conf_filename))
@@ -222,6 +228,12 @@ int main(int ac, char** av)
const std::string hostname = Config::get("hostname", "");
if (hostname.empty())
return config_help("hostname");
+ if (test_conf)
+ {
+ std::cout << "biboumi: the configuration file " << conf_filename
+ << " syntax is ok" << std::endl;
+ return 0;
+ }
#ifdef USE_DATABASE
try
diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp
index 89c694c..47f8514 100644
--- a/src/network/credentials_manager.cpp
+++ b/src/network/credentials_manager.cpp
@@ -102,7 +102,7 @@ void BasicCredentialsManager::load_certs()
if (BasicCredentialsManager::try_to_open_one_ca_bundle(paths))
BasicCredentialsManager::certs_loaded = true;
else
- log_warning("The CA could not be loaded, TLS negociation will probably fail.");
+ log_warning("The CA could not be loaded, TLS negotiation will probably fail.");
}
std::vector<Botan::Certificate_Store*> BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&)
diff --git a/src/network/dns_handler.cpp b/src/network/dns_handler.cpp
index 7f0c96a..dd6f201 100644
--- a/src/network/dns_handler.cpp
+++ b/src/network/dns_handler.cpp
@@ -9,6 +9,7 @@
#include <udns.h>
#include <cerrno>
+#include <stdexcept>
#include <cstring>
class Resolver;
diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp
index 6f67f02..7d1029f 100644
--- a/src/network/tcp_client_socket_handler.cpp
+++ b/src/network/tcp_client_socket_handler.cpp
@@ -260,8 +260,10 @@ std::string TCPClientSocketHandler::get_port() const
return this->port;
}
-bool TCPClientSocketHandler::match_port_pairt(const uint16_t local, const uint16_t remote) const
+bool TCPClientSocketHandler::match_port_pair(const uint16_t local, const uint16_t remote) const
{
+ if (!this->is_connected())
+ return false;
const auto remote_port = static_cast<uint16_t>(std::stoi(this->port));
- return this->is_connected() && local == this->local_port && remote == remote_port;
+ return local == this->local_port && remote == remote_port;
}
diff --git a/src/network/tcp_client_socket_handler.hpp b/src/network/tcp_client_socket_handler.hpp
index 74caca9..38d8b84 100644
--- a/src/network/tcp_client_socket_handler.hpp
+++ b/src/network/tcp_client_socket_handler.hpp
@@ -34,7 +34,7 @@ class TCPClientSocketHandler: public TCPSocketHandler
/**
* Whether or not this connection is using the two given TCP ports.
*/
- bool match_port_pairt(const uint16_t local, const uint16_t remote) const;
+ bool match_port_pair(const uint16_t local, const uint16_t remote) const;
protected:
bool hostname_resolution_failed;
diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp
index e05caad..5e4ae30 100644
--- a/src/network/tcp_socket_handler.cpp
+++ b/src/network/tcp_socket_handler.cpp
@@ -194,7 +194,7 @@ void TCPSocketHandler::send_data(std::string&& data)
if (this->use_tls)
try {
this->tls_send(std::move(data));
- } catch (const Botan::TLS::TLS_Exception& e) {
+ } catch (const Botan::Exception& e) {
this->on_connection_close("TLS error: "s + e.what());
this->close();
return ;
@@ -261,7 +261,7 @@ void TCPSocketHandler::tls_recv()
const bool was_active = this->tls->is_active();
try {
this->tls->received_data(recv_buf, static_cast<size_t>(size));
- } catch (const Botan::TLS::TLS_Exception& e) {
+ } catch (const Botan::Exception& e) {
// May happen if the server sends malformed TLS data (buggy server,
// or more probably we are just connected to a server that sends
// plain-text)
@@ -277,7 +277,7 @@ void TCPSocketHandler::tls_recv()
void TCPSocketHandler::tls_send(std::string&& data)
{
// We may not be connected yet, or the tls session has
- // not yet been negociated
+ // not yet been negotiated
if (this->tls && this->tls->is_active())
{
const bool was_active = this->tls->is_active();
diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp
index c598641..b12ae57 100644
--- a/src/network/tcp_socket_handler.hpp
+++ b/src/network/tcp_socket_handler.hpp
@@ -212,7 +212,7 @@ private:
* calls the output_fn callback with it as soon as it is created.
* Therefore, we do not want to create it if we do not intend to send any
* TLS-encrypted message. We create the object only when needed (for
- * example after we have negociated a TLS session using a STARTTLS
+ * example after we have negotiated a TLS session using a STARTTLS
* message, or stuf like that).
*
* See start_tls for the method where this object is created.
diff --git a/src/utils/base64.cpp b/src/utils/base64.cpp
new file mode 100644
index 0000000..c0959bb
--- /dev/null
+++ b/src/utils/base64.cpp
@@ -0,0 +1,16 @@
+#include <utils/base64.hpp>
+
+#ifdef BOTAN_FOUND
+#include <botan/base64.h>
+
+namespace base64
+{
+
+std::string encode(const std::string &input)
+{
+ return Botan::base64_encode(reinterpret_cast<const uint8_t*>(input.data()), input.size());
+}
+
+}
+
+#endif
diff --git a/src/utils/base64.hpp b/src/utils/base64.hpp
new file mode 100644
index 0000000..7c08d82
--- /dev/null
+++ b/src/utils/base64.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "biboumi.h"
+
+#ifdef BOTAN_FOUND
+
+#include <string>
+
+namespace base64
+{
+std::string encode(const std::string& input);
+}
+
+#endif
diff --git a/src/utils/empty_if_fixed_server.hpp b/src/utils/empty_if_fixed_server.hpp
index 9ccf5fd..2422ee4 100644
--- a/src/utils/empty_if_fixed_server.hpp
+++ b/src/utils/empty_if_fixed_server.hpp
@@ -7,17 +7,11 @@
namespace utils
{
- inline std::string empty_if_fixed_server(std::string&& str)
+ inline const std::string& empty_if_fixed_server(const std::string& str)
{
+ static const std::string empty{};
if (!Config::get("fixed_irc_server", "").empty())
- return {};
- return str;
- }
-
- inline std::string empty_if_fixed_server(const std::string& str)
- {
- if (!Config::get("fixed_irc_server", "").empty())
- return {};
+ return empty;
return str;
}
diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp
index 1877ee8..6129b63 100644
--- a/src/utils/get_first_non_empty.hpp
+++ b/src/utils/get_first_non_empty.hpp
@@ -11,13 +11,13 @@ template <>
bool is_empty(const std::string& val);
template <typename T>
-T get_first_non_empty(T&& last)
+T& get_first_non_empty(T&& last)
{
return last;
}
template <typename T, typename... Args>
-T get_first_non_empty(T&& first, Args&&... args)
+T& get_first_non_empty(T&& first, Args&&... args)
{
if (!is_empty(first))
return first;
diff --git a/src/utils/optional_bool.hpp b/src/utils/optional_bool.hpp
index 3d00d23..ebd063d 100644
--- a/src/utils/optional_bool.hpp
+++ b/src/utils/optional_bool.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <ostream>
#include <string>
struct OptionalBool
diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp
index fbf4ce2..f8c8e4f 100644
--- a/src/xmpp/adhoc_command.cpp
+++ b/src/xmpp/adhoc_command.cpp
@@ -26,7 +26,7 @@ void PingStep1(XmppComponent&, AdhocSession&, XmlNode& command_node)
void HelloStep1(XmppComponent&, AdhocSession&, XmlNode& command_node)
{
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Configure your name.");
@@ -65,9 +65,9 @@ void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
}
}
command_node.delete_all_children();
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
session.terminate();
}
diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp
index ff4c1e5..7a84b2e 100644
--- a/src/xmpp/adhoc_commands_handler.cpp
+++ b/src/xmpp/adhoc_commands_handler.cpp
@@ -36,16 +36,16 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
auto command_it = this->commands.find(node);
if (command_it == this->commands.end())
{
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "cancel";
- XmlSubNode condition(error, STANZA_NS":item-not-found");
+ XmlSubNode condition(error, STANZA_NS, "item-not-found");
}
else if (command_it->second.is_admin_only() &&
!Config::is_in_list("admin", jid.bare()))
{
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "cancel";
- XmlSubNode condition(error, STANZA_NS":forbidden");
+ XmlSubNode condition(error, STANZA_NS, "forbidden");
}
else
{
@@ -94,9 +94,9 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
}
else // unsupported action
{
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
}
}
return command_node;
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index 113943c..aea316d 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -34,7 +34,7 @@ void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode&
{
auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Disconnect a user from the gateway");
@@ -108,9 +108,9 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X
return;
}
}
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
session.terminate();
}
@@ -124,7 +124,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
auto options = Database::get_global_options(owner.bare());
command_node.delete_all_children();
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Configure some global default settings.");
@@ -220,9 +220,9 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session,
note.set_inner("Configuration successfully applied.");
return;
}
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
session.terminate();
}
@@ -238,7 +238,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
auto commands = Database::get_after_connection_commands(options);
command_node.delete_all_children();
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Configure the IRC server " + server_domain);
@@ -310,6 +310,20 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
fingerprint_value.set_inner(options.col<Database::TrustedFingerprint>());
}
}
+
+ {
+ XmlSubNode field(x, "field");
+ field["var"] = "sasl_password";
+ field["type"] = "text-private";
+ field["label"] = "SASL Password";
+ set_desc(field, "Use it to authenticate with your nick.");
+ if (!options.col<Database::SaslPassword>().empty())
+ {
+ XmlSubNode value(field, "value");
+ value.set_inner(options.col<Database::SaslPassword>());
+ }
+ }
+
#endif
{
@@ -473,9 +487,11 @@ void ConfigureIrcServerStep2(XmppComponent& xmpp_component, AdhocSession& sessio
}
else if (field->get_tag("var") == "fingerprint" && value)
- {
- options.col<Database::TrustedFingerprint>() = value->get_inner();
- }
+ options.col<Database::TrustedFingerprint>() = value->get_inner();
+
+ else if (field->get_tag("var") == "sasl_password" && value)
+ options.col<Database::SaslPassword>() = value->get_inner();
+
#endif // BOTAN_FOUND
@@ -549,9 +565,9 @@ void ConfigureIrcServerStep2(XmppComponent& xmpp_component, AdhocSession& sessio
note.set_inner("Configuration successfully applied.");
return;
}
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
session.terminate();
}
@@ -570,7 +586,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
auto options = Database::get_irc_channel_options_with_server_default(requester.local + "@" + requester.domain,
iid.get_server(), iid.get_local());
node.delete_all_children();
- XmlSubNode x(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 " + iid.get_local() + " on server " + iid.get_server());
@@ -655,9 +671,9 @@ void ConfigureIrcChannelStep2(XmppComponent& xmpp_component, AdhocSession& sessi
}
else
{
- XmlSubNode error(command_node, ADHOC_NS":error");
+ XmlSubNode error(command_node, ADHOC_NS, "error");
error["type"] = "modify";
- XmlSubNode condition(error, STANZA_NS":bad-request");
+ XmlSubNode condition(error, STANZA_NS, "bad-request");
session.terminate();
}
}
@@ -733,7 +749,7 @@ void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession&
{ // Send a form to select the user to disconnect
auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Disconnect a user from selected IRC servers");
@@ -778,7 +794,7 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession&
command_node.delete_all_children();
auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
- XmlSubNode x(command_node, "jabber:x:data:x");
+ XmlSubNode x(command_node, "jabber:x:data", "x");
x["type"] = "form";
XmlSubNode title(x, "title");
title.set_inner("Disconnect a user from selected IRC servers");
@@ -914,7 +930,7 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session,
#else
constexpr std::size_t timestamp_size{10 + 1 + 8 + 1};
char buf[timestamp_size] = {};
- const auto res = std::strftime(buf, timestamp_size, "%F %T", localtime(&now_c, &tm));
+ const auto res = std::strftime(buf, timestamp_size, "%F %T", localtime_r(&now_c, &tm));
if (res > 0)
ss << " since " << buf;
#endif
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index 6fe6972..3cfa9d0 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -158,39 +158,50 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
{
const std::string own_nick = bridge->get_own_nick(iid);
const XmlNode* x = stanza.get_child("x", MUC_NS);
- const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
- const XmlNode* history = x ? x->get_child("history", MUC_NS): nullptr;
- HistoryLimit history_limit;
- if (history)
+ const IrcClient* irc = bridge->find_irc_client(iid.get_server());
+ // if there is no <x/>, this is a presence status update, we don’t care about those
+ if (x)
{
- const auto seconds = history->get_tag("seconds");
- if (!seconds.empty())
+ const XmlNode* password = x->get_child("password", MUC_NS);
+ const XmlNode* history = x->get_child("history", MUC_NS);
+ HistoryLimit history_limit;
+ if (history)
{
- const auto now = std::chrono::system_clock::now();
- std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
- int int_seconds = std::atoi(seconds.data());
- timestamp -= int_seconds;
- history_limit.since = utils::to_string(timestamp);
+ const auto seconds = history->get_tag("seconds");
+ if (!seconds.empty())
+ {
+ const auto now = std::chrono::system_clock::now();
+ std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
+ int int_seconds = std::atoi(seconds.data());
+ timestamp -= int_seconds;
+ history_limit.since = utils::to_string(timestamp);
+ }
+ const auto since = history->get_tag("since");
+ if (!since.empty())
+ history_limit.since = since;
+ const auto maxstanzas = history->get_tag("maxstanzas");
+ if (!maxstanzas.empty())
+ history_limit.stanzas = std::atoi(maxstanzas.data());
+ // Ignore any other value, because this is too complex to implement,
+ // so I won’t do it.
+ if (history->get_tag("maxchars") == "0")
+ history_limit.stanzas = 0;
}
- const auto since = history->get_tag("since");
- if (!since.empty())
- history_limit.since = since;
- const auto maxstanzas = history->get_tag("maxstanzas");
- if (!maxstanzas.empty())
- history_limit.stanzas = std::atoi(maxstanzas.data());
- // Ignore any other value, because this is too complex to implement,
- // so I won’t do it.
- if (history->get_tag("maxchars") == "0")
- history_limit.stanzas = 0;
+ bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
+ from.resource, history_limit);
}
- bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
- from.resource, history_limit, x != nullptr);
- const IrcClient* irc = bridge->find_irc_client(iid.get_server());
- if (irc)
+ else
{
- const auto chan = irc->find_channel(iid.get_local());
- if (chan->joined)
- bridge->send_irc_nick_change(iid, to.resource, from.resource);
+ if (irc)
+ {
+ const auto chan = irc->find_channel(iid.get_local());
+ if (chan && chan->joined)
+ bridge->send_irc_nick_change(iid, to.resource, from.resource);
+ else
+ { // send an error if we are not joined yet, instead of treating it as a join
+ this->send_stanza_error("presence", from_str, to_str, id, "modify", "not-acceptable", "You are not joined to this MUC.");
+ }
+ }
}
}
else if (type == "unavailable")
@@ -253,7 +264,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
{
if (type != "unavailable")
this->send_stanza_error("presence", from_str, to_str, id,
- "cancel", "remote-server-not-found",
+ "cancel", "recipient-unavailable",
"Not connected to IRC server " + ex.hostname,
true);
}
@@ -291,7 +302,26 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
if (body && !body->get_inner().empty())
{
if (bridge->is_resource_in_chan(iid.to_tuple(), from.resource))
- bridge->send_channel_message(iid, body->get_inner(), id);
+ {
+ // Extract some XML nodes that we must include in the
+ // reflection (if any), because XMPP says so
+ std::vector<XmlNode> nodes_to_reflect;
+ const XmlNode* origin_id = stanza.get_child("origin-id", STABLE_ID_NS);
+ if (origin_id)
+ nodes_to_reflect.push_back(*origin_id);
+ const auto own_address = std::to_string(iid) + '@' + this->served_hostname;
+ for (const XmlNode* stanza_id: stanza.get_children("stanza-id", STABLE_ID_NS))
+ {
+ // Stanza ID generating entities, which encounter a
+ // <stanza-id/> element where the 'by' attribute matches
+ // the 'by' attribute they would otherwise set, MUST
+ // delete that element even if they are not adding their
+ // own stanza ID.
+ if (stanza_id->get_tag("by") != own_address)
+ nodes_to_reflect.push_back(*stanza_id);
+ }
+ bridge->send_channel_message(iid, body->get_inner(), id, std::move(nodes_to_reflect));
+ }
else
{
error_type = "modify";
@@ -330,7 +360,6 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
if (iid.type == Iid::Type::User && !iid.get_local().empty())
{
bridge->send_private_message(iid, body->get_inner());
- bridge->remove_preferred_from_jid(iid.get_local());
}
else if (iid.type != Iid::Type::User && !to.resource.empty())
{ // a message for chan%server@biboumi/Nick or
@@ -338,7 +367,6 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
// Convert that into a message to nick!server
Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
bridge->send_private_message(user_iid, body->get_inner());
- bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
}
else if (iid.type == Iid::Type::Server)
bridge->send_raw_message(iid.get_server(), body->get_inner());
@@ -368,7 +396,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
} catch (const IRCNotConnected& ex)
{
this->send_stanza_error("message", from_str, to_str, id,
- "cancel", "remote-server-not-found",
+ "cancel", "recipient-unavailable",
"Not connected to IRC server " + ex.hostname,
true);
}
@@ -689,7 +717,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
catch (const IRCNotConnected& ex)
{
this->send_stanza_error("iq", from, to_str, id,
- "cancel", "remote-server-not-found",
+ "cancel", "recipient-unavailable",
"Not connected to IRC server " + ex.hostname,
true);
stanza_error.disable();
@@ -997,7 +1025,7 @@ void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const
identity["category"] = "conference";
identity["type"] = "irc";
identity["name"] = ""s + iid.get_local() + " on " + iid.get_server();
- for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS, SELF_PING_FLAG, "muc_nonanonymous"})
+ for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS, SELF_PING_FLAG, "muc_nonanonymous", STABLE_ID_NS})
{
XmlSubNode feature(query, "feature");
feature["var"] = ns;
diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp
index f82f9ce..62a98ce 100644
--- a/src/xmpp/xmpp_component.cpp
+++ b/src/xmpp/xmpp_component.cpp
@@ -175,7 +175,7 @@ void XmppComponent::on_stanza(const Stanza& stanza)
void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
- Stanza node("stream:error");
+ Stanza node("stream", "error");
{
XmlSubNode error(node, name);
error["xmlns"] = STREAM_NS;
@@ -367,7 +367,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, std::string uuid, std::string id)
+Stanza XmppComponent::make_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to, std::string uuid, std::string id)
{
Stanza message("message");
message["to"] = jid_to;
@@ -399,7 +399,7 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str
stanza_id["id"] = std::move(uuid);
}
- this->send_stanza(message);
+ return message;
}
#ifdef USE_DATABASE
@@ -481,6 +481,8 @@ void XmppComponent::send_nick_change(const std::string& muc_name,
x["xmlns"] = MUC_USER_NS;
XmlSubNode item(x, "item");
item["nick"] = new_nick;
+ item["affiliation"] = affiliation;
+ item["role"] = role;
XmlSubNode status(x, "status");
status["code"] = "303";
if (self)
diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp
index 156e286..ee6b776 100644
--- a/src/xmpp/xmpp_component.hpp
+++ b/src/xmpp/xmpp_component.hpp
@@ -135,8 +135,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,
- std::string uuid, std::string id);
+ Stanza make_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to,
+ std::string uuid, std::string id);
#ifdef USE_DATABASE
/**
* Send a message, with a <delay/> element, part of a MUC history
diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp
index 781fe4c..1f25fa6 100644
--- a/src/xmpp/xmpp_parser.cpp
+++ b/src/xmpp/xmpp_parser.cpp
@@ -38,7 +38,7 @@ XmppParser::XmppParser():
void XmppParser::init_xml_parser()
{
// Create the expat parser
- this->parser = XML_ParserCreateNS("UTF-8", ':');
+ this->parser = XML_ParserCreateNS("UTF-8", '\1');
XML_SetUserData(this->parser, static_cast<void*>(this));
// Install Expat handlers
diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp
index ec42f9a..1e5e4e5 100644
--- a/src/xmpp/xmpp_parser.hpp
+++ b/src/xmpp/xmpp_parser.hpp
@@ -18,9 +18,9 @@
* stanza is reasonnably short.
*
* The element names generated by expat contain the namespace of the
- * element, a colon (':') and then the actual name of the element. To get
+ * element, a \1 separator and then the actual name of the element. To get
* an element "x" with a namespace of "http://jabber.org/protocol/muc", you
- * just look for an XmlNode named "http://jabber.org/protocol/muc:x"
+ * just look for an XmlNode named "http://jabber.org/protocol/muc\1x"
*
* TODO: enforce the size-limit for the stanza (limit the number of childs
* it can contain). For example forbid the parser going further than level
diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp
index 435f333..0103dd7 100644
--- a/src/xmpp/xmpp_stanza.cpp
+++ b/src/xmpp/xmpp_stanza.cpp
@@ -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('\1');
if (n == std::string::npos)
this->name = name;
else
@@ -67,6 +67,18 @@ XmlNode::XmlNode(const std::string& name):
{
}
+XmlNode::XmlNode(const std::string& xmlns, const std::string& name, XmlNode* parent):
+ name(name),
+ parent(parent)
+{
+ this->attributes["xmlns"] = xmlns;
+}
+
+XmlNode::XmlNode(const std::string& xmlns, const std::string& name):
+ XmlNode(xmlns, name, nullptr)
+{
+}
+
void XmlNode::delete_all_children()
{
this->children.clear();
diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp
index f4b3948..a706337 100644
--- a/src/xmpp/xmpp_stanza.hpp
+++ b/src/xmpp/xmpp_stanza.hpp
@@ -25,6 +25,8 @@ class XmlNode
public:
explicit XmlNode(const std::string& name, XmlNode* parent);
explicit XmlNode(const std::string& name);
+ explicit XmlNode(const std::string& xmlns, const std::string& name, XmlNode* parent);
+ explicit XmlNode(const std::string& xmlns, const std::string& name);
/**
* The copy constructor does not copy the parent attribute. The children
* nodes are all copied recursively.
@@ -150,6 +152,10 @@ public:
XmlNode(name),
parent_to_add(parent_ref)
{}
+ XmlSubNode(XmlNode& parent_ref, const std::string& xmlns, const std::string& name):
+ XmlNode(xmlns, name),
+ parent_to_add(parent_ref)
+ {}
~XmlSubNode()
{
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py
index e85a4f2..35edf3e 100644
--- a/tests/end_to_end/__main__.py
+++ b/tests/end_to_end/__main__.py
@@ -3,6 +3,7 @@
from functions import StanzaError, SkipStepError
import collections
+import subprocess
import importlib
import sequences
import datetime
@@ -83,6 +84,7 @@ class XMPPComponent(slixmpp.BaseXMPP):
self.scenario = scenario
self.biboumi = biboumi
+ self.timeout_handler = None
# A callable, taking a stanza as argument and raising a StanzaError
# exception if the test should fail.
self.stanza_checker = None
@@ -96,6 +98,13 @@ class XMPPComponent(slixmpp.BaseXMPP):
self.scenario.steps = []
self.failed = True
+ def on_timeout(self, xpaths):
+ error_msg = "Timeout while waiting for a stanza that would match the expected xpath(s):"
+ for xpath in xpaths:
+ error_msg += "\n" + str(xpath)
+ self.error(error_msg)
+ self.run_scenario()
+
def on_end_session(self, _):
self.loop.stop()
@@ -113,6 +122,9 @@ class XMPPComponent(slixmpp.BaseXMPP):
self.run_scenario()
def run_scenario(self):
+ if self.timeout_handler is not None:
+ self.timeout_handler.cancel()
+ self.timeout_handler = None
if self.scenario.steps:
step = self.scenario.steps.pop(0)
try:
@@ -124,10 +136,10 @@ class XMPPComponent(slixmpp.BaseXMPP):
if self.biboumi:
self.biboumi.stop()
- @asyncio.coroutine
- def accept_routine(self):
- self.accepting_server = yield from self.loop.create_server(lambda: self,
- "127.0.0.1", 8811, reuse_address=True)
+
+ async def accept_routine(self):
+ self.accepting_server = await self.loop.create_server(lambda: self,
+ "127.0.0.1", 8811, reuse_address=True)
class ProcessRunner:
@@ -136,13 +148,11 @@ class ProcessRunner:
self.signal_sent = False
self.create = None
- @asyncio.coroutine
- def start(self):
- self.process = yield from self.create
+ async def start(self):
+ self.process = await self.create
- @asyncio.coroutine
- def wait(self):
- code = yield from self.process.wait()
+ async def wait(self):
+ code = await self.process.wait()
return code
def stop(self):
@@ -174,7 +184,13 @@ class BiboumiRunner(ProcessRunner):
class IrcServerRunner(ProcessRunner):
def __init__(self):
super().__init__()
- self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", os.getcwd() + "/../tests/end_to_end/ircd.conf",
+ # Always start with a fresh state
+ try:
+ os.remove("ircd.db")
+ except FileNotFoundError:
+ pass
+ subprocess.run(["oragono", "mkcerts", "--conf", os.getcwd() + "/../tests/end_to_end/ircd.yaml"])
+ self.create = asyncio.create_subprocess_exec("oragono", "run", "--conf", os.getcwd() + "/../tests/end_to_end/ircd.yaml",
stderr=asyncio.subprocess.PIPE)
class BiboumiTest:
@@ -190,7 +206,8 @@ class BiboumiTest:
def run(self):
with_valgrind = os.environ.get("E2E_WITH_VALGRIND") is not None
- print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else ''))
+ print("Running scenario: %s%s… " % (self.scenario.name, " (with valgrind)" if with_valgrind else ''), end='')
+ sys.stdout.flush()
# Redirect the slixmpp logging into a specific file
output_filename = "slixmpp_%s_output.txt" % (self.scenario.name,)
with open(output_filename, "w"):
@@ -315,7 +332,7 @@ if __name__ == '__main__':
if not res:
print("IRC server failed to start, see irc_output.txt for more details. Exiting…")
sys.exit(1)
- if b"now running in foreground mode" in res:
+ if b"Server running" in res:
break
print("irc server started.")
diff --git a/tests/end_to_end/functions.py b/tests/end_to_end/functions.py
index 176dca5..3a21fcf 100644
--- a/tests/end_to_end/functions.py
+++ b/tests/end_to_end/functions.py
@@ -18,10 +18,10 @@ common_replacements = {
'jid_one': 'first@example.com',
'jid_two': 'second@example.com',
'jid_admin': 'admin@example.com',
- 'nick_two': 'Bobby',
- 'nick_three': 'Bernard',
+ 'nick_two': 'Nick2',
+ 'nick_three': 'Nick3',
'lower_nick_one': 'nick',
- 'lower_nick_two': 'bobby',
+ 'lower_nick_two': 'nick2',
}
class SkipStepError(Exception):
@@ -127,7 +127,9 @@ def expect_stanza(*args, optional=False, after=None):
replacements = common_replacements
replacements.update(xmpp.saved_values)
check_func = check_xpath if not optional else check_xpath_optional
- xmpp.stanza_checker = partial(check_func, [xpath.format_map(replacements) for xpath in xpaths], xmpp, after)
+ formatted_xpaths = [xpath.format_map(replacements) for xpath in xpaths]
+ xmpp.stanza_checker = partial(check_func, formatted_xpaths, xmpp, after)
+ xmpp.timeout_handler = asyncio.get_event_loop().call_later(10, partial(xmpp.on_timeout, formatted_xpaths))
return partial(f, *args, optional=optional, after=after)
def send_stanza(stanza):
@@ -148,6 +150,7 @@ def expect_unordered(*args):
formatted_xpaths.append(formatted_xpath)
formatted_list_of_xpaths.append(tuple(formatted_xpaths))
expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi)
+ xmpp.timeout_handler = asyncio.get_event_loop().call_later(10, partial(xmpp.on_timeout, formatted_list_of_xpaths))
return partial(f, *args)
def expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi):
diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf
deleted file mode 100644
index ccfbd90..0000000
--- a/tests/end_to_end/ircd.conf
+++ /dev/null
@@ -1,511 +0,0 @@
-/* doc/ircd.conf.example - brief example configuration file
- *
- * Copyright (C) 2000-2002 Hybrid Development Team
- * Copyright (C) 2002-2005 ircd-ratbox development team
- * Copyright (C) 2005-2006 charybdis development team
- *
- * See reference.conf for more information.
- */
-
-/* Extensions */
-#loadmodule "extensions/chm_operonly_compat";
-#loadmodule "extensions/chm_quietunreg_compat";
-#loadmodule "extensions/chm_sslonly_compat";
-#loadmodule "extensions/chm_operpeace";
-#loadmodule "extensions/createauthonly";
-#loadmodule "extensions/extb_account";
-#loadmodule "extensions/extb_canjoin";
-#loadmodule "extensions/extb_channel";
-#loadmodule "extensions/extb_combi";
-#loadmodule "extensions/extb_extgecos";
-#loadmodule "extensions/extb_hostmask";
-#loadmodule "extensions/extb_oper";
-#loadmodule "extensions/extb_realname";
-#loadmodule "extensions/extb_server";
-#loadmodule "extensions/extb_ssl";
-#loadmodule "extensions/extb_usermode";
-#loadmodule "extensions/hurt";
-#loadmodule "extensions/m_extendchans";
-#loadmodule "extensions/m_findforwards";
-#loadmodule "extensions/m_identify";
-#loadmodule "extensions/m_locops";
-#loadmodule "extensions/no_oper_invis";
-#loadmodule "extensions/sno_farconnect";
-#loadmodule "extensions/sno_globalkline";
-#loadmodule "extensions/sno_globalnickchange";
-#loadmodule "extensions/sno_globaloper";
-#loadmodule "extensions/sno_whois";
-#loadmodule "extensions/override";
-#loadmodule "extensions/no_kill_services";
-
-/*
- * IP cloaking extensions: use ip_cloaking_4.0
- * if you're linking 3.2 and later, otherwise use
- * ip_cloaking, for compatibility with older 3.x
- * releases.
- */
-
-#loadmodule "extensions/ip_cloaking_4.0";
-#loadmodule "extensions/ip_cloaking";
-
-serverinfo {
- name = "irc.localhost";
- sid = "42X";
- description = "charybdis test server";
- network_name = "StaticBox";
-
- /* On multi-homed hosts you may need the following. These define
- * the addresses we connect from to other servers. */
- /* for IPv4 */
- #vhost = "192.0.2.6";
- /* for IPv6 */
- #vhost6 = "2001:db8:2::6";
-
- /* ssl_private_key: our ssl private key */
- ssl_private_key = "etc/ssl.key";
-
- /* ssl_cert: certificate for our ssl server */
- ssl_cert = "etc/ssl.pem";
-
- /* ssl_dh_params: DH parameters, generate with openssl dhparam -out dh.pem 2048
- * In general, the DH parameters size should be the same as your key's size.
- * However it has been reported that some clients have broken TLS implementations which may
- * choke on keysizes larger than 2048-bit, so we would recommend using 2048-bit DH parameters
- * for now if your keys are larger than 2048-bit.
- */
- ssl_dh_params = "etc/dh.pem";
-
- /* ssld_count: number of ssld processes you want to start, if you
- * have a really busy server, using N-1 where N is the number of
- * cpu/cpu cores you have might be useful. A number greater than one
- * can also be useful in case of bugs in ssld and because ssld needs
- * two file descriptors per SSL connection.
- */
- ssld_count = 1;
-
- /* default max clients: the default maximum number of clients
- * allowed to connect. This can be changed once ircd has started by
- * issuing:
- * /quote set maxclients <limit>
- */
- default_max_clients = 1024;
-
- /* nicklen: enforced nickname length (for this server only; must not
- * be longer than the maximum length set while building).
- */
- nicklen = 30;
-};
-
-admin {
- name = "Lazy admin (lazya)";
- description = "StaticBox client server";
- email = "nobody@127.0.0.1";
-};
-
-log {
- fname_userlog = "logs/userlog";
- #fname_fuserlog = "logs/fuserlog";
- fname_operlog = "logs/operlog";
- #fname_foperlog = "logs/foperlog";
- fname_serverlog = "logs/serverlog";
- #fname_klinelog = "logs/klinelog";
- fname_killlog = "logs/killlog";
- fname_operspylog = "logs/operspylog";
- #fname_ioerrorlog = "logs/ioerror";
-};
-
-/* class {} blocks MUST be specified before anything that uses them. That
- * means they must be defined before auth {} and before connect {}.
- */
-class "users" {
- ping_time = 2 minutes;
- number_per_ident = 10;
- number_per_ip = 10;
- number_per_ip_global = 50;
- cidr_ipv4_bitlen = 24;
- cidr_ipv6_bitlen = 64;
- number_per_cidr = 200;
- max_number = 3000;
- sendq = 400 kbytes;
-};
-
-class "opers" {
- ping_time = 5 minutes;
- number_per_ip = 10;
- max_number = 1000;
- sendq = 1 megabyte;
-};
-
-class "server" {
- ping_time = 5 minutes;
- connectfreq = 5 minutes;
- max_number = 1;
- sendq = 4 megabytes;
-};
-
-listen {
- /* defer_accept: wait for clients to send IRC handshake data before
- * accepting them. if you intend to use software which depends on the
- * server replying first, such as BOPM, you should disable this feature.
- * otherwise, you probably want to leave it on.
- */
- defer_accept = yes;
-
- /* If you want to listen on a specific IP only, specify host.
- * host definitions apply only to the following port line.
- */
- #host = "192.0.2.6";
- port = 5000, 6665 .. 6669;
- sslport = 7778;
-
- /* Listen on IPv6 (if you used host= above). */
- #host = "2001:db8:2::6";
- #port = 5000, 6665 .. 6669;
- #sslport = 9999;
-};
-
-/* auth {}: allow users to connect to the ircd (OLD I:)
- * auth {} blocks MUST be specified in order of precedence. The first one
- * that matches a user will be used. So place spoofs first, then specials,
- * then general access, then restricted.
- */
-auth {
- /* user: the user@host allowed to connect. Multiple IPv4/IPv6 user
- * lines are permitted per auth block. This is matched against the
- * hostname and IP address (using :: shortening for IPv6 and
- * prepending a 0 if it starts with a colon) and can also use CIDR
- * masks.
- */
- user = "*@198.51.100.0/24";
- user = "*test@2001:db8:1:*";
-
- /* password: an optional password that is required to use this block.
- * By default this is not encrypted, specify the flag "encrypted" in
- * flags = ...; below if it is.
- */
- password = "letmein";
-
- /* spoof: fake the users user@host to be be this. You may either
- * specify a host or a user@host to spoof to. This is free-form,
- * just do everyone a favour and dont abuse it. (OLD I: = flag)
- */
- spoof = "I.still.hate.packets";
-
- /* Possible flags in auth:
- *
- * encrypted | password is encrypted with mkpasswd
- * spoof_notice | give a notice when spoofing hosts
- * exceed_limit (old > flag) | allow user to exceed class user limits
- * kline_exempt (old ^ flag) | exempt this user from k/g/xlines,
- * | dnsbls, and proxies
- * proxy_exempt | exempt this user from proxies
- * dnsbl_exempt | exempt this user from dnsbls
- * spambot_exempt | exempt this user from spambot checks
- * shide_exempt | exempt this user from serverhiding
- * jupe_exempt | exempt this user from generating
- * warnings joining juped channels
- * resv_exempt | exempt this user from resvs
- * flood_exempt | exempt this user from flood limits
- * USE WITH CAUTION.
- * no_tilde (old - flag) | don't prefix ~ to username if no ident
- * need_ident (old + flag) | require ident for user in this class
- * need_ssl | require SSL/TLS for user in this class
- * need_sasl | require SASL id for user in this class
- */
- flags = kline_exempt, exceed_limit;
-
- /* class: the class the user is placed in */
- class = "opers";
-};
-
-auth {
- user = "*@*";
- class = "users";
- flags = flood_exempt;
-};
-
-/* privset {} blocks MUST be specified before anything that uses them. That
- * means they must be defined before operator {}.
- */
-privset "local_op" {
- privs = oper:local_kill, oper:operwall;
-};
-
-privset "server_bot" {
- extends = "local_op";
- privs = oper:kline, oper:remoteban, snomask:nick_changes;
-};
-
-privset "global_op" {
- extends = "local_op";
- privs = oper:global_kill, oper:routing, oper:kline, oper:unkline, oper:xline,
- oper:resv, oper:mass_notice, oper:remoteban;
-};
-
-privset "admin" {
- extends = "global_op";
- privs = oper:admin, oper:die, oper:rehash, oper:spy, oper:grant;
-};
-
-operator "god" {
- /* name: the name of the oper must go above */
-
- /* user: the user@host required for this operator. CIDR *is*
- * supported now. auth{} spoofs work here, other spoofs do not.
- * multiple user="" lines are supported.
- */
- user = "*god@127.0.0.1";
-
- /* password: the password required to oper. Unless ~encrypted is
- * contained in flags = ...; this will need to be encrypted using
- * mkpasswd, MD5 is supported
- */
- password = "etcnjl8juSU1E";
-
- /* rsa key: the public key for this oper when using Challenge.
- * A password should not be defined when this is used, see
- * doc/challenge.txt for more information.
- */
- #rsa_public_key_file = "/usr/local/ircd/etc/oper.pub";
-
- /* umodes: the specific umodes this oper gets when they oper.
- * If this is specified an oper will not be given oper_umodes
- * These are described above oper_only_umodes in general {};
- */
- #umodes = locops, servnotice, operwall, wallop;
-
- /* fingerprint: if specified, the oper's client certificate
- * fingerprint will be checked against the specified fingerprint
- * below.
- */
- #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b";
-
- /* snomask: specific server notice mask on oper up.
- * If this is specified an oper will not be given oper_snomask.
- */
- snomask = "+Zbfkrsuy";
-
- /* flags: misc options for the operator. You may prefix an option
- * with ~ to disable it, e.g. ~encrypted.
- *
- * Default flags are encrypted.
- *
- * Available options:
- *
- * encrypted: the password above is encrypted [DEFAULT]
- * need_ssl: must be using SSL/TLS to oper up
- */
- flags = encrypted;
-
- /* privset: privileges set to grant */
- privset = "admin";
-};
-
-connect "irc.uplink.com" {
- host = "203.0.113.3";
- send_password = "password";
- accept_password = "anotherpassword";
- port = 6666;
- hub_mask = "*";
- class = "server";
- flags = compressed, topicburst;
-
- #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b";
-
- /* If the connection is IPv6, uncomment below.
- * Use 0::1, not ::1, for IPv6 localhost. */
- #aftype = ipv6;
-};
-
-connect "ssl.uplink.com" {
- host = "203.0.113.129";
- send_password = "password";
- accept_password = "anotherpassword";
- port = 9999;
- hub_mask = "*";
- class = "server";
- flags = ssl, topicburst;
-};
-
-service {
- name = "services.int";
-};
-
-cluster {
- name = "*";
- flags = kline, tkline, unkline, xline, txline, unxline, resv, tresv, unresv;
-};
-
-shared {
- oper = "*@*", "*";
- flags = all, rehash;
-};
-
-/* exempt {}: IPs that are exempt from Dlines and rejectcache. (OLD d:) */
-exempt {
- ip = "127.0.0.1";
-};
-
-channel {
- use_invex = yes;
- use_except = yes;
- use_forward = yes;
- use_knock = yes;
- knock_delay = 5 minutes;
- knock_delay_channel = 1 minute;
- max_chans_per_user = 140;
- max_chans_per_user_large = 200;
- max_bans = 100;
- max_bans_large = 500;
- default_split_user_count = 0;
- default_split_server_count = 0;
- no_create_on_split = no;
- no_join_on_split = no;
- burst_topicwho = yes;
- kick_on_split_riding = no;
- only_ascii_channels = no;
- resv_forcepart = yes;
- channel_target_change = yes;
- disable_local_channels = no;
- autochanmodes = "+nt";
- displayed_usercount = 3;
- strip_topic_colors = no;
-};
-
-serverhide {
- flatten_links = yes;
- links_delay = 5 minutes;
- hidden = no;
- disable_hidden = no;
-};
-
-alias "NickServ" {
- target = "NickServ";
-};
-
-alias "ChanServ" {
- target = "ChanServ";
-};
-
-alias "OperServ" {
- target = "OperServ";
-};
-
-alias "MemoServ" {
- target = "MemoServ";
-};
-
-alias "NS" {
- target = "NickServ";
-};
-
-alias "CS" {
- target = "ChanServ";
-};
-
-alias "OS" {
- target = "OperServ";
-};
-
-alias "MS" {
- target = "MemoServ";
-};
-
-general {
- hide_error_messages = opers;
- hide_spoof_ips = yes;
-
- /*
- * default_umodes: umodes to enable on connect.
- * If you have enabled the new ip_cloaking_4.0 module, and you want
- * to make use of it, add +x to this option, i.e.:
- * default_umodes = "+ix";
- *
- * If you have enabled the old ip_cloaking module, and you want
- * to make use of it, add +h to this option, i.e.:
- * default_umodes = "+ih";
- */
- default_umodes = "+i";
-
- default_operstring = "is an IRC Operator";
- default_adminstring = "is a Server Administrator";
- servicestring = "is a Network Service";
-
- /*
- * Nick of the network's SASL agent. Used to check whether services are here,
- * SASL credentials are only sent to its server. Needs to be a service.
- *
- * Defaults to SaslServ if unspecified.
- */
- sasl_service = "SaslServ";
- disable_fake_channels = no;
- tkline_expire_notices = no;
- default_floodcount = 10;
- failed_oper_notice = yes;
- dots_in_ident=2;
- min_nonwildcard = 4;
- min_nonwildcard_simple = 3;
- max_accept = 100;
- max_monitor = 100;
- anti_nick_flood = yes;
- max_nick_time = 20 seconds;
- max_nick_changes = 5;
- anti_spam_exit_message_time = 5 minutes;
- ts_warn_delta = 30 seconds;
- ts_max_delta = 5 minutes;
- client_exit = yes;
- collision_fnc = yes;
- resv_fnc = yes;
- global_snotices = yes;
- dline_with_reason = yes;
- kline_delay = 0 seconds;
- kline_with_reason = yes;
- kline_reason = "K-Lined";
- identify_service = "NickServ@services.int";
- identify_command = "IDENTIFY";
- non_redundant_klines = yes;
- warn_no_nline = yes;
- use_propagated_bans = yes;
- stats_e_disabled = yes;
- stats_c_oper_only=no;
- stats_h_oper_only=no;
- stats_y_oper_only=no;
- stats_o_oper_only=yes;
- stats_P_oper_only=no;
- stats_i_oper_only=masked;
- stats_k_oper_only=masked;
- map_oper_only = no;
- operspy_admin_only = no;
- operspy_dont_care_user_info = no;
- caller_id_wait = 1 minute;
- pace_wait_simple = 0 second;
- pace_wait = 0 seconds;
- short_motd = no;
- ping_cookie = no;
- connect_timeout = 30 seconds;
- default_ident_timeout = 5;
- disable_auth = no;
- no_oper_flood = yes;
- max_targets = 4;
- client_flood_max_lines = 20;
- use_whois_actually = no;
- oper_only_umodes = operwall, locops, servnotice;
- oper_umodes = locops, servnotice, operwall, wallop;
- oper_snomask = "+s";
- burst_away = yes;
- nick_delay = 0 seconds; # 15 minutes if you want to enable this
- reject_ban_time = 1 minute;
- reject_after_count = 3;
- reject_duration = 5 minutes;
- throttle_duration = 60;
- throttle_count = 8888;
- max_ratelimit_tokens = 30;
- away_interval = 30;
- certfp_method = sha1;
- hide_opers_in_whois = no;
-};
-
-modules {
- path = "modules";
- path = "modules/autoload";
-};
diff --git a/tests/end_to_end/ircd.yaml b/tests/end_to_end/ircd.yaml
new file mode 100644
index 0000000..057674c
--- /dev/null
+++ b/tests/end_to_end/ircd.yaml
@@ -0,0 +1,751 @@
+# oragono IRCd config
+
+# network configuration
+network:
+ # name of the network
+ name: BiboumiTest
+
+# server configuration
+server:
+ # server name
+ name: irc.localhost
+
+ # addresses to listen on
+ listeners:
+ # The standard plaintext port for IRC is 6667. Allowing plaintext over the
+ # public Internet poses serious security and privacy issues. Accordingly,
+ # we recommend using plaintext only on local (loopback) interfaces:
+ # "127.0.0.1:6667": # (loopback ipv4, localhost-only)
+ # "[::1]:6667": # (loopback ipv6, localhost-only)
+ # If you need to serve plaintext on public interfaces, comment out the above
+ # two lines and uncomment the line below (which listens on all interfaces):
+ ":6667":
+ # Alternately, if you have a TLS certificate issued by a recognized CA,
+ # you can configure port 6667 as an STS-only listener that only serves
+ # "redirects" to the TLS port, but doesn't allow chat. See the manual
+ # for details.
+
+ # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
+ ":7778":
+ tls:
+ key: tls.key
+ cert: tls.crt
+ # 'proxy' should typically be false. It's only for Kubernetes-style load
+ # balancing that does not terminate TLS, but sends an initial PROXY line
+ # in plaintext.
+ proxy: false
+
+ # Example of a Unix domain socket for proxying:
+ # "/tmp/oragono_sock":
+
+ # Example of a Tor listener: any connection that comes in on this listener will
+ # be considered a Tor connection. It is strongly recommended that this listener
+ # *not* be on a public interface --- it should be on 127.0.0.0/8 or unix domain:
+ # "/hidden_service_sockets/oragono_tor_sock":
+ # tor: true
+
+ # sets the permissions for Unix listen sockets. on a typical Linux system,
+ # the default is 0775 or 0755, which prevents other users/groups from connecting
+ # to the socket. With 0777, it behaves like a normal TCP socket
+ # where anyone can connect.
+ unix-bind-mode: 0777
+
+ # configure the behavior of Tor listeners (ignored if you didn't enable any):
+ tor-listeners:
+ # if this is true, connections from Tor must authenticate with SASL
+ require-sasl: false
+
+ # what hostname should be displayed for Tor connections?
+ vhost: "tor-network.onion"
+
+ # allow at most this many connections at once (0 for no limit):
+ max-connections: 64
+
+ # connection throttling (limit how many connection attempts are allowed at once):
+ throttle-duration: 10m
+ # set to 0 to disable throttling:
+ max-connections-per-duration: 64
+
+ # strict transport security, to get clients to automagically use TLS
+ sts:
+ # whether to advertise STS
+ #
+ # to stop advertising STS, leave this enabled and set 'duration' below to "0". this will
+ # advertise to connecting users that the STS policy they have saved is no longer valid
+ enabled: false
+
+ # how long clients should be forced to use TLS for.
+ # setting this to a too-long time will mean bad things if you later remove your TLS.
+ # the default duration below is 1 month, 2 days and 5 minutes.
+ duration: 1mo2d5m
+
+ # tls port - you should be listening on this port above
+ port: 6697
+
+ # should clients include this STS policy when they ship their inbuilt preload lists?
+ preload: false
+
+ # casemapping controls what kinds of strings are permitted as identifiers (nicknames,
+ # channel names, account names, etc.), and how they are normalized for case.
+ # with the recommended default of 'precis', utf-8 identifiers that are "sane"
+ # (according to RFC 8265) are allowed, and the server additionally tries to protect
+ # against confusable characters ("homoglyph attacks").
+ # the other options are 'ascii' (traditional ASCII-only identifiers), and 'permissive',
+ # which allows identifiers to contain unusual characters like emoji, but makes users
+ # vulnerable to homoglyph attacks. unless you're really confident in your decision,
+ # we recommend leaving this value at its default (changing it once the network is
+ # already up and running is problematic).
+ casemapping: "precis"
+
+ # whether to look up user hostnames with reverse DNS
+ # (to suppress this for privacy purposes, use the ip-cloaking options below)
+ lookup-hostnames: true
+ # whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for
+ # any hostname returned from reverse DNS, resolve it back to an IP address and reject it
+ # unless it matches the connecting IP
+ forward-confirm-hostnames: true
+
+ # use ident protocol to get usernames
+ check-ident: false
+
+ # password to login to the server
+ # generated using "oragono genpasswd"
+ #password: ""
+
+ # motd filename
+ # if you change the motd, you should move it to ircd.motd
+ motd:
+
+ # motd formatting codes
+ # if this is true, the motd is escaped using formatting codes like $c, $b, and $i
+ motd-formatting: true
+
+ # addresses/CIDRs the PROXY command can be used from
+ # this should be restricted to 127.0.0.1/8 and ::1/128 (unless you have a good reason)
+ # you should also add these addresses to the connection limits and throttling exemption lists
+ proxy-allowed-from:
+ # - localhost
+ # - "192.168.1.1"
+ # - "192.168.10.1/24"
+
+ # controls the use of the WEBIRC command (by IRC<->web interfaces, bouncers and similar)
+ webirc:
+ # one webirc block -- should correspond to one set of gateways
+ -
+ # SHA-256 fingerprint of the TLS certificate the gateway must use to connect
+ # (comment this out to use passwords only)
+ fingerprint: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
+
+ # password the gateway uses to connect, made with oragono genpasswd
+ password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee"
+
+ # addresses/CIDRs that can use this webirc command
+ # you should also add these addresses to the connection limits and throttling exemption lists
+ hosts:
+ # - localhost
+ # - "192.168.1.1"
+ # - "192.168.10.1/24"
+
+ # allow use of the RESUME extension over plaintext connections:
+ # do not enable this unless the ircd is only accessible over internal networks
+ allow-plaintext-resume: false
+
+ # maximum length of clients' sendQ in bytes
+ # this should be big enough to hold bursts of channel/direct messages
+ max-sendq: 96k
+
+ # compatibility with legacy clients
+ compatibility:
+ # many clients require that the final parameter of certain messages be an
+ # RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is
+ # actually required. this forces Oragono to send those parameters
+ # as trailings. this is recommended unless you're testing clients for conformance;
+ # defaults to true when unset for that reason.
+ force-trailing: true
+
+ # some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower) do not
+ # respond correctly to SASL messages with the server name as a prefix:
+ # https://github.com/znc/znc/issues/1212
+ # this works around that bug, allowing them to use SASL.
+ send-unprefixed-sasl: true
+
+ # IP-based DoS protection
+ ip-limits:
+ # whether to limit the total number of concurrent connections per IP/CIDR
+ count: false
+ # maximum concurrent connections per IP/CIDR
+ max-concurrent-connections: 16
+
+ # whether to restrict the rate of new connections per IP/CIDR
+ throttle: false
+ # how long to keep track of connections for
+ window: 10m
+ # maximum number of new connections per IP/CIDR within the given duration
+ max-connections-per-window: 32
+ # how long to ban offenders for. after banning them, the number of connections is
+ # reset, which lets you use /UNDLINE to unban people
+ throttle-ban-duration: 10m
+
+ # how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address)
+ cidr-len-ipv4: 32
+ # how wide the CIDR should be for IPv6 (a /64 is the typical prefix assigned
+ # by an ISP to an individual customer for their LAN)
+ cidr-len-ipv6: 64
+
+ # IPs/networks which are exempted from connection limits
+ exempted:
+ - "localhost"
+ # - "192.168.1.1"
+ # - "2001:0db8::/32"
+
+ # custom connection limits for certain IPs/networks. note that CIDR
+ # widths defined here override the default CIDR width --- the limit
+ # will apply to the entire CIDR no matter how large or small it is
+ custom-limits:
+ # "8.8.0.0/16":
+ # max-concurrent-connections: 128
+ # max-connections-per-window: 1024
+
+ # IP cloaking hides users' IP addresses from other users and from channel admins
+ # (but not from server admins), while still allowing channel admins to ban
+ # offending IP addresses or networks. In place of hostnames derived from reverse
+ # DNS, users see fake domain names like pwbs2ui4377257x8.oragono. These names are
+ # generated deterministically from the underlying IP address, but if the underlying
+ # IP is not already known, it is infeasible to recover it from the cloaked name.
+ ip-cloaking:
+ # whether to enable IP cloaking
+ enabled: false
+
+ # fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.oragono
+ netname: "oragono"
+
+ # secret key to prevent dictionary attacks against cloaked IPs
+ # any high-entropy secret is valid for this purpose:
+ # you MUST generate a new one for your installation.
+ # suggestion: use the output of `oragono mksecret`
+ # note that rotating this key will invalidate all existing ban masks.
+ secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4"
+
+ # name of an environment variable to pull the secret from, for use with
+ # k8s secret distribution:
+ # secret-environment-variable: "ORAGONO_CLOAKING_SECRET"
+
+ # the cloaked hostname is derived only from the CIDR (most significant bits
+ # of the IP address), up to a configurable number of bits. this is the
+ # granularity at which bans will take effect for IPv4. Note that changing
+ # this value will invalidate any stored bans.
+ cidr-len-ipv4: 32
+
+ # analogous granularity for IPv6
+ cidr-len-ipv6: 64
+
+ # number of bits of hash output to include in the cloaked hostname.
+ # more bits means less likelihood of distinct IPs colliding,
+ # at the cost of a longer cloaked hostname. if this value is set to 0,
+ # all users will receive simply `netname` as their cloaked hostname.
+ num-bits: 64
+
+ # secure-nets identifies IPs and CIDRs which are secure at layer 3,
+ # for example, because they are on a trusted internal LAN or a VPN.
+ # plaintext connections from these IPs and CIDRs will be considered
+ # secure (clients will receive the +Z mode and be allowed to resume
+ # or reattach to secure connections). note that loopback IPs are always
+ # considered secure:
+ secure-nets:
+ # - "10.0.0.0/8"
+
+
+# account options
+accounts:
+ # is account authentication enabled, i.e., can users log into existing accounts?
+ authentication-enabled: true
+
+ # account registration
+ registration:
+ # can users register new accounts for themselves? if this is false, operators with
+ # the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
+ enabled: true
+
+ # this is the bcrypt cost we'll use for account passwords
+ bcrypt-cost: 9
+
+ # length of time a user has to verify their account before it can be re-registered
+ verify-timeout: "32h"
+
+ # callbacks to allow
+ enabled-callbacks:
+ - none # no verification needed, will instantly register successfully
+
+ # example configuration for sending verification emails via a local mail relay
+ # callbacks:
+ # mailto:
+ # server: localhost
+ # port: 25
+ # tls:
+ # enabled: false
+ # username: ""
+ # password: ""
+ # sender: "admin@my.network"
+
+ # throttle account login attempts (to prevent either password guessing, or DoS
+ # attacks on the server aimed at forcing repeated expensive bcrypt computations)
+ login-throttling:
+ enabled: true
+
+ # window
+ duration: 1m
+
+ # number of attempts allowed within the window
+ max-attempts: 3
+
+ # some clients (notably Pidgin and Hexchat) offer only a single password field,
+ # which makes it impossible to specify a separate server password (for the PASS
+ # command) and SASL password. if this option is set to true, a client that
+ # successfully authenticates with SASL will not be required to send
+ # PASS as well, so it can be configured to authenticate with SASL only.
+ skip-server-password: false
+
+ # require-sasl controls whether clients are required to have accounts
+ # (and sign into them using SASL) to connect to the server
+ require-sasl:
+ # if this is enabled, all clients must authenticate with SASL while connecting
+ enabled: false
+
+ # IPs/CIDRs which are exempted from the account requirement
+ exempted:
+ - "localhost"
+ # - '10.10.0.0/16'
+
+ # nick-reservation controls how, and whether, nicknames are linked to accounts
+ nick-reservation:
+ # is there any enforcement of reserved nicknames?
+ enabled: true
+
+ # how many nicknames, in addition to the account name, can be reserved?
+ additional-nick-limit: 2
+
+ # method describes how nickname reservation is handled
+ # timeout: let the user change to the registered nickname, give them X seconds
+ # to login and then rename them if they haven't done so
+ # strict: don't let the user change to the registered nickname unless they're
+ # already logged-in using SASL or NickServ
+ # optional: no enforcement by default, but allow users to opt in to
+ # the enforcement level of their choice
+ #
+ # 'optional' matches the behavior of other NickServs, but 'strict' is
+ # preferable if all your users can enable SASL.
+ method: strict
+
+ # allow users to set their own nickname enforcement status, e.g.,
+ # to opt out of strict enforcement
+ allow-custom-enforcement: true
+
+ # rename-timeout - this is how long users have 'til they're renamed
+ rename-timeout: 30s
+
+ # rename-prefix - this is the prefix to use when renaming clients (e.g. Guest-AB54U31)
+ rename-prefix: Guest-
+
+ # multiclient controls whether oragono allows multiple connections to
+ # attach to the same client/nickname identity; this is part of the
+ # functionality traditionally provided by a bouncer like ZNC
+ multiclient:
+ # when disabled, each connection must use a separate nickname (as is the
+ # typical behavior of IRC servers). when enabled, a new connection that
+ # has authenticated with SASL can associate itself with an existing
+ # client
+ enabled: false
+
+ # if this is disabled, clients have to opt in to bouncer functionality
+ # using nickserv or the cap system. if it's enabled, they can opt out
+ # via nickserv
+ allowed-by-default: true
+
+ # whether to allow clients that remain on the server even
+ # when they have no active connections. The possible values are:
+ # "disabled", "opt-in", "opt-out", or "mandatory".
+ always-on: "disabled"
+
+ # vhosts controls the assignment of vhosts (strings displayed in place of the user's
+ # hostname/IP) by the HostServ service
+ vhosts:
+ # are vhosts enabled at all?
+ enabled: true
+
+ # maximum length of a vhost
+ max-length: 64
+
+ # regexp for testing the validity of a vhost
+ # (make sure any changes you make here are RFC-compliant)
+ valid-regexp: '^[0-9A-Za-z.\-_/]+$'
+
+ # options controlling users requesting vhosts:
+ user-requests:
+ # can users request vhosts at all? if this is false, operators with the
+ # 'vhosts' capability can still assign vhosts manually
+ enabled: false
+
+ # if uncommented, all new vhost requests will be dumped into the given
+ # channel, so opers can review them as they are sent in. ensure that you
+ # have registered and restricted the channel appropriately before you
+ # uncomment this.
+ #channel: "#vhosts"
+
+ # after a user's vhost has been approved or rejected, they need to wait
+ # this long (starting from the time of their original request)
+ # before they can request a new one.
+ cooldown: 168h
+
+ # vhosts that users can take without approval, using `/HS TAKE`
+ offer-list:
+ #- "oragono.test"
+
+ # support for deferring password checking to an external LDAP server
+ # you should probably ignore this section! consult the grafana docs for details:
+ # https://grafana.com/docs/grafana/latest/auth/ldap/
+ # you will probably want to set require-sasl and disable accounts.registration.enabled
+ # ldap:
+ # enabled: true
+ # # should we automatically create users if their LDAP login succeeds?
+ # autocreate: true
+ # # example configuration that works with Forum Systems's testing server:
+ # # https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
+ # host: "ldap.forumsys.com"
+ # port: 389
+ # timeout: 30s
+ # # example "single-bind" configuration, where we bind directly to the user's entry:
+ # bind-dn: "uid=%s,dc=example,dc=com"
+ # # example "admin bind" configuration, where we bind to an initial admin user,
+ # # then search for the user's entry with a search filter:
+ # #search-base-dns:
+ # # - "dc=example,dc=com"
+ # #bind-dn: "cn=read-only-admin,dc=example,dc=com"
+ # #bind-password: "password"
+ # #search-filter: "(uid=%s)"
+ # # example of requiring that users be in a particular group
+ # # (note that this is an OR over the listed groups, not an AND):
+ # #require-groups:
+ # # - "ou=mathematicians,dc=example,dc=com"
+ # #group-search-filter-user-attribute: "dn"
+ # #group-search-filter: "(uniqueMember=%s)"
+ # #group-search-base-dns:
+ # # - "dc=example,dc=com"
+ # # example of group membership testing via user attributes, as in AD
+ # # or with OpenLDAP's "memberOf overlay" (overrides group-search-filter):
+ # attributes:
+ # member-of: "memberOf"
+
+# channel options
+channels:
+ # modes that are set when new channels are created
+ # +n is no-external-messages and +t is op-only-topic
+ # see /QUOTE HELP cmodes for more channel modes
+ default-modes: +nt
+
+ # how many channels can a client be in at once?
+ max-channels-per-client: 1000
+
+ # if this is true, new channels can only be created by operators with the
+ # `chanreg` operator capability
+ operator-only-creation: false
+
+ # channel registration - requires an account
+ registration:
+ # can users register new channels?
+ enabled: true
+
+ # how many channels can each account register?
+ max-channels-per-account: 15
+
+# operator classes
+oper-classes:
+ # local operator
+ "local-oper":
+ # title shown in WHOIS
+ title: Local Operator
+
+ # capability names
+ capabilities:
+ - "oper:local_kill"
+ - "oper:local_ban"
+ - "oper:local_unban"
+ - "nofakelag"
+
+ # network operator
+ "network-oper":
+ # title shown in WHOIS
+ title: Network Operator
+
+ # oper class this extends from
+ extends: "local-oper"
+
+ # capability names
+ capabilities:
+ - "oper:remote_kill"
+ - "oper:remote_ban"
+ - "oper:remote_unban"
+
+ # server admin
+ "server-admin":
+ # title shown in WHOIS
+ title: Server Admin
+
+ # oper class this extends from
+ extends: "local-oper"
+
+ # capability names
+ capabilities:
+ - "oper:rehash"
+ - "oper:die"
+ - "accreg"
+ - "sajoin"
+ - "samode"
+ - "vhosts"
+ - "chanreg"
+
+# ircd operators
+opers:
+ # operator named 'dan'
+ dan:
+ # which capabilities this oper has access to
+ class: "server-admin"
+
+ # custom whois line
+ whois-line: is a cool dude
+
+ # custom hostname
+ vhost: "n"
+
+ # modes are the modes to auto-set upon opering-up
+ modes: +is acjknoqtuxv
+
+ # operators can be authenticated either by password (with the /OPER command),
+ # or by certificate fingerprint, or both. if a password hash is set, then a
+ # password is required to oper up (e.g., /OPER dan mypassword). to generate
+ # the hash, use `oragono genpasswd`.
+ password: "$2a$04$LiytCxaY0lI.guDj2pBN4eLRD5cdM2OLDwqmGAgB6M2OPirbF5Jcu"
+
+ # if a SHA-256 certificate fingerprint is configured here, then it will be
+ # required to /OPER. if you comment out the password hash above, then you can
+ # /OPER without a password.
+ #fingerprint: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
+ # if 'auto' is set (and no password hash is set), operator permissions will be
+ # granted automatically as soon as you connect with the right fingerprint.
+ #auto: true
+
+# logging, takes inspiration from Insp
+logging:
+ -
+ # how to log these messages
+ #
+ # file log to given target filename
+ # stdout log to stdout
+ # stderr log to stderr
+ # (you can specify multiple methods, e.g., to log to both stderr and a file)
+ method: stderr
+
+ # filename to log to, if file method is selected
+ # filename: ircd.log
+
+ # type(s) of logs to keep here. you can use - to exclude those types
+ #
+ # exclusions take precedent over inclusions, so if you exclude a type it will NEVER
+ # be logged, even if you explicitly include it
+ #
+ # useful types include:
+ # * everything (usually used with exclusing some types below)
+ # server server startup, rehash, and shutdown events
+ # accounts account registration and authentication
+ # channels channel creation and operations
+ # commands command calling and operations
+ # opers oper actions, authentication, etc
+ # services actions related to NickServ, ChanServ, etc.
+ # internal unexpected runtime behavior, including potential bugs
+ # userinput raw lines sent by users
+ # useroutput raw lines sent to users
+ type: "* -userinput -useroutput"
+
+ # one of: debug info warn error
+ level: info
+ #-
+ # # example of a file log that avoids logging IP addresses
+ # method: file
+ # filename: ircd.log
+ # type: "* -userinput -useroutput -localconnect -localconnect-ip"
+ # level: debug
+
+# debug options
+debug:
+ # when enabled, oragono will attempt to recover from certain kinds of
+ # client-triggered runtime errors that would normally crash the server.
+ # this makes the server more resilient to DoS, but could result in incorrect
+ # behavior. deployments that would prefer to "start from scratch", e.g., by
+ # letting the process crash and auto-restarting it with systemd, can set
+ # this to false.
+ recover-from-errors: true
+
+ # optionally expose a pprof http endpoint: https://golang.org/pkg/net/http/pprof/
+ # it is strongly recommended that you don't expose this on a public interface;
+ # if you need to access it remotely, you can use an SSH tunnel.
+ # set to `null`, "", leave blank, or omit to disable
+ # pprof-listener: "localhost:6060"
+
+# datastore configuration
+datastore:
+ # path to the datastore
+ path: ircd.db
+
+ # if the database schema requires an upgrade, `autoupgrade` will attempt to
+ # perform it automatically on startup. the database will be backed
+ # up, and if the upgrade fails, the original database will be restored.
+ autoupgrade: true
+
+ # connection information for MySQL (currently only used for persistent history):
+ mysql:
+ enabled: false
+ host: "localhost"
+ # port is unnecessary for connections via unix domain socket:
+ #port: 3306
+ user: "oragono"
+ password: "hunter2"
+ history-database: "oragono_history"
+ timeout: 3s
+
+# languages config
+languages:
+ # whether to load languages
+ enabled: false
+
+ # default language to use for new clients
+ # 'en' is the default English language in the code
+ default: en
+
+ # which directory contains our language files
+ path: languages
+
+# limits - these need to be the same across the network
+limits:
+ # nicklen is the max nick length allowed
+ nicklen: 32
+
+ # identlen is the max ident length allowed
+ identlen: 20
+
+ # channellen is the max channel length allowed
+ channellen: 64
+
+ # awaylen is the maximum length of an away message
+ awaylen: 500
+
+ # kicklen is the maximum length of a kick message
+ kicklen: 1000
+
+ # topiclen is the maximum length of a channel topic
+ topiclen: 1000
+
+ # maximum number of monitor entries a client can have
+ monitor-entries: 100
+
+ # whowas entries to store
+ whowas-entries: 100
+
+ # maximum length of channel lists (beI modes)
+ chan-list-modes: 1000
+
+ # maximum number of messages to accept during registration (prevents
+ # DoS / resource exhaustion attacks):
+ registration-messages: 1024
+
+ # message length limits for the new multiline cap
+ multiline:
+ max-bytes: 4096 # 0 means disabled
+ max-lines: 100 # 0 means no limit
+
+# fakelag: prevents clients from spamming commands too rapidly
+fakelag:
+ # whether to enforce fakelag
+ enabled: false
+
+ # time unit for counting command rates
+ window: 1s
+
+ # clients can send this many commands without fakelag being imposed
+ burst-limit: 5
+
+ # once clients have exceeded their burst allowance, they can send only
+ # this many commands per `window`:
+ messages-per-window: 2
+
+ # client status resets to the default state if they go this long without
+ # sending any commands:
+ cooldown: 2s
+
+# message history tracking, for the RESUME extension and possibly other uses in future
+history:
+ # should we store messages for later playback?
+ # by default, messages are stored in RAM only; they do not persist
+ # across server restarts. however, you should not enable this unless you understand
+ # how it interacts with the GDPR and/or any data privacy laws that apply
+ # in your country and the countries of your users.
+ enabled: false
+
+ # how many channel-specific events (messages, joins, parts) should be tracked per channel?
+ channel-length: 1024
+
+ # how many direct messages and notices should be tracked per user?
+ client-length: 256
+
+ # how long should we try to preserve messages?
+ # if `autoresize-window` is 0, the in-memory message buffers are preallocated to
+ # their maximum length. if it is nonzero, the buffers are initially small and
+ # are dynamically expanded up to the maximum length. if the buffer is full
+ # and the oldest message is older than `autoresize-window`, then it will overwrite
+ # the oldest message rather than resize; otherwise, it will expand if possible.
+ autoresize-window: 1h
+
+ # number of messages to automatically play back on channel join (0 to disable):
+ autoreplay-on-join: 0
+
+ # maximum number of CHATHISTORY messages that can be
+ # requested at once (0 disables support for CHATHISTORY)
+ chathistory-maxmessages: 100
+
+ # maximum number of messages that can be replayed at once during znc emulation
+ # (znc.in/playback, or automatic replay on initial reattach to a persistent client):
+ znc-maxmessages: 2048
+
+ # options to delete old messages, or prevent them from being retrieved
+ restrictions:
+ # if this is set, messages older than this cannot be retrieved by anyone
+ # (and will eventually be deleted from persistent storage, if that's enabled)
+ #expire-time: 1w
+
+ # if this is set, logged-in users cannot retrieve messages older than their
+ # account registration date, and logged-out users cannot retrieve messages
+ # older than their sign-on time (modulo grace-period, see below):
+ enforce-registration-date: false
+
+ # but if this is set, you can retrieve messages that are up to `grace-period`
+ # older than the above cutoff time. this is recommended to allow logged-out
+ # users to do session resumption / query history after disconnections.
+ grace-period: 1h
+
+ # options to store history messages in a persistent database (currently only MySQL):
+ persistent:
+ enabled: false
+
+ # store unregistered channel messages in the persistent database?
+ unregistered-channels: false
+
+ # for a registered channel, the channel owner can potentially customize
+ # the history storage setting. as the server operator, your options are
+ # 'disabled' (no persistent storage, regardless of per-channel setting),
+ # 'opt-in', 'opt-out', and 'mandatory' (force persistent storage, ignoring
+ # per-channel setting):
+ registered-channels: "opt-out"
+
+ # direct messages are only stored in the database for logged-in clients;
+ # you can control how they are stored here (same options as above).
+ # if you enable this, strict nickname reservation is strongly recommended
+ # as well.
+ direct-messages: "opt-out"
diff --git a/tests/end_to_end/scenarios/channel_custom_topic.py b/tests/end_to_end/scenarios/channel_custom_topic.py
index 1fbfb5c..3b3104e 100644
--- a/tests/end_to_end/scenarios/channel_custom_topic.py
+++ b/tests/end_to_end/scenarios/channel_custom_topic.py
@@ -10,7 +10,7 @@ scenario = (
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"),
# Second user joins
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
expect_unordered(
[
diff --git a/tests/end_to_end/scenarios/channel_force_join.py b/tests/end_to_end/scenarios/channel_force_join.py
index 089da51..9a24c06 100644
--- a/tests/end_to_end/scenarios/channel_force_join.py
+++ b/tests/end_to_end/scenarios/channel_force_join.py
@@ -21,25 +21,5 @@ scenario = (
"/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"
]
),
-
- # And also, that was not the same nickname, so everyone receives a nick change
- expect_unordered(
- [
- "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']",
- "/presence/muc_user:x/muc_user:status[@code='303']",
- ],
- [
- "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']",
- ],
- [
- "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']",
- "/presence/muc_user:x/muc_user:status[@code='303']",
- "/presence/muc_user:x/muc_user:status[@code='110']",
- ],
- [
- "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']",
- "/presence/muc_user:x/muc_user:status[@code='110']",
- ],
- ),
)
diff --git a/tests/end_to_end/scenarios/channel_history.py b/tests/end_to_end/scenarios/channel_history.py
index ade978b..0014d65 100644
--- a/tests/end_to_end/scenarios/channel_history.py
+++ b/tests/end_to_end/scenarios/channel_history.py
@@ -8,7 +8,7 @@ scenario = (
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"),
# Second user joins
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}%{irc_server_one}/~{nick_one}@localhost'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
# Receive the history message
diff --git a/tests/end_to_end/scenarios/channel_history_on_fixed_server.py b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py
index 40a665b..0e957e1 100644
--- a/tests/end_to_end/scenarios/channel_history_on_fixed_server.py
+++ b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py
@@ -10,7 +10,7 @@ scenario = (
expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"),
# Second user joins
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo@{biboumi_host}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}@{biboumi_host}/~{nick_one}@localhost'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
# Receive the history message
diff --git a/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py
index 1fe9908..7d675ac 100644
--- a/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py
+++ b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py
@@ -4,9 +4,8 @@ conf = "fixed_server"
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/channel_join_with_different_nick.py b/tests/end_to_end/scenarios/channel_join_with_different_nick.py
index 388b098..c499184 100644
--- a/tests/end_to_end/scenarios/channel_join_with_different_nick.py
+++ b/tests/end_to_end/scenarios/channel_join_with_different_nick.py
@@ -3,12 +3,17 @@ from scenarios import *
from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
# The same resource joins a different channel with a different nick
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
# We must receive a join presence in response, without any nick change (nick_two) must be ignored
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#bar", nick = "{nick_one}"),
+ # An different resource joins the same channel, with a different nick
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ # We must receive a join presence in response, without any nick change (nick_two) must be ignored
+ expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"),
+ expect_stanza("/message/subject"),
)
diff --git a/tests/end_to_end/scenarios/channel_join_with_password.py b/tests/end_to_end/scenarios/channel_join_with_password.py
index 4c5e508..fdebcbe 100644
--- a/tests/end_to_end/scenarios/channel_join_with_password.py
+++ b/tests/end_to_end/scenarios/channel_join_with_password.py
@@ -10,9 +10,9 @@ scenario = (
expect_stanza("/message[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='Mode #foo [+k SECRET] by {nick_one}']"),
# Second user tries to join, without a password (error ensues)
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'/>"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
- expect_stanza("/message/body[text()='{irc_host_one}: #foo: Cannot join channel (+k) - bad key']"),
+ expect_stanza("/message/body[text()='{irc_host_one}: #foo: Cannot join channel (+k)']"),
expect_stanza("/presence[@type='error'][@from='#foo%{irc_server_one}/{nick_two}']/error[@type='auth']/stanza:not-authorized"),
# Second user joins, with the correct password (success)
diff --git a/tests/end_to_end/scenarios/channel_join_with_two_users.py b/tests/end_to_end/scenarios/channel_join_with_two_users.py
index 4e22c50..3b2b102 100644
--- a/tests/end_to_end/scenarios/channel_join_with_two_users.py
+++ b/tests/end_to_end/scenarios/channel_join_with_two_users.py
@@ -4,7 +4,7 @@ scenario = (
scenarios.simple_channel_join.scenario,
# Second user joins
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
expect_unordered(
[
diff --git a/tests/end_to_end/scenarios/channel_list_escaping.py b/tests/end_to_end/scenarios/channel_list_escaping.py
index 12c3ff9..7229e97 100644
--- a/tests/end_to_end/scenarios/channel_list_escaping.py
+++ b/tests/end_to_end/scenarios/channel_list_escaping.py
@@ -1,9 +1,8 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #true/false [+nt] by {irc_host_one}']"),
expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#true\\2ffalse%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#true\\2ffalse%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/channel_list_with_rsm.py b/tests/end_to_end/scenarios/channel_list_with_rsm.py
index 79e76f4..aaf589a 100644
--- a/tests/end_to_end/scenarios/channel_list_with_rsm.py
+++ b/tests/end_to_end/scenarios/channel_list_with_rsm.py
@@ -3,13 +3,11 @@ from scenarios import *
scenario = (
scenarios.simple_channel_join.scenario,
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message/body[text()='Mode #coucou [+nt] by {irc_host_one}']"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
@@ -24,44 +22,46 @@ scenario = (
# it sends us the 2 items
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>"),
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}']"),
+ "count(/iq/disco_items:query/disco_items:item[@jid])=2",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='0']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ "!/iq/disco_items:query/rsm:set/rsm:count"),
# Ask for 12 (of 3) items. We get the whole list, and thus we have the count included.
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>"),
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/disco_items:item[@jid='#foo%{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()='#foo%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:count[text()='3']"),
+ "count(/iq/disco_items:query/disco_items:item[@jid])=3",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='0']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ "/iq/disco_items:query/rsm:set/rsm:count[text()='3']",
+ after = save_value("first", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))),
# 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.
- 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>"),
+ 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>{first}</after><max>1</max></set></query></iq>"),
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/rsm:set/rsm:count[text()='3']"),
+ "count(/iq/disco_items:query/disco_items:item)=1",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='1']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ "/iq/disco_items:query/rsm:set/rsm:count[text()='3']",
+ after = save_value("second", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))),
# Ask for 1 item, AFTER the second item (so,
# the third).
- 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>"),
+ 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>{second}</after><max>1</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
- "/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']"),
+ "count(/iq/disco_items:query/disco_items:item)=1",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='2']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ "/iq/disco_items:query/rsm:set/rsm:count[text()='3']",
+ after = save_value("third", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))),
# Ask for 1 item, AFTER the third item (so,
# the fourth). Since it doesn't exist, we get 0 item
- 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>"),
+ 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>{third}</after><max>1</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
+ "count(/iq/disco_items:query/disco_items:item)=0",
"/iq/disco_items:query/rsm:set/rsm:count[text()='3']"),
)
diff --git a/tests/end_to_end/scenarios/channel_messages.py b/tests/end_to_end/scenarios/channel_messages.py
index 8ea979c..a3ead30 100644
--- a/tests/end_to_end/scenarios/channel_messages.py
+++ b/tests/end_to_end/scenarios/channel_messages.py
@@ -6,7 +6,7 @@ scenario = (
scenarios.simple_channel_join.scenario,
# Second user joins
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
# Our presence, sent to the other user, and ourself
@@ -21,15 +21,15 @@ scenario = (
),
# Send a channel message
- send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
+ send_stanza("<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
# Receive the message, forwarded to the two users
expect_unordered(
[
- "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
+ "/message[@id='first_id'][@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]"
],
[
- "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
+ "/message[@id][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
"/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"
]
),
@@ -37,34 +37,33 @@ scenario = (
# Send a private message, to a in-room JID
send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' type='chat'><body>coucou in private</body></message>"),
# Message is received with a server-wide JID
- expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"),
+ expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}'][@type='chat']/body[text()='coucou in private']"),
# Respond to the message, to the server-wide JID
send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>yes</body></message>"),
- # The response is received from the in-room JID
- expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']",
- "/message/muc_user:x"),
+ # The response is received from the server-wide JID without MUC PM marker
+ expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}'][@type='chat']/body[text()='yes']",
+ "!/message/muc_user:x"),
# Do the exact same thing, from a different chan,
# to check if the response comes from the right JID
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#dummy%{irc_server_one}'][@type='groupchat']/subject"),
# Send a private message, to a in-room JID
send_stanza("<message from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_two}' type='chat'><body>re in private</body></message>"),
# Message is received with a server-wide JID
- expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"),
+ expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}'][@type='chat']/body[text()='re in private']"),
# Respond to the message, to the server-wide JID
send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>re</body></message>"),
- # The response is received from the in-room JID
- expect_stanza("/message[@from='#dummy%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"),
+ # The response is received from the server-wide JID
+ expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}'][@type='chat']/body[text()='re']"),
# Now we leave the room, to check if the subsequent private messages are still received properly
send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' type='unavailable' />"),
expect_stanza("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"),
- # The private messages from this nick should now come (again) from the server-wide JID
+ # The private messages from this nick should still come from the server-wide JID
send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>hihihoho</body></message>"),
- expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
+ expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}']"),
)
diff --git a/tests/end_to_end/scenarios/client_error.py b/tests/end_to_end/scenarios/client_error.py
index ca5eaee..8c6fd7e 100644
--- a/tests/end_to_end/scenarios/client_error.py
+++ b/tests/end_to_end/scenarios/client_error.py
@@ -3,7 +3,7 @@ from scenarios import *
scenario = (
scenarios.simple_channel_join.scenario,
# Second resource, same channel
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py
index 8cef926..4c7e795 100644
--- a/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py
+++ b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py
@@ -1,86 +1,73 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence"),
expect_stanza("/message"),
send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>3</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
- "/iq/disco_items:query/disco_items:item[@jid='#aaa%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#bbb%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#ccc%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:first[text()='#aaa%{irc_server_one}'][@index='0']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#ccc%{irc_server_one}']"),
+ "count(/iq/disco_items:query/disco_items:item[@jid])=3",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='0']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))),
- send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#ccc%{irc_server_one}</after><max>3</max></set></query></iq>"),
+ send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
- "/iq/disco_items:query/disco_items:item[@jid='#ddd%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#eee%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#fff%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:first[text()='#ddd%{irc_server_one}'][@index='3']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#fff%{irc_server_one}']"),
+ "count(/iq/disco_items:query/disco_items:item[@jid])=3",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='3']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))),
- send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#fff%{irc_server_one}</after><max>3</max></set></query></iq>"),
+ send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
- "/iq/disco_items:query/disco_items:item[@jid='#ggg%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#hhh%{irc_server_one}']",
- "/iq/disco_items:query/disco_items:item[@jid='#iii%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:first[text()='#ggg%{irc_server_one}'][@index='6']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#iii%{irc_server_one}']"),
+ "count(/iq/disco_items:query/disco_items:item[@jid])=3",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='6']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
+ after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))),
- send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#iii%{irc_server_one}</after><max>3</max></set></query></iq>"),
+ send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"),
expect_stanza("/iq[@type='result']/disco_items:query",
- "/iq/disco_items:query/disco_items:item[@jid='#jjj%{irc_server_one}']",
- "/iq/disco_items:query/rsm:set/rsm:first[text()='#jjj%{irc_server_one}'][@index='9']",
- "/iq/disco_items:query/rsm:set/rsm:last[text()='#jjj%{irc_server_one}']",
+ "count(/iq/disco_items:query/disco_items:item[@jid])=1",
+ "/iq/disco_items:query/rsm:set/rsm:first[@index='9']",
+ "/iq/disco_items:query/rsm:set/rsm:last",
"/iq/disco_items:query/rsm:set/rsm:count[text()='10']"),
send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' type='unavailable' />"),
diff --git a/tests/end_to_end/scenarios/default_channel_list_limit.py b/tests/end_to_end/scenarios/default_channel_list_limit.py
index 84f96b3..6858ea1 100644
--- a/tests/end_to_end/scenarios/default_channel_list_limit.py
+++ b/tests/end_to_end/scenarios/default_channel_list_limit.py
@@ -26,27 +26,19 @@ scenario = (
after = save_value("counter", counter)),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
scenarios.simple_channel_join.expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
(
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence",
after = save_value("counter", counter)),
expect_stanza("/message"),
) * 110,
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
- 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}']"),
+ expect_stanza("count(/iq/disco_items:query/disco_items:item[@jid])=100")
)
diff --git a/tests/end_to_end/scenarios/default_mam_limit.py b/tests/end_to_end/scenarios/default_mam_limit.py
index 02fcaa7..0f402f8 100644
--- a/tests/end_to_end/scenarios/default_mam_limit.py
+++ b/tests/end_to_end/scenarios/default_mam_limit.py
@@ -14,9 +14,8 @@ scenario = (
"</x></command></iq>"),
expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",
diff --git a/tests/end_to_end/scenarios/encoded_channel_join.py b/tests/end_to_end/scenarios/encoded_channel_join.py
index fd4144a..71fa09f 100644
--- a/tests/end_to_end/scenarios/encoded_channel_join.py
+++ b/tests/end_to_end/scenarios/encoded_channel_join.py
@@ -1,9 +1,8 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #biboumi@louiz.org:80 [+nt] by {irc_host_one}']"),
expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py
index 18dfe94..7f3c04c 100644
--- a/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py
+++ b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py
@@ -4,17 +4,17 @@ from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
# Admin connects to first server
- send_stanza("<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'),
expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#bar", nick = "{nick_one}"),
# Non-Admin connects to first server
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_two}"),
# Non-admin connects to second server
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#bon%{irc_server_two}/{nick_three}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#bon%{irc_server_two}/{nick_three}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("{irc_host_two}", '{jid_one}/{resource_two}'),
expect_self_join_presence(jid = '{jid_one}/{resource_two}', chan = "#bon", nick = "{nick_three}", irc_server = "{irc_server_two}"),
diff --git a/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py
index a680017..6ce5231 100644
--- a/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py
+++ b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py
@@ -3,7 +3,7 @@ from scenarios import *
from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
- send_stanza("<presence from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'),
expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
diff --git a/tests/end_to_end/scenarios/fixed_irc_server_subscription.py b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py
index 091cf2a..f255b19 100644
--- a/tests/end_to_end/scenarios/fixed_irc_server_subscription.py
+++ b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py
@@ -3,6 +3,6 @@ from scenarios import *
conf = 'fixed_server'
scenario = (
- send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' />"),
+ send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}'][@from='{biboumi_host}'][@type='subscribed']")
)
diff --git a/tests/end_to_end/scenarios/get_irc_connection_info.py b/tests/end_to_end/scenarios/get_irc_connection_info.py
index 25f2a87..7695aa1 100644
--- a/tests/end_to_end/scenarios/get_irc_connection_info.py
+++ b/tests/end_to_end/scenarios/get_irc_connection_info.py
@@ -4,11 +4,9 @@ scenario = (
send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"),
expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message"),
- expect_stanza("/presence"),
- expect_stanza("/message"),
+ simple_channel_join.expect_self_join_presence(),
send_stanza("<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"),
expect_stanza(r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"),
diff --git a/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py
index d9be151..922ea6a 100644
--- a/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py
+++ b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py
@@ -6,9 +6,8 @@ scenario = (
send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"),
expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
diff --git a/tests/end_to_end/scenarios/invite_other.py b/tests/end_to_end/scenarios/invite_other.py
index 2badf77..0e40dcb 100644
--- a/tests/end_to_end/scenarios/invite_other.py
+++ b/tests/end_to_end/scenarios/invite_other.py
@@ -1,15 +1,13 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
- send_stanza("<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_two}'),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_two}'/></x></message>"),
diff --git a/tests/end_to_end/scenarios/irc_server_connection.py b/tests/end_to_end/scenarios/irc_server_connection.py
index 25ea749..f31b60f 100644
--- a/tests/end_to_end/scenarios/irc_server_connection.py
+++ b/tests/end_to_end/scenarios/irc_server_connection.py
@@ -1,7 +1,7 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
)
diff --git a/tests/end_to_end/scenarios/irc_server_connection_failure.py b/tests/end_to_end/scenarios/irc_server_connection_failure.py
index 2abe39f..adcbc57 100644
--- a/tests/end_to_end/scenarios/irc_server_connection_failure.py
+++ b/tests/end_to_end/scenarios/irc_server_connection_failure.py
@@ -1,7 +1,7 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/message/body[text()='Connecting to doesnotexist:6697 (encrypted)']"),
expect_stanza("/message/body[re:test(text(), 'Connection failed: (Domain name not found|Name or service not known)')]"),
expect_stanza("/presence[@from='#foo%doesnotexist@{biboumi_host}/{nick_one}']/muc:x",
diff --git a/tests/end_to_end/scenarios/irc_server_presence_in_roster.py b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py
index bc2016d..24c1b60 100644
--- a/tests/end_to_end/scenarios/irc_server_presence_in_roster.py
+++ b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py
@@ -9,11 +9,10 @@ scenario = (
send_stanza("<presence from='{jid_one}' to='{irc_server_one}' type='subscribed' />"),
# Join a channel on that server
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
# We must receive the IRC server presence, in the connection sequence
sequences.connection("irc.localhost", '{jid_one}/{resource_one}', expected_irc_presence=True),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/irc_tls_connection.py b/tests/end_to_end/scenarios/irc_tls_connection.py
index 8b30893..db5d32e 100644
--- a/tests/end_to_end/scenarios/irc_tls_connection.py
+++ b/tests/end_to_end/scenarios/irc_tls_connection.py
@@ -16,9 +16,8 @@ scenario = (
"</x></command></iq>"),
expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection_tls("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/my_special_nickname']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/join_history_limit.py b/tests/end_to_end/scenarios/join_history_limit.py
index 2432f14..216e2a0 100644
--- a/tests/end_to_end/scenarios/join_history_limit.py
+++ b/tests/end_to_end/scenarios/join_history_limit.py
@@ -15,9 +15,8 @@ scenario = (
expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
@@ -41,8 +40,7 @@ scenario = (
after = save_current_timestamp_plus_delta("second_timestamp", datetime.timedelta(seconds=1))),
# join some other channel, to stay connected to the server even after leaving #foo
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#DUMMY%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#DUMMY%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message/subject"),
@@ -54,7 +52,6 @@ scenario = (
# Rejoin #foo, with some history limit
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxchars='0'/></x></presence>"),
- expect_stanza("/message"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message/subject"),
@@ -65,7 +62,6 @@ scenario = (
# Rejoin #foo, with some history limit
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='3'/></x></presence>"),
- expect_stanza("/message"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
@@ -77,7 +73,6 @@ scenario = (
# Rejoin #foo, with some history limit
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history since='{first_timestamp}'/></x></presence>"),
- expect_stanza("/message"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
@@ -87,7 +82,6 @@ scenario = (
# Rejoin #foo, with some history limit
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='1'/></x></presence>"),
- expect_stanza("/message"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
@@ -97,7 +91,6 @@ scenario = (
# Rejoin #foo, with some history limit
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='5'/></x></presence>"),
- expect_stanza("/message"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"),
expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
diff --git a/tests/end_to_end/scenarios/leave_unjoined_chan.py b/tests/end_to_end/scenarios/leave_unjoined_chan.py
index a9751d7..aeb6f55 100644
--- a/tests/end_to_end/scenarios/leave_unjoined_chan.py
+++ b/tests/end_to_end/scenarios/leave_unjoined_chan.py
@@ -1,16 +1,14 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message"),
- expect_stanza("/presence"),
- expect_stanza("/message"),
+ simple_channel_join.expect_self_join_presence(),
- send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection_begin("irc.localhost", '{jid_two}/{resource_two}'),
- expect_stanza("/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use.']"),
+ expect_stanza("/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use']"),
expect_stanza("/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"),
send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />")
)
diff --git a/tests/end_to_end/scenarios/mode_change.py b/tests/end_to_end/scenarios/mode_change.py
index 4cbf036..b45904b 100644
--- a/tests/end_to_end/scenarios/mode_change.py
+++ b/tests/end_to_end/scenarios/mode_change.py
@@ -22,6 +22,16 @@ scenario = (
["/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']"],
),
+ # Remove +v manually. User ONLY has +o now. This doesn’t change the role/affiliation
+ send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode -v {nick_two}</body></message>"),
+ expect_unordered(
+ ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"],
+ ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"],
+ ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"],
+ ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"],
+ ),
+
+
# remove the mode
send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='member' nick='{nick_two}' role='participant'/></query></iq>"),
expect_unordered(
diff --git a/tests/end_to_end/scenarios/muc_disco_info.py b/tests/end_to_end/scenarios/muc_disco_info.py
index 19e90f2..9b6f0e5 100644
--- a/tests/end_to_end/scenarios/muc_disco_info.py
+++ b/tests/end_to_end/scenarios/muc_disco_info.py
@@ -10,12 +10,12 @@ scenario = (
"/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']",
"/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']",
"/iq/disco_info:query/disco_info:feature[@var='muc_nonanonymous']",
+ "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:sid:0']",
"!/iq/disco_info:query/dataform:x/dataform:field[@var='muc#roominfo_occupants']"),
# Join the channel, and re-do the same query
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
diff --git a/tests/end_to_end/scenarios/multiline_message.py b/tests/end_to_end/scenarios/multiline_message.py
index fc88e66..cd42c6c 100644
--- a/tests/end_to_end/scenarios/multiline_message.py
+++ b/tests/end_to_end/scenarios/multiline_message.py
@@ -34,7 +34,7 @@ scenario = (
# joining the room with the new user.
sleep_for(0.2),
# Second user joins
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
# Our presence, sent to the other user
expect_unordered(
diff --git a/tests/end_to_end/scenarios/multiple_channels_join.py b/tests/end_to_end/scenarios/multiple_channels_join.py
index 84e4360..281a902 100644
--- a/tests/end_to_end/scenarios/multiple_channels_join.py
+++ b/tests/end_to_end/scenarios/multiple_channels_join.py
@@ -4,8 +4,8 @@ from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
# Join 3 rooms, on the same server, with three different nicks
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
send_stanza("<presence from='{jid_one}/{resource_one}' to='#baz%{irc_server_one}/{nick_three}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"),
sequences.connection(),
@@ -14,5 +14,9 @@ scenario = (
expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"),
expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#bar", nick="{nick_one}"),
expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#baz", nick="{nick_one}"),
+
+ send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>Le topic</subject></message>"),
+ expect_stanza("/message"),
+
)
diff --git a/tests/end_to_end/scenarios/multisession_kick.py b/tests/end_to_end/scenarios/multisession_kick.py
index 7d8679f..580beb4 100644
--- a/tests/end_to_end/scenarios/multisession_kick.py
+++ b/tests/end_to_end/scenarios/multisession_kick.py
@@ -4,19 +4,21 @@ scenario = (
scenarios.simple_channel_join.scenario,
# Second user joins, from two resources
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
expect_unordered(
- ["/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"],
- ["/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"],
- ["/presence/muc_user:x/muc_user:status[@code='110']"],
+ ["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"],
+ ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"],
+ ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",
+ "/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:status[@code='110']"],
["/message/subject"]
),
# Second resource
- send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' />"),
- expect_stanza("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"),
- expect_stanza("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",
- "/presence/muc_user:x/muc_user:status[@code='110']"
+ send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ expect_unordered(
+ ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"],
+ ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",
+ "/presence/muc_user:x/muc_user:status[@code='110']"]
),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/multisessionnick.py b/tests/end_to_end/scenarios/multisessionnick.py
index 4e72ce7..1377a94 100644
--- a/tests/end_to_end/scenarios/multisessionnick.py
+++ b/tests/end_to_end/scenarios/multisessionnick.py
@@ -4,12 +4,12 @@ from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
# Resource one joins a channel
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
# The other resources joins the same room, with the same nick
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
# We receive our own join
expect_unordered(
@@ -24,7 +24,7 @@ scenario = (
),
# A different user joins the same room
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
expect_unordered(
# The new user’s presence is sent to the the existing occupant (two resources)
@@ -51,27 +51,17 @@ scenario = (
# That second user sends a private message to the first one
send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>RELLO</body></message>"),
- # Message is received with a server-wide JID, by the two resources behind nick_one
- expect_unordered(
- [
- "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']",
- "/message/hints:no-copy",
- "/message/carbon:private",
- "!/message/muc_user:x",
- ],
- [
- "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']",
- "/message/hints:no-copy",
- "/message/carbon:private",
- "!/message/muc_user:x",
- ]
+ # Message is received with a server-wide JID to the bare JID as non-MUC-PM
+ expect_stanza(
+ "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}'][@type='chat']/body[text()='RELLO']",
+ "!/message/muc_user:x",
),
# First occupant (with the two resources) changes her/his nick to a conflicting one
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
expect_unordered(
- ["/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']"],
- ["/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']"],
+ ["/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"],
+ ["/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"],
["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"],
["/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"]
),
@@ -80,14 +70,14 @@ scenario = (
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' />"),
expect_unordered(
[
- "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']",
+ "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']",
"/presence/muc_user:x/muc_user:status[@code='303']"
],
[
"/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']"
],
[
- "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']",
+ "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']",
"/presence/muc_user:x/muc_user:status[@code='303']",
"/presence/muc_user:x/muc_user:status[@code='110']"
],
@@ -96,7 +86,7 @@ scenario = (
"/presence/muc_user:x/muc_user:status[@code='110']"
],
[
- "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']",
+ "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']",
"/presence/muc_user:x/muc_user:status[@code='303']",
"/presence/muc_user:x/muc_user:status[@code='110']"
],
@@ -117,9 +107,9 @@ scenario = (
send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>first</body></message>"),
send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>second</body></message>"),
- # The first user receives the two messages, on the connected resource, once each
+ # The first user receives the two messages to the bare JID
expect_unordered(
- ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"],
- ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"]
+ ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}'][@type='chat']/body[text()='first']"],
+ ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}'][@type='chat']/body[text()='second']"]
),
)
diff --git a/tests/end_to_end/scenarios/nick_change.py b/tests/end_to_end/scenarios/nick_change.py
new file mode 100644
index 0000000..9d06856
--- /dev/null
+++ b/tests/end_to_end/scenarios/nick_change.py
@@ -0,0 +1,33 @@
+from scenarios import *
+
+import scenarios.channel_join_with_two_users
+
+scenario = (
+ scenarios.channel_join_with_two_users.scenario,
+
+ # first users changes their nick
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' id='nick_change' />"),
+ expect_unordered(
+ ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']",
+ "/presence/muc_user:x/muc_user:status[@code='303']",
+ "/presence/muc_user:x/muc_user:item[@affiliation='admin']",
+ "/presence/muc_user:x/muc_user:item[@role='moderator']",
+ "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']"],
+
+ ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']",
+ "/presence/muc_user:x/muc_user:status[@code='303']",
+ "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']",
+ "/presence/muc_user:x/muc_user:item[@affiliation='admin']",
+ "/presence/muc_user:x/muc_user:item[@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']"],
+
+ ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']",
+ "/presence/muc_user:x/muc_user:item[@affiliation='admin']",
+ "/presence/muc_user:x/muc_user:item[@role='moderator']"],
+
+ ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']",
+ "/presence/muc_user:x/muc_user:item[@affiliation='admin']",
+ "/presence/muc_user:x/muc_user:item[@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']"]
+ ),
+)
diff --git a/tests/end_to_end/scenarios/nick_change_in_join.py b/tests/end_to_end/scenarios/nick_change_in_join.py
index 47aecc6..f4feae3 100644
--- a/tests/end_to_end/scenarios/nick_change_in_join.py
+++ b/tests/end_to_end/scenarios/nick_change_in_join.py
@@ -3,12 +3,11 @@ from scenarios import *
from scenarios.simple_channel_join import expect_self_join_presence
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
- expect_stanza("/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
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']",
"/presence/muc_user:x/muc_user:status[@code='210']", # This status signals that the server forced our nick to NOT be the one we asked
diff --git a/tests/end_to_end/scenarios/not_connected_error.py b/tests/end_to_end/scenarios/not_connected_error.py
index cc9fb35..577b324 100644
--- a/tests/end_to_end/scenarios/not_connected_error.py
+++ b/tests/end_to_end/scenarios/not_connected_error.py
@@ -6,7 +6,7 @@ scenario = (
send_stanza("<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
# Fixme: what is the purpose of this test? Check that we don’t receive anything here…?
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"),
)
diff --git a/tests/end_to_end/scenarios/persistent_channel.py b/tests/end_to_end/scenarios/persistent_channel.py
index 3521a84..ab525fe 100644
--- a/tests/end_to_end/scenarios/persistent_channel.py
+++ b/tests/end_to_end/scenarios/persistent_channel.py
@@ -17,7 +17,7 @@ scenario = (
expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='true']"),
# A second user joins the same channel
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
expect_unordered(
["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"],
@@ -26,8 +26,8 @@ scenario = (
"/presence/muc_user:x/muc_user:status[@code='110']"
],
["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"],
+ ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"]
),
- expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
# First user leaves the room (but biboumi will stay in the channel)
send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"),
diff --git a/tests/end_to_end/scenarios/raw_message.py b/tests/end_to_end/scenarios/raw_message.py
index 96a3f3d..c6cd4e7 100644
--- a/tests/end_to_end/scenarios/raw_message.py
+++ b/tests/end_to_end/scenarios/raw_message.py
@@ -1,9 +1,8 @@
from scenarios import *
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
diff --git a/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py
index 8196d12..7eb5b13 100644
--- a/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py
+++ b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py
@@ -3,9 +3,8 @@ from scenarios import *
conf = 'fixed_server'
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True),
- expect_stanza("/message"),
expect_stanza("/presence"),
expect_stanza("/message"),
diff --git a/tests/end_to_end/scenarios/raw_names_command.py b/tests/end_to_end/scenarios/raw_names_command.py
deleted file mode 100644
index 09b47be..0000000
--- a/tests/end_to_end/scenarios/raw_names_command.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from functions import send_stanza, expect_stanza
-
-import scenarios.simple_channel_join
-
-join_channel = scenarios.simple_channel_join.scenario
-
-scenario = (
- join_channel,
-
- send_stanza("<message type='chat' from='{jid_one}/{resource_one}' to='{irc_server_one}'><body>NAMES</body></message>"),
- expect_stanza("/message/body[text()='irc.localhost: = #foo @{nick_one} ']"),
- expect_stanza("/message/body[text()='irc.localhost: * End of /NAMES list. ']"),
-)
diff --git a/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py
index 8d208f0..dfb7161 100644
--- a/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py
+++ b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py
@@ -2,9 +2,8 @@ from scenarios import *
scenario = (
# Join the channel
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"),
@@ -19,15 +18,14 @@ scenario = (
expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"),
# Join the same channel, with the same JID, but a different resource
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"),
# Join some other channel with someone else
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_two}/{resource_one}'),
- expect_stanza("/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
expect_stanza("/presence[@to='{jid_two}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_one}']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/sasl.py b/tests/end_to_end/scenarios/sasl.py
new file mode 100644
index 0000000..9f2a27a
--- /dev/null
+++ b/tests/end_to_end/scenarios/sasl.py
@@ -0,0 +1,99 @@
+from scenarios import *
+
+scenario = (
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/RegisteredUser' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ sequences.connection(),
+
+ simple_channel_join.expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "RegisteredUser"),
+
+ # Create an account by talking to nickserv directly
+ send_stanza("<message from='{jid_one}/{resource_one}' to='nickserv%{irc_server_one}' type='chat'><body>register P4SSW0RD</body></message>"),
+ expect_stanza("/message/body[text()='Account created']"),
+ expect_stanza("/message/body[text()=\"You're now logged in as RegisteredUser\"]"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"),
+ expect_stanza("/presence[@type='unavailable']"),
+
+ # Configure an sasl password
+ 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>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='sasl_password']",
+ after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))),
+
+ 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='complete'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='sasl_password'><value>P4SSW0RD</value></field>"
+ "<field var='ports'><value>6667</value></field>"
+ "<field var='nick'><value>RegisteredUser</value></field>"
+ "<field var='tls_ports'><value>6697</value><value>6670</value></field>"
+ "<field var='throttle_limit'><value>9999</value></field>"
+ "</x></command></iq>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
+
+ # Joining a channel with the associated nick will work
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/ignored' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ sequences.connection(login="RegisteredUser"),
+ expect_stanza("/presence"),
+ expect_stanza("/message/subject"),
+
+ # Leave the channel and disconnect from the server to try again differently
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/RegisteredUser' type='unavailable' />"),
+ expect_stanza("/presence[@type='unavailable']"),
+
+ # Leave the same password, but remove the Nick
+ send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='sasl_password']",
+ after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))),
+
+ send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'>"
+ "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='sasl_password'><value>P4SSW0RD</value></field>"
+ "<field var='ports'><value>6667</value></field>"
+ "<field var='nick'><value></value></field>"
+ "<field var='tls_ports'><value>6697</value><value>6670</value></field>"
+ "<field var='throttle_limit'><value>9999</value></field>"
+ "</x></command></iq>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
+
+ # Joining a channel with the associated nick will work, it will use the one from our <presence/>
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/RegisteredUser' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ sequences.connection(login="RegisteredUser"),
+ expect_stanza("/presence"),
+ expect_stanza("/message/subject"),
+
+ # Leave the channel and disconnect from the server to try again differently
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/RegisteredUser' type='unavailable' />"),
+ expect_stanza("/presence[@type='unavailable']"),
+
+ # Configure an INCORRECT password
+ send_stanza("<iq type='set' id='id5' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='sasl_password']",
+ after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))),
+
+
+ send_stanza("<iq type='set' id='id6' from='{jid_one}/{resource_one}' to='{irc_server_one}'>"
+ "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='sasl_password'><value>wrong wrong wrong</value></field>"
+ "<field var='ports'><value>6667</value></field>"
+ "<field var='nick'><value>RegisteredUser</value></field>"
+ "<field var='tls_ports'><value>6697</value><value>6670</value></field>"
+ "<field var='throttle_limit'><value>9999</value></field>"
+ "</x></command></iq>"),
+ expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
+
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/ignored' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
+ # Here, the 6 connecting… connected messages from the connection attempt
+ expect_stanza("/message"),
+ expect_stanza("/message"),
+ expect_stanza("/message"),
+ expect_stanza("/message"),
+ expect_stanza("/message"),
+ expect_stanza("/message"),
+ expect_stanza("/presence[@type='error'][@from='#foo%{irc_server_one}/RegisteredUser']"),
+ expect_stanza("/message/body[text()='ERROR: Quit: SASL authentication failed: Invalid account credentials']"),
+ expect_stanza("/message/body[text()='ERROR: Connection closed.']"),
+)
diff --git a/tests/end_to_end/scenarios/self_invite.py b/tests/end_to_end/scenarios/self_invite.py
deleted file mode 100644
index 7959b3a..0000000
--- a/tests/end_to_end/scenarios/self_invite.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from scenarios import *
-
-scenario = (
- scenarios.simple_channel_join.scenario,
- send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_one}'/></x></message>"),
- expect_stanza("/message/body[text()='{nick_one} is already on channel #foo']")
-)
diff --git a/tests/end_to_end/scenarios/self_ping_on_real_channel.py b/tests/end_to_end/scenarios/self_ping_on_real_channel.py
index 6cbb210..3474288 100644
--- a/tests/end_to_end/scenarios/self_ping_on_real_channel.py
+++ b/tests/end_to_end/scenarios/self_ping_on_real_channel.py
@@ -8,7 +8,7 @@ scenario = (
expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"),
# Now join the same room, from the same bare JID, behind the same nick
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@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']"),
diff --git a/tests/end_to_end/scenarios/self_version.py b/tests/end_to_end/scenarios/self_version.py
index f567355..a5c81eb 100644
--- a/tests/end_to_end/scenarios/self_version.py
+++ b/tests/end_to_end/scenarios/self_version.py
@@ -13,7 +13,7 @@ scenario = (
expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"),
# Now join the same room, from the same bare JID, behind the same nick
- send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@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']"),
expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/simple_channel_join.py b/tests/end_to_end/scenarios/simple_channel_join.py
index b09d6be..6b57207 100644
--- a/tests/end_to_end/scenarios/simple_channel_join.py
+++ b/tests/end_to_end/scenarios/simple_channel_join.py
@@ -1,8 +1,7 @@
from scenarios import *
-def expect_self_join_presence(jid, chan, nick, irc_server="{irc_server_one}"):
+def expect_self_join_presence(jid='{jid_one}/{resource_one}', chan='#foo', nick='{nick_one}', irc_server="{irc_server_one}"):
return (
- expect_stanza("/message/body[text()='Mode " + chan + " [+nt] by irc.localhost']"),
expect_stanza("/presence[@to='" + jid +"'][@from='" + chan + "%" + irc_server + "/" + nick + "']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
"/presence/muc_user:x/muc_user:status[@code='100']", # Rooms are all non-anonymous
"/presence/muc_user:x/muc_user:status[@code='110']",
@@ -12,10 +11,9 @@ def expect_self_join_presence(jid, chan, nick, irc_server="{irc_server_one}"):
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection(),
expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"),
-
)
diff --git a/tests/end_to_end/scenarios/simple_channel_join_fixed.py b/tests/end_to_end/scenarios/simple_channel_join_fixed.py
index 6efd20f..9f5b835 100644
--- a/tests/end_to_end/scenarios/simple_channel_join_fixed.py
+++ b/tests/end_to_end/scenarios/simple_channel_join_fixed.py
@@ -3,9 +3,8 @@ from scenarios import *
conf = "fixed_server"
scenario = (
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True),
- expect_stanza("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
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']"),
expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"),
diff --git a/tests/end_to_end/scenarios/simple_kick.py b/tests/end_to_end/scenarios/simple_kick.py
index 0e06589..2949157 100644
--- a/tests/end_to_end/scenarios/simple_kick.py
+++ b/tests/end_to_end/scenarios/simple_kick.py
@@ -4,13 +4,12 @@ scenario = (
scenarios.channel_join_with_two_users.scenario,
# demonstrate bug https://lab.louiz.org/louiz/biboumi/issues/3291
# First user joins an other channel
- send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
- expect_stanza("/message"),
+ send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
expect_stanza("/message[@type='groupchat']/subject"),
# Second user joins
- send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
+ send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
expect_unordered(
["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"],
[
diff --git a/tests/end_to_end/scenarios/stable_id.py b/tests/end_to_end/scenarios/stable_id.py
new file mode 100644
index 0000000..9f3181b
--- /dev/null
+++ b/tests/end_to_end/scenarios/stable_id.py
@@ -0,0 +1,32 @@
+from scenarios import *
+
+import scenarios.simple_channel_join
+
+# see https://xmpp.org/extensions/xep-0359.html
+
+scenario = (
+ scenarios.simple_channel_join.scenario,
+
+ send_stanza("""<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'>
+ <origin-id xmlns='urn:xmpp:sid:0' id='client-origin-id-should-be-kept'/>
+ <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-removed' by='#foo%{irc_server_one}'/>
+ <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-kept' by='someother@jid'/>
+ <body>coucou</body></message>"""),
+
+ # Entities, which are routing stanzas, SHOULD NOT strip any elements
+ # qualified by the 'urn:xmpp:sid:0' namespace from message stanzas
+ # unless the preceding rule applied to those elements.
+ expect_stanza("/message/stable_id:origin-id[@id='client-origin-id-should-be-kept']",
+ # Stanza ID generating entities, which encounter a <stanza-id/>
+ # element where the 'by' attribute matches the 'by' attribute they
+ # would otherwise set, MUST delete that element even if they are not
+ # adding their own stanza ID.
+ "/message/stable_id:stanza-id[@id][@by='#foo%{irc_server_one}']",
+ "!/message/stable_id:stanza-id[@id='client-stanza-id-should-be-removed']",
+ # Entities, which are routing stanzas, SHOULD NOT strip
+ # any elements qualified by the 'urn:xmpp:sid:0'
+ # namespace from message stanzas unless the preceding
+ # rule applied to those elements.
+ "/message/stable_id:stanza-id[@id='client-stanza-id-should-be-kept'][@by='someother@jid']",
+ ),
+)
diff --git a/tests/end_to_end/sequences.py b/tests/end_to_end/sequences.py
index 8a40e52..f151bc7 100644
--- a/tests/end_to_end/sequences.py
+++ b/tests/end_to_end/sequences.py
@@ -1,4 +1,4 @@
-from functions import expect_stanza, send_stanza, common_replacements
+from functions import expect_stanza, send_stanza, common_replacements, expect_unordered
def handshake():
return (
@@ -6,14 +6,12 @@ def handshake():
send_stanza("<handshake xmlns='jabber:component:accept'/>")
)
-def connection_begin(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False):
+def connection_begin(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False, login=None):
jid = jid.format_map(common_replacements)
if fixed_irc_server:
xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']"
- xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]"
else:
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')]"
result = (
expect_stanza(xpath % ('Connecting to %s:6697 (encrypted)' % irc_host),
"/message/hints:no-copy",
@@ -28,25 +26,20 @@ def connection_begin(irc_host, jid, expected_irc_presence=False, fixed_irc_serve
if expected_irc_presence:
result += (expect_stanza("/presence[@from='" + irc_host + "@biboumi.localhost']"),)
- # These five messages can be receive in any order
+ if login is not None:
+ result += (expect_stanza("/message/body[text()='irc.localhost: You are now logged in as %s']" % (login,)),)
result += (
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')),
+ expect_stanza("/message/body[text()='irc.localhost: *** Looking up your hostname...']"),
+ expect_stanza("/message/body[text()='irc.localhost: *** Found your hostname']")
)
-
return result
def connection_tls_begin(irc_host, jid, fixed_irc_server):
jid = jid.format_map(common_replacements)
if fixed_irc_server:
xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']"
- xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]"
else:
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 (
expect_stanza(xpath % ('Connecting to %s:7778 (encrypted)' % irc_host),
@@ -54,12 +47,8 @@ def connection_tls_begin(irc_host, jid, fixed_irc_server):
"/message/carbon:private",
),
expect_stanza(xpath % 'Connected to IRC server (encrypted).'),
- # These five messages can be receive in any order
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)),
+ expect_stanza("/message/body[text()='irc.localhost: *** Looking up your hostname...']"),
+ expect_stanza("/message/body[text()='irc.localhost: *** Found your hostname']")
)
def connection_end(irc_host, jid, fixed_irc_server=False):
@@ -72,37 +61,25 @@ def connection_end(irc_host, jid, fixed_irc_server=False):
xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]"
irc_host = 'irc.localhost'
return (
- expect_stanza(xpath_re % (r'^%s: Your host is .*$' % irc_host)),
+ expect_stanza("/message/body[re:test(text(), '%s')]" % (r'^%s: Your host is %s, running version oragono-2\.0\.0(-[a-z0-9]+)? $' % (irc_host, irc_host))),
expect_stanza(xpath_re % (r'^%s: This server was created .*$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: \d+ unknown connection\(s\)$' % irc_host), optional=True),
- expect_stanza(xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True),
+ expect_stanza(xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ server\(s\)$' % irc_host)),
+ expect_stanza(xpath_re % ("%s: \d+ IRC Operators online" % irc_host,)),
+ expect_stanza(xpath_re % ("%s: \d+ unregistered connections" % irc_host,)),
+ expect_stanza(xpath_re % ("%s: \d+ channels formed" % irc_host,)),
expect_stanza(xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)),
expect_stanza(xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)),
expect_stanza(xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)),
- expect_stanza(xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)),
- expect_stanza(xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"),
- expect_stanza(xpath_re % r'^User mode for \w+ is \[\+Z?i\]$'),
- )
-
-def connection_middle(irc_host, jid, fixed_irc_server=False):
- if fixed_irc_server:
- xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]"
- else:
- xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]"
- irc_host = 'irc.localhost'
- return (
- expect_stanza(xpath_re % (r'^%s: \*\*\* You are exempt from flood limits$' % irc_host)),
+ expect_stanza(xpath % "%s: MOTD File is missing: Unspecified error" % irc_host),
+ expect_stanza(xpath_re % (r'.+? \+Z',)),
)
-def connection(irc_host="irc.localhost", jid="{jid_one}/{resource_one}", expected_irc_presence=False, fixed_irc_server=False):
- return connection_begin(irc_host, jid, expected_irc_presence, fixed_irc_server=fixed_irc_server) + \
- connection_middle(irc_host, jid, fixed_irc_server=fixed_irc_server) + \
+def connection(irc_host="irc.localhost", jid="{jid_one}/{resource_one}", expected_irc_presence=False, fixed_irc_server=False, login=None):
+ return connection_begin(irc_host, jid, expected_irc_presence, fixed_irc_server=fixed_irc_server, login=login) + \
connection_end(irc_host, jid, fixed_irc_server=fixed_irc_server)
def connection_tls(irc_host="irc.localhost", jid="{jid_one}/{resource_one}", fixed_irc_server=False):
return connection_tls_begin(irc_host, jid, fixed_irc_server) + \
- connection_middle(irc_host, jid, fixed_irc_server) +\
connection_end(irc_host, jid, fixed_irc_server)
diff --git a/tests/irc.cpp b/tests/irc.cpp
new file mode 100644
index 0000000..0f30f15
--- /dev/null
+++ b/tests/irc.cpp
@@ -0,0 +1,43 @@
+#include "catch.hpp"
+
+#include <irc/irc_message.hpp>
+
+TEST_CASE("Basic IRC message parsing")
+{
+ IrcMessage m(":prefix COMMAND un deux trois");
+ CHECK(m.prefix == "prefix");
+ CHECK(m.command == "COMMAND");
+ CHECK(m.arguments.size() == 3);
+ CHECK(m.arguments[0] == "un");
+ CHECK(m.arguments[1] == "deux");
+ CHECK(m.arguments[2] == "trois");
+}
+
+TEST_CASE("Trailing space")
+{
+ IrcMessage m(":prefix COMMAND un deux trois ");
+ CHECK(m.prefix == "prefix");
+ CHECK(m.arguments.size() == 3);
+ CHECK(m.arguments[0] == "un");
+ CHECK(m.arguments[1] == "deux");
+ CHECK(m.arguments[2] == "trois");
+}
+
+TEST_CASE("Message with :")
+{
+ IrcMessage m(":prefix COMMAND un :coucou les amis ");
+ CHECK(m.prefix == "prefix");
+ CHECK(m.arguments.size() == 2);
+ CHECK(m.arguments[0] == "un");
+ CHECK(m.arguments[1] == "coucou les amis ");
+}
+
+TEST_CASE("Message with empty :")
+{
+ IrcMessage m("COMMAND un deux :");
+ CHECK(m.prefix == "");
+ CHECK(m.arguments.size() == 3);
+ CHECK(m.arguments[0] == "un");
+ CHECK(m.arguments[1] == "deux");
+ CHECK(m.arguments[2] == "");
+}
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 6de19f0..6151733 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -28,6 +28,9 @@ TEST_CASE("String split")
CHECK(splitted.size() == 2);
CHECK(splitted[0] == "");
CHECK(splitted[1] == "a");
+ splitted = utils::split("multi-prefix ", ' ');
+ CHECK(splitted[0] == "multi-prefix");
+ CHECK(splitted.size() == 1);
}
TEST_CASE("tolower")
diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp
index 14c51da..c49c2fd 100644
--- a/tests/xmpp.cpp
+++ b/tests/xmpp.cpp
@@ -67,6 +67,8 @@ TEST_CASE("substanzas")
CHECK(!d.has_children());
}
CHECK(b.has_children());
+ XmlSubNode e(a, "namespace", "name");
+ CHECK(e.get_tag("xmlns") == "namespace");
}
CHECK(a.has_children());
}
diff --git a/unit/biboumi.service.cmake b/unit/biboumi.service.cmake
index 150045b..bcbda1f 100644
--- a/unit/biboumi.service.cmake
+++ b/unit/biboumi.service.cmake
@@ -11,6 +11,7 @@ WatchdogSec=${WATCHDOG_SEC}
Restart=always
User=${SERVICE_USER}
Group=${SERVICE_GROUP}
+AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target