Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a36a722c5 | |||
| 00c75c3dc9 | |||
| cae7850a70 | |||
| ce668bef14 | |||
| 3887b6d930 | |||
| b7bd0e196d | |||
| 6d63842ad3 | |||
| 2f3b9015e9 | |||
| 6ae48eb991 | |||
| c508795ad4 | |||
| 4a053807e0 | |||
| dd5bbda2dc | |||
| 98469678a0 | |||
| b8550e087e | |||
| 644d468b4f | |||
| 74c810eeaa | |||
| a46325166a | |||
| 654d4b81b1 | |||
| ff3d33dde4 | |||
| 3183e2f733 | |||
| e1dc686ae7 | |||
| 38b203feb1 | |||
| 8b61cf0742 | |||
| a02c75aa08 | |||
| 51af393baa | |||
| 41318e45a5 | |||
| a94f227103 | |||
| 517776acd4 | |||
| 212a5ded6e | |||
| fd9c929e37 | |||
| ce828163af | |||
| 97e1b419a0 | |||
| d70ac7f7c5 | |||
| 7065cb69f1 | |||
| 7815463ba0 | |||
| 48e6631751 | |||
| 903e6b70b4 | |||
| 5edba59b24 | |||
| 31cb4b06e4 | |||
| 10f6723f00 | |||
| f594620c68 | |||
| dacfad61d8 | |||
| 7c1da7e0cf | |||
| e709f99b47 | |||
| f150419891 | |||
| 053fd26994 | |||
| bba1a1e3ca | |||
| e5da1efea4 | |||
| fbfd41c16e | |||
| 4391921727 | |||
| 4cd3c657e2 | |||
| 7647b77225 | |||
| fe8710fe00 | |||
| 1a9b147baf | |||
| 6214e0385d | |||
| f7002c31f0 | |||
| 99b75396ad | |||
| b1c3baa7bd | |||
| 355eb5dfde | |||
| d269e32c3a | |||
| 214b76f763 | |||
| 73a8fbdfb5 | |||
| 9b6f0aeb3c | |||
| 45a6aed57f | |||
| 9d17a160b6 | |||
| 850d097660 | |||
| 8ce8f67c06 | |||
| a17c2c166d |
@@ -1,5 +1,5 @@
|
||||
#' Define default build variables
|
||||
ARG OTP_VSN='27.3.4.1'
|
||||
ARG OTP_VSN='27.3.4.2'
|
||||
ARG ELIXIR_VSN='1.18.4'
|
||||
ARG UID='9000'
|
||||
ARG USER='ejabberd'
|
||||
@@ -9,7 +9,7 @@ ARG VERSION='master'
|
||||
|
||||
################################################################################
|
||||
#' Compile ejabberdapi
|
||||
FROM docker.io/golang:1.24-alpine AS api
|
||||
FROM docker.io/golang:1.25-alpine AS api
|
||||
RUN go install -v \
|
||||
github.com/processone/ejabberd-api/cmd/ejabberd@master \
|
||||
&& mv bin/ejabberd bin/ejabberdapi
|
||||
|
||||
@@ -206,7 +206,7 @@ modules:
|
||||
mod_fail2ban: {}
|
||||
mod_http_api: {}
|
||||
mod_http_upload:
|
||||
put_url: https://@HOST@:5443/upload
|
||||
put_url: https://@HOST_URL_ENCODE@:5443/upload
|
||||
custom_headers:
|
||||
"Access-Control-Allow-Origin": "https://@HOST@"
|
||||
"Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
|
||||
|
||||
@@ -195,7 +195,9 @@ livewarning()
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To exit and detach this shell from ejabberd, press:"
|
||||
echo "To stop ejabberd gracefully:"
|
||||
echo " ejabberd:stop()."
|
||||
echo "To quit erlang immediately, press:"
|
||||
echo " control+g and then q"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
@@ -363,6 +365,13 @@ post_waiter_loop()
|
||||
# allow sync calls
|
||||
wait_status()
|
||||
{
|
||||
wait_status_node "$ERLANG_NODE" $1 $2 $3
|
||||
}
|
||||
|
||||
wait_status_node()
|
||||
{
|
||||
CONNECT_NODE=$1
|
||||
shift
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
timeout="$2"
|
||||
@@ -374,9 +383,9 @@ wait_status()
|
||||
status="$1"
|
||||
else
|
||||
run_erl "$(uid ctl)" -hidden -noinput \
|
||||
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
|
||||
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
|
||||
-s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
|
||||
-extra "$CONNECT_NODE" $NO_TIMEOUT status > /dev/null
|
||||
status="$?"
|
||||
fi
|
||||
done
|
||||
@@ -385,19 +394,26 @@ wait_status()
|
||||
|
||||
exec_other_command()
|
||||
{
|
||||
exec_other_command_node $ERLANG_NODE "$@"
|
||||
}
|
||||
|
||||
exec_other_command_node()
|
||||
{
|
||||
CONNECT_NODE=$1
|
||||
shift
|
||||
if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
|
||||
|| [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
|
||||
|| [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
|
||||
run_erl "$(uid ctl)" -hidden -noinput \
|
||||
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
|
||||
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
|
||||
-s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
|
||||
-extra "$CONNECT_NODE" $NO_TIMEOUT "$@"
|
||||
result=$?
|
||||
case $result in
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
exit $result
|
||||
return $result
|
||||
else
|
||||
exec_ctl_over_http_socket "$@"
|
||||
fi
|
||||
@@ -439,6 +455,103 @@ cd "$SPOOL_DIR" || {
|
||||
exit 6
|
||||
}
|
||||
|
||||
printe()
|
||||
{
|
||||
printf "\n"
|
||||
printf "\e[1;40;32m==> %s\e[0m\n" "$1"
|
||||
}
|
||||
|
||||
## Function copied from tools/make-installers
|
||||
user_agrees()
|
||||
{
|
||||
question="$*"
|
||||
|
||||
if [ -t 0 ]
|
||||
then
|
||||
printe "$question (y/n) [n]"
|
||||
read -r response
|
||||
case "$response" in
|
||||
[Yy]|[Yy][Ee][Ss])
|
||||
return 0
|
||||
;;
|
||||
[Nn]|[Nn][Oo]|'')
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
echo 'Please respond with "yes" or "no".'
|
||||
user_agrees "$question"
|
||||
;;
|
||||
esac
|
||||
else # Assume 'yes' if not running interactively.
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
mnesia_change()
|
||||
{
|
||||
ERLANG_NODE_OLD="$1"
|
||||
[ "$ERLANG_NODE_OLD" = "" ] \
|
||||
&& echo "Error: Please provide the old erlang node name, for example:" \
|
||||
&& echo " ejabberdctl mnesia_change ejabberd@oldmachine" \
|
||||
&& exit 1
|
||||
|
||||
SPOOL_DIR_BACKUP=$SPOOL_DIR/$ERLANG_NODE_OLD-backup/
|
||||
OLDFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE_OLD.backup
|
||||
NEWFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE.backup
|
||||
|
||||
printe "This changes your mnesia database from node name '$ERLANG_NODE_OLD' to '$ERLANG_NODE'"
|
||||
|
||||
[ -d "$SPOOL_DIR_BACKUP" ] && printe "WARNING! A backup of old node already exists in $SPOOL_DIR_BACKUP"
|
||||
|
||||
if ! user_agrees "Do you want to proceed?"
|
||||
then
|
||||
echo 'Operation aborted.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printe "Starting ejabberd with old node name $ERLANG_NODE_OLD ..."
|
||||
exec_erl "$ERLANG_NODE_OLD" $EJABBERD_OPTS -detached
|
||||
wait_status_node $ERLANG_NODE_OLD 0 30 2
|
||||
result=$?
|
||||
case $result in
|
||||
1) echo "There was a problem starting ejabberd with the old erlang node name. " \
|
||||
&& echo "Check for log errors in $EJABBERD_LOG_PATH" \
|
||||
&& exit $result;;
|
||||
*) :;;
|
||||
esac
|
||||
exec_other_command_node $ERLANG_NODE_OLD "status"
|
||||
|
||||
printe "Making backup of old database to file $OLDFILE ..."
|
||||
mkdir $SPOOL_DIR_BACKUP
|
||||
exec_other_command_node $ERLANG_NODE_OLD backup "$OLDFILE"
|
||||
|
||||
printe "Changing node name in new backup file $NEWFILE ..."
|
||||
exec_other_command_node $ERLANG_NODE_OLD mnesia_change_nodename "$ERLANG_NODE_OLD" "$ERLANG_NODE" "$OLDFILE" "$NEWFILE"
|
||||
|
||||
printe "Stopping old ejabberd..."
|
||||
exec_other_command_node $ERLANG_NODE_OLD "stop"
|
||||
wait_status_node $ERLANG_NODE_OLD 3 30 2 && stop_epmd
|
||||
|
||||
printe "Moving old mnesia spool files to backup subdirectory $SPOOL_DIR_BACKUP ..."
|
||||
mv $SPOOL_DIR/*.DAT $SPOOL_DIR_BACKUP
|
||||
mv $SPOOL_DIR/*.DCD $SPOOL_DIR_BACKUP
|
||||
mv $SPOOL_DIR/*.LOG $SPOOL_DIR_BACKUP
|
||||
|
||||
printe "Starting ejabberd with new node name $ERLANG_NODE ..."
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -detached
|
||||
wait_status 0 30 2
|
||||
exec_other_command "status"
|
||||
|
||||
printe "Installing fallback of new mnesia..."
|
||||
exec_other_command install_fallback "$NEWFILE"
|
||||
|
||||
printe "Stopping new ejabberd..."
|
||||
exec_other_command "stop"
|
||||
wait_status 3 30 2 && stop_epmd
|
||||
|
||||
printe "Finished, now you can start ejabberd normally"
|
||||
}
|
||||
|
||||
# main
|
||||
case $1 in
|
||||
start)
|
||||
@@ -501,6 +614,9 @@ case $1 in
|
||||
set_dist_client
|
||||
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
|
||||
;;
|
||||
mnesia_change)
|
||||
mnesia_change $2
|
||||
;;
|
||||
post_waiter)
|
||||
post_waiter_waiting
|
||||
;;
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Test shell scripts
|
||||
if: matrix.otp == '27'
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
- name: Run XMPP Interoperability Tests against CI server.
|
||||
if: matrix.otp == '27'
|
||||
continue-on-error: true
|
||||
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.5.0
|
||||
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.6.0
|
||||
with:
|
||||
domain: 'localhost'
|
||||
adminAccountUsername: 'admin'
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout ejabberd-contrib
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: processone/ejabberd-contrib
|
||||
path: .ejabberd-modules/sources/ejabberd-contrib
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
gem install --no-document --user-install fpm
|
||||
echo $HOME/.local/share/gem/ruby/*/bin >> $GITHUB_PATH
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build binary archives
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
if: github.ref_type == 'tag'
|
||||
steps:
|
||||
- name: Download packages
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: ejabberd-packages
|
||||
- name: Draft Release
|
||||
|
||||
@@ -31,9 +31,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp: ['20', '25', '26', '27', '28']
|
||||
otp: ['24', '25', '26', '27', '28']
|
||||
rebar: ['rebar', 'rebar3']
|
||||
exclude:
|
||||
- otp: '24'
|
||||
rebar: 'rebar'
|
||||
- otp: '27'
|
||||
rebar: 'rebar'
|
||||
- otp: '28'
|
||||
@@ -44,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Get old compatible Rebar binaries
|
||||
if: matrix.otp < 24
|
||||
@@ -184,7 +186,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Prepare libraries
|
||||
run: |
|
||||
@@ -307,7 +309,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Prepare libraries
|
||||
run: |
|
||||
|
||||
@@ -1,3 +1,55 @@
|
||||
## Version 25.08
|
||||
|
||||
#### API Commands
|
||||
|
||||
- `ban_account`: Run `sm_kick_user` event when kicking account ([#4415](https://github.com/processone/ejabberd/issues/4415))
|
||||
- `ban_account`: No need to change password ([#4415](https://github.com/processone/ejabberd/issues/4415))
|
||||
- `mnesia_change`: New command in `ejabberdctl` script that helps changing the mnesia node name
|
||||
|
||||
#### Configuration
|
||||
|
||||
- Rename `auth_password_types_hidden_in_scram1` option to `auth_password_types_hidden_in_sasl1`
|
||||
- `econf`: If a host in configuration is encoded IDNA, decode it ([#3519](https://github.com/processone/ejabberd/issues/3519))
|
||||
- `ejabberd_config`: New predefined keyword `HOST_URL_ENCODE`
|
||||
- `ejabberd.yml.example`: Use `HOST_URL_ENCODE` to handle case when vhost is non-latin1
|
||||
- `mod_conversejs`: Add option `conversejs_plugins` ([#4413](https://github.com/processone/ejabberd/issues/4413))
|
||||
- `mod_matrix_gw`: Add `leave_timeout` option ([#4386](https://github.com/processone/ejabberd/issues/4386))
|
||||
|
||||
#### Documentation and Tests
|
||||
|
||||
- `COMPILE.md`: Mention dependencies and add link to Docs ([#4431](https://github.com/processone/ejabberd/issues/4431))
|
||||
- `ejabberd_doc`: Document commands tags for modules
|
||||
- CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action ([#4425](https://github.com/processone/ejabberd/issues/4425))
|
||||
- Runtime: Raise the minimum Erlang tested to Erlang/OTP 24
|
||||
|
||||
#### Installers and Container
|
||||
|
||||
- Bump Erlang/OTP version to 27.3.4.2
|
||||
- Bump OpenSSL version to 3.5.2
|
||||
- `make-binaries`: Disable Linux-PAM's `logind` support
|
||||
|
||||
#### Core and Modules
|
||||
|
||||
- Bump `p1_acme` to fix `'AttributePKCS-10'` and OTP 28 ([processone/p1_acme#4](https://github.com/processone/p1_acme/issues/4))
|
||||
- Prevent loops in `xml_compress:decode` with corrupted data
|
||||
- `ejabberd_auth_mnesia`: Fix issue with filtering duplicates in `get_users()`
|
||||
- `ejabberd_listener`: Add secret in temporary unix domain socket path ([#4422](https://github.com/processone/ejabberd/issues/4422))
|
||||
- `ejabberd_listener`: Log error when cannot set definitive unix socket ([#4422](https://github.com/processone/ejabberd/issues/4422))
|
||||
- `ejabberd_listener`: Try to create provisional socket in final directory ([#4422](https://github.com/processone/ejabberd/issues/4422))
|
||||
- `ejabberd_logger`: Print log lines colorized in console when using rebar3
|
||||
- `mod_conversejs`: Ensure assets_path ends in `/` as required by Converse ([#4414](https://github.com/processone/ejabberd/issues/4414))
|
||||
- `mod_conversejs`: Ensure plugins URL is separated with `/` ([#4413](https://github.com/processone/ejabberd/issues/4413))
|
||||
- `mod_http_upload`: Encode URLs into IDNA when showing to XMPP client ([#3519](https://github.com/processone/ejabberd/issues/3519))
|
||||
- `mod_matrix_gw`: Add support for null values in `is_canonical_json` ([#4421](https://github.com/processone/ejabberd/issues/4421))
|
||||
- `mod_matrix_gw`: Don't send empty direct Matrix messages ([#4420](https://github.com/processone/ejabberd/issues/4420))
|
||||
- `mod_matrix_gw`: Matrix gateway updates
|
||||
- `mod_muc`: Report db failures when restoring rooms
|
||||
- `mod_muc`: Unsubscribe users from members-only rooms when expelled ([#4412](https://github.com/processone/ejabberd/issues/4412))
|
||||
- `mod_providers`: New module to serve easily XMPP Providers files
|
||||
- `mod_register`: Don't duplicate welcome subject and message
|
||||
- `mod_scram_upgrade`: Fix format of passwords updates
|
||||
- `mod_scram_upgrade`: Only offer upgrades to methods that aren't already stored
|
||||
|
||||
## Version 25.07
|
||||
|
||||
#### Security fix
|
||||
|
||||
@@ -65,6 +65,11 @@ To configure the compilation, features, install paths...
|
||||
|
||||
./configure --help
|
||||
|
||||
The build tool automatically downloads and compiles the
|
||||
erlang libraries that [ejabberd depends on][docs-repo].
|
||||
|
||||
[docs-repo]: https://docs.ejabberd.im/developer/repositories/
|
||||
|
||||
|
||||
Install in the System
|
||||
---------------------
|
||||
|
||||
+1
-1
@@ -1072,7 +1072,7 @@ Let's summarize the differences between both container images. Legend:
|
||||
| Generated by | [container.yml](https://github.com/processone/ejabberd/blob/master/.github/workflows/container.yml) | [tests.yml](https://github.com/processone/docker-ejabberd/blob/master/.github/workflows/tests.yml) |
|
||||
| Built for | stable releases <br /> `master` branch | stable releases <br /> [`master` branch zip](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) |
|
||||
| Architectures | `linux/amd64` <br /> `linux/arm64` | `linux/amd64` |
|
||||
| Software | Erlang/OTP 27.3.4.1-alpine <br /> Elixir 1.18.4 | Alpine 3.19 <br /> Erlang/OTP 26.2 <br /> Elixir 1.15.7 |
|
||||
| Software | Erlang/OTP 27.3.4.2-alpine <br /> Elixir 1.18.4 | Alpine 3.19 <br /> Erlang/OTP 26.2 <br /> Elixir 1.15.7 |
|
||||
| Published in | [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/) <br /> [ghcr.io/processone/ecs](https://github.com/processone/docker-ejabberd/pkgs/container/ecs) |
|
||||
| :black_square_button: **Additional content** |
|
||||
| [ejabberd-contrib](#ejabberd-contrib) | included | not included |
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.59)
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 25.07` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 25.08` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
|
||||
AC_ARG_WITH(min-erlang,
|
||||
AS_HELP_STRING([--with-min-erlang=version],[set minimal required erlang version, default to OTP25]),
|
||||
|
||||
@@ -179,7 +179,7 @@ modules:
|
||||
mod_fail2ban: {}
|
||||
mod_http_api: {}
|
||||
mod_http_upload:
|
||||
put_url: https://@HOST@:5443/upload
|
||||
put_url: https://@HOST_URL_ENCODE@:5443/upload
|
||||
custom_headers:
|
||||
"Access-Control-Allow-Origin": "https://@HOST@"
|
||||
"Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
|
||||
|
||||
+119
-5
@@ -318,6 +318,13 @@ check_start()
|
||||
# allow sync calls
|
||||
wait_status()
|
||||
{
|
||||
wait_status_node "$ERLANG_NODE" $1 $2 $3
|
||||
}
|
||||
|
||||
wait_status_node()
|
||||
{
|
||||
CONNECT_NODE=$1
|
||||
shift
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
timeout="$2"
|
||||
@@ -329,9 +336,9 @@ wait_status()
|
||||
status="$1"
|
||||
else
|
||||
exec_erl "$(uid ctl)" -hidden -noinput \
|
||||
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
|
||||
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
|
||||
-s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
|
||||
-extra "$CONNECT_NODE" $NO_TIMEOUT status > /dev/null
|
||||
status="$?"
|
||||
fi
|
||||
done
|
||||
@@ -340,19 +347,26 @@ wait_status()
|
||||
|
||||
exec_other_command()
|
||||
{
|
||||
exec_other_command_node $ERLANG_NODE "$@"
|
||||
}
|
||||
|
||||
exec_other_command_node()
|
||||
{
|
||||
CONNECT_NODE=$1
|
||||
shift
|
||||
if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
|
||||
|| [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
|
||||
|| [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
|
||||
exec_erl "$(uid ctl)" -hidden -noinput \
|
||||
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
|
||||
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
|
||||
-s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
|
||||
-extra "$CONNECT_NODE" $NO_TIMEOUT "$@"
|
||||
result=$?
|
||||
case $result in
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
exit $result
|
||||
return $result
|
||||
else
|
||||
exec_ctl_over_http_socket "$@"
|
||||
fi
|
||||
@@ -393,6 +407,103 @@ cd "$SPOOL_DIR" || {
|
||||
exit 6
|
||||
}
|
||||
|
||||
printe()
|
||||
{
|
||||
printf "\n"
|
||||
printf "\e[1;40;32m==> %s\e[0m\n" "$1"
|
||||
}
|
||||
|
||||
## Function copied from tools/make-installers
|
||||
user_agrees()
|
||||
{
|
||||
question="$*"
|
||||
|
||||
if [ -t 0 ]
|
||||
then
|
||||
printe "$question (y/n) [n]"
|
||||
read -r response
|
||||
case "$response" in
|
||||
[Yy]|[Yy][Ee][Ss])
|
||||
return 0
|
||||
;;
|
||||
[Nn]|[Nn][Oo]|'')
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
echo 'Please respond with "yes" or "no".'
|
||||
user_agrees "$question"
|
||||
;;
|
||||
esac
|
||||
else # Assume 'yes' if not running interactively.
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
mnesia_change()
|
||||
{
|
||||
ERLANG_NODE_OLD="$1"
|
||||
[ "$ERLANG_NODE_OLD" = "" ] \
|
||||
&& echo "Error: Please provide the old erlang node name, for example:" \
|
||||
&& echo " ejabberdctl mnesia_change ejabberd@oldmachine" \
|
||||
&& exit 1
|
||||
|
||||
SPOOL_DIR_BACKUP=$SPOOL_DIR/$ERLANG_NODE_OLD-backup/
|
||||
OLDFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE_OLD.backup
|
||||
NEWFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE.backup
|
||||
|
||||
printe "This changes your mnesia database from node name '$ERLANG_NODE_OLD' to '$ERLANG_NODE'"
|
||||
|
||||
[ -d "$SPOOL_DIR_BACKUP" ] && printe "WARNING! A backup of old node already exists in $SPOOL_DIR_BACKUP"
|
||||
|
||||
if ! user_agrees "Do you want to proceed?"
|
||||
then
|
||||
echo 'Operation aborted.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printe "Starting ejabberd with old node name $ERLANG_NODE_OLD ..."
|
||||
exec_erl "$ERLANG_NODE_OLD" $EJABBERD_OPTS -detached
|
||||
wait_status_node $ERLANG_NODE_OLD 0 30 2
|
||||
result=$?
|
||||
case $result in
|
||||
1) echo "There was a problem starting ejabberd with the old erlang node name. " \
|
||||
&& echo "Check for log errors in $EJABBERD_LOG_PATH" \
|
||||
&& exit $result;;
|
||||
*) :;;
|
||||
esac
|
||||
exec_other_command_node $ERLANG_NODE_OLD "status"
|
||||
|
||||
printe "Making backup of old database to file $OLDFILE ..."
|
||||
mkdir $SPOOL_DIR_BACKUP
|
||||
exec_other_command_node $ERLANG_NODE_OLD backup "$OLDFILE"
|
||||
|
||||
printe "Changing node name in new backup file $NEWFILE ..."
|
||||
exec_other_command_node $ERLANG_NODE_OLD mnesia_change_nodename "$ERLANG_NODE_OLD" "$ERLANG_NODE" "$OLDFILE" "$NEWFILE"
|
||||
|
||||
printe "Stopping old ejabberd..."
|
||||
exec_other_command_node $ERLANG_NODE_OLD "stop"
|
||||
wait_status_node $ERLANG_NODE_OLD 3 30 2 && stop_epmd
|
||||
|
||||
printe "Moving old mnesia spool files to backup subdirectory $SPOOL_DIR_BACKUP ..."
|
||||
mv $SPOOL_DIR/*.DAT $SPOOL_DIR_BACKUP
|
||||
mv $SPOOL_DIR/*.DCD $SPOOL_DIR_BACKUP
|
||||
mv $SPOOL_DIR/*.LOG $SPOOL_DIR_BACKUP
|
||||
|
||||
printe "Starting ejabberd with new node name $ERLANG_NODE ..."
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -detached
|
||||
wait_status 0 30 2
|
||||
exec_other_command "status"
|
||||
|
||||
printe "Installing fallback of new mnesia..."
|
||||
exec_other_command install_fallback "$NEWFILE"
|
||||
|
||||
printe "Stopping new ejabberd..."
|
||||
exec_other_command "stop"
|
||||
wait_status 3 30 2 && stop_epmd
|
||||
|
||||
printe "Finished, now you can start ejabberd normally"
|
||||
}
|
||||
|
||||
# main
|
||||
case $1 in
|
||||
start)
|
||||
@@ -452,6 +563,9 @@ case $1 in
|
||||
set_dist_client
|
||||
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
|
||||
;;
|
||||
mnesia_change)
|
||||
mnesia_change $2
|
||||
;;
|
||||
*)
|
||||
set_dist_client
|
||||
exec_other_command "$@"
|
||||
|
||||
+31
-5
@@ -39,20 +39,46 @@
|
||||
-else.
|
||||
-include_lib("kernel/include/logger.hrl").
|
||||
|
||||
-define(CLEAD, "\e[1"). % bold
|
||||
-define(CMID, "\e[0"). % normal
|
||||
-define(CCLEAN, "\e[0m"). % clean
|
||||
|
||||
-define(CDEFAULT, ";49;95m"). % light magenta
|
||||
-define(CDEBUG, ";49;90m"). % dark gray
|
||||
-define(CINFO, ";49;92m"). % green
|
||||
-define(CWARNING, ";49;93m"). % light yellow
|
||||
-define(CERROR, ";49;91m"). % light magenta
|
||||
-define(CCRITICAL,";49;31m"). % light red
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
begin ?LOG_DEBUG(Format, Args), ok end).
|
||||
begin ?LOG_DEBUG(Format, Args,
|
||||
#{clevel => ?CLEAD ++ ?CDEBUG,
|
||||
ctext => ?CMID ++ ?CDEBUG}),
|
||||
ok end).
|
||||
|
||||
-define(INFO_MSG(Format, Args),
|
||||
begin ?LOG_INFO(Format, Args), ok end).
|
||||
begin ?LOG_INFO(Format, Args,
|
||||
#{clevel => ?CLEAD ++ ?CINFO,
|
||||
ctext => ?CCLEAN}),
|
||||
ok end).
|
||||
|
||||
-define(WARNING_MSG(Format, Args),
|
||||
begin ?LOG_WARNING(Format, Args), ok end).
|
||||
begin ?LOG_WARNING(Format, Args,
|
||||
#{clevel => ?CLEAD ++ ?CWARNING,
|
||||
ctext => ?CMID ++ ?CWARNING}),
|
||||
ok end).
|
||||
|
||||
-define(ERROR_MSG(Format, Args),
|
||||
begin ?LOG_ERROR(Format, Args), ok end).
|
||||
begin ?LOG_ERROR(Format, Args,
|
||||
#{clevel => ?CLEAD ++ ?CERROR,
|
||||
ctext => ?CMID ++ ?CERROR}),
|
||||
ok end).
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
begin ?LOG_CRITICAL(Format, Args), ok end).
|
||||
begin ?LOG_CRITICAL(Format, Args,
|
||||
#{clevel => ?CLEAD++ ?CCRITICAL,
|
||||
ctext => ?CMID ++ ?CCRITICAL}),
|
||||
ok end).
|
||||
-endif.
|
||||
|
||||
%% Use only when trying to troubleshoot test problem with ExUnit
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
knock_restricted_join_rule :: boolean(),
|
||||
enforce_int_power_levels :: boolean(),
|
||||
implicit_room_creator :: boolean(),
|
||||
updated_redaction_rules :: boolean()
|
||||
updated_redaction_rules :: boolean(),
|
||||
hydra :: boolean()
|
||||
}).
|
||||
|
||||
+270
-25
@@ -2,12 +2,12 @@
|
||||
.\" Title: ejabberd.yml
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: 07/11/2025
|
||||
.\" Date: 08/22/2025
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "EJABBERD\&.YML" "5" "07/11/2025" "\ \&" "\ \&"
|
||||
.TH "EJABBERD\&.YML" "5" "08/22/2025" "\ \&" "\ \&"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
@@ -82,12 +82,12 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
|
||||
.sp
|
||||
Some options can be specified for particular virtual host(s) only using \fIhost_config\fR or \fIappend_host_config\fR options\&. Such options are called \fIlocal\fR\&. Examples are \fImodules\fR, \fIauth_method\fR and \fIdefault_db\fR\&. The options that cannot be defined per virtual host are called \fIglobal\fR\&. Examples are \fIloglevel\fR, \fIcertfiles\fR and \fIlisten\fR\&. It is a configuration mistake to put \fIglobal\fR options under \fIhost_config\fR or \fIappend_host_config\fR section \- ejabberd will refuse to load such configuration\&.
|
||||
.sp
|
||||
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/25\&.07/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
|
||||
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/25\&.08/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
|
||||
.sp
|
||||
Note that this document is intended to provide comprehensive description of all configuration options that can be consulted to understand the meaning of a particular option, its format and possible values\&. It will be quite hard to understand how to configure ejabberd by reading this document only \- for this purpose the reader is recommended to read online Configuration Guide available at https://docs\&.ejabberd\&.im/admin/configuration\&.
|
||||
.SH "TOP LEVEL OPTIONS"
|
||||
.sp
|
||||
This section describes top level options of ejabberd 25\&.07\&. The options that changed in this version are marked with 🟤\&.
|
||||
This section describes top level options of ejabberd 25\&.08\&. The options that changed in this version are marked with 🟤\&.
|
||||
.PP
|
||||
\fBaccess_rules\fR: \fI{AccessName: {allow|deny: ACLName|ACLDefinition}}\fR
|
||||
.RS 4
|
||||
@@ -461,10 +461,10 @@ option\&.
|
||||
.sp
|
||||
The default value is \fIplain\fR\&.
|
||||
.PP
|
||||
\fBauth_password_types_hidden_in_scram1 🟤\fR: \fI[plain | scram_sha1 | scram_sha256 | scram_sha512]\fR
|
||||
\fBauth_password_types_hidden_in_sasl1\fR: \fI[plain | scram_sha1 | scram_sha256 | scram_sha512]\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. List of password types that should not be offered in SCRAM1 authenticatication\&. Because SCRAM1, unlike SCRAM2, can\(cqt have list of available mechanisms tailored to individual user, it\(cqs possible that offered mechanisms will not be compatible with stored password, especially if new password type was added recently\&. This option allows disabling offering some mechanisms in SASL1, to a time until new password type will be available for all users\&.
|
||||
about this option: added in 25\&.07\&. List of password types that should not be offered in SASL1 authenticatication\&. Because SASL1, unlike SASL2, can\(cqt have list of available mechanisms tailored to individual user, it\(cqs possible that offered mechanisms will not be compatible with stored password, especially if new password type was added recently\&. This option allows disabling offering some mechanisms in SASL1, to a time until new password type will be available for all users\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
|
||||
@@ -703,13 +703,19 @@ A list of Erlang nodes to connect on ejabberd startup\&. This option is mostly i
|
||||
\fBdefault_db\fR: \fImnesia | sql\fR
|
||||
.RS 4
|
||||
\fIdatabase\&.md#default\-database|Default database\fR
|
||||
to store persistent data in ejabberd\&. Modules and other components (e\&.g\&. authentication) may have its own value\&. The default value is
|
||||
to store persistent data in ejabberd\&. Some components can be configured with specific toplevel options like
|
||||
\fIoauth_db_type\fR\&. Many modules can be configured with specific module options, usually named
|
||||
db_type\&. The default value is
|
||||
\fImnesia\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBdefault_ram_db\fR: \fImnesia | redis | sql\fR
|
||||
.RS 4
|
||||
Default volatile (in\-memory) storage for ejabberd\&. Modules and other components (e\&.g\&. session management) may have its own value\&. The default value is
|
||||
Default volatile (in\-memory) storage for ejabberd\&. Some components can be configured with specific toplevel options like
|
||||
\fIrouter_db_type\fR
|
||||
and
|
||||
\fIsm_db_type\fR\&. Some modules can be configured with specific module options, usually named
|
||||
ram_db_type\&. The default value is
|
||||
\fImnesia\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -957,7 +963,7 @@ List of one or more
|
||||
option\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBhosts_alias 🟤\fR: \fI{Alias: Host}\fR
|
||||
\fBhosts_alias\fR: \fI{Alias: Host}\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. Define aliases for existing vhosts managed by ejabberd\&. An alias may be a regexp expression\&. This option is only consulted by the
|
||||
@@ -1280,7 +1286,7 @@ This option can be used to tune tick time parameter of
|
||||
.RS 4
|
||||
Whether to use the
|
||||
\fIdatabase\&.md#default\-and\-new\-schemas|new SQL schema\fR\&. All schemas are located at
|
||||
https://github\&.com/processone/ejabberd/tree/25\&.07/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
|
||||
https://github\&.com/processone/ejabberd/tree/25\&.08/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
|
||||
\fInew\fR
|
||||
schema can handle several XMPP domains in a single ejabberd database\&. Using this
|
||||
\fInew\fR
|
||||
@@ -1547,25 +1553,25 @@ XMPP Core: section 7\&.7\&.2\&.2\&. The default value is
|
||||
\fIcloseold\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBrest_proxy 🟤\fR: \fIHost\fR
|
||||
\fBrest_proxy\fR: \fIHost\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. Address of a HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
|
||||
.RE
|
||||
.PP
|
||||
\fBrest_proxy_password 🟤\fR: \fIstring()\fR
|
||||
\fBrest_proxy_password\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. Password used to authenticate to HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
|
||||
.RE
|
||||
.PP
|
||||
\fBrest_proxy_port 🟤\fR: \fI1\&.\&.65535\fR
|
||||
\fBrest_proxy_port\fR: \fI1\&.\&.65535\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. Port of a HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
|
||||
.RE
|
||||
.PP
|
||||
\fBrest_proxy_username 🟤\fR: \fIstring()\fR
|
||||
\fBrest_proxy_username\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 25\&.07\&. Username used to authenticate to HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
|
||||
@@ -2101,7 +2107,7 @@ seconds\&.
|
||||
.RE
|
||||
.SH "MODULES"
|
||||
.sp
|
||||
This section describes modules options of ejabberd 25\&.07\&. The modules that changed in this version are marked with 🟤\&.
|
||||
This section describes modules options of ejabberd 25\&.08\&. The modules that changed in this version are marked with 🟤\&.
|
||||
.SS "mod_adhoc"
|
||||
.sp
|
||||
def:ad\-hoc command
|
||||
@@ -2261,6 +2267,8 @@ ejabberdctl srg_create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g1
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#accounts|accounts\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#erlang|erlang\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#last|last\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#private|private\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#roster|roster\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#session|session\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#shared_roster_group|shared_roster_group\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#stanza|stanza\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#statistics|statistics\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#vcard|vcard\fR
|
||||
.RE
|
||||
.SS "mod_admin_update_sql"
|
||||
.sp
|
||||
@@ -2455,7 +2463,7 @@ Same as top\-level
|
||||
option, but applied to this module only\&.
|
||||
.RE
|
||||
.RE
|
||||
.SS "mod_antispam 🟤"
|
||||
.SS "mod_antispam"
|
||||
.sp
|
||||
\fINote\fR about this option: added in 25\&.07\&.
|
||||
.sp
|
||||
@@ -3034,7 +3042,7 @@ modules:
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_conversejs 🟤"
|
||||
.SS "mod_conversejs"
|
||||
.sp
|
||||
\fINote\fR about this option: improved in 25\&.07\&.
|
||||
.sp
|
||||
@@ -3080,6 +3088,17 @@ about this option: added in 22\&.05\&. Specify additional options to be passed t
|
||||
Converse configuration\&. Only boolean, integer and string values are supported; lists are not supported\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBconversejs_plugins\fR: \fI[Filename]\fR
|
||||
.RS 4
|
||||
List of additional local files to include as scripts in the homepage\&. Please make sure those files are available in the path specified in
|
||||
\fIconversejs_resources\fR
|
||||
option, in subdirectory
|
||||
\fIplugins/\fR\&. If using the public Converse client, then
|
||||
\fI"libsignal"\fR
|
||||
gets replaced with the URL of the public library\&. The default value is
|
||||
\fI[]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBconversejs_resources\fR: \fIPath\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
@@ -3138,6 +3157,7 @@ listen:
|
||||
modules:
|
||||
mod_bosh: {}
|
||||
mod_conversejs:
|
||||
conversejs_plugins: ["libsignal"]
|
||||
websocket_url: "ws://@HOST@:5280/websocket"
|
||||
.fi
|
||||
.if n \{\
|
||||
@@ -3161,7 +3181,9 @@ listen:
|
||||
|
||||
modules:
|
||||
mod_conversejs:
|
||||
conversejs_resources: "/home/ejabberd/conversejs\-9\&.0\&.0/package/dist"
|
||||
conversejs_resources: "/home/ejabberd/conversejs\-x\&.y\&.z/package/dist"
|
||||
conversejs_plugins: ["libsignal\-protocol\&.min\&.js"]
|
||||
# File path is: /home/ejabberd/conversejs\-x\&.y\&.z/package/dist/plugins/libsignal\-protocol\&.min\&.js
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
@@ -3424,6 +3446,8 @@ hour\&.
|
||||
The number of C2S authentication failures to trigger the IP ban\&. The default value is
|
||||
\fI20\fR\&.
|
||||
.RE
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#accounts|accounts\fR
|
||||
.RE
|
||||
.SS "mod_host_meta"
|
||||
.sp
|
||||
@@ -3784,7 +3808,9 @@ A name of the service in the Service Discovery\&. The default value is
|
||||
.RS 4
|
||||
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword
|
||||
\fI@HOST@\fR
|
||||
is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is
|
||||
is replaced with the virtual host name\&. And
|
||||
\fI@HOST_URL_ENCODE@\fR
|
||||
is replaced with the host name encoded for URL, useful when your virtual hosts contain non\-latin characters\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is
|
||||
\fI"https://@HOST@:5443/upload"\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -4147,12 +4173,14 @@ option, but applied to this module only\&.
|
||||
When this option is disabled, for each individual subscriber a separate mucsub message is stored\&. With this option enabled, when a user fetches archive virtual mucsub, messages are generated from muc archives\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#mam|mam\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
|
||||
.RE
|
||||
.SS "mod_matrix_gw 🟤"
|
||||
.sp
|
||||
\fINote\fR about this option: improved in 25\&.07\&.
|
||||
\fINote\fR about this option: improved in 25\&.08\&.
|
||||
.sp
|
||||
Matrix gateway\&. Erlang/OTP 25 or higher is required to use this module\&. This module is available since ejabberd 24\&.02\&.
|
||||
Matrix gateway\&. Supports room versions 9, 10 and 11 since ejabberd 25\&.03; room versions 4 and higher since ejabberd 25\&.07; room version 12 (hydra rooms) since ejabberd 25\&.08\&. Erlang/OTP 25 or higher is required to use this module\&. This module is available since ejabberd 24\&.02\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
@@ -4182,6 +4210,13 @@ Value of the matrix signing key, in base64\&.
|
||||
Name of the matrix signing key\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBleave_timeout\fR: \fIinteger()\fR
|
||||
.RS 4
|
||||
Delay in seconds between a user leaving a MUC room and sending
|
||||
\fIleave\fR
|
||||
Matrix event\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBmatrix_domain\fR: \fIDomain\fR
|
||||
.RS 4
|
||||
Specify a domain in the Matrix federation\&. The keyword
|
||||
@@ -4206,6 +4241,11 @@ is the JID of the gateway service as set by the
|
||||
option\&. The default is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBnotary_servers\fR: \fI[Server, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
A list of notary servers\&.
|
||||
.RE
|
||||
.RE
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
@@ -5170,6 +5210,8 @@ about this option: added in 22\&.05\&. How many users can be subscribed to a roo
|
||||
API\&. The default value is
|
||||
\fI50\fR\&.
|
||||
.RE
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc|muc\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc_room|muc_room\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc_sub|muc_sub\fR
|
||||
.RE
|
||||
.SS "mod_muc_log"
|
||||
.sp
|
||||
@@ -5747,6 +5789,8 @@ modules:
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#offline|offline\fR
|
||||
.RE
|
||||
.SS "mod_ping"
|
||||
.sp
|
||||
@@ -5990,6 +6034,8 @@ Same as top\-level
|
||||
\fIuse_cache\fR
|
||||
option, but applied to this module only\&.
|
||||
.RE
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#private|private\fR
|
||||
.RE
|
||||
.SS "mod_privilege"
|
||||
.sp
|
||||
@@ -6157,6 +6203,199 @@ modules:
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_providers 🟤"
|
||||
.sp
|
||||
\fINote\fR about this option: added in 25\&.08\&.
|
||||
.sp
|
||||
This module serves JSON provider files API v2 as described by XMPP Providers\&.
|
||||
.sp
|
||||
It attempts to fill some properties gathering values automatically from your existing ejabberd configuration\&. Try enabling the module, check what values are displayed, and then customize using the options\&.
|
||||
.sp
|
||||
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR → \fIejabberd_http\fR → \fIlisten\-options\&.md#request_handlers|request_handlers\fR\&. Notice you should set in \fIlisten\&.md#ejabberd_http|ejabberd_http\fR the option \fIlisten\-options\&.md#tls|tls\fR enabled\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
.nr an-break-flag 1
|
||||
.br
|
||||
.ps +1
|
||||
\fBAvailable options:\fR
|
||||
.RS 4
|
||||
.PP
|
||||
\fBalternativeJids\fR: \fI[string()]\fR
|
||||
.RS 4
|
||||
List of JIDs (XMPP server domains) a provider offers for registration other than its main JID\&. The default value is
|
||||
\fI[]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBbusFactor\fR: \fIinteger()\fR
|
||||
.RS 4
|
||||
Bus factor of the XMPP service (i\&.e\&., the minimum number of team members that the service could not survive losing) or
|
||||
\fI\-1\fR
|
||||
for n/a\&. The default value is
|
||||
\fI\-1\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBfreeOfCharge\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Whether the XMPP service can be used for free\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBlanguages\fR: \fI[string()]\fR
|
||||
.RS 4
|
||||
List of language codes that your pages are available\&. Some options define URL where the keyword
|
||||
\fI@LANGUAGE_URL@\fR
|
||||
will be replaced with each of those language codes\&. The default value is a list with the language set in the option
|
||||
\fIlanguage\fR, for example:
|
||||
\fI[en]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBlegalNotice\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
Legal notice web page (per language)\&. The keyword
|
||||
\fI@LANGUAGE_URL@\fR
|
||||
is replaced with each language\&. The default value is
|
||||
\fI""\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBmaximumHttpFileUploadStorageTime\fR: \fIinteger()\fR
|
||||
.RS 4
|
||||
Maximum storage duration of each shared file (number in days,
|
||||
\fI0\fR
|
||||
for no limit or
|
||||
\fI\-1\fR
|
||||
for less than 1 day)\&. The default value is the same as option
|
||||
\fImax_days\fR
|
||||
from module
|
||||
\fImod_http_upload_quota\fR, or
|
||||
\fI0\fR
|
||||
otherwise\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBmaximumHttpFileUploadTotalSize\fR: \fIinteger()\fR
|
||||
.RS 4
|
||||
Maximum size of all shared files in total per user (number in megabytes (MB),
|
||||
\fI0\fR
|
||||
for no limit or
|
||||
\fI\-1\fR
|
||||
for less than 1 MB)\&. Attention: MB is used instead of MiB (e\&.g\&., 104,857,600 bytes = 100 MiB H 104 MB)\&. This property is not about the maximum size of each shared file, which is already retrieved via XMPP\&. The default value is the value of the shaper value of option
|
||||
\fIaccess_hard_quota\fR
|
||||
from module
|
||||
\fImod_http_upload_quota\fR, or
|
||||
\fI0\fR
|
||||
otherwise\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBmaximumMessageArchiveManagementStorageTime\fR: \fIinteger()\fR
|
||||
.RS 4
|
||||
Maximum storage duration of each exchanged message (number in days,
|
||||
\fI0\fR
|
||||
for no limit or
|
||||
\fI\-1\fR
|
||||
for less than 1 day)\&. The default value is
|
||||
\fI0\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBorganization\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
Type of organization providing the XMPP service\&. Allowed values are:
|
||||
\fIcompany\fR,
|
||||
\fI"commercial person"\fR,
|
||||
\fI"private person"\fR,
|
||||
\fIgovernmental\fR,
|
||||
\fI"non\-governmental"\fR
|
||||
or
|
||||
\fI""\fR\&. The default value is
|
||||
\fI""\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBpasswordReset\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
Password reset web page (per language) used for an automatic password reset (e\&.g\&., via email) or describing how to manually reset a password (e\&.g\&., by contacting the provider)\&. The keyword
|
||||
\fI@LANGUAGE_URL@\fR
|
||||
is replaced with each language\&. The default value is an URL built automatically if
|
||||
\fImod_register_web\fR
|
||||
is configured as a
|
||||
\fIrequest_handler\fR, or
|
||||
\fI""\fR
|
||||
otherwise\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBprofessionalHosting\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Whether the XMPP server is hosted with good internet connection speed, uninterruptible power supply, access protection and regular backups\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBserverLocations\fR: \fI[string()]\fR
|
||||
.RS 4
|
||||
List of language codes of Server/Backup locations\&. The default value is an empty list:
|
||||
\fI[]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBserverTesting\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Whether tests against the provider\(cqs server are allowed (e\&.g\&., certificate checks and uptime monitoring)\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBsince\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
Date since the XMPP service is available\&. The default value is an empty string:
|
||||
\fI""\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBwebsite\fR: \fIstring()\fR
|
||||
.RS 4
|
||||
Provider website\&. The keyword
|
||||
\fI@LANGUAGE_URL@\fR
|
||||
is replaced with each language\&. The default value is
|
||||
\fI""\fR\&.
|
||||
.RE
|
||||
.RE
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
.nr an-break-flag 1
|
||||
.br
|
||||
.ps +1
|
||||
\fBExample:\fR
|
||||
.RS 4
|
||||
.sp
|
||||
.if n \{\
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
listen:
|
||||
\-
|
||||
port: 443
|
||||
module: ejabberd_http
|
||||
tls: true
|
||||
request_handlers:
|
||||
/\&.well\-known/xmpp\-provider\-v2\&.json: mod_providers
|
||||
|
||||
modules:
|
||||
mod_providers:
|
||||
alternativeJids: ["example1\&.com", "example2\&.com"]
|
||||
busFactor: 1
|
||||
freeOfCharge: true
|
||||
languages: [ag, ao, bg, en]
|
||||
legalNotice: "http://@HOST@/legal/@LANGUAGE_URL@/"
|
||||
maximumHttpFileUploadStorageTime: 0
|
||||
maximumHttpFileUploadTotalSize: 0
|
||||
maximumMessageArchiveManagementStorageTime: 0
|
||||
organization: "non\-governmental"
|
||||
passwordReset: "http://@HOST@/reset/@LANGUAGE_URL@/"
|
||||
professionalHosting: true
|
||||
serverLocations: [ao, bg]
|
||||
serverTesting: true
|
||||
since: "2025\-12\-31"
|
||||
website: "http://@HOST@/website/@LANGUAGE_URL@/"
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_proxy65"
|
||||
.sp
|
||||
This module implements XEP\-0065: SOCKS5 Bytestreams\&. It allows ejabberd to act as a file transfer proxy between two XMPP clients\&.
|
||||
@@ -6618,8 +6857,10 @@ modules:
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
|
||||
.RE
|
||||
.SS "mod_pubsub_serverinfo 🟤"
|
||||
.SS "mod_pubsub_serverinfo"
|
||||
.sp
|
||||
\fINote\fR about this option: added in 25\&.07\&.
|
||||
.sp
|
||||
@@ -6741,6 +6982,8 @@ Same as top\-level
|
||||
\fIuse_cache\fR
|
||||
option, but applied to this module only\&.
|
||||
.RE
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
|
||||
.RE
|
||||
.SS "mod_push_keepalive"
|
||||
.sp
|
||||
@@ -7090,6 +7333,8 @@ modules:
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.sp
|
||||
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#roster|roster\fR
|
||||
.RE
|
||||
.SS "mod_s2s_bidi"
|
||||
.sp
|
||||
@@ -8617,7 +8862,7 @@ Should the operating system be revealed or not\&. The default value is
|
||||
.RE
|
||||
.SH "LISTENERS"
|
||||
.sp
|
||||
This section describes listeners options of ejabberd 25\&.07\&.
|
||||
This section describes listeners options of ejabberd 25\&.08\&.
|
||||
.sp
|
||||
TODO
|
||||
.SH "AUTHOR"
|
||||
@@ -8625,13 +8870,13 @@ TODO
|
||||
ProcessOne\&.
|
||||
.SH "VERSION"
|
||||
.sp
|
||||
This document describes the configuration file of ejabberd 25\&.07\&. Configuration options of other ejabberd versions may differ significantly\&.
|
||||
This document describes the configuration file of ejabberd 25\&.08\&. Configuration options of other ejabberd versions may differ significantly\&.
|
||||
.SH "REPORTING BUGS"
|
||||
.sp
|
||||
Report bugs to https://github\&.com/processone/ejabberd/issues
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
Default configuration file: https://github\&.com/processone/ejabberd/blob/25\&.07/ejabberd\&.yml\&.example
|
||||
Default configuration file: https://github\&.com/processone/ejabberd/blob/25\&.08/ejabberd\&.yml\&.example
|
||||
.sp
|
||||
Main site: https://ejabberd\&.im
|
||||
.sp
|
||||
|
||||
@@ -125,13 +125,13 @@ defmodule Ejabberd.MixProject do
|
||||
{:fast_yaml, "~> 1.0"},
|
||||
{:idna, "~> 6.0"},
|
||||
{:mqtree, "~> 1.0"},
|
||||
{:p1_acme, "~> 1.0.27"},
|
||||
{:p1_acme, ">= 1.0.28"},
|
||||
{:p1_oauth2, "~> 0.6"},
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:pkix, "~> 1.0"},
|
||||
{:stringprep, ">= 1.0.26"},
|
||||
{:xmpp, ">= 1.11.0"},
|
||||
{:yconf, ">= 1.0.18"}]
|
||||
{:xmpp, ">= 1.11.1"},
|
||||
{:yconf, ">= 1.0.21"}]
|
||||
++ cond_deps()
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
%{
|
||||
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.33", "e2542afb34f17ee3ca19d2b0f546a074922c2b99fb6b2acfb38160d7d0336ec3", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "4258009eb050b22aabe0c848e230bba58401a6895c58c2ff74dfb635e3c35900"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
|
||||
"eimp": {:hex, :eimp, "1.0.26", "c0b05f32e35629c4d9bcfb832ff879a92b0f92b19844bc7835e0a45635f2899a", [:rebar3], [{:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d96d4e8572b9dfc40f271e47f0cb1d8849373bc98a21223268781765ed52044c"},
|
||||
"epam": {:hex, :epam, "1.0.14", "aa0b85d27f4ef3a756ae995179df952a0721237e83c6b79d644347b75016681a", [:rebar3], [], "hexpm", "2f3449e72885a72a6c2a843f561add0fc2f70d7a21f61456930a547473d4d989"},
|
||||
"eredis": {:hex, :eredis, "1.7.1", "39e31aa02adcd651c657f39aafd4d31a9b2f63c6c700dc9cece98d4bc3c897ab", [:mix, :rebar3], [], "hexpm", "7c2b54c566fed55feef3341ca79b0100a6348fd3f162184b7ed5118d258c3cc1"},
|
||||
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
|
||||
"esip": {:hex, :esip, "1.0.58", "96bf0c07271f86f03f42778d4a1237099baec0714d00b0b815eb42d0007f73b4", [:rebar3], [{:fast_tls, "1.1.24", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.20", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "e0f4204a5ede0fa7d00da3cc42f6440aa362bac7faf536f71ea29fa3f0fa7c75"},
|
||||
"esip": {:hex, :esip, "1.0.59", "eb202f8c62928193588091dfedbc545fe3274c34ecd209961f86dcb6c9ebce88", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.21", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "0bdf2e3c349dc0b144f173150329e675c6a51ac473d7a0b2e362245faad3fbe6"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
|
||||
"exsync": {:hex, :exsync, "0.4.1", "0a14fe4bfcb80a509d8a0856be3dd070fffe619b9ba90fec13c58b316c176594", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "cefb22aa805ec97ffc5b75a4e1dc54bcaf781e8b32564bf74abbe5803d1b5178"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.15", "d74f5df191784744726a5b1ae9062522c606334f11086363385eb3b772d91357", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dd14ba6c12521af5cfe6923e73e3d545f4a0897dc66bfab5287fbb7ae3962eab"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.1.24", "5492125689e3d84c101323a0bff3d3996b92a903832530fe4f0935ed30b1b7d1", [:rebar3], [{:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "fff88ada39fad10464567a160643f4529ef4aed49d156919f5d1f415b6cdbbb6"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.1.25", "da8ed6f05a2452121b087158b17234749f36704c1f2b74dc51db99a1e27ed5e8", [:rebar3], [{:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "59e183b5740e670e02b8aa6be673b5e7779e5fe5bfcc679fe2d4993d1949a821"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.57", "31efc0f9bceda92069704f7a25830407da5dc3dad1272b810d6f2e13e73cc11a", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "eec34e90adacafe467d5ddab635a014ded73b98b4061554b2d1972173d929c39"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.39", "2e71168091949bab0e5f583b340a99072b4d22d93eb86624e7850a12b1517be4", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "24c7b9ab9e2b9269d64e45f4a2a1280966adb17d31e63365cfd3ee277fb0a78d"},
|
||||
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
|
||||
@@ -24,16 +24,16 @@
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
|
||||
"mqtree": {:hex, :mqtree, "1.0.19", "d769c25f898810725fc7db0dbffe5f72098647048b1be2e6d772f1c2f31d8476", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "c81065715c49a1882812f80a5ae2d842e80dd3f2d130530df35990248bf8ce3c"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
|
||||
"p1_acme": {:hex, :p1_acme, "1.0.27", "43d565cf0e22da51033ae329e773ccad0f44c4cb9c7199d0863f522e570f2767", [:rebar3], [{:base64url, "~> 1.0", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~> 1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.17", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "aa64b6a8856b1a229a128bea27631de2e1a2219835e3a833fa11137143a8d773"},
|
||||
"p1_acme": {:hex, :p1_acme, "1.0.28", "64d9c17f5412aa92d75b29206b2b984d734a4fe1b7eacb66c3d7a7c697ac612c", [:rebar3], [{:base64url, "~> 1.0", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~> 1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.17", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "ce686986de3f9d5fd285afe87523cb45329a349c6c6be7acc1ed916725d46423"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.26", "574d07c9936c53b1ec3556db3cf064cc14a6c39039835b3d940471bfa5ac8e2b", [:rebar3], [], "hexpm", "ea138083f2c54719b9cf549dbf5802a288b0019ea3e5449b354c74cc03fafdec"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.14", "1c5f82535574de87e2059695ac4b91f8f9aebacbc1c80287dae6f02552d47aea", [:rebar3], [], "hexpm", "1fd3ac474e43722d9d5a87c6df8d36f698ed87af7bb81cbbb66361451d99ae8f"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.34", "d36bd0d6c9765a47d075a87ad5e3fc3bfd153bdc4c07a1217b9979f33f73e9aa", [:rebar3], [{:xmpp, "~> 1.11.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "cb0e32e086c9c35d0e3e966e3863d832737c7b4d2b5f147316a465c0b243ea7f"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.35", "e13d89f14d717553e85c88a152ce77461916b013d88fcb851e354a0b332d4218", [:rebar3], [{:xmpp, "~> 1.11.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "e99594446c411c660696795b062336f5c4bd800451d8f620bb4d4ce304e255c2"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.28", "9a7088a98d788b4c4880fd3c82d0c135650db13f2e4ef7e10db179791bc94d59", [:rebar3], [], "hexpm", "c49bd44bc4a40ad996691af826dd7e0aa56d4d0cd730817190a1f84d1a7f0033"},
|
||||
"pkix": {:hex, :pkix, "1.0.10", "d3bfadf7b7cfe2a3636f1b256c9cce5f646a07ce31e57ee527668502850765a0", [:rebar3], [], "hexpm", "e02164f83094cb124c41b1ab28988a615d54b9adc38575f00f19a597a3ac5d0e"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.15", "e819defd280145c328457d7af897d2e45e8e5270e18812ee30b607c99cdd21af", [:rebar3], [], "hexpm", "3c0ba4e13322c2ad49de4e2ddd28311366adde54beae8dba9d9e3888f69d2857"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.33", "22f42866b4f6f3c238ea2b9cb6241791184ddedbab55e94a025511f46325f3ca", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "96f8b30bc50887f605b33b46bca1d248c19a879319b8c482790e3b4da5da98c0"},
|
||||
"stun": {:hex, :stun, "1.2.20", "62a149cea122a78a104b9e064a12d9e33105b26c23168ecf3aea6e0c26de0748", [:rebar3], [{:fast_tls, "1.1.24", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "79e49f826a4f7d522c939ab633d935c79d7d6b229e4cb7e05f62f33b50177414"},
|
||||
"stun": {:hex, :stun, "1.2.21", "735855314ad22cb7816b88597d2f5ca22e24aa5e4d6010a0ef3affb33ceed6a5", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3d7fe8efb9d05b240a6aa9a6bf8b8b7bff2d802895d170443c588987dc1e12d9"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
|
||||
"xmpp": {:hex, :xmpp, "1.11.0", "a3158c486c9b86a7090c361d876db622381f4312ede8c125d7a52ad390387932", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "34a191d6a3b74e8f0a42346f859e2cab5b3a2ae7e5c28f392e5cb56612e7ce85"},
|
||||
"yconf": {:hex, :yconf, "1.0.20", "f2b38db613fa826966e8d22bdc3e3ebae46919f2a27ab149a5a086c1d99d3bbd", [:rebar3], [{:fast_yaml, "1.0.39", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "f2b3d730756fc2e4afd1c0b0ab6efb99f0e448952d25dc15ed75ac1635bf8882"},
|
||||
"xmpp": {:hex, :xmpp, "1.11.1", "60181e7d3e8e48aa3b23b2792075cda37e2e507ec152490b866e61e5320cb1da", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "a5c933df904ab3cec15425da334e410ce84ec3ae7b81efe069e5db368a7b3716"},
|
||||
"yconf": {:hex, :yconf, "1.0.21", "dbae1589381e044529e112b7e0097c89d88df89e446ead53bd33e8d27e2bcc83", [:rebar3], [{:fast_yaml, "1.0.39", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "c524a5f1fd86875d85b469cc2e368c204f97cca1c3918736e21f5001c01d096c"},
|
||||
}
|
||||
|
||||
+1
-1
@@ -358,7 +358,7 @@
|
||||
{"Too many child elements","Túl sok gyermekelem"}.
|
||||
{"Too many <item/> elements","Túl sok <item/> elem"}.
|
||||
{"Too many <list/> elements","Túl sok <list/> elem"}.
|
||||
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Túl sok (~p) sikertelen hitelesítés erről az IP-címről (~ts) A cím ~ts-kor lesz feloldva UTC szerint"}.
|
||||
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Túl sok (~p) sikertelen hitelesítés erről az IP-címről (~s) A cím ~s-kor lesz feloldva UTC szerint"}.
|
||||
{"Too many receiver fields were specified","Túl sok fogadómező lett meghatározva"}.
|
||||
{"Too many unacked stanzas","Túl sok nyugtázatlan stanza"}.
|
||||
{"Too many users in this conference","Túl sok felhasználó ebben a konferenciában"}.
|
||||
|
||||
+7
-7
@@ -41,10 +41,10 @@
|
||||
{eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}
|
||||
}}},
|
||||
{if_var_true, sip,
|
||||
{esip, "~> 1.0.58", {git, "https://github.com/processone/esip", {tag, "1.0.58"}}}},
|
||||
{esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.59"}}}},
|
||||
{if_var_true, zlib,
|
||||
{ezlib, "~> 1.0.15", {git, "https://github.com/processone/ezlib", {tag, "1.0.15"}}}},
|
||||
{fast_tls, "~> 1.1.24", {git, "https://github.com/processone/fast_tls", {tag, "1.1.24"}}},
|
||||
{fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.25"}}},
|
||||
{fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.57"}}},
|
||||
{fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.39"}}},
|
||||
{idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
|
||||
@@ -64,21 +64,21 @@
|
||||
{luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}}
|
||||
}},
|
||||
{mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.19"}}},
|
||||
{p1_acme, "~> 1.0.27", {git, "https://github.com/processone/p1_acme", {tag, "1.0.27"}}},
|
||||
{p1_acme, "~> 1.0.28", {git, "https://github.com/processone/p1_acme", {tag, "1.0.28"}}},
|
||||
{if_var_true, mysql,
|
||||
{p1_mysql, "~> 1.0.26", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.26"}}}},
|
||||
{p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}},
|
||||
{if_var_true, pgsql,
|
||||
{p1_pgsql, "~> 1.1.34", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.34"}}}},
|
||||
{p1_pgsql, "~> 1.1.35", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.35"}}}},
|
||||
{p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.28"}}},
|
||||
{pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}},
|
||||
{if_var_true, sqlite,
|
||||
{sqlite3, "~> 1.1.15", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}},
|
||||
{stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.33"}}},
|
||||
{if_var_true, stun,
|
||||
{stun, "~> 1.2.20", {git, "https://github.com/processone/stun", {tag, "1.2.20"}}}},
|
||||
{xmpp, "~> 1.11.0", {git, "https://github.com/processone/xmpp", {tag, "1.11.0"}}},
|
||||
{yconf, "~> 1.0.20", {git, "https://github.com/processone/yconf", {tag, "1.0.20"}}}
|
||||
{stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.21"}}}},
|
||||
{xmpp, "~> 1.11.1", {git, "https://github.com/processone/xmpp", {tag, "1.11.1"}}},
|
||||
{yconf, "~> 1.0.21", {git, "https://github.com/processone/yconf", {tag, "1.0.21"}}}
|
||||
]}.
|
||||
|
||||
{gitonly_deps, [ejabberd_po]}.
|
||||
|
||||
+21
-21
@@ -4,9 +4,9 @@
|
||||
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.26">>},0},
|
||||
{<<"epam">>,{pkg,<<"epam">>,<<"1.0.14">>},0},
|
||||
{<<"eredis">>,{pkg,<<"eredis">>,<<"1.7.1">>},0},
|
||||
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.58">>},0},
|
||||
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.59">>},0},
|
||||
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.15">>},0},
|
||||
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.24">>},0},
|
||||
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.25">>},0},
|
||||
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.57">>},0},
|
||||
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.39">>},0},
|
||||
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0},
|
||||
@@ -14,18 +14,18 @@
|
||||
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.10">>},0},
|
||||
{<<"luerl">>,{pkg,<<"luerl">>,<<"1.2.3">>},0},
|
||||
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.19">>},0},
|
||||
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.27">>},0},
|
||||
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.28">>},0},
|
||||
{<<"p1_mysql">>,{pkg,<<"p1_mysql">>,<<"1.0.26">>},0},
|
||||
{<<"p1_oauth2">>,{pkg,<<"p1_oauth2">>,<<"0.6.14">>},0},
|
||||
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.34">>},0},
|
||||
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.35">>},0},
|
||||
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.28">>},0},
|
||||
{<<"pkix">>,{pkg,<<"pkix">>,<<"1.0.10">>},0},
|
||||
{<<"sqlite3">>,{pkg,<<"sqlite3">>,<<"1.1.15">>},0},
|
||||
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.33">>},0},
|
||||
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.20">>},0},
|
||||
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.21">>},0},
|
||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.1">>},1},
|
||||
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.11.0">>},0},
|
||||
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.20">>},0}]}.
|
||||
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.11.1">>},0},
|
||||
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.21">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
|
||||
@@ -33,9 +33,9 @@
|
||||
{<<"eimp">>, <<"C0B05F32E35629C4D9BCFB832FF879A92B0F92B19844BC7835E0A45635F2899A">>},
|
||||
{<<"epam">>, <<"AA0B85D27F4EF3A756AE995179DF952A0721237E83C6B79D644347B75016681A">>},
|
||||
{<<"eredis">>, <<"39E31AA02ADCD651C657F39AAFD4D31A9B2F63C6C700DC9CECE98D4BC3C897AB">>},
|
||||
{<<"esip">>, <<"96BF0C07271F86F03F42778D4A1237099BAEC0714D00B0B815EB42D0007F73B4">>},
|
||||
{<<"esip">>, <<"EB202F8C62928193588091DFEDBC545FE3274C34ECD209961F86DCB6C9EBCE88">>},
|
||||
{<<"ezlib">>, <<"D74F5DF191784744726A5B1AE9062522C606334F11086363385EB3B772D91357">>},
|
||||
{<<"fast_tls">>, <<"5492125689E3D84C101323A0BFF3D3996B92A903832530FE4F0935ED30B1B7D1">>},
|
||||
{<<"fast_tls">>, <<"DA8ED6F05A2452121B087158B17234749F36704C1F2B74DC51DB99A1E27ED5E8">>},
|
||||
{<<"fast_xml">>, <<"31EFC0F9BCEDA92069704F7A25830407DA5DC3DAD1272B810D6F2E13E73CC11A">>},
|
||||
{<<"fast_yaml">>, <<"2E71168091949BAB0E5F583B340A99072B4D22D93EB86624E7850A12B1517BE4">>},
|
||||
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
|
||||
@@ -43,27 +43,27 @@
|
||||
{<<"jose">>, <<"A903F5227417BD2A08C8A00A0CBCC458118BE84480955E8D251297A425723F83">>},
|
||||
{<<"luerl">>, <<"DF25F41944E57A7C4D9EF09D238BC3E850276C46039CFC12B8BB42ECCF36FCB1">>},
|
||||
{<<"mqtree">>, <<"D769C25F898810725FC7DB0DBFFE5F72098647048B1BE2E6D772F1C2F31D8476">>},
|
||||
{<<"p1_acme">>, <<"43D565CF0E22DA51033AE329E773CCAD0F44C4CB9C7199D0863F522E570F2767">>},
|
||||
{<<"p1_acme">>, <<"64D9C17F5412AA92D75B29206B2B984D734A4FE1B7EACB66C3D7A7C697AC612C">>},
|
||||
{<<"p1_mysql">>, <<"574D07C9936C53B1EC3556DB3CF064CC14A6C39039835B3D940471BFA5AC8E2B">>},
|
||||
{<<"p1_oauth2">>, <<"1C5F82535574DE87E2059695AC4B91F8F9AEBACBC1C80287DAE6F02552D47AEA">>},
|
||||
{<<"p1_pgsql">>, <<"D36BD0D6C9765A47D075A87AD5E3FC3BFD153BDC4C07A1217B9979F33F73E9AA">>},
|
||||
{<<"p1_pgsql">>, <<"E13D89F14D717553E85C88A152CE77461916B013D88FCB851E354A0B332D4218">>},
|
||||
{<<"p1_utils">>, <<"9A7088A98D788B4C4880FD3C82D0C135650DB13F2E4EF7E10DB179791BC94D59">>},
|
||||
{<<"pkix">>, <<"D3BFADF7B7CFE2A3636F1B256C9CCE5F646A07CE31E57EE527668502850765A0">>},
|
||||
{<<"sqlite3">>, <<"E819DEFD280145C328457D7AF897D2E45E8E5270E18812EE30B607C99CDD21AF">>},
|
||||
{<<"stringprep">>, <<"22F42866B4F6F3C238EA2B9CB6241791184DDEDBAB55E94A025511F46325F3CA">>},
|
||||
{<<"stun">>, <<"62A149CEA122A78A104B9E064A12D9E33105B26C23168ECF3AEA6E0C26DE0748">>},
|
||||
{<<"stun">>, <<"735855314AD22CB7816B88597D2F5CA22E24AA5E4D6010A0EF3AFFB33CEED6A5">>},
|
||||
{<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>},
|
||||
{<<"xmpp">>, <<"A3158C486C9B86A7090C361D876DB622381F4312EDE8C125D7A52AD390387932">>},
|
||||
{<<"yconf">>, <<"F2B38DB613FA826966E8D22BDC3E3EBAE46919F2A27AB149A5A086C1D99D3BBD">>}]},
|
||||
{<<"xmpp">>, <<"60181E7D3E8E48AA3B23B2792075CDA37E2E507EC152490B866E61E5320CB1DA">>},
|
||||
{<<"yconf">>, <<"DBAE1589381E044529E112B7E0097C89D88DF89E446EAD53BD33E8D27E2BCC83">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
|
||||
{<<"cache_tab">>, <<"4258009EB050B22AABE0C848E230BBA58401A6895C58C2FF74DFB635E3C35900">>},
|
||||
{<<"eimp">>, <<"D96D4E8572B9DFC40F271E47F0CB1D8849373BC98A21223268781765ED52044C">>},
|
||||
{<<"epam">>, <<"2F3449E72885A72A6C2A843F561ADD0FC2F70D7A21F61456930A547473D4D989">>},
|
||||
{<<"eredis">>, <<"7C2B54C566FED55FEEF3341CA79B0100A6348FD3F162184B7ED5118D258C3CC1">>},
|
||||
{<<"esip">>, <<"E0F4204A5EDE0FA7D00DA3CC42F6440AA362BAC7FAF536F71EA29FA3F0FA7C75">>},
|
||||
{<<"esip">>, <<"0BDF2E3C349DC0B144F173150329E675C6A51AC473D7A0B2E362245FAAD3FBE6">>},
|
||||
{<<"ezlib">>, <<"DD14BA6C12521AF5CFE6923E73E3D545F4A0897DC66BFAB5287FBB7AE3962EAB">>},
|
||||
{<<"fast_tls">>, <<"FFF88ADA39FAD10464567A160643F4529EF4AED49D156919F5D1F415B6CDBBB6">>},
|
||||
{<<"fast_tls">>, <<"59E183B5740E670E02B8AA6BE673B5E7779E5FE5BFCC679FE2D4993D1949A821">>},
|
||||
{<<"fast_xml">>, <<"EEC34E90ADACAFE467D5DDAB635A014DED73B98B4061554B2D1972173D929C39">>},
|
||||
{<<"fast_yaml">>, <<"24C7B9AB9E2B9269D64E45F4A2A1280966ADB17D31E63365CFD3EE277FB0A78D">>},
|
||||
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
|
||||
@@ -71,16 +71,16 @@
|
||||
{<<"jose">>, <<"0D6CD36FF8BA174DB29148FC112B5842186B68A90CE9FC2B3EC3AFE76593E614">>},
|
||||
{<<"luerl">>, <<"1B4B9D0CA5D7D280D1D2787A6A5EE9F5A212641B62BFF91556BAA53805DF3AED">>},
|
||||
{<<"mqtree">>, <<"C81065715C49A1882812F80A5AE2D842E80DD3F2D130530DF35990248BF8CE3C">>},
|
||||
{<<"p1_acme">>, <<"AA64B6A8856B1A229A128BEA27631DE2E1A2219835E3A833FA11137143A8D773">>},
|
||||
{<<"p1_acme">>, <<"CE686986DE3F9D5FD285AFE87523CB45329A349C6C6BE7ACC1ED916725D46423">>},
|
||||
{<<"p1_mysql">>, <<"EA138083F2C54719B9CF549DBF5802A288B0019EA3E5449B354C74CC03FAFDEC">>},
|
||||
{<<"p1_oauth2">>, <<"1FD3AC474E43722D9D5A87C6DF8D36F698ED87AF7BB81CBBB66361451D99AE8F">>},
|
||||
{<<"p1_pgsql">>, <<"CB0E32E086C9C35D0E3E966E3863D832737C7B4D2B5F147316A465C0B243EA7F">>},
|
||||
{<<"p1_pgsql">>, <<"E99594446C411C660696795B062336F5C4BD800451D8F620BB4D4CE304E255C2">>},
|
||||
{<<"p1_utils">>, <<"C49BD44BC4A40AD996691AF826DD7E0AA56D4D0CD730817190A1F84D1A7F0033">>},
|
||||
{<<"pkix">>, <<"E02164F83094CB124C41B1AB28988A615D54B9ADC38575F00F19A597A3AC5D0E">>},
|
||||
{<<"sqlite3">>, <<"3C0BA4E13322C2AD49DE4E2DDD28311366ADDE54BEAE8DBA9D9E3888F69D2857">>},
|
||||
{<<"stringprep">>, <<"96F8B30BC50887F605B33B46BCA1D248C19A879319B8C482790E3B4DA5DA98C0">>},
|
||||
{<<"stun">>, <<"79E49F826A4F7D522C939AB633D935C79D7D6B229E4CB7E05F62F33B50177414">>},
|
||||
{<<"stun">>, <<"3D7FE8EFB9D05B240A6AA9A6BF8B8B7BFF2D802895D170443C588987DC1E12D9">>},
|
||||
{<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>},
|
||||
{<<"xmpp">>, <<"34A191D6A3B74E8F0A42346F859E2CAB5B3A2AE7E5C28F392E5CB56612E7CE85">>},
|
||||
{<<"yconf">>, <<"F2B3D730756FC2E4AFD1C0B0AB6EFB99F0E448952D25DC15ED75AC1635BF8882">>}]}
|
||||
{<<"xmpp">>, <<"A5C933DF904AB3CEC15425DA334E410CE84EC3AE7B81EFE069E5DB368A7B3716">>},
|
||||
{<<"yconf">>, <<"C524A5F1FD86875D85B469CC2E368C204F97CCA1C3918736E21F5001C01D096C">>}]}
|
||||
].
|
||||
|
||||
@@ -482,6 +482,8 @@ domain() ->
|
||||
non_empty(binary()),
|
||||
fun(Val) ->
|
||||
try jid:tolower(jid:decode(Val)) of
|
||||
{<<"">>, <<"xn--", _/binary>> = Domain, <<"">>} ->
|
||||
unicode:characters_to_binary(idna:decode(binary_to_list(Domain)), utf8);
|
||||
{<<"">>, Domain, <<"">>} -> Domain;
|
||||
_ -> fail({bad_domain, Val})
|
||||
catch _:{bad_jid, _} ->
|
||||
|
||||
@@ -237,6 +237,7 @@ check_password(User, AuthzId, Server, Password, Digest, DigestGen) ->
|
||||
case check_password_with_authmodule(
|
||||
User, AuthzId, Server, Password, Digest, DigestGen) of
|
||||
{true, _AuthModule} -> true;
|
||||
{false, _ErrorAtom, _Reason} -> false;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
|
||||
@@ -151,13 +151,7 @@ get_users(Server, []) ->
|
||||
Users = mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, Server}], ['$1']}]),
|
||||
{_, Res} = lists:foldl(
|
||||
fun({U, S, _}, {{U2, S2}, _} = Acc) when U == U2 andalso S == S2 ->
|
||||
Acc;
|
||||
({U, S, _}, {_, Res}) ->
|
||||
{{U, S}, [{U, S} | Res]}
|
||||
end, {{none, none}, []}, Users),
|
||||
Res;
|
||||
misc:lists_uniq([{U, S} || {U, S, _} <- Users]);
|
||||
get_users(Server, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
|
||||
|
||||
@@ -416,8 +416,8 @@ unauthenticated_stream_features(#{lserver := LServer}) ->
|
||||
authenticated_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
||||
|
||||
inline_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], [], []}, [LServer]).
|
||||
inline_stream_features(#{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], [], []}, [LServer, State]).
|
||||
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) ->
|
||||
Type = ejabberd_auth:store_type(LServer),
|
||||
@@ -455,7 +455,7 @@ sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = St
|
||||
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
|
||||
(_) -> false
|
||||
end, Mechs -- Mechs1),
|
||||
case ejabberd_option:auth_password_types_hidden_in_scram1() of
|
||||
case ejabberd_option:auth_password_types_hidden_in_sasl1() of
|
||||
[] -> Mechs2;
|
||||
List ->
|
||||
Mechs3 = lists:foldl(
|
||||
|
||||
+11
-25
@@ -264,30 +264,31 @@ version() ->
|
||||
|
||||
-spec default_db(binary() | global, module()) -> atom().
|
||||
default_db(Host, Module) ->
|
||||
default_db(default_db, Host, Module, mnesia).
|
||||
default_db(default_db, db_type, Host, Module, mnesia).
|
||||
|
||||
-spec default_db(binary() | global, module(), atom()) -> atom().
|
||||
default_db(Host, Module, Default) ->
|
||||
default_db(default_db, Host, Module, Default).
|
||||
default_db(default_db, db_type, Host, Module, Default).
|
||||
|
||||
-spec default_ram_db(binary() | global, module()) -> atom().
|
||||
default_ram_db(Host, Module) ->
|
||||
default_db(default_ram_db, Host, Module, mnesia).
|
||||
default_db(default_ram_db, ram_db_type, Host, Module, mnesia).
|
||||
|
||||
-spec default_ram_db(binary() | global, module(), atom()) -> atom().
|
||||
default_ram_db(Host, Module, Default) ->
|
||||
default_db(default_ram_db, Host, Module, Default).
|
||||
default_db(default_ram_db, ram_db_type, Host, Module, Default).
|
||||
|
||||
-spec default_db(default_db | default_ram_db, binary() | global, module(), atom()) -> atom().
|
||||
default_db(Opt, Host, Mod, Default) ->
|
||||
-spec default_db(default_db | default_ram_db, db_type | ram_db_type, binary() | global, module(), atom()) -> atom().
|
||||
default_db(Opt, ModOpt, Host, Mod, Default) ->
|
||||
Type = get_option({Opt, Host}),
|
||||
DBMod = list_to_atom(atom_to_list(Mod) ++ "_" ++ atom_to_list(Type)),
|
||||
case code:ensure_loaded(DBMod) of
|
||||
{module, _} -> Type;
|
||||
{error, _} ->
|
||||
?WARNING_MSG("Module ~ts doesn't support database '~ts' "
|
||||
"defined in option '~ts', using "
|
||||
"'~ts' as fallback", [Mod, Type, Opt, Default]),
|
||||
"defined in toplevel option '~ts': will use the value "
|
||||
"set in ~ts option '~ts', or '~ts' as fallback",
|
||||
[Mod, Type, Opt, Mod, ModOpt, Default]),
|
||||
Default
|
||||
end.
|
||||
|
||||
@@ -507,7 +508,7 @@ get_predefined_keywords(Host) ->
|
||||
global ->
|
||||
[];
|
||||
_ ->
|
||||
[{<<"HOST">>, Host}]
|
||||
[{<<"HOST">>, Host}, {<<"HOST_URL_ENCODE">>, misc:url_encode(Host)}]
|
||||
end,
|
||||
Home = misc:get_home(),
|
||||
ConfigDirPath =
|
||||
@@ -631,22 +632,7 @@ callback_modules(external) ->
|
||||
end
|
||||
end, beams(external));
|
||||
callback_modules(all) ->
|
||||
lists_uniq(callback_modules(local) ++ callback_modules(external)).
|
||||
|
||||
-ifdef(OTP_BELOW_25).
|
||||
lists_uniq(List) ->
|
||||
{Res, _} = lists:foldr(
|
||||
fun(El, {Result, Existing} = Acc) ->
|
||||
case maps:is_key(El, Existing) of
|
||||
true -> Acc;
|
||||
_ -> {[El | Result], Existing#{El => true}}
|
||||
end
|
||||
end, {[], #{}}, List),
|
||||
Res.
|
||||
-else.
|
||||
lists_uniq(List) ->
|
||||
lists:uniq(List).
|
||||
-endif.
|
||||
misc:lists_uniq(callback_modules(local) ++ callback_modules(external)).
|
||||
|
||||
-spec validators(module(), [atom()], [any()]) -> econf:validators().
|
||||
validators(Mod, Disallowed, DK) ->
|
||||
|
||||
@@ -230,6 +230,8 @@ filter(_Host, captcha_host, _, _) ->
|
||||
filter(_Host, route_subdomains, _, _) ->
|
||||
warn_removed_option(route_subdomains, s2s_access),
|
||||
false;
|
||||
filter(_Host, auth_password_types_hidden_in_scram1, Val, _) ->
|
||||
{true, {auth_password_types_hidden_in_sasl1, Val}};
|
||||
filter(Host, modules, ModOpts, State) ->
|
||||
NoDialbackHosts = maps:get(remove_s2s_dialback, State, []),
|
||||
ModOpts1 = lists:filter(
|
||||
|
||||
@@ -1148,6 +1148,17 @@ get_commands_spec() ->
|
||||
desc = "Get list of commands, or help of a command (only ejabberdctl)",
|
||||
longdesc = "This command is exclusive for the ejabberdctl command-line script, "
|
||||
"don't attempt to execute it using any other API frontend."},
|
||||
#ejabberd_commands{name = mnesia_change, tags = [ejabberdctl, mnesia],
|
||||
desc = "Change the erlang node name in the mnesia database (only ejabberdctl)",
|
||||
longdesc = "This command internally calls the _`mnesia_change_nodename`_ API. "
|
||||
"This is a special command that starts and stops ejabberd several times: "
|
||||
"do not attempt to run this command when ejabberd is running. "
|
||||
"This command is exclusive for the ejabberdctl command-line script, "
|
||||
"don't attempt to execute it using any other API frontend.",
|
||||
note = "added in 25.08",
|
||||
args = [{old_node_name, string}],
|
||||
args_desc = ["Old erlang node name"],
|
||||
args_example = ["ejabberd@oldmachine"]},
|
||||
#ejabberd_commands{name = mnesia_info_ctl, tags = [ejabberdctl, mnesia],
|
||||
desc = "Show information of Mnesia system (only ejabberdctl)",
|
||||
note = "renamed in 24.02",
|
||||
|
||||
+33
-2
@@ -24,6 +24,7 @@
|
||||
%% API
|
||||
-export([man/0, man/1, have_a2x/0]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
@@ -46,7 +47,8 @@ man(Lang) ->
|
||||
DocOpts = maps:get(opts, Map, []),
|
||||
Example = maps:get(example, Map, []),
|
||||
Note = maps:get(note, Map, []),
|
||||
{[{M, Descr, DocOpts, #{example => Example, note => Note}}|Mods], SubMods};
|
||||
Apitags = get_module_apitags(M),
|
||||
{[{M, Descr, DocOpts, #{example => Example, note => Note, apitags => Apitags}}|Mods], SubMods};
|
||||
#{opts := DocOpts} ->
|
||||
{ParentMod, Backend} = strip_backend_suffix(M),
|
||||
{Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)};
|
||||
@@ -113,7 +115,8 @@ man(Lang) ->
|
||||
format_versions(Lang, Example) ++ [io_lib:nl()] ++
|
||||
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
|
||||
opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++
|
||||
format_example(0, Lang, Example)
|
||||
format_example(0, Lang, Example) ++ [io_lib:nl()] ++
|
||||
format_apitags(Lang, Example)
|
||||
end, lists:keysort(1, ModDoc1)),
|
||||
ListenOptions =
|
||||
[io_lib:nl(),
|
||||
@@ -190,6 +193,34 @@ format_versions(_Lang, #{note := Note}) when Note /= [] ->
|
||||
format_versions(_, _) ->
|
||||
[].
|
||||
|
||||
%% @format-begin
|
||||
get_module_apitags(M) ->
|
||||
AllCommands = ejabberd_commands:get_commands_definition(),
|
||||
Tags = [C#ejabberd_commands.tags || C <- AllCommands, C#ejabberd_commands.module == M],
|
||||
TagsClean =
|
||||
lists:sort(
|
||||
misc:lists_uniq(
|
||||
lists:flatten(Tags))),
|
||||
TagsStrings = [atom_to_list(C) || C <- TagsClean],
|
||||
TagFiltering =
|
||||
fun ("internal") ->
|
||||
false;
|
||||
([$v | Rest]) ->
|
||||
{error, no_integer} == string:to_integer(Rest);
|
||||
(_) ->
|
||||
true
|
||||
end,
|
||||
TagsFiltered = lists:filter(TagFiltering, TagsStrings),
|
||||
TagsUrls =
|
||||
[["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered],
|
||||
lists:join(", ", TagsUrls).
|
||||
|
||||
format_apitags(_Lang, #{apitags := TagsString}) when TagsString /= "" ->
|
||||
["**API Tags:** ", TagsString];
|
||||
format_apitags(_, _) ->
|
||||
[].
|
||||
%% @format-end
|
||||
|
||||
format_desc(Lang, #{desc := Desc}) ->
|
||||
tr_multi(Lang, Desc).
|
||||
|
||||
|
||||
+1
-14
@@ -718,7 +718,7 @@ file_format_error(Reason) ->
|
||||
url_decode_q_split_normalize(Path) ->
|
||||
{NPath, Query} = url_decode_q_split(Path),
|
||||
LPath = normalize_path([NPE
|
||||
|| NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|
||||
|| NPE <- str:tokens(misc:uri_decode(NPath), <<"/">>)]),
|
||||
{LPath, Query}.
|
||||
|
||||
% Code below is taken (with some modifications) from the yaws webserver, which
|
||||
@@ -746,19 +746,6 @@ url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 ->
|
||||
url_decode_q_split(<<>>, Ack) ->
|
||||
{path_norm_reverse(Ack), <<>>}.
|
||||
|
||||
%% @doc Decode a part of the URL and return string()
|
||||
path_decode(Path) -> path_decode(Path, <<>>).
|
||||
|
||||
path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
|
||||
Hex = list_to_integer([Hi, Lo], 16),
|
||||
if Hex == 0 -> exit(badurl);
|
||||
true -> ok
|
||||
end,
|
||||
path_decode(Tail, <<Acc/binary, Hex>>);
|
||||
path_decode(<<H, T/binary>>, Acc) when H /= 0 ->
|
||||
path_decode(T, <<Acc/binary, H>>);
|
||||
path_decode(<<>>, Acc) -> Acc.
|
||||
|
||||
path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T);
|
||||
path_norm_reverse(T) -> start_dir(0, <<"">>, T).
|
||||
|
||||
|
||||
+64
-35
@@ -113,12 +113,12 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
{Port, SockOpts}
|
||||
end,
|
||||
ExtraOpts2 = lists:keydelete(send_timeout, 1, ExtraOpts),
|
||||
case gen_udp:open(Port2, [binary,
|
||||
case {gen_udp:open(Port2, [binary,
|
||||
{active, false},
|
||||
{reuseaddr, true} |
|
||||
ExtraOpts2]) of
|
||||
{ok, Socket} ->
|
||||
set_definitive_udsocket(Port, Opts),
|
||||
ExtraOpts2]),
|
||||
set_definitive_udsocket(Port, Opts)} of
|
||||
{{ok, Socket}, ok} ->
|
||||
misc:set_proc_label({?MODULE, udp, Port}),
|
||||
case inet:sockname(Socket) of
|
||||
{ok, {Addr, Port1}} ->
|
||||
@@ -139,18 +139,18 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
{error, _} ->
|
||||
ok
|
||||
end;
|
||||
{error, Reason} = Err ->
|
||||
report_socket_error(Reason, EndPoint, Module),
|
||||
proc_lib:init_ack(Err)
|
||||
{error, Reason} ->
|
||||
return_socket_error(Reason, EndPoint, Module)
|
||||
end;
|
||||
{error, Reason} = Err ->
|
||||
report_socket_error(Reason, EndPoint, Module),
|
||||
proc_lib:init_ack(Err)
|
||||
{{error, Reason}, _} ->
|
||||
return_socket_error(Reason, EndPoint, Module);
|
||||
{_, {error, Reason} } ->
|
||||
return_socket_error(Reason, EndPoint, Module)
|
||||
end;
|
||||
init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
case listen_tcp(Port, SockOpts) of
|
||||
{ok, ListenSocket} ->
|
||||
set_definitive_udsocket(Port, Opts),
|
||||
case {listen_tcp(Port, SockOpts),
|
||||
set_definitive_udsocket(Port, Opts)} of
|
||||
{{ok, ListenSocket}, ok} ->
|
||||
case inet:sockname(ListenSocket) of
|
||||
{ok, {Addr, Port1}} ->
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
@@ -174,13 +174,13 @@ init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
{error, _} ->
|
||||
ok
|
||||
end;
|
||||
{error, Reason} = Err ->
|
||||
report_socket_error(Reason, EndPoint, Module),
|
||||
proc_lib:init_ack(Err)
|
||||
{error, Reason} ->
|
||||
return_socket_error(Reason, EndPoint, Module)
|
||||
end;
|
||||
{error, Reason} = Err ->
|
||||
report_socket_error(Reason, EndPoint, Module),
|
||||
proc_lib:init_ack(Err)
|
||||
{{error, Reason}, _} ->
|
||||
return_socket_error(Reason, EndPoint, Module);
|
||||
{_, {error, Reason}} ->
|
||||
return_socket_error(Reason, EndPoint, Module)
|
||||
end.
|
||||
|
||||
-spec listen_tcp(inet:port_number(), [gen_tcp:option()]) ->
|
||||
@@ -216,23 +216,39 @@ listen_tcp(Port, SockOpts) ->
|
||||
|
||||
setup_provisional_udsocket_dir(DefinitivePath) ->
|
||||
ProvisionalPath = get_provisional_udsocket_path(DefinitivePath),
|
||||
?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
|
||||
?INFO_MSG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
|
||||
[ProvisionalPath, DefinitivePath]),
|
||||
ProvisionalPath.
|
||||
ProvisionalPathAbsolute = relative_socket_to_mnesia(ProvisionalPath),
|
||||
create_base_dir(ProvisionalPathAbsolute),
|
||||
ProvisionalPathAbsolute.
|
||||
|
||||
get_provisional_udsocket_path(Path) ->
|
||||
PathBase64 = misc:term_to_base64(Path),
|
||||
ReproducibleSecret = binary:part(crypto:hash(sha, misc:atom_to_binary(erlang:get_cookie())), 1, 8),
|
||||
PathBase64 = misc:term_to_base64({ReproducibleSecret, Path}),
|
||||
PathBuild = filename:join(misc:get_home(), PathBase64),
|
||||
%% Shorthen the path, a long path produces a crash when opening the socket.
|
||||
binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}).
|
||||
DestPath = filename:join(filename:dirname(Path), PathBase64),
|
||||
case {byte_size(DestPath) > 107, byte_size(PathBuild) > 107} of
|
||||
{false, _} ->
|
||||
DestPath;
|
||||
{true, false} ->
|
||||
?INFO_MSG("The provisional Unix Domain Socket path ~ts is longer than 107, let's use home directory instead which is ~p", [DestPath, byte_size(PathBuild)]),
|
||||
PathBuild;
|
||||
{true, true} ->
|
||||
?ERROR_MSG("The Unix Domain Socket path ~ts is too long, "
|
||||
"and I cannot create the provisional file safely. "
|
||||
"Please configure a shorter path and try again.", [Path]),
|
||||
throw({error_socket_path_too_long, Path})
|
||||
end.
|
||||
|
||||
get_definitive_udsocket_path(<<"unix", _>> = Unix) ->
|
||||
Unix;
|
||||
get_definitive_udsocket_path(ProvisionalPath) ->
|
||||
PathBase64 = filename:basename(ProvisionalPath),
|
||||
{term, Path} = misc:base64_to_term(PathBase64),
|
||||
{term, {_, Path}} = misc:base64_to_term(PathBase64),
|
||||
relative_socket_to_mnesia(Path).
|
||||
|
||||
-spec set_definitive_udsocket(integer() | binary(), opts()) -> ok | {error, file:posix() | badarg}.
|
||||
|
||||
set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
|
||||
Prov = get_provisional_udsocket_path(Path),
|
||||
Usd = maps:get(unix_socket, Opts),
|
||||
@@ -263,16 +279,19 @@ set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
|
||||
end
|
||||
end,
|
||||
FinalPath = relative_socket_to_mnesia(Path),
|
||||
FinalPathDir = filename:dirname(FinalPath),
|
||||
case file:make_dir(FinalPathDir) of
|
||||
create_base_dir(FinalPath),
|
||||
file:rename(Prov, FinalPath);
|
||||
set_definitive_udsocket(Port, _Opts) when is_integer(Port) ->
|
||||
ok.
|
||||
|
||||
create_base_dir(Path) ->
|
||||
Dirname = filename:dirname(Path),
|
||||
case file:make_dir(Dirname) of
|
||||
ok ->
|
||||
file:change_mode(FinalPathDir, 8#00700);
|
||||
file:change_mode(Dirname, 8#00700);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
file:rename(Prov, FinalPath);
|
||||
set_definitive_udsocket(_Port, _Opts) ->
|
||||
ok.
|
||||
end.
|
||||
|
||||
relative_socket_to_mnesia(Path1) ->
|
||||
case filename:pathtype(Path1) of
|
||||
@@ -584,10 +603,20 @@ config_reloaded() ->
|
||||
end
|
||||
end, New).
|
||||
|
||||
-spec report_socket_error(inet:posix(), endpoint(), module()) -> ok.
|
||||
report_socket_error(Reason, EndPoint, Module) ->
|
||||
-spec return_socket_error(inet:posix(), endpoint(), module()) -> no_return().
|
||||
return_socket_error(Reason, EndPoint, Module) ->
|
||||
?ERROR_MSG("Failed to open socket at ~ts for ~ts: ~ts",
|
||||
[format_endpoint(EndPoint), Module, format_error(Reason)]).
|
||||
[format_endpoint(EndPoint), Module, format_error(Reason)]),
|
||||
return_init_error(Reason).
|
||||
|
||||
-ifdef(OTP_BELOW_26).
|
||||
return_init_error(Reason) ->
|
||||
proc_lib:init_ack({error, Reason}).
|
||||
-else.
|
||||
-spec return_init_error(inet:posix()) -> no_return().
|
||||
return_init_error(Reason) ->
|
||||
proc_lib:init_fail({error, Reason}, {exit, normal}).
|
||||
-endif.
|
||||
|
||||
-spec format_error(inet:posix() | atom()) -> string().
|
||||
format_error(Reason) ->
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
|
||||
-export_type([loglevel/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@@ -383,19 +385,21 @@ console_template() ->
|
||||
false ->
|
||||
[time, " [", level, "] " | msg()]
|
||||
end.
|
||||
msg() ->
|
||||
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
|
||||
msg, io_lib:nl()].
|
||||
-else.
|
||||
console_template() ->
|
||||
[time, " [", level, "] " | msg()].
|
||||
[time, " ", ?CLEAD, ?CDEFAULT, clevel, "[", level, "] ", ?CMID, ?CDEFAULT, ctext | msg()].
|
||||
msg() ->
|
||||
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
|
||||
msg, ?CCLEAN, io_lib:nl()].
|
||||
-endif.
|
||||
|
||||
file_template() ->
|
||||
[time, " [", level, "] ", pid,
|
||||
{mfa, ["@", mfa, {line, [":", line], []}], []}, " " | msg()].
|
||||
|
||||
msg() ->
|
||||
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
|
||||
msg, io_lib:nl()].
|
||||
|
||||
-spec reopen_log() -> ok.
|
||||
reopen_log() ->
|
||||
ok.
|
||||
|
||||
@@ -80,10 +80,11 @@ init([]) ->
|
||||
Schema = read_schema_file(),
|
||||
{ok, #state{schema = Schema}};
|
||||
false ->
|
||||
?CRITICAL_MSG("Node name mismatch: I'm [~ts], "
|
||||
"the database is owned by ~p", [MyNode, DbNodes]),
|
||||
?CRITICAL_MSG("Erlang node name mismatch: I'm running in node [~ts], "
|
||||
"but the mnesia database is owned by ~p", [MyNode, DbNodes]),
|
||||
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
|
||||
"or change node name in Mnesia", []),
|
||||
"or change node name in Mnesia by running: "
|
||||
"ejabberdctl mnesia_change ~ts", [hd(DbNodes)]),
|
||||
{stop, node_name_mismatch}
|
||||
end.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-export([auth_method/0, auth_method/1]).
|
||||
-export([auth_opts/0, auth_opts/1]).
|
||||
-export([auth_password_format/0, auth_password_format/1]).
|
||||
-export([auth_password_types_hidden_in_scram1/0, auth_password_types_hidden_in_scram1/1]).
|
||||
-export([auth_password_types_hidden_in_sasl1/0, auth_password_types_hidden_in_sasl1/1]).
|
||||
-export([auth_scram_hash/0, auth_scram_hash/1]).
|
||||
-export([auth_stored_password_types/0, auth_stored_password_types/1]).
|
||||
-export([auth_use_cache/0, auth_use_cache/1]).
|
||||
@@ -264,12 +264,12 @@ auth_password_format() ->
|
||||
auth_password_format(Host) ->
|
||||
ejabberd_config:get_option({auth_password_format, Host}).
|
||||
|
||||
-spec auth_password_types_hidden_in_scram1() -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||
auth_password_types_hidden_in_scram1() ->
|
||||
auth_password_types_hidden_in_scram1(global).
|
||||
-spec auth_password_types_hidden_in_scram1(global | binary()) -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||
auth_password_types_hidden_in_scram1(Host) ->
|
||||
ejabberd_config:get_option({auth_password_types_hidden_in_scram1, Host}).
|
||||
-spec auth_password_types_hidden_in_sasl1() -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||
auth_password_types_hidden_in_sasl1() ->
|
||||
auth_password_types_hidden_in_sasl1(global).
|
||||
-spec auth_password_types_hidden_in_sasl1(global | binary()) -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||
auth_password_types_hidden_in_sasl1(Host) ->
|
||||
ejabberd_config:get_option({auth_password_types_hidden_in_sasl1, Host}).
|
||||
|
||||
-spec auth_scram_hash() -> 'sha' | 'sha256' | 'sha512'.
|
||||
auth_scram_hash() ->
|
||||
|
||||
@@ -79,7 +79,7 @@ opt_type(auth_opts) ->
|
||||
end;
|
||||
opt_type(auth_stored_password_types) ->
|
||||
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
|
||||
opt_type(auth_password_types_hidden_in_scram1) ->
|
||||
opt_type(auth_password_types_hidden_in_sasl1) ->
|
||||
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
|
||||
opt_type(auth_password_format) ->
|
||||
econf:enum([plain, scram]);
|
||||
@@ -566,7 +566,7 @@ options() ->
|
||||
{auth_password_format, plain},
|
||||
{auth_scram_hash, sha},
|
||||
{auth_stored_password_types, []},
|
||||
{auth_password_types_hidden_in_scram1, []},
|
||||
{auth_password_types_hidden_in_sasl1, []},
|
||||
{auth_external_user_exists_check, true},
|
||||
{auth_use_cache,
|
||||
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
|
||||
|
||||
@@ -109,14 +109,20 @@ doc() ->
|
||||
desc =>
|
||||
?T("_`database.md#default-database|Default database`_ "
|
||||
"to store persistent data in ejabberd. "
|
||||
"Modules and other components (e.g. authentication) "
|
||||
"may have its own value. The default value is 'mnesia'.")}},
|
||||
"Some components can be configured with specific toplevel options "
|
||||
"like _`oauth_db_type`_. "
|
||||
"Many modules can be configured with specific module options, "
|
||||
"usually named `db_type`. "
|
||||
"The default value is 'mnesia'.")}},
|
||||
{default_ram_db,
|
||||
#{value => "mnesia | redis | sql",
|
||||
desc =>
|
||||
?T("Default volatile (in-memory) storage for ejabberd. "
|
||||
"Modules and other components (e.g. session management) "
|
||||
"may have its own value. The default value is 'mnesia'.")}},
|
||||
"Some components can be configured with specific toplevel options "
|
||||
"like _`router_db_type`_ and _`sm_db_type`_. "
|
||||
"Some modules can be configured with specific module options, "
|
||||
"usually named `ram_db_type`. "
|
||||
"The default value is 'mnesia'.")}},
|
||||
{queue_type,
|
||||
#{value => "ram | file",
|
||||
desc =>
|
||||
@@ -393,12 +399,12 @@ doc() ->
|
||||
"depends on the _`auth_scram_hash`_ option."), "",
|
||||
?T("The default value is 'plain'."), ""]}},
|
||||
|
||||
{auth_password_types_hidden_in_scram1,
|
||||
{auth_password_types_hidden_in_sasl1,
|
||||
#{value => "[plain | scram_sha1 | scram_sha256 | scram_sha512]",
|
||||
note => "added in 25.07",
|
||||
desc =>
|
||||
?T("List of password types that should not be offered in SCRAM1 authenticatication. "
|
||||
"Because SCRAM1, unlike SCRAM2, can't have list of available mechanisms tailored to "
|
||||
?T("List of password types that should not be offered in SASL1 authenticatication. "
|
||||
"Because SASL1, unlike SASL2, can't have list of available mechanisms tailored to "
|
||||
"individual user, it's possible that offered mechanisms will not be compatible "
|
||||
"with stored password, especially if new password type was added recently. "
|
||||
"This option allows disabling offering some mechanisms in SASL1, to a time until new "
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
-export([start_link/0, new/1, update/2, match/3, get_max_rate/1]).
|
||||
-export([reload_from_config/0]).
|
||||
-export([read_shaper_rules/2]).
|
||||
-export([validator/1, shaper_rules_validator/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
|
||||
+46
-37
@@ -43,10 +43,12 @@
|
||||
is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
|
||||
get_my_ipv4_address/0, get_my_ipv6_address/0, parse_ip_mask/1,
|
||||
crypto_hmac/3, crypto_hmac/4, uri_parse/1, uri_parse/2, uri_quote/1,
|
||||
uri_decode/1,
|
||||
json_encode/1, json_decode/1,
|
||||
set_proc_label/1,
|
||||
match_ip_mask/3, format_hosts_list/1, format_cycle/1, delete_dir/1,
|
||||
semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1]).
|
||||
semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1,
|
||||
lists_uniq/1]).
|
||||
|
||||
%% Deprecated functions
|
||||
-export([decode_base64/1, encode_base64/1]).
|
||||
@@ -71,45 +73,11 @@
|
||||
-type distance_cache() :: #{{string(), string()} => non_neg_integer()}.
|
||||
|
||||
-spec uri_parse(binary()|string()) -> {ok, string(), string(), string(), number(), string(), string()} | {error, term()}.
|
||||
-ifdef(USE_OLD_HTTP_URI).
|
||||
uri_parse(URL) when is_binary(URL) ->
|
||||
uri_parse(binary_to_list(URL));
|
||||
uri_parse(URL) ->
|
||||
uri_parse(URL, []).
|
||||
yconf:parse_uri(URL).
|
||||
|
||||
uri_parse(URL, Protocols) when is_binary(URL) ->
|
||||
uri_parse(binary_to_list(URL), Protocols);
|
||||
uri_parse(URL, Protocols) ->
|
||||
case http_uri:parse(URL, [{scheme_defaults, Protocols}]) of
|
||||
{ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
|
||||
{ok, atom_to_list(Scheme), UserInfo, Host, Port, Path, Query};
|
||||
{error, _} = E ->
|
||||
E
|
||||
end.
|
||||
|
||||
-else.
|
||||
uri_parse(URL) when is_binary(URL) ->
|
||||
uri_parse(binary_to_list(URL));
|
||||
uri_parse(URL) ->
|
||||
uri_parse(URL, [{http, 80}, {https, 443}]).
|
||||
|
||||
uri_parse(URL, Protocols) when is_binary(URL) ->
|
||||
uri_parse(binary_to_list(URL), Protocols);
|
||||
uri_parse(URL, Protocols) ->
|
||||
case uri_string:parse(URL) of
|
||||
#{scheme := Scheme, host := Host, port := Port, path := Path} = M1 ->
|
||||
{ok, Scheme, maps:get(userinfo, M1, ""), Host, Port, Path, maps:get(query, M1, "")};
|
||||
#{scheme := Scheme, host := Host, path := Path} = M2 ->
|
||||
case lists:keyfind(list_to_atom(Scheme), 1, Protocols) of
|
||||
{_, Port} ->
|
||||
{ok, Scheme, maps:get(userinfo, M2, ""), Host, Port, Path, maps:get(query, M2, "")};
|
||||
_ ->
|
||||
{error, unknown_protocol}
|
||||
end;
|
||||
{error, Atom, _} ->
|
||||
{error, Atom}
|
||||
end.
|
||||
-endif.
|
||||
yconf:parse_uri(URL, Protocols).
|
||||
|
||||
-ifdef(OTP_BELOW_25).
|
||||
-ifdef(OTP_BELOW_24).
|
||||
@@ -124,6 +92,26 @@ uri_quote(Data) ->
|
||||
uri_string:quote(Data).
|
||||
-endif.
|
||||
|
||||
%% @doc Decode a part of the URL and return string()
|
||||
%% -spec url_decode(binary()) -> bitstring().
|
||||
|
||||
-ifdef(OTP_BELOW_24).
|
||||
uri_decode(Path) -> uri_decode(Path, <<>>).
|
||||
|
||||
uri_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
|
||||
Hex = list_to_integer([Hi, Lo], 16),
|
||||
if Hex == 0 -> exit(badurl);
|
||||
true -> ok
|
||||
end,
|
||||
uri_decode(Tail, <<Acc/binary, Hex>>);
|
||||
uri_decode(<<H, T/binary>>, Acc) when H /= 0 ->
|
||||
uri_decode(T, <<Acc/binary, H>>);
|
||||
uri_decode(<<>>, Acc) -> Acc.
|
||||
-else.
|
||||
uri_decode(Path) ->
|
||||
uri_string:percent_decode(Path).
|
||||
-endif.
|
||||
|
||||
-ifdef(USE_OLD_CRYPTO_HMAC).
|
||||
crypto_hmac(Type, Key, Data) -> crypto:hmac(Type, Key, Data).
|
||||
crypto_hmac(Type, Key, Data, MacL) -> crypto:hmac(Type, Key, Data, MacL).
|
||||
@@ -145,6 +133,9 @@ json_encode({[{_Key, _Value} | _]} = Term) ->
|
||||
(Val, Encoder) ->
|
||||
json:encode_value(Val, Encoder)
|
||||
end));
|
||||
json_encode({[]}) ->
|
||||
%% Jiffy was able to handle this case, but Json library does not
|
||||
<<"{}">>;
|
||||
json_encode(Term) ->
|
||||
iolist_to_binary(json:encode(Term)).
|
||||
json_decode(Bin) ->
|
||||
@@ -819,3 +810,21 @@ set_proc_label(_Label) ->
|
||||
set_proc_label(Label) ->
|
||||
proc_lib:set_label(Label).
|
||||
-endif.
|
||||
|
||||
-ifdef(OTP_BELOW_25).
|
||||
-spec lists_uniq([term()]) -> [term()].
|
||||
lists_uniq(List) ->
|
||||
lists_uniq_int(List, #{}).
|
||||
|
||||
lists_uniq_int([El | Rest], Existing) ->
|
||||
case maps:is_key(El, Existing) of
|
||||
true -> lists_uniq_int(Rest, Existing);
|
||||
_ -> [El | lists_uniq_int(Rest, Existing#{El => true})]
|
||||
end;
|
||||
lists_uniq_int([], _) ->
|
||||
[].
|
||||
-else.
|
||||
-spec lists_uniq([term()]) -> [term()].
|
||||
lists_uniq(List) ->
|
||||
lists:uniq(List).
|
||||
-endif.
|
||||
|
||||
+33
-67
@@ -268,14 +268,14 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = ban_account, tags = [accounts],
|
||||
desc = "Ban an account",
|
||||
longdesc = "This command kicks the account sessions, "
|
||||
"sets a random password, and stores ban details in the "
|
||||
"account private storage. "
|
||||
"stores ban details in the account private storage, "
|
||||
"which blocks login to the account. "
|
||||
"This command requires _`mod_private`_ to be enabled. "
|
||||
"Check also _`get_ban_details`_ API "
|
||||
"and _`unban_account`_ API.",
|
||||
module = ?MODULE, function = ban_account_v2,
|
||||
version = 2,
|
||||
note = "improved in 24.06",
|
||||
note = "improved in 25.08",
|
||||
args = [{user, binary}, {host, binary}, {reason, binary}],
|
||||
args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
|
||||
args_desc = ["User name to ban", "Server name",
|
||||
@@ -301,7 +301,7 @@ get_commands_spec() ->
|
||||
{"lastdate", "2024-04-22T08:39:12Z"},
|
||||
{"lastreason", "Connection reset by peer"}]},
|
||||
#ejabberd_commands{name = unban_account, tags = [accounts],
|
||||
desc = "Revert the ban from an account: set back the old password",
|
||||
desc = "Remove the ban from an account",
|
||||
longdesc = "Check _`ban_account`_ API.",
|
||||
module = ?MODULE, function = unban_account,
|
||||
version = 2,
|
||||
@@ -1158,6 +1158,7 @@ ban_account(User, Host, ReasonText) ->
|
||||
ok.
|
||||
|
||||
kick_sessions(User, Server, Reason) ->
|
||||
ejabberd_hooks:run(sm_kick_user, Server, [User, Server]),
|
||||
lists:map(
|
||||
fun(Resource) ->
|
||||
kick_this_session(User, Server, Resource, Reason)
|
||||
@@ -1189,27 +1190,29 @@ prepare_reason(Reason) when is_binary(Reason) ->
|
||||
%% Ban account v2
|
||||
|
||||
ban_account_v2(User, Host, ReasonText) ->
|
||||
case gen_mod:is_loaded(Host, mod_private) of
|
||||
false ->
|
||||
IsPrivateEnabled = gen_mod:is_loaded(Host, mod_private),
|
||||
Exists = ejabberd_auth:user_exists(User, Host),
|
||||
IsBanned = is_banned(User, Host),
|
||||
case {IsPrivateEnabled, Exists, IsBanned} of
|
||||
{true, true, false} ->
|
||||
ban_account_v2_b(User, Host, ReasonText);
|
||||
{false, _, _} ->
|
||||
mod_private_is_required_but_disabled;
|
||||
true ->
|
||||
case is_banned(User, Host) of
|
||||
true ->
|
||||
account_was_already_banned;
|
||||
false ->
|
||||
ban_account_v2_b(User, Host, ReasonText)
|
||||
end
|
||||
{_, false, _} ->
|
||||
account_does_not_exist;
|
||||
{_, _, true} ->
|
||||
account_was_already_banned;
|
||||
{_, _, _} ->
|
||||
other_error
|
||||
end.
|
||||
|
||||
ban_account_v2_b(User, Host, ReasonText) ->
|
||||
Reason = prepare_reason(ReasonText),
|
||||
Pass = ejabberd_auth:get_password_s(User, Host),
|
||||
Last = get_last(User, Host),
|
||||
BanDate = xmpp_util:encode_timestamp(erlang:timestamp()),
|
||||
Hash = get_hash_value(User, Host),
|
||||
BanPrivateXml = build_ban_xmlel(Reason, Pass, Last, BanDate, Hash),
|
||||
BanPrivateXml = build_ban_xmlel(Reason, Last, BanDate, Hash),
|
||||
ok = private_set2(User, Host, BanPrivateXml),
|
||||
ok = set_random_password_v2(User, Host),
|
||||
kick_sessions(User, Host, Reason),
|
||||
ok.
|
||||
|
||||
@@ -1217,36 +1220,16 @@ get_hash_value(User, Host) ->
|
||||
Cookie = misc:atom_to_binary(erlang:get_cookie()),
|
||||
misc:term_to_base64(crypto:hash(sha256, <<User/binary, Host/binary, Cookie/binary>>)).
|
||||
|
||||
set_random_password_v2(User, Server) ->
|
||||
NewPass = p1_rand:get_string(),
|
||||
ok = ejabberd_auth:set_password(User, Server, NewPass).
|
||||
|
||||
build_ban_xmlel(Reason, Pass, {LastDate, LastReason}, BanDate, Hash) ->
|
||||
PassEls = build_pass_els(Pass),
|
||||
build_ban_xmlel(Reason, {LastDate, LastReason}, BanDate, Hash) ->
|
||||
#xmlel{name = <<"banned">>,
|
||||
attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}],
|
||||
children = [#xmlel{name = <<"reason">>, attrs = [], children = [{xmlcdata, Reason}]},
|
||||
#xmlel{name = <<"password">>, attrs = [], children = PassEls},
|
||||
#xmlel{name = <<"lastdate">>, attrs = [], children = [{xmlcdata, LastDate}]},
|
||||
#xmlel{name = <<"lastreason">>, attrs = [], children = [{xmlcdata, LastReason}]},
|
||||
#xmlel{name = <<"bandate">>, attrs = [], children = [{xmlcdata, BanDate}]},
|
||||
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, Hash}]}
|
||||
]}.
|
||||
|
||||
build_pass_els(Pass) when is_binary(Pass) ->
|
||||
[{xmlcdata, Pass}];
|
||||
build_pass_els(#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
hash = Hash,
|
||||
iterationcount = IterationCount}) ->
|
||||
[#xmlel{name = <<"storedkey">>, attrs = [], children = [{xmlcdata, StoredKey}]},
|
||||
#xmlel{name = <<"serverkey">>, attrs = [], children = [{xmlcdata, ServerKey}]},
|
||||
#xmlel{name = <<"salt">>, attrs = [], children = [{xmlcdata, Salt}]},
|
||||
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, misc:atom_to_binary(Hash)}]},
|
||||
#xmlel{name = <<"iterationcount">>, attrs = [], children = [{xmlcdata, integer_to_binary(IterationCount)}]}
|
||||
].
|
||||
|
||||
%%
|
||||
%% Get ban details
|
||||
|
||||
@@ -1286,43 +1269,26 @@ is_banned(User, Host) ->
|
||||
%% Unban account
|
||||
|
||||
unban_account(User, Host) ->
|
||||
case gen_mod:is_loaded(Host, mod_private) of
|
||||
false ->
|
||||
IsPrivateEnabled = gen_mod:is_loaded(Host, mod_private),
|
||||
Exists = ejabberd_auth:user_exists(User, Host),
|
||||
IsBanned = is_banned(User, Host),
|
||||
case {IsPrivateEnabled, Exists, IsBanned} of
|
||||
{true, true, true} ->
|
||||
unban_account2(User, Host);
|
||||
{false, _, _} ->
|
||||
mod_private_is_required_but_disabled;
|
||||
true ->
|
||||
case is_banned(User, Host) of
|
||||
false ->
|
||||
account_was_not_banned;
|
||||
true ->
|
||||
unban_account2(User, Host)
|
||||
end
|
||||
{_, false, _} ->
|
||||
account_does_not_exist;
|
||||
{_, _, false} ->
|
||||
account_was_not_banned;
|
||||
{_, _, _} ->
|
||||
other_error
|
||||
end.
|
||||
|
||||
unban_account2(User, Host) ->
|
||||
OldPass = get_oldpass(User, Host),
|
||||
ok = ejabberd_auth:set_password(User, Host, OldPass),
|
||||
UnBanPrivateXml = build_unban_xmlel(),
|
||||
private_set2(User, Host, UnBanPrivateXml).
|
||||
|
||||
get_oldpass(User, Host) ->
|
||||
[El] = private_get2(User, Host, <<"banned">>, <<"jabber:ejabberd:banned">>),
|
||||
Pass = fxml:get_subtag(El, <<"password">>),
|
||||
get_pass(Pass).
|
||||
|
||||
get_pass(#xmlel{children = [{xmlcdata, Pass}]}) ->
|
||||
Pass;
|
||||
get_pass(#xmlel{children = ScramEls} = Pass) when is_list(ScramEls) ->
|
||||
StoredKey = fxml:get_subtag_cdata(Pass, <<"storedkey">>),
|
||||
ServerKey = fxml:get_subtag_cdata(Pass, <<"serverkey">>),
|
||||
Salt = fxml:get_subtag_cdata(Pass, <<"salt">>),
|
||||
Hash = fxml:get_subtag_cdata(Pass, <<"hash">>),
|
||||
IterationCount = fxml:get_subtag_cdata(Pass, <<"iterationcount">>),
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
hash = binary_to_existing_atom(Hash, latin1),
|
||||
iterationcount = binary_to_integer(IterationCount)}.
|
||||
|
||||
build_unban_xmlel() ->
|
||||
#xmlel{name = <<"banned">>, attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}]}.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]).
|
||||
-export([mod_doc/0]).
|
||||
%% Hooks
|
||||
-export([c2s_inline_features/2, c2s_handle_sasl2_inline/1,
|
||||
-export([c2s_inline_features/3, c2s_handle_sasl2_inline/1,
|
||||
get_tokens/3, get_mechanisms/1, remove_user_tokens/2]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
@@ -131,7 +131,7 @@ get_tokens(LServer, LUser, UA) ->
|
||||
{{Type, CreatedAt < ToRefresh}, Token}
|
||||
end, Mod:get_tokens(LServer, LUser, ua_hash(UA))).
|
||||
|
||||
c2s_inline_features({Sasl, Bind, Extra}, Host) ->
|
||||
c2s_inline_features({Sasl, Bind, Extra}, Host, _State) ->
|
||||
{Sasl ++ [#fast{mechs = get_mechanisms(Host)}], Bind, Extra}.
|
||||
|
||||
gen_token(#{sasl2_ua_id := UA, server := Server, user := User}) ->
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
iq_handler/1, disco_features/5,
|
||||
depends/2, mod_options/1, mod_doc/0]).
|
||||
-export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1,
|
||||
c2s_inline_features/2, c2s_handle_bind2_inline/1]).
|
||||
c2s_inline_features/3, c2s_handle_bind2_inline/1]).
|
||||
%% For debugging purposes
|
||||
-export([list/2]).
|
||||
|
||||
@@ -145,7 +145,7 @@ c2s_session_resumed(State) ->
|
||||
c2s_session_opened(State) ->
|
||||
maps:remove(carboncopy, State).
|
||||
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host) ->
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host, _State) ->
|
||||
case gen_mod:is_loaded(Host, ?MODULE) of
|
||||
true ->
|
||||
{Sasl, [#bind2_feature{var = ?NS_CARBONS_2} | Bind], Extra};
|
||||
|
||||
+40
-3
@@ -64,11 +64,12 @@ process([], #request{method = 'GET', host = Host, q = Query, raw_path = RawPath1
|
||||
CSS = get_file_url(Host, conversejs_css,
|
||||
<<RawPath/binary, "/converse.min.css">>,
|
||||
<<"https://cdn.conversejs.org/dist/converse.min.css">>),
|
||||
PluginsHtml = get_plugins_html(Host, RawPath),
|
||||
Init = [{<<"discover_connection_methods">>, false},
|
||||
{<<"default_domain">>, Domain},
|
||||
{<<"domain_placeholder">>, Domain},
|
||||
{<<"registration_domain">>, Domain},
|
||||
{<<"assets_path">>, RawPath},
|
||||
{<<"assets_path">>, <<RawPath/binary, "/">>},
|
||||
{<<"view_mode">>, <<"fullscreen">>}
|
||||
| ExtraOptions],
|
||||
Init2 =
|
||||
@@ -89,7 +90,8 @@ process([], #request{method = 'GET', host = Host, q = Query, raw_path = RawPath1
|
||||
<<"<meta charset='utf-8'>">>,
|
||||
<<"<link rel='stylesheet' type='text/css' media='screen' href='">>,
|
||||
fxml:crypt(CSS), <<"'>">>,
|
||||
<<"<script src='">>, fxml:crypt(Script), <<"' charset='utf-8'></script>">>,
|
||||
<<"<script src='">>, fxml:crypt(Script), <<"' charset='utf-8'></script>">>
|
||||
] ++ PluginsHtml ++ [
|
||||
<<"</head>">>,
|
||||
<<"<body>">>,
|
||||
<<"<script>">>,
|
||||
@@ -115,6 +117,7 @@ is_served_file([<<"emojis.js">>]) -> true;
|
||||
is_served_file([<<"locales">>, _]) -> true;
|
||||
is_served_file([<<"locales">>, <<"dayjs">>, _]) -> true;
|
||||
is_served_file([<<"webfonts">>, _]) -> true;
|
||||
is_served_file([<<"plugins">>, _]) -> true;
|
||||
is_served_file(_) -> false.
|
||||
|
||||
serve(Host, LocalPath) ->
|
||||
@@ -224,6 +227,25 @@ get_auto_file_url(Host, Filename, Default) ->
|
||||
_ -> Filename
|
||||
end.
|
||||
|
||||
get_plugins_html(Host, RawPath) ->
|
||||
Resources = get_conversejs_resources(Host),
|
||||
lists:map(fun(F) ->
|
||||
Plugin =
|
||||
case {F, Resources} of
|
||||
{<<"libsignal">>, undefined} ->
|
||||
<<"https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js">>;
|
||||
{<<"libsignal">>, Path} ->
|
||||
?WARNING_MSG("~p is configured to use local Converse files "
|
||||
"from path ~ts but the public plugin ~ts!",
|
||||
[?MODULE, Path, F]),
|
||||
<<"https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js">>;
|
||||
_ ->
|
||||
fxml:crypt(<<RawPath/binary, "/plugins/", F/binary>>)
|
||||
end,
|
||||
<<"<script src='", Plugin/binary, "' charset='utf-8'></script>">>
|
||||
end,
|
||||
gen_mod:get_module_opt(Host, ?MODULE, conversejs_plugins)).
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% WebAdmin link and autologin
|
||||
%%----------------------------------------------------------------------
|
||||
@@ -305,6 +327,8 @@ mod_opt_type(conversejs_script) ->
|
||||
econf:binary();
|
||||
mod_opt_type(conversejs_css) ->
|
||||
econf:binary();
|
||||
mod_opt_type(conversejs_plugins) ->
|
||||
econf:list(econf:binary());
|
||||
mod_opt_type(default_domain) ->
|
||||
econf:host().
|
||||
|
||||
@@ -315,6 +339,7 @@ mod_options(Host) ->
|
||||
{conversejs_resources, undefined},
|
||||
{conversejs_options, []},
|
||||
{conversejs_script, auto},
|
||||
{conversejs_plugins, []},
|
||||
{conversejs_css, auto}].
|
||||
|
||||
mod_doc() ->
|
||||
@@ -345,6 +370,7 @@ mod_doc() ->
|
||||
"modules:",
|
||||
" mod_bosh: {}",
|
||||
" mod_conversejs:",
|
||||
" conversejs_plugins: [\"libsignal\"]",
|
||||
" websocket_url: \"ws://@HOST@:5280/websocket\""]},
|
||||
{?T("Host Converse locally and let auto detection of WebSocket and Converse URLs:"),
|
||||
["listen:",
|
||||
@@ -358,7 +384,9 @@ mod_doc() ->
|
||||
"",
|
||||
"modules:",
|
||||
" mod_conversejs:",
|
||||
" conversejs_resources: \"/home/ejabberd/conversejs-9.0.0/package/dist\""]},
|
||||
" conversejs_resources: \"/home/ejabberd/conversejs-x.y.z/package/dist\"",
|
||||
" conversejs_plugins: [\"libsignal-protocol.min.js\"]",
|
||||
" # File path is: /home/ejabberd/conversejs-x.y.z/package/dist/plugins/libsignal-protocol.min.js"]},
|
||||
{?T("Configure some additional options for Converse"),
|
||||
["modules:",
|
||||
" mod_conversejs:",
|
||||
@@ -410,6 +438,15 @@ mod_doc() ->
|
||||
"See https://conversejs.org/docs/html/configuration.html[Converse configuration]. "
|
||||
"Only boolean, integer and string values are supported; "
|
||||
"lists are not supported.")}},
|
||||
{conversejs_plugins,
|
||||
#{value => ?T("[Filename]"),
|
||||
desc =>
|
||||
?T("List of additional local files to include as scripts in the homepage. "
|
||||
"Please make sure those files are available in the path specified in "
|
||||
"'conversejs_resources' option, in subdirectory 'plugins/'. "
|
||||
"If using the public Converse client, then '\"libsignal\"' "
|
||||
"gets replaced with the URL of the public library. "
|
||||
"The default value is '[]'.")}},
|
||||
{conversejs_script,
|
||||
#{value => ?T("auto | URL"),
|
||||
desc =>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
-export([bosh_service_url/1]).
|
||||
-export([conversejs_css/1]).
|
||||
-export([conversejs_options/1]).
|
||||
-export([conversejs_plugins/1]).
|
||||
-export([conversejs_resources/1]).
|
||||
-export([conversejs_script/1]).
|
||||
-export([default_domain/1]).
|
||||
@@ -29,6 +30,12 @@ conversejs_options(Opts) when is_map(Opts) ->
|
||||
conversejs_options(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_options).
|
||||
|
||||
-spec conversejs_plugins(gen_mod:opts() | global | binary()) -> [binary()].
|
||||
conversejs_plugins(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(conversejs_plugins, Opts);
|
||||
conversejs_plugins(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_plugins).
|
||||
|
||||
-spec conversejs_resources(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
|
||||
conversejs_resources(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(conversejs_resources, Opts);
|
||||
|
||||
+22
-6
@@ -319,8 +319,10 @@ mod_doc() ->
|
||||
desc =>
|
||||
?T("This option specifies the initial part of the PUT URLs "
|
||||
"used for file uploads. The keyword '@HOST@' is replaced "
|
||||
"with the virtual host name. NOTE: different virtual "
|
||||
"hosts cannot use the same PUT URL. "
|
||||
"with the virtual host name. "
|
||||
"And '@HOST_URL_ENCODE@' is replaced with the host name encoded for URL, "
|
||||
"useful when your virtual hosts contain non-latin characters. "
|
||||
"NOTE: different virtual hosts cannot use the same PUT URL. "
|
||||
"The default value is '\"https://@HOST@:5443/upload\"'.")}},
|
||||
{get_url,
|
||||
#{value => ?T("URL"),
|
||||
@@ -531,7 +533,8 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
|
||||
[Method, encode_addr(IP), Host]),
|
||||
http_response(404);
|
||||
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
length = Length} = Request) ->
|
||||
length = Length} = Request0) ->
|
||||
Request = Request0#request{host = redecode_url(Host)},
|
||||
{Proc, Slot} = parse_http_request(Request),
|
||||
try gen_server:call(Proc, {use_slot, Slot, Length}, ?CALL_TIMEOUT) of
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
|
||||
@@ -571,9 +574,10 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
[encode_addr(IP), Host, Error]),
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
|
||||
when Method == 'GET';
|
||||
Method == 'HEAD' ->
|
||||
Request = Request0#request{host = redecode_url(Host)},
|
||||
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
|
||||
try gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
|
||||
{ok, DocRoot, CustomHeaders} ->
|
||||
@@ -907,8 +911,8 @@ mk_slot(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS, Query) ->
|
||||
GetURL = str:join([GetPrefix | Slot], <<$/>>),
|
||||
mk_slot(PutURL, GetURL, XMLNS, Query);
|
||||
mk_slot(PutURL, GetURL, XMLNS, Query) ->
|
||||
PutURL1 = <<(misc:url_encode(PutURL))/binary, Query/binary>>,
|
||||
GetURL1 = misc:url_encode(GetURL),
|
||||
PutURL1 = <<(reencode_url(PutURL))/binary, Query/binary>>,
|
||||
GetURL1 = reencode_url(GetURL),
|
||||
case XMLNS of
|
||||
?NS_HTTP_UPLOAD_0 ->
|
||||
#upload_slot_0{get = GetURL1, put = PutURL1, xmlns = XMLNS};
|
||||
@@ -916,6 +920,18 @@ mk_slot(PutURL, GetURL, XMLNS, Query) ->
|
||||
#upload_slot{get = GetURL1, put = PutURL1, xmlns = XMLNS}
|
||||
end.
|
||||
|
||||
reencode_url(UrlString) ->
|
||||
{ok, _, _, Host, _, _, _} = yconf:parse_uri(UrlString),
|
||||
HostDecoded = misc:uri_decode(Host),
|
||||
HostIdna = idna:encode(HostDecoded),
|
||||
re:replace(UrlString, Host, HostIdna, [{return, binary}]).
|
||||
|
||||
redecode_url(UrlString) ->
|
||||
{ok, _, _, HostIdna, _, _, _} = yconf:parse_uri(<<"http://", UrlString/binary>>),
|
||||
HostDecoded = idna:decode(HostIdna),
|
||||
Host = misc:uri_quote(HostDecoded),
|
||||
re:replace(UrlString, HostIdna, Host, [{return, binary}]).
|
||||
|
||||
-spec make_user_string(jid(), sha1 | node) -> binary().
|
||||
make_user_string(#jid{luser = U, lserver = S}, sha1) ->
|
||||
str:sha(<<U/binary, $@, S/binary>>);
|
||||
|
||||
+41
-16
@@ -611,22 +611,28 @@ parse_auth4(<<>>, Key, Val, Ts) ->
|
||||
|
||||
prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
|
||||
RoomVersion) ->
|
||||
Event2 =
|
||||
Keys =
|
||||
case RoomVersion#room_version.updated_redaction_rules of
|
||||
false ->
|
||||
maps:with(
|
||||
[<<"event_id">>, <<"type">>, <<"room_id">>, <<"sender">>,
|
||||
<<"state_key">>, <<"content">>, <<"hashes">>,
|
||||
<<"signatures">>, <<"depth">>, <<"prev_events">>,
|
||||
<<"prev_state">>, <<"auth_events">>, <<"origin">>,
|
||||
<<"origin_server_ts">>, <<"membership">>], Event);
|
||||
[<<"event_id">>, <<"type">>, <<"room_id">>, <<"sender">>,
|
||||
<<"state_key">>, <<"content">>, <<"hashes">>,
|
||||
<<"signatures">>, <<"depth">>, <<"prev_events">>,
|
||||
<<"prev_state">>, <<"auth_events">>, <<"origin">>,
|
||||
<<"origin_server_ts">>, <<"membership">>];
|
||||
true ->
|
||||
maps:with(
|
||||
[<<"event_id">>, <<"type">>, <<"room_id">>, <<"sender">>,
|
||||
<<"state_key">>, <<"content">>, <<"hashes">>,
|
||||
<<"signatures">>, <<"depth">>, <<"prev_events">>,
|
||||
<<"auth_events">>, <<"origin_server_ts">>], Event)
|
||||
[<<"event_id">>, <<"type">>, <<"room_id">>, <<"sender">>,
|
||||
<<"state_key">>, <<"content">>, <<"hashes">>,
|
||||
<<"signatures">>, <<"depth">>, <<"prev_events">>,
|
||||
<<"auth_events">>, <<"origin_server_ts">>]
|
||||
end,
|
||||
Keys2 =
|
||||
case {RoomVersion#room_version.hydra, Type} of
|
||||
{true, <<"m.room.create">>} ->
|
||||
lists:delete(<<"room_id">>, Keys);
|
||||
_ ->
|
||||
Keys
|
||||
end,
|
||||
Event2 = maps:with(Keys2, Event),
|
||||
Content2 =
|
||||
case Type of
|
||||
<<"m.room.member">> ->
|
||||
@@ -738,6 +744,8 @@ is_canonical_json(B) when is_binary(B) ->
|
||||
true;
|
||||
is_canonical_json(B) when is_boolean(B) ->
|
||||
true;
|
||||
is_canonical_json(null) ->
|
||||
true;
|
||||
is_canonical_json(Map) when is_map(Map) ->
|
||||
maps:fold(
|
||||
fun(_K, V, true) ->
|
||||
@@ -976,7 +984,11 @@ mod_opt_type(key) ->
|
||||
crypto:generate_key(eddsa, ed25519, Key2)
|
||||
end;
|
||||
mod_opt_type(matrix_id_as_jid) ->
|
||||
econf:bool().
|
||||
econf:bool();
|
||||
mod_opt_type(notary_servers) ->
|
||||
econf:list(econf:host());
|
||||
mod_opt_type(leave_timeout) ->
|
||||
econf:non_neg_int().
|
||||
|
||||
-spec mod_options(binary()) -> [{key, {binary(), binary()}} |
|
||||
{atom(), any()}].
|
||||
@@ -986,14 +998,19 @@ mod_options(Host) ->
|
||||
{host, <<"matrix.", Host/binary>>},
|
||||
{key_name, <<"">>},
|
||||
{key, {<<"">>, <<"">>}},
|
||||
{matrix_id_as_jid, false}].
|
||||
{matrix_id_as_jid, false},
|
||||
{notary_servers, []},
|
||||
{leave_timeout, 0}].
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("https://matrix.org/[Matrix] gateway. "),
|
||||
?T("Supports room versions 9, 10 and 11 since ejabberd 25.03; "
|
||||
"room versions 4 and higher since ejabberd 25.07; "
|
||||
"room version 12 (hydra rooms) since ejabberd 25.08. "),
|
||||
?T("Erlang/OTP 25 or higher is required to use this module."),
|
||||
?T("This module is available since ejabberd 24.02.")],
|
||||
note => "improved in 25.07",
|
||||
note => "improved in 25.08",
|
||||
example =>
|
||||
["listen:",
|
||||
" -",
|
||||
@@ -1042,7 +1059,15 @@ mod_doc() ->
|
||||
"Matrix user '@user:matrixdomain.tld', the client must send a message "
|
||||
"to the JID 'user%matrixdomain.tld@matrix.myxmppdomain.tld', where "
|
||||
"'matrix.myxmppdomain.tld' is the JID of the gateway service as set by the "
|
||||
"'host' option. The default is 'false'.")}}
|
||||
"'host' option. The default is 'false'.")}},
|
||||
{notary_servers,
|
||||
#{value => "[Server, ...]",
|
||||
desc =>
|
||||
?T("A list of notary servers.")}},
|
||||
{leave_timeout,
|
||||
#{value => "integer()",
|
||||
desc =>
|
||||
?T("Delay in seconds between a user leaving a MUC room and sending 'leave' Matrix event.")}}
|
||||
]
|
||||
}.
|
||||
-endif.
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
-export([host/1]).
|
||||
-export([key/1]).
|
||||
-export([key_name/1]).
|
||||
-export([leave_timeout/1]).
|
||||
-export([matrix_domain/1]).
|
||||
-export([matrix_id_as_jid/1]).
|
||||
-export([notary_servers/1]).
|
||||
|
||||
-spec host(gen_mod:opts() | global | binary()) -> binary().
|
||||
host(Opts) when is_map(Opts) ->
|
||||
@@ -27,6 +29,12 @@ key_name(Opts) when is_map(Opts) ->
|
||||
key_name(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, key_name).
|
||||
|
||||
-spec leave_timeout(gen_mod:opts() | global | binary()) -> non_neg_integer().
|
||||
leave_timeout(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(leave_timeout, Opts);
|
||||
leave_timeout(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, leave_timeout).
|
||||
|
||||
-spec matrix_domain(gen_mod:opts() | global | binary()) -> binary().
|
||||
matrix_domain(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(matrix_domain, Opts);
|
||||
@@ -39,3 +47,9 @@ matrix_id_as_jid(Opts) when is_map(Opts) ->
|
||||
matrix_id_as_jid(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, matrix_id_as_jid).
|
||||
|
||||
-spec notary_servers(gen_mod:opts() | global | binary()) -> [binary()].
|
||||
notary_servers(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(notary_servers, Opts);
|
||||
notary_servers(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, notary_servers).
|
||||
|
||||
|
||||
+661
-325
File diff suppressed because it is too large
Load Diff
+168
-149
@@ -44,12 +44,23 @@
|
||||
{to :: binary(),
|
||||
pid :: pid()}).
|
||||
|
||||
-record(pending,
|
||||
{request_id :: any(),
|
||||
servers :: [binary()],
|
||||
key_queue = []}).
|
||||
|
||||
-record(wait,
|
||||
{timer_ref :: reference(),
|
||||
last :: integer()}).
|
||||
|
||||
-record(data,
|
||||
{host :: binary(),
|
||||
matrix_server :: binary(),
|
||||
matrix_host_port :: {binary(), integer()} | undefined,
|
||||
keys = #{},
|
||||
key_queue = #{}}).
|
||||
state :: #pending{} | #wait{}}).
|
||||
|
||||
-define(KEYS_REQUEST_TIMEOUT, 600000).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -227,8 +238,11 @@ init([Host, MatrixServer]) ->
|
||||
#matrix_s2s{to = MatrixServer,
|
||||
pid = self()}),
|
||||
{ok, state_name,
|
||||
#data{host = Host,
|
||||
matrix_server = MatrixServer}}.
|
||||
request_keys(
|
||||
MatrixServer,
|
||||
#data{host = Host,
|
||||
matrix_server = MatrixServer,
|
||||
state = #wait{timer_ref = make_ref(), last = 0}})}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
@@ -246,7 +260,7 @@ init([Host, MatrixServer]) ->
|
||||
handle_event({call, From}, get_matrix_host_port, _State, Data) ->
|
||||
case Data#data.matrix_host_port of
|
||||
undefined ->
|
||||
Result = do_get_matrix_host_port(Data),
|
||||
Result = do_get_matrix_host_port(Data#data.matrix_server),
|
||||
Data2 = Data#data{matrix_host_port = Result},
|
||||
{keep_state, Data2, [{reply, From, Result}]};
|
||||
Result ->
|
||||
@@ -254,104 +268,59 @@ handle_event({call, From}, get_matrix_host_port, _State, Data) ->
|
||||
end;
|
||||
handle_event({call, From}, {get_key, KeyID}, State, Data) ->
|
||||
case maps:find(KeyID, Data#data.keys) of
|
||||
{ok, {ok, _, _} = Result} ->
|
||||
{keep_state, Data, [{reply, From, Result}]};
|
||||
{ok, error = Result} ->
|
||||
{keep_state, Data, [{reply, From, Result}]};
|
||||
{ok, pending} ->
|
||||
KeyQueue = maps:update_with(
|
||||
KeyID,
|
||||
fun(Xs) ->
|
||||
[From | Xs]
|
||||
end,
|
||||
[From],
|
||||
Data#data.key_queue),
|
||||
{next_state, State,
|
||||
Data#data{key_queue = KeyQueue}, []};
|
||||
{ok, {Key, ValidUntil}} ->
|
||||
{keep_state, Data, [{reply, From, {ok, Key, ValidUntil}}]};
|
||||
error ->
|
||||
{MHost, MPort} = do_get_matrix_host_port(Data),
|
||||
URL = <<"https://", MHost/binary,
|
||||
":", (integer_to_binary(MPort))/binary,
|
||||
"/_matrix/key/v2/server/", KeyID/binary>>,
|
||||
Self = self(),
|
||||
httpc:request(get, {URL, []},
|
||||
[{timeout, 5000}],
|
||||
[{sync, false},
|
||||
{receiver,
|
||||
fun({_RequestId, Result}) ->
|
||||
gen_statem:cast(
|
||||
Self, {key_reply, KeyID, Result})
|
||||
end}]),
|
||||
Keys = (Data#data.keys)#{KeyID => pending},
|
||||
KeyQueue = maps:update_with(
|
||||
KeyID,
|
||||
fun(Xs) ->
|
||||
[From | Xs]
|
||||
end,
|
||||
[From],
|
||||
Data#data.key_queue),
|
||||
{next_state, State,
|
||||
Data#data{keys = Keys,
|
||||
key_queue = KeyQueue},
|
||||
[]}
|
||||
case Data#data.state of
|
||||
#pending{key_queue = KeyQueue} = St ->
|
||||
KeyQueue2 = [{From, KeyID} | KeyQueue],
|
||||
{next_state, State,
|
||||
Data#data{state = St#pending{key_queue = KeyQueue2}}, []};
|
||||
#wait{timer_ref = TimerRef, last = Last} ->
|
||||
TS = erlang:system_time(millisecond),
|
||||
if
|
||||
Last + ?KEYS_REQUEST_TIMEOUT =< TS ->
|
||||
Data2 = request_keys(Data#data.matrix_server, Data),
|
||||
#pending{key_queue = KeyQueue} = St = Data2#data.state,
|
||||
KeyQueue2 = [{From, KeyID} | KeyQueue],
|
||||
{next_state, State,
|
||||
Data2#data{state = St#pending{key_queue = KeyQueue2}}, []};
|
||||
true ->
|
||||
Timeout =
|
||||
case erlang:read_timer(TimerRef) of
|
||||
false ->
|
||||
Last + ?KEYS_REQUEST_TIMEOUT - TS;
|
||||
Left ->
|
||||
erlang:cancel_timer(TimerRef),
|
||||
min(Left,
|
||||
Last + ?KEYS_REQUEST_TIMEOUT - TS)
|
||||
end,
|
||||
TRef = erlang:start_timer(Timeout, self(), []),
|
||||
{next_state, State,
|
||||
Data#data{state = #wait{timer_ref = TRef,
|
||||
last = Last}},
|
||||
[{reply, From, error}]}
|
||||
end
|
||||
end
|
||||
end;
|
||||
handle_event(cast, {query, AuthParams, _Query, _JSON, _Request} = Msg,
|
||||
State, Data) ->
|
||||
#{<<"key">> := KeyID} = AuthParams,
|
||||
case maps:find(KeyID, Data#data.keys) of
|
||||
{ok, {ok, VerifyKey, _ValidUntil}} ->
|
||||
Data2 = process_unverified_query(
|
||||
KeyID, VerifyKey, Msg, Data),
|
||||
{next_state, State, Data2, []};
|
||||
{ok, error} ->
|
||||
%TODO
|
||||
{next_state, State, Data, []};
|
||||
{ok, pending} ->
|
||||
KeyQueue = maps:update_with(
|
||||
KeyID,
|
||||
fun(Xs) ->
|
||||
[Msg | Xs]
|
||||
end,
|
||||
[Msg],
|
||||
Data#data.key_queue),
|
||||
{next_state, State,
|
||||
Data#data{key_queue = KeyQueue}, []};
|
||||
error ->
|
||||
{MHost, MPort} = do_get_matrix_host_port(Data),
|
||||
URL = <<"https://", MHost/binary,
|
||||
":", (integer_to_binary(MPort))/binary,
|
||||
"/_matrix/key/v2/server/", KeyID/binary>>,
|
||||
Self = self(),
|
||||
httpc:request(get, {URL, []},
|
||||
[{timeout, 5000}],
|
||||
[{sync, false},
|
||||
{receiver,
|
||||
fun({_RequestId, Result}) ->
|
||||
gen_statem:cast(
|
||||
Self, {key_reply, KeyID, Result})
|
||||
end}]),
|
||||
Keys = (Data#data.keys)#{KeyID => pending},
|
||||
KeyQueue = maps:update_with(
|
||||
KeyID,
|
||||
fun(Xs) ->
|
||||
[Msg | Xs]
|
||||
end,
|
||||
[Msg],
|
||||
Data#data.key_queue),
|
||||
{next_state, State,
|
||||
Data#data{keys = Keys,
|
||||
key_queue = KeyQueue},
|
||||
[]}
|
||||
end;
|
||||
handle_event(cast, {key_reply, KeyID, HTTPResult}, State, Data) ->
|
||||
KeyVal =
|
||||
handle_event(cast, {key_reply, RequestID, HTTPResult}, State,
|
||||
#data{state = #pending{request_id = RequestID,
|
||||
servers = Servers,
|
||||
key_queue = KeyQueue} = St} = Data) ->
|
||||
TS = erlang:system_time(millisecond),
|
||||
Res =
|
||||
case HTTPResult of
|
||||
{{_, 200, _}, _, SJSON} ->
|
||||
try
|
||||
JSON = misc:json_decode(SJSON),
|
||||
?DEBUG("key ~p~n", [JSON]),
|
||||
JSON1 = misc:json_decode(SJSON),
|
||||
JSON =
|
||||
case JSON1 of
|
||||
#{<<"server_keys">> := [J]} -> J;
|
||||
_ -> JSON1
|
||||
end,
|
||||
?DEBUG("keys ~p~n", [JSON]),
|
||||
#{<<"verify_keys">> := VerifyKeys} = JSON,
|
||||
#{KeyID := KeyData} = VerifyKeys,
|
||||
{KeyID, KeyData, _} = maps:next(maps:iterator(VerifyKeys)),
|
||||
#{<<"key">> := SKey} = KeyData,
|
||||
VerifyKey = mod_matrix_gw:base64_decode(SKey),
|
||||
?DEBUG("key ~p~n", [VerifyKey]),
|
||||
@@ -366,22 +335,77 @@ handle_event(cast, {key_reply, KeyID, HTTPResult}, State, Data) ->
|
||||
ValidUntil2 =
|
||||
min(ValidUntil,
|
||||
erlang:system_time(millisecond) + timer:hours(24 * 7)),
|
||||
{ok, VerifyKey, ValidUntil2}
|
||||
OldKeysJSON =
|
||||
case JSON of
|
||||
#{<<"old_verify_keys">> := OldKeysJ}
|
||||
when is_map(OldKeysJ) ->
|
||||
OldKeysJ;
|
||||
_ ->
|
||||
#{}
|
||||
end,
|
||||
OldKeys =
|
||||
maps:filtermap(
|
||||
fun(_KID,
|
||||
#{<<"key">> := SK,
|
||||
<<"expired_ts">> := Exp})
|
||||
when is_integer(Exp),
|
||||
is_binary(SK) ->
|
||||
{true, {mod_matrix_gw:base64_decode(SK),
|
||||
Exp}};
|
||||
(_, _) -> false
|
||||
end, OldKeysJSON),
|
||||
NewKeys =
|
||||
maps:filtermap(
|
||||
fun(_KID,
|
||||
#{<<"key">> := SK})
|
||||
when is_binary(SK) ->
|
||||
{true, {mod_matrix_gw:base64_decode(SK),
|
||||
ValidUntil2}};
|
||||
(_, _) -> false
|
||||
end, VerifyKeys),
|
||||
{ok, maps:merge(OldKeys, NewKeys), ValidUntil2}
|
||||
catch
|
||||
_:_ ->
|
||||
error
|
||||
{ok, Data#data.keys, TS + ?KEYS_REQUEST_TIMEOUT}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
case Servers of
|
||||
[] ->
|
||||
{ok, Data#data.keys, TS + ?KEYS_REQUEST_TIMEOUT};
|
||||
[S | Servers1] ->
|
||||
{error,
|
||||
request_keys(
|
||||
S,
|
||||
Data#data{state = St#pending{servers = Servers1}})}
|
||||
end
|
||||
end,
|
||||
Keys = (Data#data.keys)#{KeyID => KeyVal},
|
||||
Froms = maps:get(KeyID, Data#data.key_queue, []),
|
||||
KeyQueue = maps:remove(KeyID, Data#data.key_queue),
|
||||
Data2 = Data#data{keys = Keys,
|
||||
key_queue = KeyQueue},
|
||||
Replies = lists:map(fun(From) -> {reply, From, KeyVal} end, Froms),
|
||||
?DEBUG("KEYS ~p~n", [{Keys, Data2}]),
|
||||
{next_state, State, Data2, Replies};
|
||||
case Res of
|
||||
{ok, Keys, ValidTS} ->
|
||||
Replies =
|
||||
lists:map(
|
||||
fun({From, KeyID}) ->
|
||||
case maps:find(KeyID, Keys) of
|
||||
{ok, {Key, KeyValidUntil}} ->
|
||||
{reply, From, {ok, Key, KeyValidUntil}};
|
||||
error ->
|
||||
{reply, From, error}
|
||||
end
|
||||
end,
|
||||
KeyQueue),
|
||||
TimerRef = erlang:start_timer(max(ValidTS - TS, ?KEYS_REQUEST_TIMEOUT),
|
||||
self(), []),
|
||||
Data2 = Data#data{keys = Keys,
|
||||
state = #wait{timer_ref = TimerRef,
|
||||
last = TS}},
|
||||
?DEBUG("KEYS ~p~n", [{Keys, Data2}]),
|
||||
{next_state, State, Data2, Replies};
|
||||
{error, Data2} ->
|
||||
{next_state, State, Data2, []}
|
||||
end;
|
||||
handle_event(info, {timeout, TimerRef, []}, State,
|
||||
#data{state = #wait{timer_ref = TimerRef}} = Data) ->
|
||||
Data2 = request_keys(Data#data.matrix_server, Data),
|
||||
{next_state, State, Data2, []};
|
||||
handle_event(cast, Msg, State, Data) ->
|
||||
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
|
||||
{next_state, State, Data, []};
|
||||
@@ -427,8 +451,7 @@ callback_mode() ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
do_get_matrix_host_port(Data) ->
|
||||
MatrixServer = Data#data.matrix_server,
|
||||
do_get_matrix_host_port(MatrixServer) ->
|
||||
case binary:split(MatrixServer, <<":">>) of
|
||||
[Addr] ->
|
||||
case inet:parse_address(binary_to_list(Addr)) of
|
||||
@@ -530,47 +553,43 @@ check_signature(JSON, SignatureName, KeyID, VerifyKey) ->
|
||||
false
|
||||
end.
|
||||
|
||||
%process_unverified_queries(KeyID, Data) ->
|
||||
% case maps:find(KeyID, Data#data.keys) of
|
||||
% {ok, {ok, VerifyKey, _ValidUntil}} ->
|
||||
% Queue = maps:get(KeyID, Data#data.key_queue, []),
|
||||
% KeyQueue = maps:remove(KeyID, Data#data.key_queue),
|
||||
% Data2 = Data#data{key_queue = KeyQueue},
|
||||
% lists:foldl(
|
||||
% fun(Query, DataAcc) ->
|
||||
% process_unverified_query(KeyID, VerifyKey, Query, DataAcc)
|
||||
% end, Data2, Queue);
|
||||
% _ ->
|
||||
% %% TODO
|
||||
% Data
|
||||
% end.
|
||||
|
||||
process_unverified_query(
|
||||
KeyID, VerifyKey, {query, AuthParams, _Query, Content, Request} = _Msg, Data) ->
|
||||
Destination = mod_matrix_gw_opt:matrix_domain(Data#data.host),
|
||||
#{<<"sig">> := Sig} = AuthParams,
|
||||
JSON = #{<<"method">> => atom_to_binary(Request#request.method, latin1),
|
||||
<<"uri">> => Request#request.raw_path,
|
||||
<<"origin">> => Data#data.matrix_server,
|
||||
<<"destination">> => Destination,
|
||||
<<"signatures">> => #{
|
||||
Data#data.matrix_server => #{KeyID => Sig}
|
||||
}
|
||||
},
|
||||
JSON2 =
|
||||
case Content of
|
||||
none -> JSON;
|
||||
_ ->
|
||||
JSON#{<<"content">> => Content}
|
||||
request_keys(Via, Data) ->
|
||||
{MHost, MPort} = do_get_matrix_host_port(Via),
|
||||
URL =
|
||||
case Data#data.matrix_server of
|
||||
Via ->
|
||||
<<"https://", MHost/binary,
|
||||
":", (integer_to_binary(MPort))/binary,
|
||||
"/_matrix/key/v2/server">>;
|
||||
MatrixServer ->
|
||||
<<"https://", MHost/binary,
|
||||
":", (integer_to_binary(MPort))/binary,
|
||||
"/_matrix/key/v2/query/", MatrixServer/binary>>
|
||||
end,
|
||||
case check_signature(JSON2, Data#data.matrix_server, KeyID, VerifyKey) of
|
||||
true ->
|
||||
todo_remove_me;
|
||||
%process_query(Msg, Data);
|
||||
false ->
|
||||
?WARNING_MSG("Failed authentication: ~p", [JSON]),
|
||||
%% TODO
|
||||
Data
|
||||
Self = self(),
|
||||
{ok, RequestID} =
|
||||
httpc:request(get, {URL, []},
|
||||
[{timeout, 5000}],
|
||||
[{sync, false},
|
||||
{receiver,
|
||||
fun({RequestId, Result}) ->
|
||||
gen_statem:cast(
|
||||
Self, {key_reply, RequestId, Result})
|
||||
end}]),
|
||||
case Data#data.state of
|
||||
#pending{request_id = OldReqID} = St ->
|
||||
case OldReqID of
|
||||
undefined ->
|
||||
ok;
|
||||
_ ->
|
||||
httpc:cancel_request(OldReqID)
|
||||
end,
|
||||
Data#data{state = St#pending{request_id = RequestID}};
|
||||
#wait{timer_ref = TimerRef} ->
|
||||
erlang:cancel_timer(TimerRef),
|
||||
NotaryServers = mod_matrix_gw_opt:notary_servers(Data#data.host),
|
||||
Data#data{state = #pending{request_id = RequestID,
|
||||
servers = NotaryServers}}
|
||||
end.
|
||||
|
||||
-endif.
|
||||
|
||||
+6
-7
@@ -97,7 +97,7 @@
|
||||
-callback import(binary(), binary(), [binary()]) -> ok.
|
||||
-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
|
||||
-callback store_changes(binary(), binary(), binary(), list()) -> {atomic, any()}.
|
||||
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
|
||||
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error | {error, atom()}.
|
||||
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
|
||||
-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
|
||||
-callback get_rooms(binary(), binary()) -> [#muc_room{}].
|
||||
@@ -591,20 +591,17 @@ extract_password(#iq{} = IQ) ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec unhibernate_room(binary(), binary(), binary()) -> {ok, pid()} | error.
|
||||
-spec unhibernate_room(binary(), binary(), binary()) -> {ok, pid()} | {error, notfound | db_failure | term()}.
|
||||
unhibernate_room(ServerHost, Host, Room) ->
|
||||
unhibernate_room(ServerHost, Host, Room, true).
|
||||
|
||||
-spec unhibernate_room(binary(), binary(), binary(), boolean()) -> {ok, pid()} | error.
|
||||
-spec unhibernate_room(binary(), binary(), binary(), boolean()) -> {ok, pid()} | {error, notfound | db_failure | term()}.
|
||||
unhibernate_room(ServerHost, Host, Room, ResetHibernationTime) ->
|
||||
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
|
||||
case RMod:find_online_room(ServerHost, Room, Host) of
|
||||
error ->
|
||||
Proc = procname(ServerHost, {Room, Host}),
|
||||
case ?GEN_SERVER:call(Proc, {unhibernate, Room, Host, ResetHibernationTime}, 20000) of
|
||||
{ok, _} = R -> R;
|
||||
_ -> error
|
||||
end;
|
||||
?GEN_SERVER:call(Proc, {unhibernate, Room, Host, ResetHibernationTime}, 20000);
|
||||
{ok, _} = R2 -> R2
|
||||
end.
|
||||
|
||||
@@ -888,6 +885,8 @@ load_room(RMod, Host, ServerHost, Room, ResetHibernationTime) ->
|
||||
case restore_room(ServerHost, Host, Room) of
|
||||
error ->
|
||||
{error, notfound};
|
||||
{error, _} = Err ->
|
||||
Err;
|
||||
Opts0 ->
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case proplists:get_bool(persistent, Opts0) of
|
||||
|
||||
+30
-4
@@ -1289,6 +1289,8 @@ create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
|
||||
{error, _} ->
|
||||
throw({error, "Unable to start room"})
|
||||
end;
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "Room already exists"})
|
||||
end.
|
||||
@@ -1307,6 +1309,8 @@ destroy_room(Name1, Service1) ->
|
||||
case get_room_pid_validate(Name1, Service1, <<"service">>) of
|
||||
{room_not_found, _, _} ->
|
||||
throw({error, "Room doesn't exists"});
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
{Pid, _, _} ->
|
||||
mod_muc_room:destroy(Pid),
|
||||
ok
|
||||
@@ -1698,6 +1702,8 @@ change_room_option(Name, Service, OptionString, ValueString) ->
|
||||
case get_room_pid_validate(Name, Service, <<"service">>) of
|
||||
{room_not_found, _, _} ->
|
||||
throw({error, "Room not found"});
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
{Pid, _, _} ->
|
||||
{Option, Value} = format_room_option(OptionString, ValueString),
|
||||
change_room_option(Pid, Option, Value)
|
||||
@@ -1823,25 +1829,29 @@ parse_nodes(_, _) ->
|
||||
throw({error, "Invalid 'subscribers' - unknown node name used"}).
|
||||
|
||||
-spec get_room_pid_validate(binary(), binary(), binary()) ->
|
||||
{pid() | room_not_found, binary(), binary()}.
|
||||
{pid() | room_not_found | db_failure, binary(), binary()}.
|
||||
get_room_pid_validate(Name, Service, ServiceArg) ->
|
||||
Name2 = validate_room(Name),
|
||||
{ServerHost, Service2} = validate_muc2(Service, ServiceArg),
|
||||
case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
|
||||
error ->
|
||||
{error, notfound} ->
|
||||
{room_not_found, Name2, Service2};
|
||||
{error, db_failure} ->
|
||||
{db_failure, Name2, Service2};
|
||||
{ok, Pid} ->
|
||||
{Pid, Name2, Service2}
|
||||
end.
|
||||
|
||||
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
|
||||
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | invalid_service | unknown_service.
|
||||
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | db_failure | invalid_service | unknown_service.
|
||||
get_room_pid(Name, Service) ->
|
||||
try get_room_serverhost(Service) of
|
||||
ServerHost ->
|
||||
case mod_muc:unhibernate_room(ServerHost, Service, Name) of
|
||||
error ->
|
||||
{error, notfound} ->
|
||||
room_not_found;
|
||||
{error, db_failure} ->
|
||||
db_failure;
|
||||
{ok, Pid} ->
|
||||
Pid
|
||||
end
|
||||
@@ -1954,6 +1964,8 @@ get_room_affiliations(Name, Service) ->
|
||||
({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
|
||||
{Uname, Domain, Aff, <<>>}
|
||||
end, Affiliations);
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist."})
|
||||
end.
|
||||
@@ -1975,6 +1987,8 @@ get_room_affiliations_v3(Name, Service) ->
|
||||
Jid = makeencode(Uname, Domain),
|
||||
{Jid, Aff, <<>>}
|
||||
end, Affiliations);
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist."})
|
||||
end.
|
||||
@@ -1993,6 +2007,8 @@ get_room_history(Name, Service) ->
|
||||
_ ->
|
||||
throw({error, "Unable to fetch room state."})
|
||||
end;
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist."})
|
||||
end.
|
||||
@@ -2012,6 +2028,8 @@ get_room_affiliation(Name, Service, JID) ->
|
||||
{ok, StateData} = mod_muc_room:get_state(Pid),
|
||||
UserJID = jid:decode(JID),
|
||||
mod_muc_room:get_affiliation(UserJID, StateData);
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist."})
|
||||
end.
|
||||
@@ -2052,6 +2070,8 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
|
||||
{error, _} ->
|
||||
throw({error, "Unable to perform change"})
|
||||
end;
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "Room doesn't exists"})
|
||||
end.
|
||||
@@ -2084,6 +2104,8 @@ subscribe_room(User, Nick, Room, NodeList) ->
|
||||
{error, Reason} ->
|
||||
throw({error, binary_to_list(Reason)})
|
||||
end;
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist"})
|
||||
end
|
||||
@@ -2129,6 +2151,8 @@ unsubscribe_room(User, Room) ->
|
||||
{error, Reason} ->
|
||||
throw({error, binary_to_list(Reason)})
|
||||
end;
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist"})
|
||||
end
|
||||
@@ -2146,6 +2170,8 @@ get_subscribers(Name, Host) ->
|
||||
{Pid, _, _} when is_pid(Pid) ->
|
||||
{ok, JIDList} = mod_muc_room:get_subscribers(Pid),
|
||||
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
|
||||
{db_failure, _Name, _Host} ->
|
||||
throw({error, "Database error"});
|
||||
_ ->
|
||||
throw({error, "The room does not exist"})
|
||||
end.
|
||||
|
||||
@@ -76,9 +76,11 @@ store_room(_LServer, Host, Name, Opts, _) ->
|
||||
mnesia:transaction(F).
|
||||
|
||||
restore_room(_LServer, Host, Name) ->
|
||||
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
|
||||
try mnesia:dirty_read(muc_room, {Name, Host}) of
|
||||
[#muc_room{opts = Opts}] -> Opts;
|
||||
_ -> error
|
||||
catch
|
||||
_:_ -> {error, db_failure}
|
||||
end.
|
||||
|
||||
forget_room(_LServer, Host, Name) ->
|
||||
|
||||
@@ -3221,6 +3221,7 @@ process_item_change(Item, SD, UJID) ->
|
||||
true ->
|
||||
send_kickban_presence(UJID, JID, Reason, 321, none, SD),
|
||||
maybe_send_affiliation(JID, none, SD),
|
||||
unsubscribe_from_room(JID, SD),
|
||||
SD1 = set_affiliation(JID, none, SD),
|
||||
set_role(JID, none, SD1);
|
||||
_ ->
|
||||
@@ -3237,6 +3238,7 @@ process_item_change(Item, SD, UJID) ->
|
||||
{JID, affiliation, outcast, Reason} ->
|
||||
send_kickban_presence(UJID, JID, Reason, 301, outcast, SD),
|
||||
maybe_send_affiliation(JID, outcast, SD),
|
||||
unsubscribe_from_room(JID, SD),
|
||||
{result, undefined, SD2} =
|
||||
process_iq_mucsub(JID,
|
||||
#iq{type = set,
|
||||
@@ -3279,6 +3281,30 @@ process_item_change(Item, SD, UJID) ->
|
||||
{error, xmpp:err_internal_server_error()}
|
||||
end.
|
||||
|
||||
-spec unsubscribe_from_room(jid(), state()) -> ok | error.
|
||||
unsubscribe_from_room(JID, SD) ->
|
||||
case SD#state.config#config.members_only of
|
||||
false ->
|
||||
ok;
|
||||
true ->
|
||||
case mod_muc:unhibernate_room(SD#state.server_host, SD#state.host, SD#state.room) of
|
||||
{error, _Reason0} ->
|
||||
error;
|
||||
{ok, Pid} ->
|
||||
_UnsubPid =
|
||||
spawn(fun() ->
|
||||
case unsubscribe(Pid, JID) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
?WARNING_MSG("Failed to automatically unsubscribe expelled member from room: ~ts",
|
||||
[Reason]),
|
||||
error
|
||||
end
|
||||
end)
|
||||
end
|
||||
end.
|
||||
|
||||
-spec find_changed_items(jid(), affiliation(), role(),
|
||||
[muc_item()], binary(), state(), [admin_action()]) ->
|
||||
{result, [admin_action()]}.
|
||||
|
||||
+4
-2
@@ -220,10 +220,12 @@ restore_room(LServer, Host, Name) ->
|
||||
Opts2 = lists:keystore(subscribers, 1, OptsD, {subscribers, SubData}),
|
||||
mod_muc:opts_to_binary(Opts2);
|
||||
_ ->
|
||||
error
|
||||
{error, db_failure}
|
||||
end;
|
||||
{selected, _} ->
|
||||
error;
|
||||
_ ->
|
||||
error
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
forget_room(LServer, Host, Name) ->
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_providers.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Serve xmpp-provider-v2.json files as described by XMPP Providers
|
||||
%%% Created : 7 August 2025 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2025 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Definitions
|
||||
|
||||
%% This module is based in mod_host_meta.erl
|
||||
|
||||
%% @format-begin
|
||||
|
||||
-module(mod_providers).
|
||||
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, reload/3, process/2, mod_opt_type/1, mod_options/1, depends/2]).
|
||||
-export([mod_doc/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| gen_mod callbacks
|
||||
|
||||
start(_Host, _Opts) ->
|
||||
report_hostmeta_listener(),
|
||||
ok.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
report_hostmeta_listener(),
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| HTTP handlers
|
||||
|
||||
process([],
|
||||
#request{method = 'GET',
|
||||
host = Host,
|
||||
path = Path}) ->
|
||||
case lists:last(Path) of
|
||||
<<"xmpp-provider-v2.json">> ->
|
||||
file_json(Host)
|
||||
end;
|
||||
process(_Path, _Request) ->
|
||||
{404, [], "Not Found"}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| JSON
|
||||
|
||||
file_json(Host) ->
|
||||
Content =
|
||||
#{website => build_urls(Host, website),
|
||||
alternativeJids => gen_mod:get_module_opt(Host, ?MODULE, alternativeJids),
|
||||
busFactor => gen_mod:get_module_opt(Host, ?MODULE, busFactor),
|
||||
organization => gen_mod:get_module_opt(Host, ?MODULE, organization),
|
||||
passwordReset => get_password_url(Host),
|
||||
serverTesting => gen_mod:get_module_opt(Host, ?MODULE, serverTesting),
|
||||
maximumHttpFileUploadTotalSize => get_upload_size(Host),
|
||||
maximumHttpFileUploadStorageTime => get_upload_time(Host),
|
||||
maximumMessageArchiveManagementStorageTime =>
|
||||
gen_mod:get_module_opt(Host, ?MODULE, maximumMessageArchiveManagementStorageTime),
|
||||
professionalHosting => gen_mod:get_module_opt(Host, ?MODULE, professionalHosting),
|
||||
freeOfCharge => gen_mod:get_module_opt(Host, ?MODULE, freeOfCharge),
|
||||
legalNotice => build_urls(Host, legalNotice),
|
||||
serverLocations => gen_mod:get_module_opt(Host, ?MODULE, serverLocations),
|
||||
since => gen_mod:get_module_opt(Host, ?MODULE, since)},
|
||||
{200,
|
||||
[html,
|
||||
{<<"Content-Type">>, <<"application/json">>},
|
||||
{<<"Access-Control-Allow-Origin">>, <<"*">>}],
|
||||
[misc:json_encode(Content)]}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Upload Size
|
||||
|
||||
get_upload_size(Host) ->
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, maximumHttpFileUploadTotalSize) of
|
||||
default_value ->
|
||||
get_upload_size_mhuq(Host);
|
||||
I when is_integer(I) ->
|
||||
I
|
||||
end.
|
||||
|
||||
get_upload_size_mhuq(Host) ->
|
||||
case gen_mod:is_loaded(Host, mod_http_upload_quota) of
|
||||
true ->
|
||||
Access = gen_mod:get_module_opt(Host, mod_http_upload_quota, access_hard_quota),
|
||||
Rules = ejabberd_shaper:read_shaper_rules(Access, Host),
|
||||
get_upload_size_rules(Rules);
|
||||
false ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_upload_size_rules(Rules) ->
|
||||
case lists:keysearch([{acl, all}], 2, Rules) of
|
||||
{value, {Size, _}} ->
|
||||
Size;
|
||||
false ->
|
||||
0
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Upload Time
|
||||
|
||||
get_upload_time(Host) ->
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, maximumHttpFileUploadStorageTime) of
|
||||
default_value ->
|
||||
get_upload_time_mhuq(Host);
|
||||
I when is_integer(I) ->
|
||||
I
|
||||
end.
|
||||
|
||||
get_upload_time_mhuq(Host) ->
|
||||
case gen_mod:is_loaded(Host, mod_http_upload_quota) of
|
||||
true ->
|
||||
case gen_mod:get_module_opt(Host, mod_http_upload_quota, max_days) of
|
||||
infinity ->
|
||||
0;
|
||||
I when is_integer(I) ->
|
||||
I
|
||||
end;
|
||||
false ->
|
||||
0
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Password URL
|
||||
|
||||
get_password_url(Host) ->
|
||||
build_urls(Host, get_password_url2(Host)).
|
||||
|
||||
get_password_url2(Host) ->
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, passwordReset) of
|
||||
default_value ->
|
||||
get_password_url3(Host);
|
||||
U when is_binary(U) ->
|
||||
U
|
||||
end.
|
||||
|
||||
get_password_url3(Host) ->
|
||||
case find_handler_port_path2(any, mod_register_web) of
|
||||
[] ->
|
||||
<<"">>;
|
||||
[{ThisTls, Port, Path} | _] ->
|
||||
Protocol =
|
||||
case ThisTls of
|
||||
false ->
|
||||
<<"http">>;
|
||||
true ->
|
||||
<<"https">>
|
||||
end,
|
||||
<<Protocol/binary,
|
||||
"://",
|
||||
Host/binary,
|
||||
":",
|
||||
(integer_to_binary(Port))/binary,
|
||||
"/",
|
||||
(str:join(Path, <<"/">>))/binary,
|
||||
"/">>
|
||||
end.
|
||||
|
||||
%% TODO Ya hay otra funciona como esta
|
||||
find_handler_port_path2(Tls, Module) ->
|
||||
lists:filtermap(fun ({{Port, _, _},
|
||||
ejabberd_http,
|
||||
#{tls := ThisTls, request_handlers := Handlers}})
|
||||
when (Tls == any) or (Tls == ThisTls) ->
|
||||
case lists:keyfind(Module, 2, Handlers) of
|
||||
false ->
|
||||
false;
|
||||
{Path, Module} ->
|
||||
{true, {ThisTls, Port, Path}}
|
||||
end;
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
ets:tab2list(ejabberd_listener)).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Build URLs
|
||||
|
||||
build_urls(Host, Option) when is_atom(Option) ->
|
||||
build_urls(Host, gen_mod:get_module_opt(Host, ?MODULE, Option));
|
||||
build_urls(_Host, <<"">>) ->
|
||||
#{};
|
||||
build_urls(Host, Url) when not is_atom(Url) ->
|
||||
Languages = gen_mod:get_module_opt(Host, ?MODULE, languages),
|
||||
maps:from_list([{L, misc:expand_keyword(<<"@LANGUAGE_URL@">>, Url, L)}
|
||||
|| L <- Languages]).
|
||||
|
||||
find_handler_port_path(Tls, Module) ->
|
||||
lists:filtermap(fun ({{Port, _, _},
|
||||
ejabberd_http,
|
||||
#{tls := ThisTls, request_handlers := Handlers}})
|
||||
when is_integer(Port) and ((Tls == any) or (Tls == ThisTls)) ->
|
||||
case lists:keyfind(Module, 2, Handlers) of
|
||||
false ->
|
||||
false;
|
||||
{Path, Module} ->
|
||||
{true, {ThisTls, Port, Path}}
|
||||
end;
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
ets:tab2list(ejabberd_listener)).
|
||||
|
||||
report_hostmeta_listener() ->
|
||||
case {find_handler_port_path(false, ?MODULE), find_handler_port_path(true, ?MODULE)} of
|
||||
{[], []} ->
|
||||
?CRITICAL_MSG("It seems you enabled ~p in 'modules' but forgot to "
|
||||
"add it as a request_handler in an ejabberd_http "
|
||||
"listener.",
|
||||
[?MODULE]);
|
||||
{[_ | _], _} ->
|
||||
?WARNING_MSG("Apparently ~p is enabled in a request_handler in a "
|
||||
"non-encrypted ejabberd_http listener. If this is "
|
||||
"not desired, enable 'tls' in that "
|
||||
"listener, or setup a proxy encryption mechanism.",
|
||||
[?MODULE]);
|
||||
{[], [_ | _]} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Options
|
||||
|
||||
mod_opt_type(languages) ->
|
||||
econf:list(
|
||||
econf:binary());
|
||||
mod_opt_type(website) ->
|
||||
econf:binary();
|
||||
mod_opt_type(alternativeJids) ->
|
||||
econf:list(
|
||||
econf:domain(), [unique]);
|
||||
mod_opt_type(busFactor) ->
|
||||
econf:int();
|
||||
mod_opt_type(organization) ->
|
||||
econf:enum([company,
|
||||
'commercial person',
|
||||
'private person',
|
||||
governmental,
|
||||
'non-governmental']);
|
||||
mod_opt_type(passwordReset) ->
|
||||
econf:binary();
|
||||
mod_opt_type(serverTesting) ->
|
||||
econf:bool();
|
||||
mod_opt_type(maximumHttpFileUploadTotalSize) ->
|
||||
econf:int();
|
||||
mod_opt_type(maximumHttpFileUploadStorageTime) ->
|
||||
econf:int();
|
||||
mod_opt_type(maximumMessageArchiveManagementStorageTime) ->
|
||||
econf:int();
|
||||
mod_opt_type(professionalHosting) ->
|
||||
econf:bool();
|
||||
mod_opt_type(freeOfCharge) ->
|
||||
econf:bool();
|
||||
mod_opt_type(legalNotice) ->
|
||||
econf:binary();
|
||||
mod_opt_type(serverLocations) ->
|
||||
econf:list(
|
||||
econf:binary());
|
||||
mod_opt_type(since) ->
|
||||
econf:binary().
|
||||
|
||||
mod_options(Host) ->
|
||||
[{languages, [ejabberd_option:language(Host)]},
|
||||
{website, <<"">>},
|
||||
{alternativeJids, []},
|
||||
{busFactor, -1},
|
||||
{organization, ''},
|
||||
{passwordReset, default_value},
|
||||
{serverTesting, false},
|
||||
{maximumHttpFileUploadTotalSize, default_value},
|
||||
{maximumHttpFileUploadStorageTime, default_value},
|
||||
{maximumMessageArchiveManagementStorageTime, 0},
|
||||
{professionalHosting, false},
|
||||
{freeOfCharge, false},
|
||||
{legalNotice, <<"">>},
|
||||
{serverLocations, []},
|
||||
{since, <<"">>}].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%| Doc
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("This module serves JSON provider files API v2 as described by "
|
||||
"https://providers.xmpp.net/provider-file-generator/[XMPP Providers]."),
|
||||
"",
|
||||
?T("It attempts to fill some properties gathering values automatically from your existing ejabberd configuration. Try enabling the module, check what values are displayed, and then customize using the options."),
|
||||
"",
|
||||
?T("To use this module, in addition to adding it to the 'modules' "
|
||||
"section, you must also enable it in 'listen' -> 'ejabberd_http' -> "
|
||||
"_`listen-options.md#request_handlers|request_handlers`_. "
|
||||
"Notice you should set in _`listen.md#ejabberd_http|ejabberd_http`_ "
|
||||
"the option _`listen-options.md#tls|tls`_ enabled.")],
|
||||
note => "added in 25.08",
|
||||
opts =>
|
||||
[{languages,
|
||||
#{value => "[string()]",
|
||||
desc =>
|
||||
?T("List of language codes that your pages are available. "
|
||||
"Some options define URL where the keyword '@LANGUAGE_URL@' "
|
||||
"will be replaced with each of those language codes. "
|
||||
"The default value is a list with the language set in the "
|
||||
"option _`language`_, for example: '[en]'.")}},
|
||||
{website,
|
||||
#{value => "string()",
|
||||
desc =>
|
||||
?T("Provider website. "
|
||||
"The keyword '@LANGUAGE_URL@' is replaced with each language. "
|
||||
"The default value is '\"\"'.")}},
|
||||
{alternativeJids,
|
||||
#{value => "[string()]",
|
||||
desc =>
|
||||
?T("List of JIDs (XMPP server domains) a provider offers for "
|
||||
"registration other than its main JID. "
|
||||
"The default value is '[]'.")}},
|
||||
{busFactor,
|
||||
#{value => "integer()",
|
||||
desc =>
|
||||
?T("Bus factor of the XMPP service (i.e., the minimum number of "
|
||||
"team members that the service could not survive losing) or '-1' for n/a. "
|
||||
"The default value is '-1'.")}},
|
||||
{organization,
|
||||
#{value => "string()",
|
||||
desc =>
|
||||
?T("Type of organization providing the XMPP service. "
|
||||
"Allowed values are: 'company', '\"commercial person\"', '\"private person\"', "
|
||||
"'governmental', '\"non-governmental\"' or '\"\"'. "
|
||||
"The default value is '\"\"'.")}},
|
||||
{passwordReset,
|
||||
#{value => "string()",
|
||||
desc =>
|
||||
?T("Password reset web page (per language) used for an automatic password reset "
|
||||
"(e.g., via email) or describing how to manually reset a password "
|
||||
"(e.g., by contacting the provider). "
|
||||
"The keyword '@LANGUAGE_URL@' is replaced with each language. "
|
||||
"The default value is an URL built automatically "
|
||||
"if _`mod_register_web`_ is configured as a 'request_handler', "
|
||||
"or '\"\"' otherwise.")}},
|
||||
{serverTesting,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("Whether tests against the provider's server are allowed "
|
||||
"(e.g., certificate checks and uptime monitoring). "
|
||||
"The default value is 'false'.")}},
|
||||
{maximumHttpFileUploadTotalSize,
|
||||
#{value => "integer()",
|
||||
desc =>
|
||||
?T("Maximum size of all shared files in total per user (number in megabytes (MB), "
|
||||
"'0' for no limit or '-1' for less than 1 MB). "
|
||||
"Attention: MB is used instead of MiB (e.g., 104,857,600 bytes = 100 MiB ≈ 104 MB). "
|
||||
"This property is not about the maximum size of each shared file, "
|
||||
"which is already retrieved via XMPP. "
|
||||
"The default value is the value of the shaper value "
|
||||
"of option 'access_hard_quota' "
|
||||
"from module _`mod_http_upload_quota`_, or '0' otherwise.")}},
|
||||
{maximumHttpFileUploadStorageTime,
|
||||
#{value => "integer()",
|
||||
desc =>
|
||||
?T("Maximum storage duration of each shared file "
|
||||
"(number in days, '0' for no limit or '-1' for less than 1 day). "
|
||||
"The default value is the same as option 'max_days' "
|
||||
"from module _`mod_http_upload_quota`_, or '0' otherwise.")}},
|
||||
{maximumMessageArchiveManagementStorageTime,
|
||||
#{value => "integer()",
|
||||
desc =>
|
||||
?T("Maximum storage duration of each exchanged message "
|
||||
"(number in days, '0' for no limit or '-1' for less than 1 day). "
|
||||
"The default value is '0'.")}},
|
||||
{professionalHosting,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("Whether the XMPP server is hosted with good internet connection speed, "
|
||||
"uninterruptible power supply, access protection and regular backups. "
|
||||
"The default value is 'false'.")}},
|
||||
{freeOfCharge,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("Whether the XMPP service can be used for free. "
|
||||
"The default value is 'false'.")}},
|
||||
{legalNotice,
|
||||
#{value => "string()",
|
||||
desc =>
|
||||
?T("Legal notice web page (per language). "
|
||||
"The keyword '@LANGUAGE_URL@' is replaced with each language. "
|
||||
"The default value is '\"\"'.")}},
|
||||
{serverLocations,
|
||||
#{value => "[string()]",
|
||||
desc =>
|
||||
?T("List of language codes of Server/Backup locations. "
|
||||
"The default value is an empty list: '[]'.")}},
|
||||
{since,
|
||||
#{value => "string()",
|
||||
desc =>
|
||||
?T("Date since the XMPP service is available. "
|
||||
"The default value is an empty string: '\"\"'.")}}],
|
||||
example =>
|
||||
["listen:",
|
||||
" -",
|
||||
" port: 443",
|
||||
" module: ejabberd_http",
|
||||
" tls: true",
|
||||
" request_handlers:",
|
||||
" /.well-known/xmpp-provider-v2.json: mod_providers",
|
||||
"",
|
||||
"modules:",
|
||||
" mod_providers:",
|
||||
" alternativeJids: [\"example1.com\", \"example2.com\"]",
|
||||
" busFactor: 1",
|
||||
" freeOfCharge: true",
|
||||
" languages: [ag, ao, bg, en]",
|
||||
" legalNotice: \"http://@HOST@/legal/@LANGUAGE_URL@/\"",
|
||||
" maximumHttpFileUploadStorageTime: 0",
|
||||
" maximumHttpFileUploadTotalSize: 0",
|
||||
" maximumMessageArchiveManagementStorageTime: 0",
|
||||
" organization: \"non-governmental\"",
|
||||
" passwordReset: \"http://@HOST@/reset/@LANGUAGE_URL@/\"",
|
||||
" professionalHosting: true",
|
||||
" serverLocations: [ao, bg]",
|
||||
" serverTesting: true",
|
||||
" since: \"2025-12-31\"",
|
||||
" website: \"http://@HOST@/website/@LANGUAGE_URL@/\""]}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%%| vim: set foldmethod=marker foldmarker=%%|,%%-:
|
||||
@@ -0,0 +1,111 @@
|
||||
%% Generated automatically
|
||||
%% DO NOT EDIT: run `make options` instead
|
||||
|
||||
-module(mod_providers_opt).
|
||||
|
||||
-export([alternativeJids/1]).
|
||||
-export([busFactor/1]).
|
||||
-export([freeOfCharge/1]).
|
||||
-export([languages/1]).
|
||||
-export([legalNotice/1]).
|
||||
-export([maximumHttpFileUploadStorageTime/1]).
|
||||
-export([maximumHttpFileUploadTotalSize/1]).
|
||||
-export([maximumMessageArchiveManagementStorageTime/1]).
|
||||
-export([organization/1]).
|
||||
-export([passwordReset/1]).
|
||||
-export([professionalHosting/1]).
|
||||
-export([serverLocations/1]).
|
||||
-export([serverTesting/1]).
|
||||
-export([since/1]).
|
||||
-export([website/1]).
|
||||
|
||||
-spec alternativeJids(gen_mod:opts() | global | binary()) -> [binary()].
|
||||
alternativeJids(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(alternativeJids, Opts);
|
||||
alternativeJids(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, alternativeJids).
|
||||
|
||||
-spec busFactor(gen_mod:opts() | global | binary()) -> integer().
|
||||
busFactor(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(busFactor, Opts);
|
||||
busFactor(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, busFactor).
|
||||
|
||||
-spec freeOfCharge(gen_mod:opts() | global | binary()) -> boolean().
|
||||
freeOfCharge(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(freeOfCharge, Opts);
|
||||
freeOfCharge(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, freeOfCharge).
|
||||
|
||||
-spec languages(gen_mod:opts() | global | binary()) -> [binary()].
|
||||
languages(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(languages, Opts);
|
||||
languages(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, languages).
|
||||
|
||||
-spec legalNotice(gen_mod:opts() | global | binary()) -> binary().
|
||||
legalNotice(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(legalNotice, Opts);
|
||||
legalNotice(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, legalNotice).
|
||||
|
||||
-spec maximumHttpFileUploadStorageTime(gen_mod:opts() | global | binary()) -> 'default_value' | integer().
|
||||
maximumHttpFileUploadStorageTime(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(maximumHttpFileUploadStorageTime, Opts);
|
||||
maximumHttpFileUploadStorageTime(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, maximumHttpFileUploadStorageTime).
|
||||
|
||||
-spec maximumHttpFileUploadTotalSize(gen_mod:opts() | global | binary()) -> 'default_value' | integer().
|
||||
maximumHttpFileUploadTotalSize(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(maximumHttpFileUploadTotalSize, Opts);
|
||||
maximumHttpFileUploadTotalSize(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, maximumHttpFileUploadTotalSize).
|
||||
|
||||
-spec maximumMessageArchiveManagementStorageTime(gen_mod:opts() | global | binary()) -> integer().
|
||||
maximumMessageArchiveManagementStorageTime(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(maximumMessageArchiveManagementStorageTime, Opts);
|
||||
maximumMessageArchiveManagementStorageTime(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, maximumMessageArchiveManagementStorageTime).
|
||||
|
||||
-spec organization(gen_mod:opts() | global | binary()) -> '' | 'commercial person' | 'company' | 'governmental' | 'non-governmental' | 'private person'.
|
||||
organization(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(organization, Opts);
|
||||
organization(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, organization).
|
||||
|
||||
-spec passwordReset(gen_mod:opts() | global | binary()) -> 'default_value' | binary().
|
||||
passwordReset(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(passwordReset, Opts);
|
||||
passwordReset(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, passwordReset).
|
||||
|
||||
-spec professionalHosting(gen_mod:opts() | global | binary()) -> boolean().
|
||||
professionalHosting(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(professionalHosting, Opts);
|
||||
professionalHosting(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, professionalHosting).
|
||||
|
||||
-spec serverLocations(gen_mod:opts() | global | binary()) -> [binary()].
|
||||
serverLocations(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(serverLocations, Opts);
|
||||
serverLocations(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, serverLocations).
|
||||
|
||||
-spec serverTesting(gen_mod:opts() | global | binary()) -> boolean().
|
||||
serverTesting(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(serverTesting, Opts);
|
||||
serverTesting(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, serverTesting).
|
||||
|
||||
-spec since(gen_mod:opts() | global | binary()) -> binary().
|
||||
since(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(since, Opts);
|
||||
since(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, since).
|
||||
|
||||
-spec website(gen_mod:opts() | global | binary()) -> binary().
|
||||
website(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(website, Opts);
|
||||
website(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_providers, website).
|
||||
|
||||
@@ -420,11 +420,6 @@ send_welcome_message(JID) ->
|
||||
to = JID,
|
||||
type = chat,
|
||||
subject = xmpp:mk_text(Subj),
|
||||
body = xmpp:mk_text(<<Subj/binary, "\n\n", Body/binary>>)}),
|
||||
ejabberd_router:route(
|
||||
#message{from = jid:make(Host),
|
||||
to = JID,
|
||||
subject = xmpp:mk_text(Subj),
|
||||
body = xmpp:mk_text(Body)})
|
||||
end.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]).
|
||||
-export([mod_doc/0]).
|
||||
%% Hooks
|
||||
-export([c2s_inline_features/2, c2s_handle_sasl2_inline/1,
|
||||
-export([c2s_inline_features/3, c2s_handle_sasl2_inline/1,
|
||||
c2s_handle_sasl2_task_next/4, c2s_handle_sasl2_task_data/3]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
@@ -76,11 +76,23 @@ mod_doc() ->
|
||||
" - sha256",
|
||||
" - sha512"]}.
|
||||
|
||||
c2s_inline_features({Sasl, Bind, Extra}, Host) ->
|
||||
Methods = lists:map(
|
||||
fun(sha256) -> #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-256">>};
|
||||
(sha512) -> #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-512">>}
|
||||
end, mod_scram_upgrade_opt:offered_upgrades(Host)),
|
||||
c2s_inline_features({Sasl, Bind, Extra}, Host, State) ->
|
||||
KnowTypes = case State of
|
||||
#{sasl2_password_fun := Fun} ->
|
||||
case Fun(<<>>) of
|
||||
{Pass, _} -> lists:filtermap(
|
||||
fun(#scram{hash = sha256}) -> {true, sha256};
|
||||
(#scram{hash = sha512}) -> {true, sha512};
|
||||
(_) -> false
|
||||
end, Pass);
|
||||
_ -> []
|
||||
end;
|
||||
_ -> []
|
||||
end,
|
||||
Methods = lists:filtermap(
|
||||
fun(sha256) -> {true, #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-256">>}};
|
||||
(sha512) -> {true, #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-512">>}}
|
||||
end, mod_scram_upgrade_opt:offered_upgrades(Host) -- KnowTypes),
|
||||
{Sasl, Bind, Methods ++ Extra}.
|
||||
|
||||
c2s_handle_sasl2_inline({State, Els, _Results} = Acc) ->
|
||||
@@ -108,8 +120,10 @@ c2s_handle_sasl2_task_data({_, #{user := User, server := Server,
|
||||
StoredKey = scram:stored_key(Algo, scram:client_key(Algo, SaltedPassword)),
|
||||
ServerKey = scram:server_key(Algo, SaltedPassword),
|
||||
ejabberd_auth:set_password_instance(User, Server,
|
||||
#scram{hash = Algo, iterationcount = Iter, salt = Salt,
|
||||
serverkey = ServerKey, storedkey = StoredKey}),
|
||||
#scram{hash = Algo, iterationcount = Iter,
|
||||
salt = base64:encode(Salt),
|
||||
serverkey = base64:encode(ServerKey),
|
||||
storedkey = base64:encode(StoredKey)}),
|
||||
State2 = maps:remove(scram_upgrade, State),
|
||||
InlineEls2 = lists:keydelete(sasl_upgrade, 1, InlineEls),
|
||||
{State3, NewEls, Results} = ejabberd_c2s:handle_sasl2_inline(InlineEls2, State2),
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
c2s_authenticated_packet/2, c2s_unauthenticated_packet/2,
|
||||
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
|
||||
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_cast/2,
|
||||
c2s_handle_call/3, c2s_handle_recv/3, c2s_inline_features/2,
|
||||
c2s_handle_call/3, c2s_handle_recv/3, c2s_inline_features/3,
|
||||
c2s_handle_sasl2_inline/1, c2s_handle_sasl2_inline_post/3,
|
||||
c2s_handle_bind2_inline/1]).
|
||||
%% adjust pending session timeout / access queue
|
||||
@@ -122,7 +122,7 @@ c2s_stream_features(Acc, Host) ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host) ->
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host, _State) ->
|
||||
case gen_mod:is_loaded(Host, ?MODULE) of
|
||||
true ->
|
||||
{[#feature_sm{xmlns = ?NS_STREAM_MGMT_3} | Sasl],
|
||||
|
||||
@@ -87,8 +87,8 @@ convert_dir(Path, Host, Type) ->
|
||||
case eval_file(FilePath) of
|
||||
{ok, Data} ->
|
||||
Name = iolist_to_binary(filename:rootname(File)),
|
||||
convert_data(url_decode(Host), Type,
|
||||
url_decode(Name), Data);
|
||||
convert_data(misc:uri_decode(Host), Type,
|
||||
misc:uri_decode(Name), Data);
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
@@ -410,16 +410,6 @@ convert_privacy_item({_, Item}) ->
|
||||
match_presence_in = MatchPresIn,
|
||||
match_presence_out = MatchPresOut}.
|
||||
|
||||
url_decode(Encoded) ->
|
||||
url_decode(Encoded, <<>>).
|
||||
url_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
|
||||
Hex = list_to_integer([Hi, Lo], 16),
|
||||
url_decode(Tail, <<Acc/binary, Hex>>);
|
||||
url_decode(<<H, Tail/binary>>, Acc) ->
|
||||
url_decode(Tail, <<Acc/binary, H>>);
|
||||
url_decode(<<>>, Acc) ->
|
||||
Acc.
|
||||
|
||||
decode_pubsub_host(Host) ->
|
||||
try jid:decode(Host) of
|
||||
#jid{luser = <<>>, lserver = LServer} -> LServer;
|
||||
|
||||
+53
-48
@@ -480,8 +480,11 @@ encode(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
|
||||
decode(<<$<, _/binary>> = Data, _J1, _J2) ->
|
||||
fxml_stream:parse_element(Data);
|
||||
decode(<<1:8, Rest/binary>>, J1, J2) ->
|
||||
{El, _} = decode(Rest, <<"jabber:client">>, J1, J2),
|
||||
El.
|
||||
try decode(Rest, <<"jabber:client">>, J1, J2, false) of
|
||||
{El, _} -> El
|
||||
catch throw:loop_detected ->
|
||||
{error, {loop_detected, <<"Compressed data corrupted">>}}
|
||||
end.
|
||||
|
||||
decode_string(Data) ->
|
||||
case Data of
|
||||
@@ -489,35 +492,37 @@ decode_string(Data) ->
|
||||
{Str, Rest};
|
||||
<<1:2, L1:6, 0:2, L2:6, Rest/binary>> ->
|
||||
L = L2*64 + L1,
|
||||
<<Str:L/binary, Rest2/binary>> = Rest,
|
||||
<<Str:L/binary, Rest2/binary>> = Rest,
|
||||
{Str, Rest2};
|
||||
<<1:2, L1:6, 1:2, L2:6, L3:8, Rest/binary>> ->
|
||||
L = (L3*64 + L2)*64 + L1,
|
||||
<<Str:L/binary, Rest2/binary>> = Rest,
|
||||
<<Str:L/binary, Rest2/binary>> = Rest,
|
||||
{Str, Rest2}
|
||||
end.
|
||||
|
||||
decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2) ->
|
||||
decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2, _) ->
|
||||
{Text, Rest2} = decode_string(Rest),
|
||||
{{xmlcdata, Text}, Rest2};
|
||||
decode_child(<<2:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode_child(<<2:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
{Name, Rest2} = decode_string(Rest),
|
||||
{Attrs, Rest3} = decode_attrs(Rest2),
|
||||
{Children, Rest4} = decode_children(Rest3, PNs, J1, J2),
|
||||
{{xmlel, Name, Attrs, Children}, Rest4};
|
||||
decode_child(<<3:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode_child(<<3:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
{Ns, Rest2} = decode_string(Rest),
|
||||
{Name, Rest3} = decode_string(Rest2),
|
||||
{Attrs, Rest4} = decode_attrs(Rest3),
|
||||
{Children, Rest5} = decode_children(Rest4, Ns, J1, J2),
|
||||
{{xmlel, Name, add_ns(PNs, Ns, Attrs), Children}, Rest5};
|
||||
decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2) ->
|
||||
decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2, _) ->
|
||||
{stop, Rest};
|
||||
decode_child(Other, PNs, J1, J2) ->
|
||||
decode(Other, PNs, J1, J2).
|
||||
decode_child(_Other, _PNs, _J1, _J2, true) ->
|
||||
throw(loop_detected);
|
||||
decode_child(Other, PNs, J1, J2, _) ->
|
||||
decode(Other, PNs, J1, J2, true).
|
||||
|
||||
decode_children(Data, PNs, J1, J2) ->
|
||||
prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2) end, Data).
|
||||
prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2, false) end, Data).
|
||||
|
||||
decode_attr(<<1:8, Rest/binary>>) ->
|
||||
{Name, Rest2} = decode_string(Rest),
|
||||
@@ -545,7 +550,7 @@ add_ns(Ns, Ns, Attrs) ->
|
||||
add_ns(_, Ns, Attrs) ->
|
||||
[{<<"xmlns">>, Ns} | Attrs].
|
||||
|
||||
decode(<<5:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<5:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"eu.siacs.conversations.axolotl">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -563,12 +568,12 @@ decode(<<5:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"key">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<12:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<12:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"eu.siacs.conversations.axolotl">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"encrypted">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<13:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<13:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"eu.siacs.conversations.axolotl">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -581,17 +586,17 @@ decode(<<13:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"header">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<14:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<14:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"eu.siacs.conversations.axolotl">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"iv">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<15:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<15:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"eu.siacs.conversations.axolotl">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"payload">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<6:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<6:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"jabber:client">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -636,7 +641,7 @@ decode(<<6:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"message">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<8:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<8:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"jabber:client">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = prefix_map(fun (<<9:8, Rest5/binary>>) ->
|
||||
@@ -650,25 +655,25 @@ decode(<<8:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
32,104,116,116,112,115,58,47,47,99,111,110,118,101,114,115,
|
||||
97,116,105,111,110,115,46,105,109,47,111,109,101,109,111>>}, Rest5};
|
||||
(Other) ->
|
||||
decode_child(Other, Ns, J1, J2)
|
||||
decode_child(Other, Ns, J1, J2, false)
|
||||
end, Rest2),
|
||||
{{xmlel, <<"body">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<31:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<31:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"jabber:client">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"subject">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<32:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<32:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"jabber:client">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"thread">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<7:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<7:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:hints">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"store">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<10:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<10:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:sid:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -681,7 +686,7 @@ decode(<<10:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"origin-id">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<22:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<22:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:sid:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -697,12 +702,12 @@ decode(<<22:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"stanza-id">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<11:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<11:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:chat-markers:0">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"markable">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<20:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<20:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:chat-markers:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -724,7 +729,7 @@ decode(<<20:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"displayed">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<24:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<24:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:chat-markers:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -737,7 +742,7 @@ decode(<<24:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"received">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<16:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<16:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:eme:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -757,7 +762,7 @@ decode(<<16:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"encryption">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<17:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<17:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:delay">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -775,7 +780,7 @@ decode(<<17:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"delay">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<18:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<18:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/address">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -796,12 +801,12 @@ decode(<<18:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"address">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<19:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<19:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/address">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"addresses">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<21:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<21:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:mam:tmp">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -817,12 +822,12 @@ decode(<<21:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"archived">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<23:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<23:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:receipts">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"request">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<25:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<25:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:receipts">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -835,17 +840,17 @@ decode(<<25:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"received">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<26:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<26:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"active">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<39:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<39:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"composing">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<27:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<27:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -861,17 +866,17 @@ decode(<<27:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"invite">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<28:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<28:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"reason">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<29:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<29:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/muc#user">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<30:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<30:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"jabber:x:conference">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -886,12 +891,12 @@ decode(<<30:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<33:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<33:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/pubsub#event">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"event">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<34:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<34:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/pubsub#event">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -904,7 +909,7 @@ decode(<<34:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"item">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<35:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<35:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"http://jabber.org/protocol/pubsub#event">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -919,7 +924,7 @@ decode(<<35:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"items">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<36:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<36:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"p1:push:custom">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -935,12 +940,12 @@ decode(<<36:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<37:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<37:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"p1:pushed">>,
|
||||
{Attrs, Rest2} = decode_attrs(Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(<<38:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
decode(<<38:8, Rest/binary>>, PNs, J1, J2, _) ->
|
||||
Ns = <<"urn:xmpp:message-correct:0">>,
|
||||
{Attrs, Rest2} = prefix_map(fun
|
||||
(<<3:8, Rest3/binary>>) ->
|
||||
@@ -953,6 +958,6 @@ decode(<<38:8, Rest/binary>>, PNs, J1, J2) ->
|
||||
end, Rest),
|
||||
{Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
|
||||
{{xmlel, <<"replace">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
|
||||
decode(Other, PNs, J1, J2) ->
|
||||
decode_child(Other, PNs, J1, J2).
|
||||
decode(Other, PNs, J1, J2, Loop) ->
|
||||
decode_child(Other, PNs, J1, J2, Loop).
|
||||
|
||||
|
||||
@@ -961,8 +961,6 @@ presence_broadcast(Config) ->
|
||||
sub_els = [#disco_info{node = Node}]} = recv_iq(Config),
|
||||
#message{type = chat,
|
||||
subject = [#text{lang = <<"en">>,data = <<"Welcome!">>}]} = recv_message(Config),
|
||||
#message{type = normal,
|
||||
subject = [#text{lang = <<"en">>,data = <<"Welcome!">>}]} = recv_message(Config),
|
||||
#presence{from = JID, to = JID} = recv_presence(Config),
|
||||
send(Config, #iq{type = result, id = IQ#iq.id,
|
||||
to = JID, sub_els = [Info]}),
|
||||
|
||||
+4
-4
@@ -70,8 +70,8 @@ termcap_vsn='1.3.1'
|
||||
expat_vsn='2.7.1'
|
||||
zlib_vsn='1.3.1'
|
||||
yaml_vsn='0.2.5'
|
||||
ssl_vsn='3.5.1'
|
||||
otp_vsn='27.3.4.1'
|
||||
ssl_vsn='3.5.2'
|
||||
otp_vsn='27.3.4.2'
|
||||
elixir_vsn='1.18.4'
|
||||
pam_vsn='1.6.1' # Newer Linux-PAM versions use Meson, we don't support that yet.
|
||||
png_vsn='1.6.45'
|
||||
@@ -629,8 +629,8 @@ build_deps()
|
||||
info "Building Linux-PAM $pam_vsn for $arch ..."
|
||||
cd "$target_src_dir/$pam_dir"
|
||||
$configure --prefix="$prefix" --includedir="$prefix/include/security" \
|
||||
--enable-static --disable-shared --disable-doc --disable-examples \
|
||||
--enable-db=no \
|
||||
--enable-static --disable-shared --disable-logind --disable-doc \
|
||||
--disable-examples --enable-db=no \
|
||||
CFLAGS="$CFLAGS -O3 -fPIC -Wno-error=implicit-function-declaration"
|
||||
make
|
||||
make install
|
||||
|
||||
+20
-15
@@ -93,41 +93,46 @@ gen_decode(Dev, Data, VerId) ->
|
||||
io:format(Dev, "decode(<<$<, _/binary>> = Data, _J1, _J2) ->~n"
|
||||
" fxml_stream:parse_element(Data);~n"
|
||||
"decode(<<~s, Rest/binary>>, J1, J2) ->~n"
|
||||
" {El, _} = decode(Rest, <<\"jabber:client\">>, J1, J2),~n"
|
||||
" El.~n~n", [VerId]),
|
||||
" try decode(Rest, <<\"jabber:client\">>, J1, J2, false) of~n"
|
||||
" {El, _} -> El~n"
|
||||
" catch throw:loop_detected ->~n"
|
||||
" {error, {loop_detected, <<\"Compressed data corrupted\">>}}~n"
|
||||
" end.~n~n", [VerId]),
|
||||
io:format(Dev, "decode_string(Data) ->~n"
|
||||
" case Data of~n"
|
||||
" <<0:2, L:6, Str:L/binary, Rest/binary>> ->~n"
|
||||
" {Str, Rest};~n"
|
||||
" <<1:2, L1:6, 0:2, L2:6, Rest/binary>> ->~n"
|
||||
" L = L2*64 + L1,~n"
|
||||
" <<Str:L/binary, Rest2/binary>> = Rest,~n"
|
||||
" <<Str:L/binary, Rest2/binary>> = Rest,~n"
|
||||
" {Str, Rest2};~n"
|
||||
" <<1:2, L1:6, 1:2, L2:6, L3:8, Rest/binary>> ->~n"
|
||||
" L = (L3*64 + L2)*64 + L1,~n"
|
||||
" <<Str:L/binary, Rest2/binary>> = Rest,~n"
|
||||
" <<Str:L/binary, Rest2/binary>> = Rest,~n"
|
||||
" {Str, Rest2}~n"
|
||||
" end.~n~n", []),
|
||||
io:format(Dev, "decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2) ->~n"
|
||||
io:format(Dev, "decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2, _) ->~n"
|
||||
" {Text, Rest2} = decode_string(Rest),~n"
|
||||
" {{xmlcdata, Text}, Rest2};~n", []),
|
||||
io:format(Dev, "decode_child(<<2:8, Rest/binary>>, PNs, J1, J2) ->~n"
|
||||
io:format(Dev, "decode_child(<<2:8, Rest/binary>>, PNs, J1, J2, _) ->~n"
|
||||
" {Name, Rest2} = decode_string(Rest),~n"
|
||||
" {Attrs, Rest3} = decode_attrs(Rest2),~n"
|
||||
" {Children, Rest4} = decode_children(Rest3, PNs, J1, J2),~n"
|
||||
" {{xmlel, Name, Attrs, Children}, Rest4};~n", []),
|
||||
io:format(Dev, "decode_child(<<3:8, Rest/binary>>, PNs, J1, J2) ->~n"
|
||||
io:format(Dev, "decode_child(<<3:8, Rest/binary>>, PNs, J1, J2, _) ->~n"
|
||||
" {Ns, Rest2} = decode_string(Rest),~n"
|
||||
" {Name, Rest3} = decode_string(Rest2),~n"
|
||||
" {Attrs, Rest4} = decode_attrs(Rest3),~n"
|
||||
" {Children, Rest5} = decode_children(Rest4, Ns, J1, J2),~n"
|
||||
" {{xmlel, Name, add_ns(PNs, Ns, Attrs), Children}, Rest5};~n", []),
|
||||
io:format(Dev, "decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2) ->~n"
|
||||
io:format(Dev, "decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2, _) ->~n"
|
||||
" {stop, Rest};~n", []),
|
||||
io:format(Dev, "decode_child(Other, PNs, J1, J2) ->~n"
|
||||
" decode(Other, PNs, J1, J2).~n~n", []),
|
||||
io:format(Dev, "decode_child(_Other, _PNs, _J1, _J2, true) ->~n"
|
||||
" throw(loop_detected);~n", []),
|
||||
io:format(Dev, "decode_child(Other, PNs, J1, J2, _) ->~n"
|
||||
" decode(Other, PNs, J1, J2, true).~n~n", []),
|
||||
io:format(Dev, "decode_children(Data, PNs, J1, J2) ->~n"
|
||||
" prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2) end, Data).~n~n", []),
|
||||
" prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2, false) end, Data).~n~n", []),
|
||||
io:format(Dev, "decode_attr(<<1:8, Rest/binary>>) ->~n"
|
||||
" {Name, Rest2} = decode_string(Rest),~n"
|
||||
" {Val, Rest3} = decode_string(Rest2),~n"
|
||||
@@ -153,7 +158,7 @@ gen_decode(Dev, Data, VerId) ->
|
||||
fun({Ns, Els}) ->
|
||||
lists:foreach(
|
||||
fun({Name, Id, Attrs, Text}) ->
|
||||
io:format(Dev, "decode(<<~s, Rest/binary>>, PNs, J1, J2) ->~n"
|
||||
io:format(Dev, "decode(<<~s, Rest/binary>>, PNs, J1, J2, _) ->~n"
|
||||
" Ns = ~p,~n", [Id, Ns]),
|
||||
case Attrs of
|
||||
[] ->
|
||||
@@ -209,14 +214,14 @@ gen_decode(Dev, Data, VerId) ->
|
||||
end, Text),
|
||||
|
||||
io:format(Dev, " (Other) ->~n"
|
||||
" decode_child(Other, Ns, J1, J2)~n"
|
||||
" decode_child(Other, Ns, J1, J2, false)~n"
|
||||
" end, Rest2),~n", [])
|
||||
end,
|
||||
io:format(Dev, " {{xmlel, ~p, add_ns(PNs, Ns, Attrs), Children}, Rest6};~n", [Name])
|
||||
end, Els)
|
||||
end, Data),
|
||||
io:format(Dev, "decode(Other, PNs, J1, J2) ->~n"
|
||||
" decode_child(Other, PNs, J1, J2).~n~n", []).
|
||||
io:format(Dev, "decode(Other, PNs, J1, J2, Loop) ->~n"
|
||||
" decode_child(Other, PNs, J1, J2, Loop).~n~n", []).
|
||||
|
||||
|
||||
gen_encode(Dev, Data, VerId) ->
|
||||
|
||||
Reference in New Issue
Block a user