Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 543404bcb8 | |||
| b7166d7da0 | |||
| cf54608c71 | |||
| ec20691188 | |||
| c1af36ac20 | |||
| f6e8eb52f0 | |||
| ad7db90c80 | |||
| cbfb8eb805 | |||
| df60818883 | |||
| d5de93b8fa | |||
| ab6da9530d | |||
| ca82376657 | |||
| 87f18aa8d7 | |||
| 6340d61397 | |||
| 80d1e36542 | |||
| f75909db4c | |||
| 12d47455ba | |||
| 9acf591242 | |||
| bab8673055 | |||
| 3851a77134 | |||
| 8e324e67a4 | |||
| 10245b40ee | |||
| 9534ca2da1 | |||
| f9d11265d0 | |||
| d85c125bef | |||
| 62d3d7a32d | |||
| a3f4a05b0c | |||
| 86465c418d | |||
| a63d3bf0d6 | |||
| 762e4951f2 | |||
| d4fc54be18 | |||
| f327f4cc67 | |||
| 9ba645503b | |||
| 5406693a1e | |||
| d55955f7d8 | |||
| 765770aaa5 | |||
| c3e0b746d7 | |||
| accb0bc35a | |||
| b16530bb6a | |||
| ad00553bf8 | |||
| b70bef77cb | |||
| ac47b7b8cb | |||
| abe0817553 | |||
| ab431b378a | |||
| a534196315 | |||
| cd421f98d7 | |||
| b33d660f88 | |||
| 245c9ae446 | |||
| 8d39431d68 | |||
| 739a231259 | |||
| 426fd14b11 | |||
| 4735372682 | |||
| 4b3fa13163 | |||
| d40250c3d6 | |||
| 7d2cfd2aaa | |||
| 11fdd417dd | |||
| 1b7b23fab6 | |||
| 10882af7c8 | |||
| 6c573cc9fd | |||
| 2782430887 | |||
| 7522c29f25 | |||
| 706424f0d2 | |||
| 57d404a99b | |||
| 19e2e169b1 | |||
| 2a6ea79260 | |||
| 40333066d6 | |||
| 6d596063de | |||
| a7c3c9b77d | |||
| a01de8d944 | |||
| c0e7774937 | |||
| 00c76003cb | |||
| b29f87a978 | |||
| 2dc843cddd | |||
| 9f08b4aa15 | |||
| a84fbd6a74 | |||
| f8af3a0005 | |||
| 83e51c815d | |||
| ff24700156 | |||
| 7683691f5a | |||
| 3479f88dab | |||
| 86fc2f157e | |||
| ffa07c649b | |||
| 2bd61abd71 | |||
| 66df953da1 | |||
| 550a586d2a | |||
| 16473ab691 | |||
| c5afd0322e | |||
| c4563c429c | |||
| eeacace02a | |||
| caf3807bcc | |||
| 6c7e85d3d8 | |||
| f0db7623d1 | |||
| 26ed6539ba | |||
| a9347cd248 | |||
| 60002fc145 | |||
| 20a8654be2 | |||
| 25411333da | |||
| 07d4282603 | |||
| 4bd77797fc | |||
| 03ffbe00c1 | |||
| 5a9099f49c | |||
| b501ee2b8d | |||
| a7c3368635 | |||
| 84ee724aa3 | |||
| da7fe59834 | |||
| 3710dc1e3b | |||
| db03c7428c | |||
| 9c6fe98f76 | |||
| c03af0afb3 | |||
| d109d7f0c5 | |||
| bf9b257eab | |||
| 11dc0c1774 | |||
| 0a5eda0777 | |||
| a657a6d2f6 | |||
| 121acd1da7 | |||
| ec86079747 | |||
| f1b0a9cb32 | |||
| 0b6cb77b3c | |||
| 8a740d5087 | |||
| 8288774787 | |||
| d349e3a88e | |||
| 2ef9fbc111 | |||
| 78f81de252 | |||
| 8d9ee8e35b | |||
| e66ba2e424 | |||
| fafb48e88f | |||
| 6272c0e901 | |||
| 54314e5bb9 | |||
| ffbcf19156 | |||
| dcc8149f58 | |||
| d2c54fd5fe | |||
| f40a7b1c77 | |||
| 16f758e13f | |||
| c333cc0776 | |||
| 3263e81972 | |||
| 397a08afca | |||
| 19070e4b04 | |||
| 0bbc255814 | |||
| 1b06f4ca4f | |||
| b3eeac637f | |||
| 480b42b36d | |||
| 436074c67a | |||
| c9a2117570 | |||
| 6155b001b4 | |||
| 8f05af7810 | |||
| 33ac7916d3 | |||
| 461c1ddf3d | |||
| 2428f74fbd | |||
| bb8e892323 | |||
| 3eecf4ae8a | |||
| 4a53d4cb56 | |||
| 1818a29c29 | |||
| dd2efc360b | |||
| 8e64992f47 | |||
| 040c72f1c8 | |||
| d95a1bac3e | |||
| c6b295b5a0 | |||
| 2a4a6bec18 | |||
| 0d3f8c7b9f | |||
| d299b97261 | |||
| 5b8ebed81b |
+1
-1
@@ -43,4 +43,4 @@ Mnesia.nonode@nohost/
|
||||
/ejabberd-*.rpm
|
||||
/ejabberd-*.run
|
||||
/ejabberd-*.tar.gz
|
||||
|
||||
/.github/container/Dockerfile
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#' Define default build variables
|
||||
## specifc ARGs for METHOD='direct'
|
||||
ARG OTP_VSN='25.3'
|
||||
ARG ELIXIR_VSN='1.14.4'
|
||||
## specifc ARGs for METHOD='package'
|
||||
ARG ALPINE_VSN='3.17'
|
||||
## general ARGs
|
||||
ARG UID='9000'
|
||||
ARG USER='ejabberd'
|
||||
ARG HOME="opt/$USER"
|
||||
@@ -9,7 +14,7 @@ ARG VERSION='master'
|
||||
|
||||
################################################################################
|
||||
#' METHOD='direct' - build and install ejabberd directly from source
|
||||
FROM alpine:${ALPINE_VSN} AS direct
|
||||
FROM docker.io/erlang:${OTP_VSN}-alpine AS direct
|
||||
|
||||
RUN apk -U add --no-cache \
|
||||
autoconf \
|
||||
@@ -17,9 +22,6 @@ RUN apk -U add --no-cache \
|
||||
bash \
|
||||
build-base \
|
||||
curl \
|
||||
elixir \
|
||||
erlang-odbc \
|
||||
erlang-reltool \
|
||||
expat-dev \
|
||||
file \
|
||||
gd-dev \
|
||||
@@ -33,6 +35,13 @@ RUN apk -U add --no-cache \
|
||||
yaml-dev \
|
||||
zlib-dev
|
||||
|
||||
ARG ELIXIR_VSN
|
||||
RUN wget -O - https://github.com/elixir-lang/elixir/archive/v$ELIXIR_VSN.tar.gz \
|
||||
| tar -xzf -
|
||||
|
||||
WORKDIR elixir-$ELIXIR_VSN
|
||||
RUN make install clean
|
||||
|
||||
RUN mix local.hex --force \
|
||||
&& mix local.rebar --force
|
||||
|
||||
@@ -66,7 +75,7 @@ RUN wget -O "$HOME/conf/cacert.pem" 'https://curl.se/ca/cacert.pem' \
|
||||
|
||||
################################################################################
|
||||
#' METHOD='package' - install ejabberd from binary tarball package
|
||||
FROM alpine:${ALPINE_VSN} AS package
|
||||
FROM docker.io/alpine:${ALPINE_VSN} AS package
|
||||
COPY tarballs/ejabberd-*-linux-musl-*.tar.gz /tmp/
|
||||
WORKDIR /rootfs
|
||||
ARG HOME
|
||||
@@ -80,7 +89,7 @@ RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
|
||||
FROM ${METHOD} AS ejabberd
|
||||
RUN apk -U add --no-cache \
|
||||
git \
|
||||
libcap-utils \
|
||||
libcap \
|
||||
openssl
|
||||
|
||||
WORKDIR /rootfs
|
||||
@@ -119,30 +128,39 @@ RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
|
||||
> usr/local/bin/ejabberdctl \
|
||||
&& chmod +x usr/local/bin/* \
|
||||
&& scanelf --needed --nobanner --format '%n#p' --recursive $home_root_dir \
|
||||
| tr ',' '\n' | sort -u | awk 'system("[ -e $home_root_dir" $1 " ]") == 0 { next } \
|
||||
{ print "so:" $1 }' > /tmp/runDeps
|
||||
| tr ',' '\n' \
|
||||
| sort -u \
|
||||
| awk 'system("[ -e $home_root_dir" $1 " ]") == 0 { next } { print "so:" $1 }' \
|
||||
| sed -e "s|so:libc.so|so:libc.musl-$(uname -m).so.1|" \
|
||||
> /tmp/runDeps
|
||||
|
||||
ARG UID
|
||||
RUN chown -R $UID:$UID $HOME
|
||||
|
||||
################################################################################
|
||||
#' METHOD='package' - install runtime dependencies
|
||||
FROM alpine:${ALPINE_VSN} AS runtime-package
|
||||
#' METHOD='direct' - Remove erlang/OTP & rebar3
|
||||
FROM docker.io/erlang:${OTP_VSN}-alpine AS runtime-direct
|
||||
RUN apk del .erlang-rundeps \
|
||||
&& rm -f $(which rebar3) \
|
||||
&& find /usr -type d -name 'erlang' -exec rm -rf {} + \
|
||||
&& find /usr -type l -exec test ! -e {} \; -delete
|
||||
|
||||
################################################################################
|
||||
#' METHOD='package' - define runtime base image
|
||||
FROM docker.io/alpine:${ALPINE_VSN} AS runtime-package
|
||||
|
||||
################################################################################
|
||||
#' Update alpine, finalize runtime environment
|
||||
FROM runtime-${METHOD} AS runtime
|
||||
COPY --from=ejabberd /tmp/runDeps /tmp/runDeps
|
||||
RUN apk -U upgrade --available --no-cache \
|
||||
&& apk add --no-cache \
|
||||
libcap2 \
|
||||
tini
|
||||
$(cat /tmp/runDeps) \
|
||||
so:libcap.so.2 \
|
||||
so:libtdsodbc.so.0 \
|
||||
tini \
|
||||
&& ln -fs /usr/lib/libtdsodbc.so.0 /usr/lib/libtdsodbc.so
|
||||
|
||||
################################################################################
|
||||
#' METHOD='direct' - install runtime dependencies
|
||||
FROM runtime-package AS runtime-direct
|
||||
COPY --from=ejabberd /tmp/runDeps /tmp/runDeps
|
||||
RUN apk add --no-cache \
|
||||
$(cat /tmp/runDeps)
|
||||
|
||||
################################################################################
|
||||
#' Finalize runtime environment
|
||||
FROM runtime-${METHOD} AS runtime
|
||||
ARG USER
|
||||
ARG UID
|
||||
ARG HOME
|
||||
@@ -170,5 +188,5 @@ USER $USER
|
||||
VOLUME ["/$HOME"]
|
||||
EXPOSE 1883 4369-4399 5210 5222 5269 5280 5443
|
||||
|
||||
ENTRYPOINT ["/sbin/tini","--","/usr/local/bin/ejabberdctl"]
|
||||
ENTRYPOINT ["/sbin/tini","--","ejabberdctl"]
|
||||
CMD ["foreground"]
|
||||
|
||||
@@ -77,7 +77,7 @@ if [ -n "$FIREWALL_WINDOW" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
if [ -n "$INET_DIST_INTERFACE" ] ; then
|
||||
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
INET_DIST_INTERFACE2=$("$ERL" $ERLANG_OPTS -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
if [ -n "$INET_DIST_INTERFACE2" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||
fi
|
||||
@@ -129,8 +129,8 @@ run_cmd()
|
||||
exec_cmd()
|
||||
{
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -s /bin/sh -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) exec "$@" ;;
|
||||
as_install_user) su -s /bin/sh -c 'exec "$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
esac
|
||||
}
|
||||
run_erl()
|
||||
@@ -162,9 +162,11 @@ debugwarning()
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To detach this shell from ejabberd, press:"
|
||||
echo " control+c, control+c"
|
||||
echo "To exit and detach this shell from ejabberd, press:"
|
||||
echo " control+g and then q"
|
||||
echo ""
|
||||
#vt100 echo "Please do NOT use control+c in this debug shell !"
|
||||
#vt100 echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
@@ -283,6 +285,12 @@ post_waiter_loop()
|
||||
TAIL=${LIST#* ; }
|
||||
echo ":> ejabberdctl $HEAD"
|
||||
$0 $HEAD
|
||||
ctlstatus=$?
|
||||
if [ $ctlstatus -ne 0 ] ; then
|
||||
echo ":> FAILURE in command '$HEAD' !!! Stopping ejabberd..."
|
||||
$0 halt > /dev/null
|
||||
exit $ctlstatus
|
||||
fi
|
||||
[ "$HEAD" = "$TAIL" ] || post_waiter_loop $TAIL
|
||||
}
|
||||
|
||||
@@ -352,7 +360,7 @@ case $1 in
|
||||
;;
|
||||
iexlive)
|
||||
livewarning
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS"
|
||||
;;
|
||||
ping)
|
||||
PEER=${2:-$ERLANG_NODE}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp: ['20.0', '25.3', '26.0-rc3']
|
||||
otp: ['20.0', '25.3', '26.1']
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
redis:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Test shell scripts
|
||||
if: matrix.otp == '25.3'
|
||||
@@ -100,19 +100,6 @@ jobs:
|
||||
sudo apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
|
||||
libsqlite3-dev libwebp-dev libyaml-dev
|
||||
|
||||
- name: Prepare rebar
|
||||
run: |
|
||||
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
|
||||
]}.' >>rebar.config
|
||||
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
|
||||
locals_not_used, undefined_function_calls, undefined_functions]}.
|
||||
% Disabled: exports_not_used,' >>rebar.config
|
||||
echo '{dialyzer, [{get_warnings, true}, {plt_extra_apps, [cache_tab,
|
||||
eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
|
||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
||||
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
|
||||
echo "{ct_opts, [{keep_logs, 20}]}." >>rebar.config
|
||||
|
||||
- name: Remove syntax_tools from release
|
||||
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
|
||||
|
||||
@@ -149,7 +136,6 @@ jobs:
|
||||
- run: make options
|
||||
- run: make xref
|
||||
- run: make dialyzer
|
||||
if: matrix.otp != '26.0-rc3'
|
||||
|
||||
- name: Check Production Release
|
||||
run: |
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -75,13 +75,13 @@ jobs:
|
||||
mv ejabberd-*.tar.gz tarballs
|
||||
|
||||
- name: Checkout ejabberd-contrib
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: processone/ejabberd-contrib
|
||||
path: .ejabberd-modules/sources/ejabberd-contrib
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
labels: |
|
||||
@@ -102,13 +102,13 @@ jobs:
|
||||
org.opencontainers.image.vendor=ProcessOne
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
build-args: |
|
||||
METHOD=package
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/build/
|
||||
key: ${{runner.os}}-ct-ng-1.25.0
|
||||
key: ${{runner.os}}-ct-ng-1.26.0
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
@@ -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@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build binary archives
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare libraries
|
||||
run: |
|
||||
@@ -62,14 +62,6 @@ jobs:
|
||||
make update
|
||||
make
|
||||
|
||||
- name: Prepare rebar
|
||||
run: |
|
||||
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
|
||||
]}.' >>rebar.config
|
||||
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
|
||||
locals_not_used, undefined_function_calls, undefined_functions]}.
|
||||
% Disabled: exports_not_used,' >>rebar.config
|
||||
|
||||
- run: make xref
|
||||
|
||||
- name: Test rel (rebar2)
|
||||
@@ -111,28 +103,36 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp: ['21.3', '22.0', '25.0']
|
||||
elixir: ['1.10.3', '1.11.4', '1.12.3', '1.13.0', '1.14.0']
|
||||
otp: ['21.3', '25.0', '26']
|
||||
elixir: ['1.10.3', '1.11.4', '1.12.3', '1.13.4', '1.14.5', '1.15']
|
||||
exclude:
|
||||
- otp: '21.3'
|
||||
elixir: '1.12.3'
|
||||
- otp: '21.3'
|
||||
elixir: '1.13.0'
|
||||
elixir: '1.13.4'
|
||||
- otp: '21.3'
|
||||
elixir: '1.14.0'
|
||||
- otp: '22.0'
|
||||
elixir: '1.14.0'
|
||||
elixir: '1.14.5'
|
||||
- otp: '21.3'
|
||||
elixir: '1.15'
|
||||
- otp: '25.0'
|
||||
elixir: '1.10.3'
|
||||
- otp: '25.0'
|
||||
elixir: '1.11.4'
|
||||
- otp: '25.0'
|
||||
elixir: '1.12.3'
|
||||
- otp: '26'
|
||||
elixir: '1.10.3'
|
||||
- otp: '26'
|
||||
elixir: '1.11.4'
|
||||
- otp: '26'
|
||||
elixir: '1.12.3'
|
||||
- otp: '26'
|
||||
elixir: '1.13.4'
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get specific Erlang/OTP
|
||||
uses: erlef/setup-beam@v1
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
\#*#
|
||||
.#*
|
||||
.edts
|
||||
.tool-versions
|
||||
*.dump
|
||||
/Makefile
|
||||
/doc
|
||||
|
||||
+108
@@ -1,3 +1,111 @@
|
||||
# Version 23.10
|
||||
|
||||
Compilation:
|
||||
- Erlang/OTP: Raise the requirement to Erlang/OTP 20.0 as a minimum
|
||||
- CI: Update tests to Erlang/OTP 26 and recent Elixir
|
||||
- Move Xref and Dialyzer options from workflows to `rebar.config`
|
||||
- Add sections to `rebar.config` to organize its content
|
||||
- Dialyzer dirty workarounds because `re:mp()` is not an exported type
|
||||
- When installing module already configured, keep config as example
|
||||
- Elixir 1.15 removed support for `--app`
|
||||
- Elixir: Improve support to stop external modules written in Elixir
|
||||
- Elixir: Update syntax of function calls as recommended by Elixir compiler
|
||||
- Elixir: When building OTP release with mix, keep `ERLANG_NODE=ejabberd@localhost`
|
||||
- `ejabberdctl`: Pass `ERLANG_OPTS` when calling `erl` to parse the `INET_DIST_INTERFACE` ([#4066](https://github.com/processone/ejabberd/issues/#4066)
|
||||
|
||||
Commands:
|
||||
- `create_room_with_opts`: Fix typo and move examples to `args_example` ([#4080](https://github.com/processone/ejabberd/issues/#4080))
|
||||
- `etop`: Let `ejabberdctl etop` work in a release (if `observer` application is available)
|
||||
- `get_roster`: Command now returns groups in a list instead of newlines ([#4088](https://github.com/processone/ejabberd/issues/#4088))
|
||||
- `halt`: New command to halt ejabberd abruptly with an error status code
|
||||
- `ejabberdctl`: Fix calling ejabberdctl command with wrong number of arguments with Erlang 26
|
||||
- `ejabberdctl`: Improve printing lists in results
|
||||
- `ejabberdctl`: Support `policy=user` in the help and return proper arguments
|
||||
- `ejabberdctl`: Document how to stop a debug shell: control+g
|
||||
|
||||
Container:
|
||||
- Dockerfile: Add missing dependency for mssql databases
|
||||
- Dockerfile: Reorder stages and steps for consistency
|
||||
- Dockerfile: Use Alpine as base for `METHOD=package`
|
||||
- Dockerfile: Rename packages to improve compatibility
|
||||
- Dockerfile: Provide specific OTP and elixir vsn for direct compilation
|
||||
- Halt ejabberd if a command in `CTL_ON_` fails during ejabberd startup
|
||||
|
||||
Core:
|
||||
- `auth_external_user_exists_check`: New option ([#3377](https://github.com/processone/ejabberd/issues/#3377))
|
||||
- `gen_mod`: Extend `gen_mod` API to simplify hooks and IQ handlers registration
|
||||
- `gen_mod`: Add shorter forms for `gen_mod` hook/`iq_handler` API
|
||||
- `gen_mod`: Update modules to the new `gen_mod` API
|
||||
- `install_contrib_modules`: New option to define contrib modules to install automatically
|
||||
- `unix_socket`: New listener option, useful when setting unix socket files ([#4059](https://github.com/processone/ejabberd/issues/#4059))
|
||||
- `ejabberd_systemd`: Add a few debug messages
|
||||
- `ejabberd_systemd`: Avoid using `gen_server` timeout ([#4054](https://github.com/processone/ejabberd/issues/#4054))([#4058](https://github.com/processone/ejabberd/issues/#4058))
|
||||
- `ejabberd_listener`: Increase default listen queue backlog value to 128, which is the default value on both Linux and FreeBSD ([#4025](https://github.com/processone/ejabberd/issues/#4025))
|
||||
- OAuth: Handle `badpass` error message
|
||||
- When sending message on behalf of user, trigger `user_send_packet` ([#3990](https://github.com/processone/ejabberd/issues/#3990))
|
||||
- Web Admin: In roster page move the `AddJID` textbox to top ([#4067](https://github.com/processone/ejabberd/issues/#4067))
|
||||
- Web Admin: Show a warning when visiting webadmin with non-privileged account ([#4089](https://github.com/processone/ejabberd/issues/#4089))
|
||||
|
||||
Docs:
|
||||
- Example configuration: clarify 5223 tls options; specify s2s shaper
|
||||
- Make sure that `policy=user` commands have `host` instead of `server` arg in docs
|
||||
- Improve syntax of many command descriptions for the Docs site
|
||||
- Move example Perl extauth script from ejabberd git to Docs site
|
||||
- Remove obsolete example files, and add link in Docs to the archived copies
|
||||
|
||||
Installers (`make-binaries`):
|
||||
- Bump Erlang/OTP version to 26.1.1, and other dependencies
|
||||
- Remove outdated workaround
|
||||
- Don't build Linux-PAM examples
|
||||
- Fix check for current Expat version
|
||||
- Apply minor simplifications
|
||||
- Don't duplicate config entries
|
||||
- Don't hard-code musl version
|
||||
- Omit unnecessary glibc setting
|
||||
- Set kernel version for all builds
|
||||
- Let curl fail on HTTP errors
|
||||
|
||||
Modules:
|
||||
- `mod_muc_log`: Add trailing backslash to URLs shown in disco info
|
||||
- `mod_muc_occupantid`: New module with support for XEP-0421 Occupant Id ([#3397](https://github.com/processone/ejabberd/issues/#3397))
|
||||
- `mod_muc_rtbl`: Better error handling in ([#4050](https://github.com/processone/ejabberd/issues/#4050))
|
||||
- `mod_private`: Add support for XEP-0402 PEP Native Bookmarks
|
||||
- `mod_privilege`: Don't fail to edit roster ([#3942](https://github.com/processone/ejabberd/issues/#3942))
|
||||
- `mod_pubsub`: Fix usage of `plugins` option, which produced `default_node_config` ignore ([#4070](https://github.com/processone/ejabberd/issues/#4070))
|
||||
- `mod_pubsub`: Add `pubsub_delete_item` hook
|
||||
- `mod_pubsub`: Report support of `config-node-max` in pep
|
||||
- `mod_pubsub`: Relay pubsub iq queries to muc members without using bare jid ([#4093](https://github.com/processone/ejabberd/issues/#4093))
|
||||
- `mod_pubsub`: Allow pubsub node owner to overwrite items published by other persons
|
||||
- `mod_push_keepalive`: Delay `wake_on_start`
|
||||
- `mod_push_keepalive`: Don't let hook crash
|
||||
- `mod_push`: Add `notify_on` option
|
||||
- `mod_push`: Set `last-message-sender` to bare JID
|
||||
- `mod_register_web`: Make redirect to page that end with `/` ([#3177](https://github.com/processone/ejabberd/issues/#3177))
|
||||
- `mod_shared_roster_ldap`: Don't crash in `get_member_jid` on empty output ([#3614](https://github.com/processone/ejabberd/issues/#3614))
|
||||
|
||||
MUC:
|
||||
- Add support to register nick in a room ([#3455](https://github.com/processone/ejabberd/issues/#3455))
|
||||
- Convert `allow_private_message` MUC room option to `allowpm` ([#3736](https://github.com/processone/ejabberd/issues/#3736))
|
||||
- Update xmpp version to send `roomconfig_changesubject` in disco#info ([#4085](https://github.com/processone/ejabberd/issues/#4085))
|
||||
- Fix crash when loading room from DB older than ffa07c6, 23.04
|
||||
- Fix support to retract a MUC room message
|
||||
- Don't always store messages passed through `muc_filter_message` ([#4083](https://github.com/processone/ejabberd/issues/#4083))
|
||||
- Pass also MUC room retract messages over the `muc_filter_message` ([#3397](https://github.com/processone/ejabberd/issues/#3397))
|
||||
- Pass MUC room private messages over the `muc_filter_message` too ([#3397](https://github.com/processone/ejabberd/issues/#3397))
|
||||
- Store the subject author JID, and run `muc_filter_message` when sending subject ([#3397](https://github.com/processone/ejabberd/issues/#3397))
|
||||
- Remove existing role information for users that are kicked from room ([#4035](https://github.com/processone/ejabberd/issues/#4035))
|
||||
- Expand rule "mucsub subscribers are members in members only rooms" to more places
|
||||
|
||||
SQL:
|
||||
- Add ability to force alternative upsert implementation in mysql
|
||||
- Properly parse mysql version even if it doesn't have type tag
|
||||
- Use prepared statement with mysql
|
||||
- Add alternate version of mysql upsert
|
||||
- `ejabberd_auth_sql`: Reset scram fields when setting plain password
|
||||
- `mod_privacy_sql`: Fix return values from `calculate_diff`
|
||||
- `mod_privacy_sql`: Optimize `set_list`
|
||||
- `mod_privacy_sql`: Use more efficient way to calculate changes in `set_privacy_list`
|
||||
|
||||
# Version 23.04
|
||||
|
||||
General:
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ To compile ejabberd you need:
|
||||
- GCC
|
||||
- Libexpat ≥ 1.95
|
||||
- Libyaml ≥ 0.1.4
|
||||
- Erlang/OTP ≥ 19.3
|
||||
- Erlang/OTP ≥ 20.0
|
||||
- OpenSSL ≥ 1.0.0
|
||||
|
||||
Other optional libraries are:
|
||||
|
||||
+4
-4
@@ -1,11 +1,10 @@
|
||||
|
||||
[](https://github.com/processone/ejabberd/tags)
|
||||
[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
|
||||
[](https://hub.docker.com/r/ejabberd/ecs/)
|
||||
[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
|
||||
|
||||
|
||||
ejabberd Container
|
||||
==================
|
||||
`ejabberd` Container Image
|
||||
==========================
|
||||
|
||||
[ejabberd][home] is an open-source,
|
||||
robust, scalable and extensible realtime platform built using [Erlang/OTP][erlang],
|
||||
@@ -20,6 +19,7 @@ that includes [XMPP][xmpp] Server, [MQTT][mqtt] Broker and [SIP][sip] Service.
|
||||
This document explains how to use the `ejabberd` container image available in
|
||||
[ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd),
|
||||
built using the files in `.github/container/`.
|
||||
This image is based in Alpine 3.17, includes Erlang/OTP 25.3 and Elixir 1.14.4.
|
||||
|
||||
Alternatively, there is also the `ecs` container image available in
|
||||
[docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/),
|
||||
|
||||
@@ -442,7 +442,9 @@ Makefile: Makefile.in
|
||||
|
||||
ifeq "$(REBAR_VER)" "3"
|
||||
dialyzer:
|
||||
find src/*_opt.erl -type f \! -regex ".*git.*" -exec sed -i 's/re:mp/ tuple/g' {} \;
|
||||
$(REBAR) dialyzer
|
||||
find src/*_opt.erl -type f \! -regex ".*git.*" -exec sed -i 's/ tuple/re:mp/g' {} \;
|
||||
else
|
||||
deps := $(wildcard $(DEPSDIR)/*/ebin)
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="https://www.process-one.net/wp-content/uploads/2022/05/ejabberd-logo-rounded-index.png"
|
||||
height="216">
|
||||
<img src="https://www.process-one.net/wp-content/uploads/2022/05/ejabberd-logo-rounded-index.png">
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/processone/ejabberd/tags" alt="GitHub tag (latest SemVer)">
|
||||
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?sort=semver&logo=embarcadero&label=&color=3fb0d2&logoWidth=20" /></a>
|
||||
<a href="https://hex.pm/packages/ejabberd" alt="Hex version">
|
||||
<img src="https://img.shields.io/hexpm/v/ejabberd.svg" /></a>
|
||||
<a href="https://github.com/processone/ejabberd/pkgs/container/ejabberd" alt="GitHub Container">
|
||||
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?label=container&sort=semver" /></a>
|
||||
<a href="https://hub.docker.com/r/ejabberd/ecs/" alt="Docker Image Version (latest semver)">
|
||||
<img src="https://img.shields.io/docker/v/ejabberd/ecs?label=docker" /></a>
|
||||
<a href="https://formulae.brew.sh/formula/ejabberd" alt="homebrew version">
|
||||
<img src="https://img.shields.io/homebrew/v/ejabberd" /></a>
|
||||
<a href="https://hub.docker.com/r/ejabberd/ecs/" alt="Docker Image Version (latest semver)">
|
||||
<img src="https://img.shields.io/docker/v/ejabberd/ecs?label=ecs&logo=docker" /></a>
|
||||
<a href="https://github.com/processone/ejabberd/pkgs/container/ejabberd" alt="GitHub Container">
|
||||
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?label=ejabberd&sort=semver&logo=docker" /></a>
|
||||
<br />
|
||||
<a href="https://github.com/processone/ejabberd/actions/workflows/ci.yml" alt="CI">
|
||||
<img src="https://github.com/processone/ejabberd/actions/workflows/ci.yml/badge.svg" /></a>
|
||||
@@ -39,8 +38,8 @@ There are several ways to install ejabberd:
|
||||
|
||||
- Source code: compile yourself, see [COMPILE](COMPILE.md)
|
||||
- Installers from [ProcessOne Download][p1download] or [ejabberd GitHub Releases][releases] (run/deb/rpm for x64 and arm64)
|
||||
- Container image from [ejabberd Docker Hub][hubecs], see [ecs README][docker-ecs-readme] (for x64)
|
||||
- Container image from [ejabberd Github Packages][packages], see [CONTAINER](CONTAINER.md) (for x64 and arm64)
|
||||
- `ecs` container image available in [Docker Hub][hubecs] and [Github Packages][packagesecs], see [ecs README][docker-ecs-readme] (for x64)
|
||||
- `ejabberd` container image available in [Github Packages][packages], see [CONTAINER](CONTAINER.md) (for x64 and arm64)
|
||||
- Using your [Operating System package][osp]
|
||||
- Using the [Homebrew][homebrew] package manager
|
||||
|
||||
@@ -86,7 +85,6 @@ Community
|
||||
There are several places to get in touch with other ejabberd developers and administrators:
|
||||
|
||||
- ejabberd XMPP chatroom: [ejabberd@conference.process-one.net][muc]
|
||||
- [Mailing list][list]
|
||||
- [GitHub Discussions][discussions]
|
||||
- [Stack Overflow][stackoverflow]
|
||||
|
||||
@@ -109,7 +107,6 @@ and [ejabberd translations](https://github.com/processone/ejabberd-po/) under MI
|
||||
[hubecs]: https://hub.docker.com/r/ejabberd/ecs/
|
||||
[im]: https://ejabberd.im/
|
||||
[issues]: https://github.com/processone/ejabberd/issues
|
||||
[list]: https://lists.jabber.ru/mailman/listinfo/ejabberd
|
||||
[localization]: https://docs.ejabberd.im/developer/extending-ejabberd/localization/
|
||||
[mqtt]: https://mqtt.org/
|
||||
[muc]: xmpp:ejabberd@conference.process-one.net
|
||||
@@ -118,6 +115,7 @@ and [ejabberd translations](https://github.com/processone/ejabberd-po/) under MI
|
||||
[p1download]: https://www.process-one.net/en/ejabberd/downloads/
|
||||
[p1home]: https://www.process-one.net/en/ejabberd/
|
||||
[packages]: https://github.com/processone/ejabberd/pkgs/container/ejabberd
|
||||
[packagesecs]: https://github.com/processone/docker-ejabberd/pkgs/container/ecs
|
||||
[releases]: https://github.com/processone/ejabberd/releases
|
||||
[sip]: https://en.wikipedia.org/wiki/Session_Initiation_Protocol
|
||||
[stackoverflow]: https://stackoverflow.com/questions/tagged/ejabberd?sort=newest
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
# 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 23.04` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="8.3 (Erlang/OTP 19.3)"
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 23.10` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="9.0.5 (Erlang/OTP 20.0)"
|
||||
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
|
||||
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
@@ -638,6 +638,15 @@
|
||||
<xmpp:note>mod_avatar, mod_vcard_xupdate</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
|
||||
<xmpp:version>1.1.3</xmpp:version>
|
||||
<xmpp:since>23.10</xmpp:since>
|
||||
<xmpp:status></xmpp:status>
|
||||
<xmpp:note>mod_private</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0405.html"/>
|
||||
@@ -665,6 +674,15 @@
|
||||
<xmpp:note>mod_private</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0421.html"/>
|
||||
<xmpp:version>0.1.0</xmpp:version>
|
||||
<xmpp:since>23.10</xmpp:since>
|
||||
<xmpp:status></xmpp:status>
|
||||
<xmpp:note>mod_muc_occupantid</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0425.html"/>
|
||||
|
||||
@@ -36,17 +36,17 @@ listen:
|
||||
-
|
||||
port: 5223
|
||||
ip: "::"
|
||||
tls: true
|
||||
module: ejabberd_c2s
|
||||
max_stanza_size: 262144
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
starttls_required: true
|
||||
tls: true
|
||||
-
|
||||
port: 5269
|
||||
ip: "::"
|
||||
module: ejabberd_s2s_in
|
||||
max_stanza_size: 524288
|
||||
shaper: s2s_shaper
|
||||
-
|
||||
port: 5443
|
||||
ip: "::"
|
||||
|
||||
+15
-4
@@ -108,10 +108,8 @@
|
||||
#.
|
||||
#' ERL_OPTIONS: Additional Erlang options
|
||||
#
|
||||
# The next variable allows to specify additional options passed to erlang while
|
||||
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
|
||||
# ejabberd is started from an init.d script options -noshell and -detached are
|
||||
# added implicitly. See erl(1) for more info.
|
||||
# The next variable allows to specify additional options passed to
|
||||
# erlang. See erl(1) for more info.
|
||||
#
|
||||
# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you
|
||||
# want to add local modules in this path.
|
||||
@@ -120,6 +118,19 @@
|
||||
#
|
||||
#ERL_OPTIONS=""
|
||||
|
||||
#.
|
||||
#' EJABBERD_OPTS: Additional Erlang options to start ejabberd
|
||||
#
|
||||
# The next variable allows to specify additional options passed to erlang while
|
||||
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
|
||||
# ejabberd is started from an init.d script options -noshell and -detached are
|
||||
# added implicitly. See erl(1) for more info.
|
||||
#
|
||||
# Default: ""
|
||||
#
|
||||
#EJABBERD_OPTS=""
|
||||
EJABBERD_OPTS="-heart -env HEART_BEAT_TIMEOUT 120 -env ERL_CRASH_DUMP_SECONDS 60"
|
||||
|
||||
#.
|
||||
#' ERLANG_NODE: Erlang node name
|
||||
#
|
||||
|
||||
@@ -76,7 +76,7 @@ if [ -n "$FIREWALL_WINDOW" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
if [ -n "$INET_DIST_INTERFACE" ] ; then
|
||||
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
INET_DIST_INTERFACE2=$("$ERL" $ERLANG_OPTS -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
if [ -n "$INET_DIST_INTERFACE2" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||
fi
|
||||
@@ -121,7 +121,7 @@ set_dist_client()
|
||||
exec_cmd()
|
||||
{
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -s /bin/sh -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_install_user) su -s /bin/sh -c 'exec "$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) "$@" ;;
|
||||
esac
|
||||
}
|
||||
@@ -149,9 +149,11 @@ debugwarning()
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To detach this shell from ejabberd, press:"
|
||||
echo " control+c, control+c"
|
||||
echo "To exit and detach this shell from ejabberd, press:"
|
||||
echo " control+g and then q"
|
||||
echo ""
|
||||
#vt100 echo "Please do NOT use control+c in this debug shell !"
|
||||
#vt100 echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
@@ -305,8 +307,8 @@ case $1 in
|
||||
;;
|
||||
etop)
|
||||
set_dist_client
|
||||
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
|
||||
-s erlang halt -output text
|
||||
exec_erl "$(uid top)" -hidden -remsh "$ERLANG_NODE" -s etop \
|
||||
-output text
|
||||
;;
|
||||
iexdebug)
|
||||
debugwarning
|
||||
@@ -315,7 +317,7 @@ case $1 in
|
||||
;;
|
||||
iexlive)
|
||||
livewarning
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS"
|
||||
;;
|
||||
ping)
|
||||
PEER=${2:-$ERLANG_NODE}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use Unix::Syslog qw(:macros :subs);
|
||||
|
||||
my $domain = $ARGV[0] || "example.com";
|
||||
|
||||
while(1)
|
||||
{
|
||||
# my $rin = '',$rout;
|
||||
# vec($rin,fileno(STDIN),1) = 1;
|
||||
# $ein = $rin;
|
||||
# my $nfound = select($rout=$rin,undef,undef,undef);
|
||||
|
||||
my $buf = "";
|
||||
syslog LOG_INFO,"waiting for packet";
|
||||
my $nread = sysread STDIN,$buf,2;
|
||||
do { syslog LOG_INFO,"port closed"; exit; } unless $nread == 2;
|
||||
my $len = unpack "n",$buf;
|
||||
my $nread = sysread STDIN,$buf,$len;
|
||||
|
||||
my ($op,$user,$host,$password) = split /:/,$buf;
|
||||
#$user =~ s/\./\//og;
|
||||
my $jid = "$user\@$domain";
|
||||
my $result;
|
||||
|
||||
syslog(LOG_INFO,"request (%s)", $op);
|
||||
|
||||
SWITCH:
|
||||
{
|
||||
$op eq 'auth' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'setpass' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'isuser' and do
|
||||
{
|
||||
# password is null. Return 1 if the user $user\@$domain exitst.
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'tryregister' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'removeuser' and do
|
||||
{
|
||||
# password is null. Return 1 if the user $user\@$domain exitst.
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'removeuser3' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
};
|
||||
my $out = pack "nn",2,$result ? 1 : 0;
|
||||
syswrite STDOUT,$out;
|
||||
}
|
||||
|
||||
closelog;
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: ejabberd
|
||||
# REQUIRE: DAEMON
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
|
||||
HOME=/usr/pkg/jabber D=/usr/pkg/jabber/ejabberd export HOME
|
||||
|
||||
name="ejabberd"
|
||||
rcvar=$name
|
||||
|
||||
if [ -r /etc/rc.conf ]
|
||||
then
|
||||
. /etc/rc.conf
|
||||
else
|
||||
eval ${rcvar}=YES
|
||||
fi
|
||||
|
||||
# $flags from environment overrides ${rcvar}_flags
|
||||
if [ -n "${flags}" ]
|
||||
then
|
||||
eval ${rcvar}_flags="${flags}"
|
||||
fi
|
||||
|
||||
checkyesno()
|
||||
{
|
||||
eval _value=\$${1}
|
||||
case $_value in
|
||||
[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
|
||||
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
|
||||
*)
|
||||
echo "\$${1} is not set properly."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd=${1:-start}
|
||||
case ${cmd} in
|
||||
force*)
|
||||
cmd=${cmd#force}
|
||||
eval ${rcvar}=YES
|
||||
;;
|
||||
esac
|
||||
|
||||
if checkyesno ${rcvar}
|
||||
then
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case ${cmd} in
|
||||
start)
|
||||
if [ -x $D/src ]; then
|
||||
echo "Starting ${name}."
|
||||
cd $D/src
|
||||
ERL_MAX_PORTS=32000 export ERL_MAX_PORTS
|
||||
ulimit -n $ERL_MAX_PORTS
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberd -s ejabberd -heart -detached -sasl sasl_error_logger '{file, \"ejabberd-sasl.log\"}' &" \
|
||||
1>/dev/null 2>&1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, stop, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdstop"
|
||||
;;
|
||||
restart)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, restart, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdrestart"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo '1. fetch, compile, and install erlang'
|
||||
|
||||
if [ ! pkg_info erlang 1>/dev/null 2>&1 ]; then
|
||||
cd /usr/pkgsrc/lang/erlang
|
||||
make fetch-list|sh
|
||||
make
|
||||
make install
|
||||
fi
|
||||
if pkg_info erlang | grep -q erlang-9.1nb1; then
|
||||
else
|
||||
echo "erlang-9.1nb1 not installed" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo '2. install crypt_drv.so'
|
||||
|
||||
if [ ! -d /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib ] ; then
|
||||
mkdir -p /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/crypto_drv.so ]; then
|
||||
cp work/otp*/lib/crypto/priv/*/*/crypto_drv.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '3. compile and install elibcrypto.so'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/elibcrypto.so ]; then
|
||||
cd /usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/crypto/c_src
|
||||
ld -r -u CRYPTO_set_mem_functions -u MD5 -u MD5_Init -u MD5_Update \
|
||||
-u MD5_Final -u SHA1 -u SHA1_Init -u SHA1_Update -u SHA1_Final \
|
||||
-u des_set_key -u des_ncbc_encrypt -u des_ede3_cbc_encrypt \
|
||||
-L/usr/lib -lcrypto -o ../priv/obj/i386--netbsdelf/elibcrypto.o
|
||||
cc -shared \
|
||||
-L/usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/erl_interface/obj/i386--netbsdelf \
|
||||
-o ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
../priv/obj/i386--netbsdelf/elibcrypto.o -L/usr/lib -lcrypto
|
||||
cp ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '4. compile and install ssl_esock'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/bin/ssl_esock ]; then
|
||||
cd /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/obj/
|
||||
make
|
||||
fi
|
||||
|
||||
|
||||
echo '5. initial ejabberd configuration'
|
||||
|
||||
cd /usr/pkg/jabber/ejabberd/src
|
||||
./configure
|
||||
|
||||
|
||||
echo '6. edit ejabberd Makefiles'
|
||||
|
||||
for M in Makefile mod_*/Makefile; do
|
||||
if [ ! -f $M.orig ]; then
|
||||
mv $M $M.orig
|
||||
sed -e s%/usr/local%/usr/pkg%g < $M.orig > $M
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo '7. compile ejabberd'
|
||||
|
||||
gmake
|
||||
for A in mod_muc mod_pubsub; do
|
||||
(cd $A; gmake)
|
||||
done
|
||||
|
||||
|
||||
echo ''
|
||||
echo 'now edit ejabberd.cfg'
|
||||
echo ''
|
||||
echo 'to start ejabberd: erl -sname ejabberd -s ejabberd'
|
||||
@@ -1,65 +0,0 @@
|
||||
% jabber.dbc.mtview.ca.us
|
||||
|
||||
override_acls.
|
||||
|
||||
{acl, admin, {user, "mrose", "jabber.dbc.mtview.ca.us"}}.
|
||||
|
||||
|
||||
{access, announce, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
{access, configure, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, disco_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, muc_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, register, [{deny, all}]}.
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
|
||||
{auth_method, internal}.
|
||||
{host, "jabber.dbc.mtview.ca.us"}.
|
||||
{outgoing_s2s_port, 5269}.
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
{welcome_message, none}.
|
||||
|
||||
|
||||
{listen, [{5222, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper}]},
|
||||
{5223, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper},
|
||||
{ssl, [{certfile, "/etc/openssl/certs/ejabberd.pem"}]}]},
|
||||
{5269, ejabberd_s2s_in,
|
||||
[{shaper, s2s_shaper}]}]}.
|
||||
|
||||
|
||||
{modules, [
|
||||
{mod_register, []},
|
||||
{mod_roster, []},
|
||||
{mod_privacy, []},
|
||||
{mod_configure, []},
|
||||
{mod_disco, []},
|
||||
{mod_stats, []},
|
||||
{mod_vcard, []},
|
||||
{mod_offline, []},
|
||||
{mod_echo, [{host, "echo.jabber.dbc.mtview.ca.us"}]},
|
||||
{mod_private, []},
|
||||
{mod_muc, []},
|
||||
{mod_pubsub, []},
|
||||
{mod_time, []},
|
||||
{mod_last, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
|
||||
|
||||
% Local Variables:
|
||||
% mode: erlang
|
||||
% End:
|
||||
@@ -34,12 +34,14 @@
|
||||
format_query :: fun(),
|
||||
format_res :: fun(),
|
||||
args :: fun(),
|
||||
flags :: non_neg_integer(),
|
||||
loc :: {module(), pos_integer()}}).
|
||||
-else.
|
||||
-record(sql_query, {hash :: binary(),
|
||||
format_query :: fun(),
|
||||
format_res :: fun(),
|
||||
args :: fun(),
|
||||
flags :: non_neg_integer(),
|
||||
loc :: {module(), {pos_integer(), pos_integer()}}}).
|
||||
-endif.
|
||||
|
||||
@@ -48,3 +50,20 @@
|
||||
boolean :: fun((boolean()) -> binary()),
|
||||
in_array_string :: fun((binary()) -> binary()),
|
||||
like_escape :: fun(() -> binary())}).
|
||||
|
||||
|
||||
-record(sql_index, {columns,
|
||||
unique = false :: boolean()}).
|
||||
-record(sql_column, {name :: binary(),
|
||||
type,
|
||||
default = false,
|
||||
opts = []}).
|
||||
-record(sql_table, {name :: binary(),
|
||||
columns :: [#sql_column{}],
|
||||
indices = [] :: [#sql_index{}],
|
||||
post_create}).
|
||||
-record(sql_schema, {version :: integer(),
|
||||
tables :: [#sql_table{}],
|
||||
update = []}).
|
||||
-record(sql_references, {table :: binary(),
|
||||
column :: binary()}).
|
||||
|
||||
@@ -62,5 +62,7 @@
|
||||
_ -> 'Elixir.Logger':bare_log(error, io_lib:format(Format, Args), [?MODULE])
|
||||
end).
|
||||
|
||||
-type re_mp() :: {re_pattern, _, _, _, _}. % Copied from re.erl
|
||||
|
||||
%% Uncomment if you want to debug p1_fsm/gen_fsm
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
description = <<"">> :: binary(),
|
||||
allow_change_subj = true :: boolean(),
|
||||
allow_query_users = true :: boolean(),
|
||||
allow_private_messages = true :: boolean(),
|
||||
allowpm = anyone :: anyone | participants | moderators | none,
|
||||
allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
|
||||
allow_visitor_status = true :: boolean(),
|
||||
allow_visitor_nickchange = true :: boolean(),
|
||||
@@ -125,7 +125,7 @@
|
||||
roles = #{} :: roles(),
|
||||
history = #lqueue{} :: lqueue(),
|
||||
subject = [] :: [text()],
|
||||
subject_author = <<"">> :: binary(),
|
||||
subject_author = {<<"">>, #jid{}} :: {binary(), jid()},
|
||||
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
|
||||
just_created = erlang:system_time(microsecond) :: true | integer(),
|
||||
activity = treap:empty() :: treap:treap(),
|
||||
|
||||
@@ -36,8 +36,8 @@ defmodule Ejabberd.Config do
|
||||
|
||||
case force do
|
||||
true ->
|
||||
Ejabberd.Config.Store.stop
|
||||
Ejabberd.Config.Store.start_link
|
||||
Ejabberd.Config.Store.stop()
|
||||
Ejabberd.Config.Store.start_link()
|
||||
do_init(file_path)
|
||||
false ->
|
||||
if not init_already_executed, do: do_init(file_path)
|
||||
|
||||
@@ -14,15 +14,15 @@ defmodule Mix.Tasks.Ejabberd.Deps.Tree do
|
||||
def run(_argv) do
|
||||
# First we need to start manually the store to be available
|
||||
# during the compilation of the config file.
|
||||
Ejabberd.Config.Store.start_link
|
||||
Ejabberd.Config.Store.start_link()
|
||||
Ejabberd.Config.init(:ejabberd_config.path())
|
||||
|
||||
Mix.shell.info "ejabberd modules"
|
||||
Mix.shell().info "ejabberd modules"
|
||||
|
||||
Ejabberd.Config.Store.get(:modules)
|
||||
|> Enum.reverse # Because of how mods are stored inside the store
|
||||
|> format_mods
|
||||
|> Mix.shell.info
|
||||
|> Mix.shell().info
|
||||
end
|
||||
|
||||
defp format_mods(mods) when is_list(mods) do
|
||||
|
||||
+64
-15
@@ -2,12 +2,12 @@
|
||||
.\" Title: ejabberd.yml
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: 04/17/2023
|
||||
.\" Date: 10/16/2023
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "EJABBERD\&.YML" "5" "04/17/2023" "\ \&" "\ \&"
|
||||
.TH "EJABBERD\&.YML" "5" "10/16/2023" "\ \&" "\ \&"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
@@ -82,7 +82,7 @@ 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/23\&.04/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/23\&.10/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"
|
||||
@@ -303,7 +303,9 @@ acme:
|
||||
.PP
|
||||
\fBallow_contrib_modules\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Whether to allow installation of third\-party modules or not\&. The default value is
|
||||
Whether to allow installation of third\-party modules or not\&. See
|
||||
ejabberd\-contrib
|
||||
documentation section\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -396,6 +398,18 @@ Same as
|
||||
\fIcache_size\fR
|
||||
will be used\&.
|
||||
.RE
|
||||
.sp
|
||||
\fINote\fR about the next option: added in 23\&.10:
|
||||
.PP
|
||||
\fBauth_external_user_exists_check\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Supplement check for user existence based on
|
||||
\fImod_last\fR
|
||||
data, for authentication methods that don\(cqt have a way to reliable tell if user exists (like is the case for
|
||||
\fIjwt\fR
|
||||
and certificate based authentication)\&. This helps with processing offline message for those users\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBauth_method\fR: \fI[mnesia | sql | anonymous | external | jwt | ldap | pam, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
@@ -881,6 +895,16 @@ Disallows the usage of those options in the included file
|
||||
\fIFilename\fR\&. The options that match this criteria are not accepted\&. The default value is an empty list\&.
|
||||
.RE
|
||||
.RE
|
||||
.sp
|
||||
\fINote\fR about the next option: added in 23\&.10:
|
||||
.PP
|
||||
\fBinstall_contrib_modules\fR: \fI[Module, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
Modules to install from
|
||||
ejabberd\-contrib
|
||||
at start time\&. The default value is an empty list of modules:
|
||||
\fI[]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBjwt_auth_only_rule\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
@@ -1115,7 +1139,7 @@ This option can be used to tune tick time parameter of
|
||||
Whether to use
|
||||
\fInew\fR
|
||||
SQL schema\&. All schemas are located at
|
||||
https://github\&.com/processone/ejabberd/tree/23\&.04/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
|
||||
https://github\&.com/processone/ejabberd/tree/23\&.10/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
|
||||
\fInew\fR
|
||||
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
|
||||
\fInew\fR
|
||||
@@ -3930,6 +3954,8 @@ This module provides support for XEP\-0045: Multi\-User Chat\&. Users can discov
|
||||
.sp
|
||||
The MUC service allows any Jabber ID to register a nickname, so nobody else can use that nickname in any room in the MUC service\&. To register a nickname, open the Service Discovery in your XMPP client and register in the MUC service\&.
|
||||
.sp
|
||||
It is also possible to register a nickname in a room, so nobody else can use that nickname in that room\&. If a nick is registered in the MUC service, that nick cannot be registered in any room, and vice versa: a nick that is registered in a room cannot be registered at the MUC service\&.
|
||||
.sp
|
||||
This module supports clustering and load balancing\&. One module can be started per cluster node\&. Rooms are distributed at creation time on all available MUC module instances\&. The multi\-user chat module is clustered but the rooms themselves are not clustered nor fault\-tolerant: if the node managing a set of rooms goes down, the rooms disappear and they will be recreated on an available node on first connection attempt\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
@@ -3972,12 +3998,14 @@ To configure who is allowed to modify the
|
||||
room option\&. The default value is
|
||||
\fIall\fR, which means everyone is allowed to modify that option\&.
|
||||
.RE
|
||||
.sp
|
||||
\fINote\fR about the next option: improved in 23\&.10:
|
||||
.PP
|
||||
\fBaccess_register\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
This option specifies who is allowed to register nickname within the Multi\-User Chat service\&. The default is
|
||||
This option specifies who is allowed to register nickname within the Multi\-User Chat service and rooms\&. The default is
|
||||
\fIall\fR
|
||||
for backward compatibility, which means that any user is allowed to register any free nick\&.
|
||||
for backward compatibility, which means that any user is allowed to register any free nick in the MUC service and in the rooms\&.
|
||||
.RE
|
||||
.sp
|
||||
\fINote\fR about the next option: added in 22\&.05:
|
||||
@@ -4009,12 +4037,6 @@ Allow occupants to change the subject\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBallow_private_messages\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Occupants can send private messages to other occupants\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBallow_private_messages_from_visitors\fR: \fIanyone | moderators | nobody\fR
|
||||
.RS 4
|
||||
Visitors can send private messages to other occupants\&. The default value is
|
||||
@@ -4059,6 +4081,12 @@ Allow visitors in a moderated room to request voice\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBallowpm\fR: \fIanyone | participants | moderators | none\fR
|
||||
.RS 4
|
||||
Who can send private messages\&. The default value is
|
||||
\fIanyone\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBanonymous\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
The room is anonymous: occupants don\(cqt see the real JIDs of other occupants\&. Note that the room moderators can always see the real JIDs of the occupants\&. The default value is
|
||||
@@ -4675,6 +4703,15 @@ or a conference JID is appended to the
|
||||
otherwise\&. There is no default value\&.
|
||||
.RE
|
||||
.RE
|
||||
.SS "mod_muc_occupantid"
|
||||
.sp
|
||||
This module implements XEP\-0421: Anonymous unique occupant identifiers for MUCs\&.
|
||||
.sp
|
||||
When the module is enabled, the feature is enabled in all semi\-anonymous rooms\&.
|
||||
.sp
|
||||
This module is available since ejabberd 23\&.10\&.
|
||||
.sp
|
||||
The module has no options\&.
|
||||
.SS "mod_muc_rtbl"
|
||||
.sp
|
||||
This module implement Real\-time blocklists for MUC rooms\&.
|
||||
@@ -5197,6 +5234,8 @@ This module adds support for XEP\-0049: Private XML Storage\&.
|
||||
.sp
|
||||
Using this method, XMPP entities can store private data on the server, retrieve it whenever necessary and share it between multiple connected clients of the same user\&. The data stored might be anything, as long as it is a valid XML\&. One typical usage is storing a bookmark of all user\(cqs conferences (XEP\-0048: Bookmarks)\&.
|
||||
.sp
|
||||
It also implements the bookmark conversion described in XEP\-0402: PEP Native Bookmarks, see the command bookmarks_to_pep\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
.nr an-break-flag 1
|
||||
@@ -5938,6 +5977,16 @@ If this option is set to
|
||||
\fItrue\fR, the sender\(cqs JID is included with push notifications generated for incoming messages with a body\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.sp
|
||||
\fINote\fR about the next option: added in 23\&.10:
|
||||
.PP
|
||||
\fBnotify_on\fR: \fImessages | all\fR
|
||||
.RS 4
|
||||
If this option is set to
|
||||
\fImessages\fR, notifications are generated only for actual chat messages with a body text (or some encrypted payload)\&. If it\(cqs set to
|
||||
\fIall\fR, any kind of XMPP stanza will trigger a notification\&. If unsure, it\(cqs strongly recommended to stick to
|
||||
\fIall\fR, which is the default value\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBuse_cache\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
@@ -7748,13 +7797,13 @@ TODO
|
||||
ProcessOne\&.
|
||||
.SH "VERSION"
|
||||
.sp
|
||||
This document describes the configuration file of ejabberd 23\&.04\&. Configuration options of other ejabberd versions may differ significantly\&.
|
||||
This document describes the configuration file of ejabberd 23\&.10\&. 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/23\&.04/ejabberd\&.yml\&.example
|
||||
Default configuration file: https://github\&.com/processone/ejabberd/blob/23\&.10/ejabberd\&.yml\&.example
|
||||
.sp
|
||||
Main site: https://ejabberd\&.im
|
||||
.sp
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule Ejabberd.MixProject do
|
||||
elixir: elixir_required_version(),
|
||||
elixirc_paths: ["lib"],
|
||||
compile_path: ".",
|
||||
compilers: [:asn1] ++ Mix.compilers,
|
||||
compilers: [:asn1] ++ Mix.compilers(),
|
||||
erlc_options: erlc_options(),
|
||||
erlc_paths: ["asn1", "src"],
|
||||
# Elixir tests are starting the part of ejabberd they need
|
||||
@@ -114,7 +114,7 @@ defmodule Ejabberd.MixProject do
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:pkix, "~> 1.0"},
|
||||
{:stringprep, ">= 1.0.26"},
|
||||
{:xmpp, ">= 1.6.2"},
|
||||
{:xmpp, git: "https://github.com/processone/xmpp.git", ref: "68cb07d5d0f36d5c51bfea496c638f3ee9b36027", override: true},
|
||||
{:yconf, "~> 1.0"}]
|
||||
++ cond_deps()
|
||||
end
|
||||
@@ -138,7 +138,7 @@ defmodule Ejabberd.MixProject do
|
||||
{config(:zlib), {:ezlib, "~> 1.0"}},
|
||||
{if_version_below('22', true), {:lager, "~> 3.9.1"}},
|
||||
{config(:lua), {:luerl, "~> 1.0"}},
|
||||
{config(:mysql), {:p1_mysql, "~> 1.0.20"}},
|
||||
{config(:mysql), {:p1_mysql, " >= 1.0.22"}},
|
||||
{config(:pgsql), {:p1_pgsql, "~> 1.1"}},
|
||||
{config(:sqlite), {:sqlite3, "~> 1.1"}},
|
||||
{config(:stun), {:stun, "~> 1.0"}}], do:
|
||||
@@ -260,7 +260,7 @@ defmodule Ejabberd.MixProject do
|
||||
end
|
||||
|
||||
# Mix/Elixir lower than 1.11.0 use config/releases.exs instead of runtime.exs
|
||||
case Version.match?(System.version, "~> 1.11") do
|
||||
case Version.match?(System.version(), "~> 1.11") do
|
||||
true ->
|
||||
:ok
|
||||
false ->
|
||||
@@ -271,8 +271,7 @@ defmodule Ejabberd.MixProject do
|
||||
Mix.Generator.copy_template("ejabberdctl.example1", "ejabberdctl.example2", assigns)
|
||||
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2> ejabberdctl.example2a")
|
||||
Mix.Generator.copy_template("ejabberdctl.example2a", "ejabberdctl.example2b", assigns)
|
||||
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2b > ejabberdctl.example3")
|
||||
execute.("sed -e 's|^ERLANG_NODE=ejabberd@localhost|ERLANG_NODE=ejabberd|g' ejabberdctl.example3 > ejabberdctl.example4")
|
||||
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2b > ejabberdctl.example4")
|
||||
execute.("sed -e 's|^ERLANG_OPTS=\"|ERLANG_OPTS=\"-boot ../releases/#{release.version}/start_clean -boot_var RELEASE_LIB ../lib |' ejabberdctl.example4 > ejabberdctl.example5")
|
||||
execute.("sed -e 's|^INSTALLUSER=|ERL_OPTIONS=\"-setcookie \\$\\(cat \"\\${SCRIPT_DIR%/*}/releases/COOKIE\")\"\\nINSTALLUSER=|g' ejabberdctl.example5 > ejabberdctl.example6")
|
||||
Mix.Generator.copy_template("ejabberdctl.example6", "#{ro}/bin/ejabberdctl", assigns)
|
||||
@@ -339,7 +338,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
|
||||
def run(args) do
|
||||
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean])
|
||||
|
||||
project = Mix.Project.config
|
||||
project = Mix.Project.config()
|
||||
source_paths = project[:asn1_paths] || ["asn1"]
|
||||
dest_paths = project[:asn1_target] || ["src"]
|
||||
mappings = Enum.zip(source_paths, dest_paths)
|
||||
@@ -361,7 +360,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
|
||||
end
|
||||
|
||||
def manifests, do: [manifest()]
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
||||
|
||||
def clean, do: Erlang.clean(manifest())
|
||||
end
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
%{
|
||||
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.30", "6d35eecfb65fbe5fc85988503a27338d32de01243f3fc8ea3ee7161af08725a4", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "6d8a5e00d8f84c42627706a6dbedb02e34d58495f3ed61935c8475ca0531cda0"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
|
||||
"eimp": {:hex, :eimp, "1.0.22", "fa9b376ef0b50e8455db15c7c11dea4522c6902e04412288aab436d26335f6eb", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b3b9ffb1d9a5f4a2ba88ac418a819164932d9a9d3a2fc3d32ca338ce855c4392"},
|
||||
"epam": {:hex, :epam, "1.0.12", "2a5625d4133bca4b3943791a3f723ba764455a461ae9b6ba5debb262efcf4b40", [:rebar3], [], "hexpm", "54c166c4459cef72f2990a3d89a8f0be27180fe0ab0f24b28ddcc3b815f49f7f"},
|
||||
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm", "d9b5abef2c2c8aba8f32aa018203e0b3dc8b1157773b254ab1d4c2002317f1e1"},
|
||||
"esip": {:hex, :esip, "1.0.49", "7949c288d1e094cb44bff5499231939e34c2ace06de8bef950a341edb1743357", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.7", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "a1379ced50c3a2a8f82a77b3184e94c3b87782e90e5ddc0d2baf5b654ecfaa11"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
|
||||
"esip": {:hex, :esip, "1.0.50", "e657d3af332c711311f4eb540e73eb540ea485a25977aef8736fb8cd3845ed9f", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.10", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "7dfb9f16c65c5e49eeba77025d0f894b5fb240be745d11b978ea1438cd47533d"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.12", "ffe906ba10d03aaee7977e1e0e81d9ffc3bb8b47fb9cd8e2e453507a2e56221f", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "30e94355fb42260aab6e12582cb0c56bf233515e655c8aeaf48760e7561e4ebb"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.1.16", "85fa7f3112ea4ff5ccb4f3abadc130a8c855ad74eb00869487399cb0c322d208", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "aa08cca89b4044e74f1f12e399817d8beaeae3ee006c98a893c0bfb1d81fba51"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.49", "67d9bfcadd04efd930e0ee1412b5ea09d3e791f1fdbd4d3e9a8c8f29f8bfed8c", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "01da064d2f740818956961036637fee2475c17bf8aab9442217f90dc77883593"},
|
||||
@@ -16,20 +16,20 @@
|
||||
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
|
||||
"luerl": {:hex, :luerl, "1.0.0", "1b68c30649323590d5339b967b419260500ffe520cd3abc1987482a82d3b5a6c", [:rebar3], [], "hexpm", "c17bc45cb4b0845ec975387f9a5d8c81ab60456698527a29c96f78992af86bd1"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||
"mqtree": {:hex, :mqtree, "1.0.15", "bc54d8b88698fdaebc1e27a9ac43688b927e3dbc05bd5cee4057e69a89a8cf17", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "294ac43c9b3d372e24eeea56c259e19c655522dcff64a55c401a639663b9d829"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||
"p1_acme": {:hex, :p1_acme, "1.0.22", "b40a8031ef0f4592e97e6a8e08e53dbd31a2198cb8377b249f0caea4f8025a1d", [:rebar3], [{:base64url, "1.0.1", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.11.5", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.15", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "c2b25a7b295a435dac4f278a73d8417ff2b0020c45e1683504e8692ef03e2057"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.21", "5972add935e7b1b03d981fa88a0d01e96de357443eaf96ca2fb62e465a717f47", [:rebar3], [], "hexpm", "16f197adb99dab034139c429b256d65948a4057d3e4d553adbe5ce3236c4aabf"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.22", "593107adbce3df1bb460fd442320ff540dfba0232f45278441ef8d2de8d08163", [:rebar3], [], "hexpm", "188f04b3ba265a6e7657c67a6780a2f5f973fe86670159ef593aaf44dbd3d5a2"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.11", "96b4e85c08355720523c2f892011a81a07994d15c179ce4dd82d704fecad15b2", [:rebar3], [], "hexpm", "9c3c6ae59382b9525473bb02a32949889808f33f95f6db10594fd92acd1f63db"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.22", "f2ca59b87e8c5dbbbe84d08e307c21f078384232e8fb78a783dbeffa6d37da28", [:rebar3], [{:xmpp, "1.6.2", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "bbc38d3878c7b58ab86c257a2a2ce1bacbd68a5034ebea2735db6a70c1aa12bc"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.23", "4a8c17b642dcf5265a910d1a0b86ffb2a9dd057c7b51c3def5eacbcc4f27ced9", [:rebar3], [{:xmpp, "1.7.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "819222bcb5a74581263282ff9cdc679adeefc663dcf49ddc778aeaae90be05a6"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.25", "2d39b5015a567bbd2cc7033eeb93a7c60d8c84efe1ef69a3473faa07fa268187", [:rebar3], [], "hexpm", "9219214428f2c6e5d3187ff8eb9a8783695c2427420be9a259840e07ada32847"},
|
||||
"pkix": {:hex, :pkix, "1.0.9", "eb20b2715d71a23b4fe7e754dae9281a964b51113d0bba8adf9da72bf9d65ac2", [:rebar3], [], "hexpm", "daab2c09cdd4eda05c9b45a5c00e994a1a5f27634929e1377e2e59b707103e3a"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.14", "f9ea0cff8540865fdfdb7e24eef34dc46677364b1c070896e99b5bf08c8a7fd7", [:rebar3], [], "hexpm", "85054b6ca297343c159ed6794a473ff2c8eeabd854b6fe02f711c0bfd373ce86"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.29", "02f23e8c3a219a3dfe40a22e908bece3a2f68af0ff599ea8a7b714ecb21e62ee", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "928eba304c3006eb1512110ebd7b87db163b00859a09375a1e4466152c6c462a"},
|
||||
"stun": {:hex, :stun, "1.2.7", "d6bdcf0aa72c927fbe8b779fc4ef1f3916c5450b2ff136c800a7a0361fb1ddff", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3fb1f07aaa630b2276e83d267557d1ceb3d2ce52d1145de71864160210655852"},
|
||||
"stun": {:hex, :stun, "1.2.10", "53f8be69e14f9476dcaf1dfb626b9dad2380f3fba8faf2c30bdf74311cfdc008", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "19d3eecbfcc6935f0880f8ef7e77ff373900c604092937a1acda166ae3fb40e9"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
|
||||
"xmpp": {:hex, :xmpp, "1.6.2", "8045dfea83e8996415b9a5161f685cb97dc3c40c0b9c46763a5eca2408017221", [:rebar3], [{:ezlib, "1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.49", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "db2ee6115961fe159bc2629093797ac4535083176817cdbe2ae186a0ff540fde"},
|
||||
"xmpp": {:git, "https://github.com/processone/xmpp.git", "68cb07d5d0f36d5c51bfea496c638f3ee9b36027", [ref: "68cb07d5d0f36d5c51bfea496c638f3ee9b36027"]},
|
||||
"yconf": {:hex, :yconf, "1.0.15", "e22998b3d7728270bdd06162a9515bd142b14fae8927cbdbd3ef639c32aa6f7a", [:rebar3], [{:fast_yaml, "1.0.36", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "7ff2ab24d3c9833842716b9aaaa01a8f96641a7695cbb701b03445c4def01117"},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
%% Generated automatically
|
||||
%% DO NOT EDIT: run `make translations` instead
|
||||
%% To improve translations please read:
|
||||
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
|
||||
|
||||
{" (Add * to the end of field to match substring)"," (Добавете * в края на полето, за да съответства на подниза)"}.
|
||||
{" has set the subject to: "," е задал темата на: "}.
|
||||
{"# participants","# участници"}.
|
||||
{"A description of the node","Описание на нода"}.
|
||||
{"A friendly name for the node","Удобно име на нода"}.
|
||||
{"A password is required to enter this room","Необходима е парола за влизане в тази стая"}.
|
||||
{"A Web Page","Уеб страница"}.
|
||||
{"Accept","Приемам"}.
|
||||
{"Access denied by service policy","Достъпът е отказан спрямо политиката на услугата"}.
|
||||
{"Access model","Модел на достъп"}.
|
||||
{"Account doesn't exist","Профилът не съществува"}.
|
||||
{"Action on user","Действие върху потребител"}.
|
||||
{"Add a hat to a user","Добави шапка към потребител"}.
|
||||
{"Add Jabber ID","Добави Jabber ID"}.
|
||||
{"Add New","Добави нов"}.
|
||||
{"Add User","Добави потребител"}.
|
||||
{"Administration of ","Администриране на "}.
|
||||
{"Administration","Администриране"}.
|
||||
{"Administrator privileges required","Изискватт се администраторски права"}.
|
||||
{"All activity","Цялата статистика"}.
|
||||
{"All Users","Всички потребители"}.
|
||||
{"Allow subscription","Разреши абониране"}.
|
||||
{"Allow this Jabber ID to subscribe to this pubsub node?","Позволявате ли това Jabber ID да се абонира за pubsub нода?"}.
|
||||
{"Allow this person to register with the room?","Позволявате ли този потребителя да се регистрира в стаята?"}.
|
||||
{"Allow users to change the subject","Позволи потребителите да сменят темата"}.
|
||||
{"Allow users to query other users","Позволи на потребителите да правят заявки към други потребители"}.
|
||||
{"Allow users to send invites","Разреши на потребителите да изпращат покани"}.
|
||||
{"Allow users to send private messages","Разреши на потребителите да изпращат лични съобщения"}.
|
||||
{"Allow visitors to change nickname","Разреши на посетителите да променят псевдонима си"}.
|
||||
{"Allow visitors to send private messages to","Разреши на потребителите да изпращат лични съобщения до"}.
|
||||
{"Allow visitors to send status text in presence updates","Разреши на посетителите да изпращат текст за състоянието в актуализациите за присъствие"}.
|
||||
{"Allow visitors to send voice requests","Разреши на посетителите да изпращат заявки за гласово повикване"}.
|
||||
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Асоциирана LDAP група, която определя членството в стая; това трябва да бъде LDAP отличително име според специфично за изпълнението/внедряването определение на група."}.
|
||||
{"Announcements","Съобщения"}.
|
||||
{"Answer associated with a picture","Отговор, свързан с картина"}.
|
||||
{"Answer associated with a video","Отговор, свързан с видеоклип"}.
|
||||
{"Answer associated with speech","Отговор, свързан с аудио клип"}.
|
||||
{"Answer to a question","Отговор на въпрос"}.
|
||||
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Всеки в посочения списък от групата с контакти, може да се абонира и извлича елементи"}.
|
||||
{"Anyone may associate leaf nodes with the collection","Всеки може да асоциира \"leaf\" нодове с колекцията"}.
|
||||
{"Anyone may publish","Всеки може да публикува"}.
|
||||
{"Anyone may subscribe and retrieve items","Всеки може да се абонира и да извлича елементи"}.
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Всеки, който има абонамент за присъствие на двете или от: може да се абонира и да извлича елементи"}.
|
||||
{"Anyone with Voice","Всеки, с възможност за гласово обаждане"}.
|
||||
{"Anyone","Всеки"}.
|
||||
{"April","Април"}.
|
||||
{"Attribute 'channel' is required for this request","Атрибутът 'канал' е задължителен за тази заявка"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","Атрибутът 'id' е задължителен за MIX съобщения"}.
|
||||
{"Attribute 'jid' is not allowed here","Атрибутът 'jid' не е разрешен тук"}.
|
||||
{"Attribute 'node' is not allowed here","Атрибутът 'нод' не е разрешен тук"}.
|
||||
{"Attribute 'to' of stanza that triggered challenge","Атрибут 'до' на строфата, който е предизвикал предизвикателството"}.
|
||||
{"August","Август"}.
|
||||
{"Automatic node creation is not enabled","Автоматичното създаване на нод не е включено"}.
|
||||
{"Backup Management","Управление на архивирането"}.
|
||||
{"Backup of ~p","Резервно копие на ~p"}.
|
||||
{"Backup to File at ","Архивиране във файл на "}.
|
||||
{"Backup","Резервно копие"}.
|
||||
{"Bad format","Лош формат"}.
|
||||
{"Birthday","Рожден ден"}.
|
||||
{"Both the username and the resource are required","Изискват се потребителското име и ресурсът"}.
|
||||
{"Bytestream already activated","Bytestream вече е активиран"}.
|
||||
{"Cannot remove active list","Активният списък не може да бъде премахнат"}.
|
||||
{"Cannot remove default list","Не можете да премахнете списъка по подразбиране"}.
|
||||
{"CAPTCHA web page","CAPTCHA уеб страница"}.
|
||||
{"Challenge ID","ID на предизвикателството"}.
|
||||
{"Change Password","Смяна на парола"}.
|
||||
{"Change User Password","Смяна на потребителска парола"}.
|
||||
{"Changing password is not allowed","Смяната на парола не е разрешена"}.
|
||||
{"Changing role/affiliation is not allowed","Смяната на роля/принадлежност не е разрешена"}.
|
||||
{"Channel already exists","Каналът вече съществува"}.
|
||||
{"Channel does not exist","Каналът не съществува"}.
|
||||
{"Channel JID","JID на канал"}.
|
||||
{"Channels","Канали"}.
|
||||
{"Characters not allowed:","Неразрешени символи:"}.
|
||||
{"Chatroom configuration modified","Конфигурацията на стаята за чат е променена"}.
|
||||
{"Chatroom is created","Стаята за чат е създадена"}.
|
||||
{"Chatroom is destroyed","Стаята за чат е унищожена"}.
|
||||
{"Chatroom is started","Стаята за чат е стартирана"}.
|
||||
{"Chatroom is stopped","Стаята за чат е спряна"}.
|
||||
{"Chatrooms","Чат стаи"}.
|
||||
{"Choose a username and password to register with this server","Изберете потребителско име и парола, за да се регистрирате на този сървър"}.
|
||||
{"Choose storage type of tables","Изберете тип за съхранение на таблици"}.
|
||||
{"Choose whether to approve this entity's subscription.","Изберете дали да одобрите абонамента на този субект."}.
|
||||
{"City","Град"}.
|
||||
{"Client acknowledged more stanzas than sent by server","Клиентът потвърди повече строфи от изпратените от сървъра"}.
|
||||
{"Commands","Команди"}.
|
||||
{"Conference room does not exist","Конферентната стая не съществува"}.
|
||||
{"Configuration of room ~s","Конфигурация на стая ~s"}.
|
||||
{"Configuration","Конфигурация"}.
|
||||
{"Connected Resources:","Свързани ресурси:"}.
|
||||
{"Contact Addresses (normally, room owner or owners)","Адреси за контакт (обикновено собственик или собственици на стая)"}.
|
||||
{"Contrib Modules","Сътруднически модули"}.
|
||||
{"Country","Държава"}.
|
||||
{"CPU Time:","Процесорно време:"}.
|
||||
{"Current Discussion Topic","Текуща тема на дискусита"}.
|
||||
{"Database failure","Грешка в базата данни"}.
|
||||
{"Database Tables at ~p","Таблици на базата данни при ~p"}.
|
||||
{"Database Tables Configuration at ","Конфигурация на таблиците в базата данни при "}.
|
||||
{"Database","База данни"}.
|
||||
{"December","Декември"}.
|
||||
{"Default users as participants","Потребители по подразбиране като участници"}.
|
||||
{"Delete content","Изтрий съдържанието"}.
|
||||
{"Delete message of the day on all hosts","Изтрий съобщението на деня от всички нодове"}.
|
||||
{"Delete message of the day","Изтрий съобщението на деня"}.
|
||||
{"Delete Selected","Изтрий избраните"}.
|
||||
{"Delete table","Изтрий таблицата"}.
|
||||
{"Delete User","Изтрий потребителя"}.
|
||||
{"Deliver event notifications","Достави известията за събития"}.
|
||||
{"Deliver payloads with event notifications","Достави прикачените обекти с известията за събития"}.
|
||||
{"Description:","Описание:"}.
|
||||
{"Disc only copy","Копие само на диска"}.
|
||||
{"'Displayed groups' not added (they do not exist!): ","'Показаните групи' не са добавени (не съществуват!): "}.
|
||||
{"Displayed:","Показва се:"}.
|
||||
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Не казвайте паролата си на никого, дори на администраторите на XMPP сървъра."}.
|
||||
{"Dump Backup to Text File at ","Архивиране в текстов файл при "}.
|
||||
{"Dump to Text File","Архивиране в текстов файл"}.
|
||||
{"Duplicated groups are not allowed by RFC6121","Дублирани групи не са разрешени от RFC6121"}.
|
||||
{"Dynamically specify a replyto of the item publisher","Динамично задаване на отговор към публикувалия елемента"}.
|
||||
{"Edit Properties","Редактиране на свойства"}.
|
||||
{"Either approve or decline the voice request.","Одобрете или отхвърлете заявката за гласова връзка."}.
|
||||
{"Elements","Елементи"}.
|
||||
{"Email Address","Имейл адрес"}.
|
||||
{"Email","Илейл"}.
|
||||
{"Enable hats","Активиране на шапки"}.
|
||||
{"Enable logging","Активирай запис на хронологията"}.
|
||||
{"Enable message archiving","Активирай архивиране на съобщенията"}.
|
||||
{"Enabling push without 'node' attribute is not supported","Активиране на известията без атрибут 'нод' не се поддържа"}.
|
||||
{"End User Session","Прекрати сесията на потребителя"}.
|
||||
{"Enter nickname you want to register","Въведете псевдонима, който желаете да регистрирате"}.
|
||||
{"Enter path to backup file","Въведете пътя към архивния файл"}.
|
||||
{"Enter path to jabberd14 spool dir","Въведете пътя към jabberd14 spool директорията"}.
|
||||
{"Enter path to jabberd14 spool file","Въведете пътя към jabberd14 spool файла"}.
|
||||
{"Enter path to text file","Въведете пътя към текстовия файл"}.
|
||||
{"Enter the text you see","Въведете текста, който виждате"}.
|
||||
{"Erlang XMPP Server","Erlang XMPP сървър"}.
|
||||
{"Error","Грешка"}.
|
||||
{"Exclude Jabber IDs from CAPTCHA challenge","Изключи CAPTCHA предизвикателство за следните Jabber ID-та"}.
|
||||
{"Export all tables as SQL queries to a file:","Експортирай всички таблици като SQL заявки във файл:"}.
|
||||
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Експортирай данните за всички потребители на сървъра в PIEFXIS файлове (XEP-0227):"}.
|
||||
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Експортирай данните за потребителите на този хост в PIEFXIS файлове (XEP-0227):"}.
|
||||
{"External component failure","Неуспех породен от външен компонент"}.
|
||||
{"External component timeout","Времето за изчакване на външен компонент изтече"}.
|
||||
{"Failed to activate bytestream","Неуспешно активиране на bytestream"}.
|
||||
{"Failed to extract JID from your voice request approval","Неуспешно извличане на JID от одобрението за гласова заявка"}.
|
||||
{"Failed to map delegated namespace to external component","Неуспешно съпоставяне на делегирано пространство от имена с външен компонент"}.
|
||||
{"Failed to parse HTTP response","Неуспешно анализиран HTTP отговор"}.
|
||||
{"Failed to process option '~s'","Неуспешо обработена опция '~s'"}.
|
||||
{"Family Name","Фамилно име"}.
|
||||
{"FAQ Entry","Въвеждане на ЧЗВ"}.
|
||||
{"February","Февруари"}.
|
||||
{"File larger than ~w bytes","Файлът е по-голям от ~w байта"}.
|
||||
{"Fill in the form to search for any matching XMPP User","Попълнете формата, за да търсите съвпадащ XMPP потребител"}.
|
||||
{"Friday","Петък"}.
|
||||
{"From ~ts","От ~ts"}.
|
||||
{"From","От"}.
|
||||
{"Full List of Room Admins","Пълен списък на администраторите на стаята"}.
|
||||
{"Full List of Room Owners","Пълен списък на собствениците на стаята"}.
|
||||
{"Full Name","Пълно име"}.
|
||||
{"Get List of Online Users","Списък на онлайн потребителите"}.
|
||||
{"Get List of Registered Users","Списък на регистрираните потребители"}.
|
||||
{"Get Number of Online Users","Брой на онлайн потребителите"}.
|
||||
{"Get Number of Registered Users","Брой на регистрираните потребители"}.
|
||||
{"Get Pending","Виж чакащи"}.
|
||||
{"Get User Last Login Time","Покажи времето, когато потребителят е влязъл за последно"}.
|
||||
{"Get User Password","Покажи паролата на потребителя"}.
|
||||
{"Get User Statistics","Покажи статистика за потребителя"}.
|
||||
{"Given Name","Име"}.
|
||||
{"Grant voice to this person?","Предоставяне на глас за потребителя?"}.
|
||||
{"Groups that will be displayed to the members","Групи, които ще се показват на членовете"}.
|
||||
{"Groups","Групи"}.
|
||||
{"Group","Група"}.
|
||||
{"Hat title","Заглавие на шапката"}.
|
||||
{"Hat URI","URI адрес за шапка"}.
|
||||
{"Hats limit exceeded","Превишен е лимитът за шапка"}.
|
||||
{"Host unknown","Неизвестен хост"}.
|
||||
{"Host","Хост"}.
|
||||
{"HTTP File Upload","Качване на файл по HTTP"}.
|
||||
{"Idle connection","Неактивна връзка"}.
|
||||
{"If you don't see the CAPTCHA image here, visit the web page.","Ако не виждате CAPTCHA изображението, посетете уеб страницата."}.
|
||||
{"Import Directory","Импорт на директория"}.
|
||||
{"Import File","Импорт на файл"}.
|
||||
{"Import user data from jabberd14 spool file:","Импорт на потребители от jabberd14 Spool файл:"}.
|
||||
{"Import User from File at ","Импорт на потребител от файл на "}.
|
||||
{"Import users data from a PIEFXIS file (XEP-0227):","Импорт на потребителски данни от PIEFXIS файл (XEP-0227):"}.
|
||||
{"Import users data from jabberd14 spool directory:","Импорт на потребители от jabberd14 Spool директория:"}.
|
||||
{"Import Users from Dir at ","Импорт на потребители от директория на "}.
|
||||
{"Import Users From jabberd14 Spool Files","Импорт на потребители от jabberd14 Spool файлове"}.
|
||||
{"Improper domain part of 'from' attribute","Неправилна част за домейн в атрибута 'from'"}.
|
||||
{"Improper message type","Неправилен тип съобщение"}.
|
||||
{"Incoming s2s Connections:","Входящи s2s връзки:"}.
|
||||
{"Incorrect CAPTCHA submit","Неправилно CAPTCHA въвеждане"}.
|
||||
{"Incorrect data form","Неправилна форма на данните"}.
|
||||
{"Incorrect password","Грешна парола"}.
|
||||
{"Incorrect value of 'action' attribute","Неправилна стойност на атрибута 'action'"}.
|
||||
{"Incorrect value of 'action' in data form","Неправилна стойност на 'action' във формата за данни"}.
|
||||
{"Incorrect value of 'path' in data form","Неправилна стойност на 'path' във формата за данни"}.
|
||||
{"Installed Modules:","Инсталирани модули:"}.
|
||||
{"Install","Инсталирай"}.
|
||||
{"Insufficient privilege","Недостатъчни права"}.
|
||||
{"Internal server error","Вътрешна сървърна грешка"}.
|
||||
{"Invalid 'from' attribute in forwarded message","Невалиден атрибут 'from' в препратеното съобщение"}.
|
||||
{"Invalid node name","Невалидно име на нода"}.
|
||||
{"Invalid 'previd' value","Невалидна стойност на 'previd'"}.
|
||||
{"Invitations are not allowed in this conference","Поканите не са разрешени в тази конференция"}.
|
||||
{"IP addresses","IP адреси"}.
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не е позволено да изпращате съобщения за грешки в стаята. Участникът (~s) е изпратил съобщение за грешка (~s) и е бил отстранен от стаята"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Изпращането на лични съобщения от тип \"групов чат\" не е разрешено"}.
|
||||
{"It is not allowed to send private messages to the conference","Изпращането на лични съобщения до конференцията не е разрешено"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Януари"}.
|
||||
{"JID normalization denied by service policy","Политиката на услугата не допуска нормализирането на JID"}.
|
||||
{"JID normalization failed","Нормализирането на JID е неуспешно"}.
|
||||
{"Joined MIX channels of ~ts","Свързани MIX канали на ~ts"}.
|
||||
{"Joined MIX channels:","Свързани MIX канали:"}.
|
||||
{"July","Юли"}.
|
||||
{"June","Юни"}.
|
||||
{"Just created","Току що създаден"}.
|
||||
{"Label:","Етикет:"}.
|
||||
{"Last Activity","Последна активност"}.
|
||||
{"Last login","Последно влизане"}.
|
||||
{"Last message","Последно съобщение"}.
|
||||
{"Last month","Миналия месец"}.
|
||||
{"Last year","Миналата година"}.
|
||||
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Най-малко значимите битове SHA-256 хеш на текст трябва да са равни на шестнайсетичния етикет"}.
|
||||
{"List of rooms","Списък на стаите"}.
|
||||
{"List of users with hats","Списък на потребителите с шапки"}.
|
||||
{"List users with hats","Избройте потребителите с шапки"}.
|
||||
{"Logging","Регистриране на събития"}.
|
||||
{"Low level update script","Скрипт за актуализация на ниско ниво"}.
|
||||
{"Make participants list public","Направи списъка с участниците публичен"}.
|
||||
{"Make room CAPTCHA protected","Защити стаята с CAPTCHA"}.
|
||||
{"Make room members-only","Направи стаята само за членове"}.
|
||||
{"Make room moderated","Направи стая модерирана"}.
|
||||
{"Make room password protected","Защити стаята с парола"}.
|
||||
{"Make room persistent","Направи стая постоянна"}.
|
||||
{"Make room public searchable","Направи стаята да е публично търсена"}.
|
||||
{"Malformed username","Неправилно формирано потребителско име"}.
|
||||
{"MAM preference modification denied by service policy","Промяна на предпочитанията за МАМ е отказана, поради политика на услугата"}.
|
||||
{"March","Март"}.
|
||||
{"Max # of items to persist, or `max` for no specific limit other than a server imposed maximum","Максимален # на елементи, които да се запазят, или `max` за неспецифичен лимит, различен от наложения от сървъра максимум"}.
|
||||
{"Max payload size in bytes","Максимален размер на прикачения обект в байтове"}.
|
||||
{"Maximum file size","Максимален размер на файла"}.
|
||||
{"Maximum Number of History Messages Returned by Room","Максимален брой съобщения от хронологията, върнати от стая"}.
|
||||
{"Maximum number of items to persist","Максимален брой елементи за запазване"}.
|
||||
{"Maximum Number of Occupants","Максимален брой потребители"}.
|
||||
{"May","Май"}.
|
||||
{"Members not added (inexistent vhost!): ","Членовете не са добавени (несъществуващ vhost!): "}.
|
||||
{"Membership is required to enter this room","Изисква се членство, за вход в тази стая"}.
|
||||
{"Members:","Членове:"}.
|
||||
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","Запомнете паролата си или я запишете на лист хартия, поставен на сигурно място. В XMPP няма автоматичен начин за възстановяване на паролата в случай, че я забравите."}.
|
||||
{"Memory","Памет"}.
|
||||
{"Mere Availability in XMPP (No Show Value)","Наличност в XMPP (Не показвай стойност)"}.
|
||||
{"Message body","Текст на съобщението"}.
|
||||
{"Message not found in forwarded payload","Съобщението не е намерено в препратения прикачен елемент"}.
|
||||
{"Messages from strangers are rejected","Съобщенията от непознати се отхвърлят"}.
|
||||
{"Messages of type headline","Съобщения от тип заглавие"}.
|
||||
{"Messages of type normal","Съобщения от тип нормален"}.
|
||||
{"Middle Name","Презиме"}.
|
||||
{"Minimum interval between voice requests (in seconds)","Минимален интервал между заявките за гласова комуникация (в секунди)"}.
|
||||
{"Moderator privileges required","Изискват се права на модератор"}.
|
||||
{"Moderators Only","Само модератори"}.
|
||||
{"Moderator","Модератор"}.
|
||||
{"Modified modules","Модифицирани модули"}.
|
||||
{"Module failed to handle the query","Модулът не успя да обработи заявката"}.
|
||||
{"Monday","Понеделник"}.
|
||||
{"Multicast","Мултикаст"}.
|
||||
{"Multiple <item/> elements are not allowed by RFC6121","Повече от един <item/> елемента не се разрешават от RFC6121"}.
|
||||
{"Multi-User Chat","Групов чат (MUC)"}.
|
||||
{"Name in the rosters where this group will be displayed","Име в списъците с контакти, където ще се показва тази група"}.
|
||||
{"Name","Име"}.
|
||||
{"Name:","Име:"}.
|
||||
{"Natural Language for Room Discussions","Език за дискусии в стаята"}.
|
||||
{"Natural-Language Room Name","Име на стаята на предпочитания език"}.
|
||||
{"Neither 'jid' nor 'nick' attribute found","Атрибутите 'jid' и 'nick' не са намерени"}.
|
||||
{"Neither 'role' nor 'affiliation' attribute found","Атрибути 'role' или 'affiliation' не са намерени"}.
|
||||
{"Never","Никога"}.
|
||||
{"New Password:","Нова парола:"}.
|
||||
{"Nickname can't be empty","Псевдонимът не може да бъде празен"}.
|
||||
{"Nickname Registration at ","Регистрация на псевдоним в "}.
|
||||
{"Nickname ~s does not exist in the room","Псевдонимът ~s не присъства в стаята"}.
|
||||
{"Nickname","Псевдоним"}.
|
||||
{"No address elements found","Не е намерен адресен елемент"}.
|
||||
{"No addresses element found","Не са намерени адресни елементи"}.
|
||||
{"No 'affiliation' attribute found","Атрибут 'affiliation' не е намерен"}.
|
||||
{"No available resource found","Не е намерен наличен ресурс"}.
|
||||
{"No body provided for announce message","Не е предоставен текст за съобщение тип обява"}.
|
||||
{"No child elements found","Не са открити подчинени елементи"}.
|
||||
{"No data form found","Не е намерена форма за данни"}.
|
||||
{"No Data","Няма данни"}.
|
||||
{"No features available","Няма налични функции"}.
|
||||
{"No <forwarded/> element found","Елементът <forwarded/> не е намерен"}.
|
||||
{"No hook has processed this command","Никоя кука не е обработила тази команда"}.
|
||||
{"No info about last activity found","Няма информация за последна активновт"}.
|
||||
{"No 'item' element found","Елементът 'item' не е намерен"}.
|
||||
{"No items found in this query","Няма намерени елементи в тази заявка"}.
|
||||
{"No limit","Няма ограничение"}.
|
||||
{"No module is handling this query","Нито един модул не обработва тази заявка"}.
|
||||
{"No node specified","Не е посочен нод"}.
|
||||
{"No 'password' found in data form","Не е намерен 'password' във формата за данни"}.
|
||||
{"No 'password' found in this query","В заявката не е намерен 'password'"}.
|
||||
{"No 'path' found in data form","Не е намерен 'path' във формата за данни"}.
|
||||
{"No pending subscriptions found","Не са намерени чакащи абонаменти"}.
|
||||
{"No privacy list with this name found","Не е намерен списък за поверителност с това име"}.
|
||||
{"No private data found in this query","Няма открити лични данни в тази заявка"}.
|
||||
{"No running node found","Не е намерен работещ нод"}.
|
||||
{"No services available","Няма налични услуги"}.
|
||||
{"No statistics found for this item","Не е налична статистика за този елемент"}.
|
||||
{"No 'to' attribute found in the invitation","Атрибутът 'to' не е намерен в поканата"}.
|
||||
{"Nobody","Никой"}.
|
||||
{"Node already exists","Нодът вече съществува"}.
|
||||
{"Node ID","ID на нода"}.
|
||||
{"Node index not found","Индексът на нода не е намерен"}.
|
||||
{"Node not found","Нодът не е намерен"}.
|
||||
{"Node ~p","Нод ~p"}.
|
||||
{"Nodeprep has failed","Nodeprep е неуспешен"}.
|
||||
{"Nodes","Нодове"}.
|
||||
{"Node","Нод"}.
|
||||
{"None","Нито един"}.
|
||||
{"Not allowed","Не е разрешено"}.
|
||||
{"Not Found","Не е намерен"}.
|
||||
{"Not subscribed","Няма абонамент"}.
|
||||
{"Notify subscribers when items are removed from the node","Уведоми абонатите, когато елементите бъдат премахнати от нода"}.
|
||||
{"Notify subscribers when the node configuration changes","Уведоми абонатите, когато конфигурацията на нода се промени"}.
|
||||
{"Notify subscribers when the node is deleted","Уведоми абонатите, когато нодът бъде изтрит"}.
|
||||
{"November","Ноември"}.
|
||||
{"Number of answers required","Брой на необходимите отговори"}.
|
||||
{"Number of occupants","Брой потребители"}.
|
||||
{"Number of Offline Messages","Брой офлайн съобщения"}.
|
||||
{"Number of online users","Брой онлайн потребители"}.
|
||||
{"Number of registered users","Брой регистрирани потребители"}.
|
||||
{"Number of seconds after which to automatically purge items, or `max` for no specific limit other than a server imposed maximum","Брой секунди, след които автоматично да се изчистят елементите, или `max` за липса на конкретно ограничение, различно от наложения от сървъра максимум"}.
|
||||
{"Occupants are allowed to invite others","На участниците е позволено да канят други"}.
|
||||
{"Occupants are allowed to query others","Участниците могат да отправят заявки към други лица"}.
|
||||
{"Occupants May Change the Subject","Участниците могат да променят темата"}.
|
||||
{"October","Октомври"}.
|
||||
{"Offline Messages","Офлайн съобщения"}.
|
||||
{"Offline Messages:","Офлайн съобщения:"}.
|
||||
{"OK","ДОБРЕ"}.
|
||||
{"Old Password:","Стара парола:"}.
|
||||
{"Online Users","Онлайн потребители"}.
|
||||
{"Online Users:","Онлайн потребители:"}.
|
||||
{"Online","Онлайн"}.
|
||||
{"Only admins can see this","Само администратори могат да видят това"}.
|
||||
{"Only deliver notifications to available users","Доставяне на известия само до наличните потребители"}.
|
||||
{"Only <enable/> or <disable/> tags are allowed","Само тагове <enable/> и <disable/> са разрешени"}.
|
||||
{"Only <list/> element is allowed in this query","Само елементът <list/> е разрешен за тази заявка"}.
|
||||
{"Only members may query archives of this room","Само членовете могат да търсят архиви на тази стая"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","Само модератори и участници имат право да променят темата в тази стая"}.
|
||||
{"Only moderators are allowed to change the subject in this room","Само модераторите имат право да сменят темата в тази стая"}.
|
||||
{"Only moderators are allowed to retract messages","Само модераторите имат право да оттеглят съобщения"}.
|
||||
{"Only moderators can approve voice requests","Само модераторите могат да одобряват гласови заявки"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Само обитателите имат право да изпращат съобщения до конференцията"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Само обитателите имат право да изпращат запитвания до конференцията"}.
|
||||
{"Only publishers may publish","Само издателите могат да публикуват"}.
|
||||
{"Only service administrators are allowed to send service messages","Само администраторите на услуги имат право да изпращат системни съобщения"}.
|
||||
{"Only those on a whitelist may subscribe and retrieve items","Само тези в белия списък могат да се абонират и да извличат елементи"}.
|
||||
{"Organization Name","Име на организацията"}.
|
||||
{"Organization Unit","Отдел"}.
|
||||
{"Other Modules Available:","Други налични модули:"}.
|
||||
{"Outgoing s2s Connections","Изходящи s2s връзки"}.
|
||||
{"Outgoing s2s Connections:","Изходящи s2s връзки:"}.
|
||||
{"Owner privileges required","Изискват се привилегии на собственик"}.
|
||||
{"Packet relay is denied by service policy","Предаването на пакети е отказано от политиката на услугата"}.
|
||||
{"Packet","Пакет"}.
|
||||
{"Participant ID","ID на участник"}.
|
||||
{"Participant","Участник"}.
|
||||
{"Password Verification","Проверка на паролата"}.
|
||||
{"Password Verification:","Проверка на паролата:"}.
|
||||
{"Password","Парола"}.
|
||||
{"Password:","Парола:"}.
|
||||
{"Path to Dir","Път към директория"}.
|
||||
{"Path to File","Път до файл"}.
|
||||
{"Pending","В очакване"}.
|
||||
{"Period: ","Период: "}.
|
||||
{"Persist items to storage","Запазване на елементите в хранилището"}.
|
||||
{"Persistent","Постоянен"}.
|
||||
{"Ping query is incorrect","Заявката за пинг е неправилна"}.
|
||||
{"Ping","Пинг"}.
|
||||
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Обърнете внимание, че тези опции ще направят резервно копие само на вградената (Mnesia) база данни. Ако използвате модула ODBC, трябва да направите резервно копие на SQL базата данни отделно."}.
|
||||
{"Please, wait for a while before sending new voice request","Моля, изчакайте известно време, преди да изпратите нова заявка за гласова връзка"}.
|
||||
{"Pong","Понг"}.
|
||||
{"Possessing 'ask' attribute is not allowed by RFC6121","Притежаването на атрибут 'ask' не е разрешено от RFC6121"}.
|
||||
{"Present real Jabber IDs to","Покажи истински Jabber ID-та на"}.
|
||||
{"Previous session not found","Предишната сесия не е намерена"}.
|
||||
{"Previous session PID has been killed","PID от предишната сесия е унищожен"}.
|
||||
{"Previous session PID has exited","Предишният PID на сесията е излязъл"}.
|
||||
{"Previous session PID is dead","PID от предишната сесия не съществува"}.
|
||||
{"Previous session timed out","Времето на предишната сесия изтече"}.
|
||||
{"Public","Публичен"}.
|
||||
{"Publish model","Модел за публикуване"}.
|
||||
{"Publish-Subscribe","Публикуване-Абониране"}.
|
||||
{"PubSub subscriber request","Заявка от абонат за PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Изчисти всички елементи, когато съответният издател премине в режим офлайн"}.
|
||||
{"Push record not found","Push записът не е намерен"}.
|
||||
{"Queries to the conference members are not allowed in this room","В тази зала не се допускат запитвания към членовете на конференцията"}.
|
||||
{"Query to another users is forbidden","Заявка към други потребители е забранена"}.
|
||||
{"RAM and disc copy","Копие в RAM и на диск"}.
|
||||
{"RAM copy","Копие в RAM"}.
|
||||
{"Really delete message of the day?","Наистина ли желаете да изтриете съобщението на деня?"}.
|
||||
{"Receive notification from all descendent nodes","Получаване на известие от всички низходящи нодове"}.
|
||||
{"Receive notification from direct child nodes only","Получаване на известия само от директни подчинени нодове"}.
|
||||
{"Receive notification of new items only","Получаване на известия само за нови елементи"}.
|
||||
{"Receive notification of new nodes only","Получаване на известия само за нови нодове"}.
|
||||
{"Recipient is not in the conference room","Получателят не е в конферентната зала"}.
|
||||
{"Register an XMPP account","Регистрирай XMPP акаунт"}.
|
||||
{"Registered Users","Регистрирани потребители"}.
|
||||
{"Registered Users:","Регистрирани потребители:"}.
|
||||
{"Register","Регистрирай"}.
|
||||
{"Remote copy","Отдалечено копие"}.
|
||||
{"Remove a hat from a user","Премахни шапка от потребител"}.
|
||||
{"Remove All Offline Messages","Премахни всички офлайн съобщения"}.
|
||||
{"Remove User","Премахни потребител"}.
|
||||
{"Remove","Премахни"}.
|
||||
{"Replaced by new connection","Заменен от нова връзка"}.
|
||||
{"Request has timed out","Времето за заявка изтече"}.
|
||||
{"Request is ignored","Заявката е игнорирано"}.
|
||||
{"Requested role","Заявена роля"}.
|
||||
{"Resources","Ресурси"}.
|
||||
{"Restart Service","Рестартирай услугата"}.
|
||||
{"Restart","Рестартирай"}.
|
||||
{"Restore Backup from File at ","Възстанови резервно копие от файл в "}.
|
||||
{"Restore binary backup after next ejabberd restart (requires less memory):","Възстановяване на бинарно копие след следващото рестартиране на ejabberd (изисква по-малко памет):"}.
|
||||
{"Restore binary backup immediately:","Възстанови незабавно двоично копие:"}.
|
||||
{"Restore plain text backup immediately:","Възстановете незабавно копие от обикновен текст:"}.
|
||||
{"Restore","Възстанови"}.
|
||||
{"Roles and Affiliations that May Retrieve Member List","Роли и принадлежности, които могат да извличат списък с членове"}.
|
||||
{"Roles for which Presence is Broadcasted","Роли, за които се излъчва присъствие"}.
|
||||
{"Roles that May Send Private Messages","Роли, които могат да изпращат лични съобщения"}.
|
||||
{"Room Configuration","Конфигурация на стаята"}.
|
||||
{"Room creation is denied by service policy","Създаването на стая е отказано поради политика на услугата"}.
|
||||
{"Room description","Описание на стаята"}.
|
||||
{"Room Occupants","Обитатели на стаята"}.
|
||||
{"Room terminates","Стаята се прекратява"}.
|
||||
{"Room title","Заглавие на стаята"}.
|
||||
{"Roster groups allowed to subscribe","Групи от списъци с контакти, на които е разрешено да се абонират"}.
|
||||
{"Roster of ~ts","Списък с контакти на ~ts"}.
|
||||
{"Roster size","Размер на списъка с контакти"}.
|
||||
{"Roster:","Списък с контакти:"}.
|
||||
{"RPC Call Error","Грешка при RPC повикване"}.
|
||||
{"Running Nodes","Работещи нодове"}.
|
||||
{"Saturday","Събота"}.
|
||||
{"Script check","Проверка на скрипт"}.
|
||||
{"Search from the date","Търси от дата"}.
|
||||
{"Search Results for ","Резултати от търсенето за "}.
|
||||
{"Search the text","Търси текста"}.
|
||||
{"Search until the date","Търси до дата"}.
|
||||
{"Search users in ","Търси потребители в "}.
|
||||
{"Select All","Избери всички"}.
|
||||
{"Send announcement to all online users on all hosts","Изпрати съобщение до всички онлайн потребители на всички хостове"}.
|
||||
{"Send announcement to all online users","Изпрати съобщение до всички онлайн потребители"}.
|
||||
{"Send announcement to all users on all hosts","Изпрати съобщение до всички потребители на всички хостове"}.
|
||||
{"Send announcement to all users","Изпрати съобщение до всички потребители"}.
|
||||
{"September","Септември"}.
|
||||
{"Server:","Сървър:"}.
|
||||
{"Service list retrieval timed out","Времето за изчакване на извличането на списъка с услуги изтече"}.
|
||||
{"Session state copying timed out","Времето за изчакване на копирането на състоянието на сесията изтече"}.
|
||||
{"Set message of the day and send to online users","Задай съобщение на деня и го изпрати на онлайн потребителите"}.
|
||||
{"Set message of the day on all hosts and send to online users","Задавай съобщение на деня на всички хостове и изпрати на онлайн потребителите"}.
|
||||
{"Shared Roster Groups","Споделени групи от списъци с контакти"}.
|
||||
{"Show Integral Table","Покажи интегрална таблица"}.
|
||||
{"Show Ordinary Table","Покажи обикновена таблица"}.
|
||||
{"Shut Down Service","Изключи услугата"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 байтови потоци"}.
|
||||
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Някои XMPP клиенти могат да съхраняват паролата Ви в компютъра, но от съображения за сигурност трябва да го правите само на личния си компютър."}.
|
||||
{"Sources Specs:","Спецификации на източниците:"}.
|
||||
{"Specify the access model","Задай модела за достъп"}.
|
||||
{"Specify the event message type","Задай типа на съобщението за събитие"}.
|
||||
{"Specify the publisher model","Задай модела на издателя"}.
|
||||
{"Stanza id is not valid","Невалидно ID на строфата"}.
|
||||
{"Stanza ID","ID на строфа"}.
|
||||
{"Statically specify a replyto of the node owner(s)","Статично задаване на replyto на собственика(ците) на нода"}.
|
||||
{"Statistics of ~p","Статистики на ~p"}.
|
||||
{"Statistics","Статистики"}.
|
||||
{"Stopped Nodes","Спрени нодове"}.
|
||||
{"Stop","Спри"}.
|
||||
{"Storage Type","Тип хранилище"}.
|
||||
{"Store binary backup:","Запази бинарен архив:"}.
|
||||
{"Store plain text backup:","Запази архив като обикновен текст:"}.
|
||||
{"Stream management is already enabled","Управлението на потока вече е активирано"}.
|
||||
{"Stream management is not enabled","Управлението на потока не е активирано"}.
|
||||
{"Subject","Тема"}.
|
||||
{"Submitted","Изпратено"}.
|
||||
{"Submit","Изпрати"}.
|
||||
{"Subscriber Address","Адрес на абоната"}.
|
||||
{"Subscribers may publish","Абонатите могат да публикуват"}.
|
||||
{"Subscription requests must be approved and only subscribers may retrieve items","Заявките за абонамент трябва да бъдат одобрени и само абонатите могат да извличат елементи"}.
|
||||
{"Subscriptions are not allowed","Абонаментите не са разрешени"}.
|
||||
{"Subscription","Абонамент"}.
|
||||
{"Sunday","Неделя"}.
|
||||
{"Text associated with a picture","Текст, свързан със снимка"}.
|
||||
{"Text associated with a sound","Текст, свързан със звук"}.
|
||||
{"Text associated with a video","Текст, свързан с видео"}.
|
||||
{"Text associated with speech","Текст, свързан с реч"}.
|
||||
{"That nickname is already in use by another occupant","Този псевдоним вече се използва от друг потребител"}.
|
||||
{"That nickname is registered by another person","Този псевдоним е регистриран от друго лице"}.
|
||||
{"The account already exists","Профилът вече съществува"}.
|
||||
{"The account was not unregistered","Профилът не е дерегистриран"}.
|
||||
{"The body text of the last received message","Текстът на последното получено съобщение"}.
|
||||
{"The CAPTCHA is valid.","CAPTCHA предизвикателството е валидно."}.
|
||||
{"The CAPTCHA verification has failed","Проверката на CAPTCHA предизвикателството е неуспешна"}.
|
||||
{"The captcha you entered is wrong","Въведеният captcha код е грешен"}.
|
||||
{"The DateTime at which a leased subscription will end or has ended","Датата и часът, на който абонамент ще приключи или е приключил"}.
|
||||
{"The datetime when the node was created","Датата, когато нодът е бил създаден"}.
|
||||
{"The default language of the node","Езикът по подразбиране на нода"}.
|
||||
{"The feature requested is not supported by the conference","Исканата функция не се поддържа от конференцията"}.
|
||||
{"The JID of the node creator","JID на създателя на нода"}.
|
||||
{"The JIDs of those to contact with questions","JID на лицата, с които да се свържете при въпроси"}.
|
||||
{"The JIDs of those with an affiliation of owner","JID на лицата с принадлежност на собственик"}.
|
||||
{"The JIDs of those with an affiliation of publisher","JID-та на лица с принадлежност към публикуващи"}.
|
||||
{"The list of all online users","Списък на всички онлайн потребители"}.
|
||||
{"The list of all users","Списък на всички потребители"}.
|
||||
{"The maximum number of child nodes that can be associated with a collection, or `max` for no specific limit other than a server imposed maximum","Максимален брой подчинени нодове, които могат да бъдат свързани с колекция, или `max` за липса на конкретен лимит, различен от наложения от сървъра максимум"}.
|
||||
{"The minimum number of milliseconds between sending any two notification digests","Минималният брой милисекунди между изпращането на две извадки на известия"}.
|
||||
{"The name of the node","Името на нода"}.
|
||||
{"The node is a collection node","Нодът е от тип колекция"}.
|
||||
{"The NodeID of the relevant node","NodeID на съответния нод"}.
|
||||
{"The number of pending incoming presence subscription requests","Броят на чакащите входящи заявки за абонамент за присъствие"}.
|
||||
{"The number of subscribers to the node","Бротят абонати на нода"}.
|
||||
{"The number of unread or undelivered messages","Броят непрочетени или недоставени съобщения"}.
|
||||
{"The password contains unacceptable characters","Паролата съдържа недопустими символи"}.
|
||||
{"The password is too weak","Паролата е твърде слаба"}.
|
||||
{"The password of your XMPP account was successfully changed.","Паролата на вашия XMPP профил беше успешно променена."}.
|
||||
{"The password was not changed","Паролата не е променена"}.
|
||||
{"The passwords are different","Паролите са различни"}.
|
||||
{"The presence states for which an entity wants to receive notifications","Състояния на присъствие, за които даден субект желае да получава известия"}.
|
||||
{"The query is only allowed from local users","Заявката е разрешена само за локални потребители"}.
|
||||
{"The query must not contain <item/> elements","Заявката не може да съдържа елементи <item/>"}.
|
||||
{"The room subject can be modified by participants","Темата на стаята може да бъде променяна от участниците"}.
|
||||
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","Информацията за семантичния тип данни на нода, обикновено зададена от пространството на имената на прикачените данни (ако има такива)"}.
|
||||
{"The sender of the last received message","Подателят на последното получено съобщение"}.
|
||||
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Строфата ТРЯБВА да съдържа само един <active/> елемент, един <default/> елемент или един <list/> елемент"}.
|
||||
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","URL адрес на XSL трансформацията, която може да се приложи към прикачените данни, за да се генерира подходящ елемент от тялото на съобщението."}.
|
||||
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","URL адрес на XSL трансформацията, която може да се приложи към формата на прикачените данни, за да се генерира валиден резултат от Data Forms, който клиентът може да покаже с помощта на общ механизъм за визуализация на Data Forms."}.
|
||||
{"View Roster","Преглед на списъка с контакти"}.
|
||||
+7
-6
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Una Pàgina Web"}.
|
||||
{"Accept","Acceptar"}.
|
||||
{"Access denied by service policy","Accés denegat per la política del servei"}.
|
||||
{"Access model of authorize","Model d'Accés de autoritzar"}.
|
||||
{"Access model of open","Model d'Accés de obert"}.
|
||||
{"Access model of presence","Model d'Accés de presència"}.
|
||||
{"Access model of roster","Model d'Accés de contactes"}.
|
||||
{"Access model of whitelist","Model d'Accés de llista blanca"}.
|
||||
{"Access model","Model d'Accés"}.
|
||||
{"Account doesn't exist","El compte no existeix"}.
|
||||
{"Action on user","Acció en l'usuari"}.
|
||||
@@ -53,6 +48,7 @@
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Qualsevol amb una subscripció de presencia de 'both' o 'from' pot subscriure's i publicar elements"}.
|
||||
{"Anyone with Voice","Qualsevol amb Veu"}.
|
||||
{"Anyone","Qualsevol"}.
|
||||
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentment el teu compte no te privilegis d'administrador en este servidor. Per favor consulta com obtindre privilegis d'administrador en: https://docs.ejabberd.im/admin/installation/#administration-account"}.
|
||||
{"April","Abril"}.
|
||||
{"Attribute 'channel' is required for this request","L'atribut 'channel' és necessari per a aquesta petició"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","L'atribut 'id' es necessari per a missatges MIX"}.
|
||||
@@ -230,7 +226,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No està permés enviar missatges d'error a la sala. El participant (~s) ha enviat un missatge d'error (~s) i ha sigut expulsat de la sala"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","No està permés enviar missatges del tipus \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","No està permès l'enviament de missatges privats a la sala"}.
|
||||
{"It is not allowed to send private messages","No està permés enviar missatges privats"}.
|
||||
{"Jabber ID","ID Jabber"}.
|
||||
{"January","Gener"}.
|
||||
{"JID normalization denied by service policy","S'ha denegat la normalització del JID per política del servei"}.
|
||||
@@ -375,6 +370,7 @@
|
||||
{"Only members may query archives of this room","Només membres poden consultar l'arxiu de missatges d'aquesta sala"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","Només els moderadors i participants poden canviar el tema d'aquesta sala"}.
|
||||
{"Only moderators are allowed to change the subject in this room","Només els moderadors poden canviar el tema d'aquesta sala"}.
|
||||
{"Only moderators are allowed to retract messages","Només els moderadors tenen permís per a retractar missatges"}.
|
||||
{"Only moderators can approve voice requests","Només els moderadors poden aprovar les peticions de veu"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Sols els ocupants poden enviar missatges a la sala"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Sols els ocupants poden enviar sol·licituds a la sala"}.
|
||||
@@ -398,6 +394,7 @@
|
||||
{"Password:","Contrasenya:"}.
|
||||
{"Path to Dir","Ruta al directori"}.
|
||||
{"Path to File","Ruta al fitxer"}.
|
||||
{"Payload semantic type information","Informació sobre el tipus semàntic de la carrega útil"}.
|
||||
{"Pending","Pendent"}.
|
||||
{"Period: ","Període: "}.
|
||||
{"Persist items to storage","Persistir elements al guardar"}.
|
||||
@@ -496,6 +493,7 @@
|
||||
{"Specify the access model","Especificar el model d'accés"}.
|
||||
{"Specify the event message type","Especifica el tipus de missatge d'event"}.
|
||||
{"Specify the publisher model","Especificar el model del publicant"}.
|
||||
{"Stanza id is not valid","L'identificador del paquet no es vàlid"}.
|
||||
{"Stanza ID","ID del paquet"}.
|
||||
{"Statically specify a replyto of the node owner(s)","Especifica estaticament una adreça on respondre al propietari del node"}.
|
||||
{"Statistics of ~p","Estadístiques de ~p"}.
|
||||
@@ -560,6 +558,7 @@
|
||||
{"The query is only allowed from local users","La petició està permesa només d'usuaris locals"}.
|
||||
{"The query must not contain <item/> elements","La petició no pot contenir elements <item/>"}.
|
||||
{"The room subject can be modified by participants","El tema de la sala pot modificar-lo els participants"}.
|
||||
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","La informació semàntica de les dades al node, usualment especificat pel espai de noms de la càrrega util (si n'hi ha)"}.
|
||||
{"The sender of the last received message","Qui ha enviat l'ultim missatge rebut"}.
|
||||
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","El paquet DEU contindre només un element <active/>, un element <default/>, o un element <list/>"}.
|
||||
{"The subscription identifier associated with the subscription request","L'identificador de subscripció associat amb la petició de subscripció"}.
|
||||
@@ -661,6 +660,7 @@
|
||||
{"Whether to allow subscriptions","Permetre subscripcions"}.
|
||||
{"Whether to make all subscriptions temporary, based on subscriber presence","Si fer totes les subscripcions temporals, basat en la presencia del subscriptor"}.
|
||||
{"Whether to notify owners about new subscribers and unsubscribes","Si notificar als propietaris sobre noves subscripcions i desubscripcions"}.
|
||||
{"Who can send private messages","Qui pot enviar missatges privats"}.
|
||||
{"Who may associate leaf nodes with a collection","Qui pot associar nodes fulla amb una col·lecció"}.
|
||||
{"Wrong parameters in the web formulary","Paràmetres incorrectes en el formulari web"}.
|
||||
{"Wrong xmlns","El xmlns ès incorrecte"}.
|
||||
@@ -672,6 +672,7 @@
|
||||
{"XMPP Show Value of XA (Extended Away)","Valor 'show' de XMPP: XA (Molt Ausent)"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","URI XMPP del Node Associat Publish-Subscribe"}.
|
||||
{"You are being removed from the room because of a system shutdown","Has sigut expulsat de la sala perquè el sistema va a apagar-se"}.
|
||||
{"You are not allowed to send private messages","No tens permés enviar missatges privats"}.
|
||||
{"You are not joined to the channel","No t'has unit al canal"}.
|
||||
{"You can later change your password using an XMPP client.","Podràs canviar la teva contrasenya més endavant utilitzant un client XMPP."}.
|
||||
{"You have been banned from this room","Has sigut bloquejat en aquesta sala"}.
|
||||
|
||||
@@ -159,7 +159,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Není povoleno posílat chybové zprávy do místnosti. Účastník (~s) odeslal chybovou zprávu (~s) a byl vyhozen z místnosti"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Není dovoleno odeslání soukromé zprávy typu \"skupinová zpráva\" "}.
|
||||
{"It is not allowed to send private messages to the conference","Není povoleno odesílat soukromé zprávy v této místnosti"}.
|
||||
{"It is not allowed to send private messages","Je zakázáno posílat soukromé zprávy"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January",". ledna"}.
|
||||
{"joins the room","vstoupil(a) do místnosti"}.
|
||||
|
||||
+3
-6
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Eine Webseite"}.
|
||||
{"Accept","Akzeptieren"}.
|
||||
{"Access denied by service policy","Zugriff aufgrund der Dienstrichtlinien verweigert"}.
|
||||
{"Access model of authorize","Zugriffsmodell von 'authorize'"}.
|
||||
{"Access model of open","Zugriffsmodell von 'open'"}.
|
||||
{"Access model of presence","Zugriffsmodell von 'presence'"}.
|
||||
{"Access model of roster","Zugriffsmodell der Kontaktliste"}.
|
||||
{"Access model of whitelist","Zugriffsmodell von 'whitelist'"}.
|
||||
{"Access model","Zugriffsmodell"}.
|
||||
{"Account doesn't exist","Konto existiert nicht"}.
|
||||
{"Action on user","Aktion auf Benutzer"}.
|
||||
@@ -229,11 +224,11 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer (~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum geworfen"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Es ist nicht erlaubt private Nachrichten des Typs \"groupchat\" zu senden"}.
|
||||
{"It is not allowed to send private messages to the conference","Es ist nicht erlaubt private Nachrichten an die Konferenz zu senden"}.
|
||||
{"It is not allowed to send private messages","Es ist nicht erlaubt private Nachrichten zu senden"}.
|
||||
{"Jabber ID","Jabber-ID"}.
|
||||
{"January","Januar"}.
|
||||
{"JID normalization denied by service policy","JID-Normalisierung aufgrund der Dienstrichtlinien verweigert"}.
|
||||
{"JID normalization failed","JID-Normalisierung fehlgeschlagen"}.
|
||||
{"Joined MIX channels of ~ts","Beigetretene MIX-Channels von ~ts"}.
|
||||
{"Joined MIX channels:","Beigetretene MIX-Channels:"}.
|
||||
{"joins the room","betritt den Raum"}.
|
||||
{"July","Juli"}.
|
||||
@@ -373,6 +368,7 @@
|
||||
{"Only members may query archives of this room","Nur Mitglieder dürfen den Verlauf dieses Raumes abrufen"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","Nur Moderatoren und Teilnehmer dürfen das Thema in diesem Raum ändern"}.
|
||||
{"Only moderators are allowed to change the subject in this room","Nur Moderatoren dürfen das Thema in diesem Raum ändern"}.
|
||||
{"Only moderators are allowed to retract messages","Nur Moderatoren dürfen Nachrichten zurückziehen"}.
|
||||
{"Only moderators can approve voice requests","Nur Moderatoren können Sprachrecht-Anforderungen genehmigen"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Nur Teilnehmer dürfen Nachrichten an die Konferenz senden"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Nur Teilnehmer dürfen Anfragen an die Konferenz senden"}.
|
||||
@@ -494,6 +490,7 @@
|
||||
{"Specify the access model","Geben Sie das Zugangsmodell an"}.
|
||||
{"Specify the event message type","Geben Sie den Ereignisnachrichtentyp an"}.
|
||||
{"Specify the publisher model","Geben Sie das Veröffentlichermodell an"}.
|
||||
{"Stanza id is not valid","Stanza-ID ist ungültig"}.
|
||||
{"Stanza ID","Stanza-ID"}.
|
||||
{"Statically specify a replyto of the node owner(s)","Ein 'replyto' des/der Nodebesitzer(s) statisch angeben"}.
|
||||
{"Statistics of ~p","Statistiken von ~p"}.
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Μία ιστοσελίδα"}.
|
||||
{"Accept","Αποδοχή"}.
|
||||
{"Access denied by service policy","Άρνηση πρόσβασης, λόγω τακτικής παροχής υπηρεσιών"}.
|
||||
{"Access model of authorize","Μοντέλο πρόσβασης της πιστοποίησης"}.
|
||||
{"Access model of open","Μοντέλο πρόσβασης του ανοικτού"}.
|
||||
{"Access model of presence","Μοντέλο πρόσβασης της παρουσίας"}.
|
||||
{"Access model of roster","Μοντέλο πρόσβασης της Λίστας Επαφών"}.
|
||||
{"Access model of whitelist","Μοντέλο πρόσβασης της Λευκής Λίστας"}.
|
||||
{"Access model","Καθορίστε το μοντέλο πρόσβασης"}.
|
||||
{"Account doesn't exist","Ο λογαριασμός δεν υπάρχει"}.
|
||||
{"Action on user","Eνέργεια για το χρήστη"}.
|
||||
@@ -219,7 +214,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Δεν επιτρέπεται η αποστολή μηνυμάτων σφάλματος στο δωμάτιο. Ο συμμετέχων (~s) έχει στείλει ένα μήνυμα σφάλματος (~s) και έχει πεταχτεί έξω από την αίθουσα"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Δεν επιτρέπεται η αποστολή προσωπικών μηνυμάτων του τύπου \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Δεν επιτρέπεται να στείλει προσωπικά μηνύματα για τη διάσκεψη"}.
|
||||
{"It is not allowed to send private messages","Δεν επιτρέπεται η αποστολή προσωπικών μηνυμάτων"}.
|
||||
{"Jabber ID","Ταυτότητα Jabber"}.
|
||||
{"January","Ιανουάριος"}.
|
||||
{"JID normalization denied by service policy","Απετράπη η κανονικοποίηση του JID, λόγω της τακτικής Παροχής Υπηρεσιών"}.
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
{"A Web Page","Retpaĝo"}.
|
||||
{"Accept","Akcepti"}.
|
||||
{"Access denied by service policy","Atingo rifuzita de serv-politiko"}.
|
||||
{"Access model of open","Atingomodelo de malfermo"}.
|
||||
{"Access model of presence","Atingomodelo de ĉeesto"}.
|
||||
{"Access model of whitelist","Atingomodelo de permesolisto"}.
|
||||
{"Access model","Atingomodelo"}.
|
||||
{"Account doesn't exist","Konto ne ekzistas"}.
|
||||
{"Action on user","Ago je uzanto"}.
|
||||
@@ -163,7 +160,6 @@
|
||||
{"is now known as","nun nomiĝas"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Malpermesas sendi mesaĝojn de tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Nur partoprenantoj rajtas sendi privatajn mesaĝojn al la babilejo"}.
|
||||
{"It is not allowed to send private messages","Ne estas permesata sendi privatajn mesaĝojn"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Januaro"}.
|
||||
{"joins the room","eniras la babilejo"}.
|
||||
|
||||
+8
-7
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Una página web"}.
|
||||
{"Accept","Aceptar"}.
|
||||
{"Access denied by service policy","Acceso denegado por la política del servicio"}.
|
||||
{"Access model of authorize","Modelo de acceso de Autorizar"}.
|
||||
{"Access model of open","Modelo de acceso de Abierto"}.
|
||||
{"Access model of presence","Modelo de acceso de Presencia"}.
|
||||
{"Access model of roster","Modelo de acceso de Roster"}.
|
||||
{"Access model of whitelist","Modelo de acceso de Lista Blanca"}.
|
||||
{"Access model","Modelo de Acceso"}.
|
||||
{"Account doesn't exist","La cuenta no existe"}.
|
||||
{"Action on user","Acción en el usuario"}.
|
||||
@@ -53,6 +48,7 @@
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Cualquiera con una suscripción a la presencia de 'ambos' o 'de' puede suscribirse y recibir elementos"}.
|
||||
{"Anyone with Voice","Cualquiera con Voz"}.
|
||||
{"Anyone","Cualquiera"}.
|
||||
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentemente tu cuenta no tiene permisos de administración en este servidor. Por favor consulta cómo concederle privilegios de administrador en: https://docs.ejabberd.im/admin/installation/#administration-account"}.
|
||||
{"April","Abril"}.
|
||||
{"Attribute 'channel' is required for this request","El atributo 'channel' es necesario para esta petición"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","El atributo 'id' es necesario para mensajes MIX"}.
|
||||
@@ -230,7 +226,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No está permitido enviar mensajes de error a la sala. Este participante (~s) ha enviado un mensaje de error (~s) y fue expulsado de la sala"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","No está permitido enviar mensajes privados del tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Impedir el envio de mensajes privados a la sala"}.
|
||||
{"It is not allowed to send private messages","No está permitido enviar mensajes privados"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Enero"}.
|
||||
{"JID normalization denied by service policy","Se ha denegado la normalización del JID por política del servicio"}.
|
||||
@@ -290,7 +285,7 @@
|
||||
{"Modified modules","Módulos modificados"}.
|
||||
{"Module failed to handle the query","El módulo falló al gestionar la petición"}.
|
||||
{"Monday","Lunes"}.
|
||||
{"Multicast","Multicast"}.
|
||||
{"Multicast","Multidifusión"}.
|
||||
{"Multiple <item/> elements are not allowed by RFC6121","No se permiten múltiples elementos <item/> en RFC6121"}.
|
||||
{"Multi-User Chat","Salas de Charla"}.
|
||||
{"Name in the rosters where this group will be displayed","Nombre del grupo con que aparecerá en las listas de contactos"}.
|
||||
@@ -375,6 +370,7 @@
|
||||
{"Only members may query archives of this room","Solo miembros pueden consultar el archivo de mensajes de la sala"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","Solo los moderadores y participantes pueden cambiar el asunto de esta sala"}.
|
||||
{"Only moderators are allowed to change the subject in this room","Solo los moderadores pueden cambiar el asunto de esta sala"}.
|
||||
{"Only moderators are allowed to retract messages","Solo los moderadores pueden retractarse de los mensajes"}.
|
||||
{"Only moderators can approve voice requests","Solo los moderadores pueden aprobar peticiones de voz"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Solo los ocupantes pueden enviar mensajes a la sala"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Solo los ocupantes pueden enviar solicitudes a la sala"}.
|
||||
@@ -398,6 +394,7 @@
|
||||
{"Password:","Contraseña:"}.
|
||||
{"Path to Dir","Ruta al directorio"}.
|
||||
{"Path to File","Ruta al fichero"}.
|
||||
{"Payload semantic type information","Información sobre el tipo semántico de la carga útil"}.
|
||||
{"Pending","Pendiente"}.
|
||||
{"Period: ","Periodo: "}.
|
||||
{"Persist items to storage","Persistir elementos al almacenar"}.
|
||||
@@ -496,6 +493,7 @@
|
||||
{"Specify the access model","Especifica el modelo de acceso"}.
|
||||
{"Specify the event message type","Especifica el tipo del mensaje de evento"}.
|
||||
{"Specify the publisher model","Especificar el modelo del publicante"}.
|
||||
{"Stanza id is not valid","El identificador de la estrofa no es válido"}.
|
||||
{"Stanza ID","ID del paquete"}.
|
||||
{"Statically specify a replyto of the node owner(s)","Especificar de forma estática un 'replyto' de dueño(s) del nodo"}.
|
||||
{"Statistics of ~p","Estadísticas de ~p"}.
|
||||
@@ -560,6 +558,7 @@
|
||||
{"The query is only allowed from local users","La solicitud está permitida solo para usuarios locales"}.
|
||||
{"The query must not contain <item/> elements","La solicitud no debe contener elementos <item/>"}.
|
||||
{"The room subject can be modified by participants","El asunto de la sala puede ser modificado por los participantes"}.
|
||||
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","La información semántica de los datos del nodo, normalmente es especificada por el espacio de los nombres de la carga útil (si existe)"}.
|
||||
{"The sender of the last received message","El emisor del último mensaje recibido"}.
|
||||
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","El paquete DEBE contener solo un elemento <active/>, un elemento <default/>, o un elemento <list/>"}.
|
||||
{"The subscription identifier associated with the subscription request","El identificador de suscripción asociado con la petición de suscripción"}.
|
||||
@@ -661,6 +660,7 @@
|
||||
{"Whether to allow subscriptions","Permitir subscripciones"}.
|
||||
{"Whether to make all subscriptions temporary, based on subscriber presence","Si hacer que todas las suscripciones sean temporales, basado en la presencia del suscriptor"}.
|
||||
{"Whether to notify owners about new subscribers and unsubscribes","Si notificar a los dueños sobre nuevas suscripciones y desuscripciones"}.
|
||||
{"Who can send private messages","Quién puede enviar mensajes privados"}.
|
||||
{"Who may associate leaf nodes with a collection","Quien puede asociar nodos hoja con una colección"}.
|
||||
{"Wrong parameters in the web formulary","Parámetros incorrectos en el formulario web"}.
|
||||
{"Wrong xmlns","XMLNS incorrecto"}.
|
||||
@@ -672,6 +672,7 @@
|
||||
{"XMPP Show Value of XA (Extended Away)","Valor 'Show' de XMPP: XA (Ausente Extendido)"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","URI XMPP del Nodo Asociado de Publicar-Subscribir"}.
|
||||
{"You are being removed from the room because of a system shutdown","Estás siendo expulsado de la sala porque el sistema se va a detener"}.
|
||||
{"You are not allowed to send private messages","No tienes permitido enviar mensajes privados"}.
|
||||
{"You are not joined to the channel","No has entrado en el canal"}.
|
||||
{"You can later change your password using an XMPP client.","Puedes cambiar tu contraseña después, usando un cliente XMPP."}.
|
||||
{"You have been banned from this room","Has sido bloqueado en esta sala"}.
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Une page Web"}.
|
||||
{"Accept","Accepter"}.
|
||||
{"Access denied by service policy","L'accès au service est refusé"}.
|
||||
{"Access model of authorize","Modèle d’accès de « autoriser »"}.
|
||||
{"Access model of open","Modèle d’accès de « ouvrir »"}.
|
||||
{"Access model of presence","Modèle d’accès de « présence »"}.
|
||||
{"Access model of roster","Modèle d’accès de « liste »"}.
|
||||
{"Access model of whitelist","Modèle d’accès de « liste blanche »"}.
|
||||
{"Access model","Modèle d’accès"}.
|
||||
{"Account doesn't exist","Le compte n’existe pas"}.
|
||||
{"Action on user","Action sur l'utilisateur"}.
|
||||
@@ -220,7 +215,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","L'envoyer de messages d'erreur au salon n'est pas autorisé. Le participant (~s) à envoyé un message d'erreur (~s) et à été expulsé du salon"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Il n'est pas permis d'envoyer des messages privés de type \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Il n'est pas permis d'envoyer des messages privés à la conférence"}.
|
||||
{"It is not allowed to send private messages","L'envoi de messages privés n'est pas autorisé"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Janvier"}.
|
||||
{"joins the room","rejoint le salon"}.
|
||||
|
||||
@@ -159,7 +159,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Non está permitido enviar mensaxes de erro á sala. Este participante (~s) enviou unha mensaxe de erro (~s) e foi expulsado da sala"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Non está permitido enviar mensaxes privadas do tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Impedir o envio de mensaxes privadas á sala"}.
|
||||
{"It is not allowed to send private messages","Non está permitido enviar mensaxes privadas"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Xaneiro"}.
|
||||
{"joins the room","entra na sala"}.
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","אין זה מותר לשלוח הודעות שגיאה לחדר. משתתף זה (~s) שלח הודעת שגיאה (~s) ונבעט מתוך החדר"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","אין זה מותר לשלוח הודעות פרטיות מן טיפוס \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","אין זה מותר לשלוח הודעות פרטיות לועידה"}.
|
||||
{"It is not allowed to send private messages","אין זה מותר לשלוח הודעות פרטיות"}.
|
||||
{"Jabber ID","מזהה Jabber"}.
|
||||
{"January","ינואר"}.
|
||||
{"joins the room","נכנס/ת אל החדר"}.
|
||||
|
||||
@@ -170,7 +170,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Nem engedélyezett hibaüzeneteket küldeni a szobába. A résztvevő (~s) hibaüzenetet (~s) küldött, és ki lett rúgva a szobából"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Nem engedélyezett „groupchat” típusú személyes üzeneteket küldeni"}.
|
||||
{"It is not allowed to send private messages to the conference","Nem engedélyezett személyes üzeneteket küldeni a konferenciába"}.
|
||||
{"It is not allowed to send private messages","Nem engedélyezett személyes üzeneteket küldeni"}.
|
||||
{"Jabber ID","Jabber-azonosító"}.
|
||||
{"January","január"}.
|
||||
{"JID normalization denied by service policy","A Jabber-azonosító normalizálása megtagadva a szolgáltatási irányelv miatt"}.
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Halaman web"}.
|
||||
{"Accept","Diterima"}.
|
||||
{"Access denied by service policy","Akses ditolak oleh kebijakan layanan"}.
|
||||
{"Access model of authorize","Model akses otorisasi"}.
|
||||
{"Access model of open","Model akses terbuka"}.
|
||||
{"Access model of presence","Model akses kehadiran"}.
|
||||
{"Access model of roster","model akses daftar kontak"}.
|
||||
{"Access model of whitelist","Model akses daftar putih"}.
|
||||
{"Access model","Model akses"}.
|
||||
{"Account doesn't exist","Akun tidak ada"}.
|
||||
{"Action on user","Tindakan pada pengguna"}.
|
||||
@@ -207,7 +202,6 @@
|
||||
{"is now known as","sekarang dikenal sebagai"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi jenis \"groupchat \""}.
|
||||
{"It is not allowed to send private messages to the conference","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi ke konferensi"}.
|
||||
{"It is not allowed to send private messages","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Januari"}.
|
||||
{"joins the room","bergabung ke ruangan"}.
|
||||
|
||||
@@ -125,7 +125,6 @@
|
||||
{"is now known as","è ora conosciuta/o come"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Non è consentito l'invio di messaggi privati di tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Non è consentito l'invio di messaggi privati alla conferenza"}.
|
||||
{"It is not allowed to send private messages","Non è consentito l'invio di messaggi privati"}.
|
||||
{"Jabber ID","Jabber ID (Jabber ID)"}.
|
||||
{"January","Gennaio"}.
|
||||
{"joins the room","entra nella stanza"}.
|
||||
|
||||
@@ -166,7 +166,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","このルームにエラーメッセージを送ることは許可されていません。参加者(~s)はエラーメッセージを(~s)を送信してルームからキックされました。"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","種別が\"groupchat\" であるプライベートメッセージを送信することはできません"}.
|
||||
{"It is not allowed to send private messages to the conference","この会議にプライベートメッセージを送信することはできません"}.
|
||||
{"It is not allowed to send private messages","プライベートメッセージを送信することはできません"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","1月"}.
|
||||
{"joins the room","がチャットルームに参加しました"}.
|
||||
|
||||
@@ -129,7 +129,6 @@
|
||||
{"is now known as","heet nu"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Er mogen geen privéberichten van het type \"groupchat\" worden verzonden"}.
|
||||
{"It is not allowed to send private messages to the conference","Er mogen geen privéberichten naar de chatruimte worden verzonden"}.
|
||||
{"It is not allowed to send private messages","Het is niet toegestaan priveberichten te sturen"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Januari"}.
|
||||
{"joins the room","betrad de chatruimte"}.
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
{"A password is required to enter this room","Et passord kreves for tilgang til samtalerommet"}.
|
||||
{"Accept","Godta"}.
|
||||
{"Access denied by service policy","Tilgang nektes på grunn av en tjenesteregel"}.
|
||||
{"Access model of presence","Tilgangsmodell for tilstedeværelse"}.
|
||||
{"Access model of roster","Tilgangsmodell for kontaktliste"}.
|
||||
{"Action on user","Handling på bruker"}.
|
||||
{"Add Jabber ID","Legg til Jabber-ID"}.
|
||||
{"Add New","Legg til ny"}.
|
||||
@@ -141,7 +139,6 @@
|
||||
{"IP addresses","IP-adresser"}.
|
||||
{"is now known as","er nå kjent som"}.
|
||||
{"It is not allowed to send private messages to the conference","Det er ikke tillatt å sende private meldinger til konferansen"}.
|
||||
{"It is not allowed to send private messages","Det er ikke tillatt å sende private meldinger"}.
|
||||
{"Jabber ID","Jabber-ID"}.
|
||||
{"January","januar"}.
|
||||
{"JID normalization failed","JID-normalisering mislyktes"}.
|
||||
|
||||
@@ -159,7 +159,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Użytkownik nie może wysyłać wiadomości o błędach do pokoju. Użytkownik (~s) wysłał błąd (~s) i został wyrzucony z pokoju"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Nie można wysyłać prywatnych wiadomości typu \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Nie wolno wysyłac prywatnych wiadomości na konferencję"}.
|
||||
{"It is not allowed to send private messages","Wysyłanie prywatnych wiadomości jest zabronione"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Styczeń"}.
|
||||
{"joins the room","dołącza do pokoju"}.
|
||||
|
||||
+11
-10
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Uma página da web"}.
|
||||
{"Accept","Aceito"}.
|
||||
{"Access denied by service policy","Acesso negado pela política do serviço"}.
|
||||
{"Access model of authorize","Modelo de acesso da autorização"}.
|
||||
{"Access model of open","Modelo para acesso aberto"}.
|
||||
{"Access model of presence","Modelo para acesso presença"}.
|
||||
{"Access model of roster","Modelo para acesso lista"}.
|
||||
{"Access model of whitelist","Modelo de acesso da lista branca"}.
|
||||
{"Access model","Modelo de acesso"}.
|
||||
{"Account doesn't exist","A conta não existe"}.
|
||||
{"Action on user","Ação no usuário"}.
|
||||
@@ -53,6 +48,7 @@
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Qualquer pessoa com uma assinatura presente dos dois ou de ambos pode se inscrever e recuperar os itens"}.
|
||||
{"Anyone with Voice","Qualquer pessoa com voz"}.
|
||||
{"Anyone","Qualquer pessoa"}.
|
||||
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentemente, a sua conta não tem direitos de administração neste servidor. Verifique como conceder os direitos administrativos em: https://docs.ejabberd.im/admin/installation/#administration-account"}.
|
||||
{"April","Abril"}.
|
||||
{"Attribute 'channel' is required for this request","O atributo 'canal' é necessário para esta solicitação"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","O atributo 'id' é obrigatório para mensagens MIX"}.
|
||||
@@ -182,7 +178,7 @@
|
||||
{"Get User Last Login Time","Obter a Data do Último Login"}.
|
||||
{"Get User Password","Obter Senha do Usuário"}.
|
||||
{"Get User Statistics","Obter Estatísticas do Usuário"}.
|
||||
{"Given Name","Sobrenome"}.
|
||||
{"Given Name","Prenome"}.
|
||||
{"Grant voice to this person?","Dar voz a esta pessoa?"}.
|
||||
{"Group","Grupo"}.
|
||||
{"Groups that will be displayed to the members","Os grupos que serão exibidos para os membros"}.
|
||||
@@ -230,7 +226,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro para a sala. O membro (~s) enviou uma mensagem de erro (~s) e foi expulso da sala"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Não é permitido enviar mensagens privadas para a sala de conferência"}.
|
||||
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
|
||||
{"Jabber ID","ID Jabber"}.
|
||||
{"January","Janeiro"}.
|
||||
{"JID normalization denied by service policy","Normalização JID negada por causa da política de serviços"}.
|
||||
@@ -250,7 +245,7 @@
|
||||
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Bits menos significativos do hash sha-256 do texto devem ser iguais ao rótulo hexadecimal"}.
|
||||
{"leaves the room","Sair da sala"}.
|
||||
{"List of rooms","Lista de salas"}.
|
||||
{"List of users with hats","Lista os usuários com chapéus"}.
|
||||
{"List of users with hats","Lista dos usuários com chapéus"}.
|
||||
{"List users with hats","Lista os usuários com chapéus"}.
|
||||
{"Logging","Registrando no log"}.
|
||||
{"Low level update script","Script de atualização low level"}.
|
||||
@@ -351,7 +346,7 @@
|
||||
{"Notify subscribers when the node is deleted","Notificar assinantes quando o nó for eliminado se elimine"}.
|
||||
{"November","Novembro"}.
|
||||
{"Number of answers required","Quantidade de respostas necessárias"}.
|
||||
{"Number of occupants","Número de participantes"}.
|
||||
{"Number of occupants","Quantidade de ocupantes"}.
|
||||
{"Number of Offline Messages","Quantidade das mensagens offline"}.
|
||||
{"Number of online users","Número de usuários online"}.
|
||||
{"Number of registered users","Número de usuários registrados"}.
|
||||
@@ -375,6 +370,7 @@
|
||||
{"Only members may query archives of this room","Somente os membros podem procurar nos arquivos desta sala"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","Somente os moderadores e os participamentes podem alterar o assunto desta sala"}.
|
||||
{"Only moderators are allowed to change the subject in this room","Somente os moderadores podem alterar o assunto desta sala"}.
|
||||
{"Only moderators are allowed to retract messages","Apenas moderadores estão autorizados a retirar mensagens"}.
|
||||
{"Only moderators can approve voice requests","Somente moderadores podem aprovar requisições de voz"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Somente os ocupantes podem enviar mensagens à sala de conferência"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Somente os ocupantes podem enviar consultas à sala de conferência"}.
|
||||
@@ -398,6 +394,7 @@
|
||||
{"Password:","Senha:"}.
|
||||
{"Path to Dir","Caminho para o diretório"}.
|
||||
{"Path to File","Caminho do arquivo"}.
|
||||
{"Payload semantic type information","Informações de tipo semântico de carga útil"}.
|
||||
{"Pending","Pendente"}.
|
||||
{"Period: ","Período: "}.
|
||||
{"Persist items to storage","Persistir elementos ao armazenar"}.
|
||||
@@ -458,7 +455,7 @@
|
||||
{"Room Configuration","Configuração de salas"}.
|
||||
{"Room creation is denied by service policy","Sala não pode ser criada devido à política do serviço"}.
|
||||
{"Room description","Descrição da Sala"}.
|
||||
{"Room Occupants","Número de participantes"}.
|
||||
{"Room Occupants","Ocupantes do quarto"}.
|
||||
{"Room terminates","Terminação da sala"}.
|
||||
{"Room title","Título da sala"}.
|
||||
{"Roster groups allowed to subscribe","Listar grupos autorizados"}.
|
||||
@@ -496,6 +493,7 @@
|
||||
{"Specify the access model","Especificar os modelos de acesso"}.
|
||||
{"Specify the event message type","Especificar o tipo de mensagem para o evento"}.
|
||||
{"Specify the publisher model","Especificar o modelo do publicante"}.
|
||||
{"Stanza id is not valid","A Stanza ID não é válido"}.
|
||||
{"Stanza ID","ID da estrofe"}.
|
||||
{"Statically specify a replyto of the node owner(s)","Defina uma resposta fixa do(s) proprietário(s) do nó"}.
|
||||
{"Statistics of ~p","Estatísticas de ~p"}.
|
||||
@@ -560,6 +558,7 @@
|
||||
{"The query is only allowed from local users","Esta consulta só é permitida a partir de usuários locais"}.
|
||||
{"The query must not contain <item/> elements","A consulta não pode conter elementos <item/>"}.
|
||||
{"The room subject can be modified by participants","O tema da sala pode ser alterada pelos próprios participantes"}.
|
||||
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","Informações de tipo semântico dos dados no nó, geralmente especificadas pelo espaço de nomes da carga útil (se houver)"}.
|
||||
{"The sender of the last received message","O remetente da última mensagem que foi recebida"}.
|
||||
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","A instância DEVE conter apenas um elemento <active/>, um elemento <default/>, ou um elemento <list/>"}.
|
||||
{"The subscription identifier associated with the subscription request","O identificador da assinatura associado à solicitação da assinatura"}.
|
||||
@@ -661,6 +660,7 @@
|
||||
{"Whether to allow subscriptions","Permitir subscrições"}.
|
||||
{"Whether to make all subscriptions temporary, based on subscriber presence","Caso todas as assinaturas devam ser temporárias, com base na presença do assinante"}.
|
||||
{"Whether to notify owners about new subscribers and unsubscribes","Caso deva notificar os proprietários sobre os novos assinantes e aqueles que cancelaram a assinatura"}.
|
||||
{"Who can send private messages","Quem pode enviar mensagens privadas"}.
|
||||
{"Who may associate leaf nodes with a collection","Quem pode associar as folhas dos nós em uma coleção"}.
|
||||
{"Wrong parameters in the web formulary","O formulário web está com os parâmetros errados"}.
|
||||
{"Wrong xmlns","Xmlns errado"}.
|
||||
@@ -672,6 +672,7 @@
|
||||
{"XMPP Show Value of XA (Extended Away)","XMPP Exiba o valor do XA (Ausência Estendida)"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","XMPP URI da publicação do nó associado da assinatura"}.
|
||||
{"You are being removed from the room because of a system shutdown","Você está sendo removido da sala por causa do desligamento do sistema"}.
|
||||
{"You are not allowed to send private messages","Você não tem permissão para enviar mensagens privadas"}.
|
||||
{"You are not joined to the channel","Você não está inscrito no canal"}.
|
||||
{"You can later change your password using an XMPP client.","Você pode alterar a sua senha mais tarde usando um cliente XMPP."}.
|
||||
{"You have been banned from this room","Você foi banido desta sala"}.
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","Uma página da web"}.
|
||||
{"Accept","Aceito"}.
|
||||
{"Access denied by service policy","Acesso negado pela política de serviço"}.
|
||||
{"Access model of authorize","Modelo de acesso da autorização"}.
|
||||
{"Access model of open","Modelo para acesso aberto"}.
|
||||
{"Access model of presence","Modelo para acesso presença"}.
|
||||
{"Access model of roster","Modelo para acesso lista"}.
|
||||
{"Access model of whitelist","Modelo de acesso da lista branca"}.
|
||||
{"Access model","Modelo de acesso"}.
|
||||
{"Account doesn't exist","A conta não existe"}.
|
||||
{"Action on user","Acção no utilizador"}.
|
||||
@@ -230,7 +225,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro para a sala. O membro (~s) enviou uma mensagem de erro (~s) e foi expulso da sala"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Impedir o envio de mensagens privadas para a sala"}.
|
||||
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
|
||||
{"Jabber ID","ID Jabber"}.
|
||||
{"January","Janeiro"}.
|
||||
{"JID normalization denied by service policy","Normalização JID negada por causa da política de serviços"}.
|
||||
|
||||
@@ -180,7 +180,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Запрещено посылать сообщения об ошибках в эту комнату. Участник (~s) послал сообщение об ошибке (~s) и был выкинут из комнаты"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Нельзя посылать частные сообщения типа \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Не разрешается посылать частные сообщения прямо в конференцию"}.
|
||||
{"It is not allowed to send private messages","Запрещено посылать приватные сообщения"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","января"}.
|
||||
{"joins the room","вошёл(а) в комнату"}.
|
||||
|
||||
@@ -124,7 +124,6 @@
|
||||
{"is now known as","sa premenoval(a) na"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Nie je dovolené odoslanie súkromnej správy typu \"Skupinová správa\" "}.
|
||||
{"It is not allowed to send private messages to the conference","Nie je povolené odosielať súkromné správy do konferencie"}.
|
||||
{"It is not allowed to send private messages","Nieje povolené posielať súkromné správy"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Január"}.
|
||||
{"joins the room","vstúpil(a) do miestnosti"}.
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
{"IP addresses","Adresa IP"}.
|
||||
{"is now known as","tani njihet si"}.
|
||||
{"It is not allowed to send private messages to the conference","Nuk lejohet të dërgohen mesazhe private te konferenca"}.
|
||||
{"It is not allowed to send private messages","Nuk lejohet të dërgohen mesazhe private"}.
|
||||
{"Jabber ID","ID Jabber"}.
|
||||
{"January","Janar"}.
|
||||
{"JID normalization failed","Normalizimi JID dështoi"}.
|
||||
|
||||
@@ -110,7 +110,6 @@
|
||||
{"is now known as","är känd som"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Det är inte tillåtet att skicka privata medelanden med typen \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Det är inte tillåtet att skicka privata medelanden till den här konferensen"}.
|
||||
{"It is not allowed to send private messages","Det ar inte tillåtet att skicka privata meddelanden"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Januari"}.
|
||||
{"joins the room","joinar rummet"}.
|
||||
|
||||
@@ -123,7 +123,6 @@
|
||||
{"is now known as","isim değiştirdi :"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","\"groupchat\" tipinde özel mesajlar gönderilmesine izin verilmiyor"}.
|
||||
{"It is not allowed to send private messages to the conference","Konferansa özel mesajlar gönderilmesine izin verilmiyor"}.
|
||||
{"It is not allowed to send private messages","Özel mesaj gönderilmesine izin verilmiyor"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","Ocak"}.
|
||||
{"joins the room","odaya katıldı"}.
|
||||
|
||||
+38
-33
@@ -34,75 +34,81 @@
|
||||
{"Allow visitors to send private messages to","Дозволити відвідувачам відсилати приватні повідомлення"}.
|
||||
{"Allow visitors to send status text in presence updates","Дозволити відвідувачам відсилати текст статусу в оновленнях присутності"}.
|
||||
{"Allow visitors to send voice requests","Дозволити відвідувачам надсилати голосові запрошення"}.
|
||||
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Асоційована група LDAP, яка визначає членство в кімнаті; це повинно бути відмінне ім'я LDAP відповідно до специфічного для реалізації або специфічного для розгортання визначення групи."}.
|
||||
{"Announcements","Сповіщення"}.
|
||||
{"Answer associated with a picture","Відповідь, пов’язана зі зображенням"}.
|
||||
{"Answer associated with a video","Відповідь, пов'язана з відео"}.
|
||||
{"Answer associated with speech","Відповідь, пов'язана з мовленням"}.
|
||||
{"Answer to a question","Відповідь на запитання"}.
|
||||
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Будь-хто в зазначеному списку груп(и) може підписатися та отримати елементи"}.
|
||||
{"Anyone may associate leaf nodes with the collection","Будь-хто може зв'язати вузли листів з колекцією"}.
|
||||
{"Anyone may publish","Будь-хто може опублікувати"}.
|
||||
{"Anyone may subscribe and retrieve items","Будь-хто може підписатися та отримати елементи"}.
|
||||
{"Anyone with Voice","Усі, хто має голос"}.
|
||||
{"April","квітня"}.
|
||||
{"Attribute 'channel' is required for this request","Для цього запиту потрібен атрибут \"канал\""}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","Для MIX повідомлень потрібен атрибут \"id\""}.
|
||||
{"Attribute 'jid' is not allowed here","Атрибут 'jid' тут заборонений"}.
|
||||
{"Attribute 'node' is not allowed here","Атрибут \"вузол\" тут заборонений"}.
|
||||
{"August","серпня"}.
|
||||
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Будь-хто в зазначеній групі (групах) реєстру може підписатися та отримувати матеріали"}.
|
||||
{"Anyone may associate leaf nodes with the collection","Будь-хто може асоціювати листові вузли з колекцією"}.
|
||||
{"Anyone may publish","Будь-хто може публікувати"}.
|
||||
{"Anyone may subscribe and retrieve items","Будь-хто може підписатися та отримувати публікації"}.
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Будь-хто, хто має підписку на отримання інформації про присутність в обох випадках або може підписуватись та отримувати матеріали"}.
|
||||
{"Anyone with Voice","Будь-хто, хто має голос"}.
|
||||
{"Anyone","Будь-хто"}.
|
||||
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Очевидно, ваш обліковий запис не має прав адміністратора на цьому сервері. Будь ласка, перевірте, як отримати права адміністратора за посиланням: https://docs.ejabberd.im/admin/installation/#administration-account"}.
|
||||
{"April","Квітень"}.
|
||||
{"Attribute 'channel' is required for this request","Атрибут \"канал\" є обов'язковим для цього запиту"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","Атрибут 'id' обов'язковий для MIX повідомлень"}.
|
||||
{"Attribute 'jid' is not allowed here","Атрибут 'jid' заборонений"}.
|
||||
{"Attribute 'node' is not allowed here","Атрибут \"вузол\" заборонений"}.
|
||||
{"Attribute 'to' of stanza that triggered challenge","Атрибут \"до\" рядка, який спровокував виклик"}.
|
||||
{"August","Серпень"}.
|
||||
{"Automatic node creation is not enabled","Автоматичне створення вузлів не ввімкнено"}.
|
||||
{"Backup Management","Керування резервним копіюванням"}.
|
||||
{"Backup of ~p","Резервне копіювання ~p"}.
|
||||
{"Backup to File at ","Резервне копіювання в файл на "}.
|
||||
{"Backup","Резервне копіювання"}.
|
||||
{"Bad format","Неправильний формат"}.
|
||||
{"Bad format","Невірний формат"}.
|
||||
{"Birthday","День народження"}.
|
||||
{"Both the username and the resource are required","Потрібне ім'я користувача та ресурс"}.
|
||||
{"Both the username and the resource are required","Обов'язково потрібне ім'я користувача та джерело"}.
|
||||
{"Bytestream already activated","Потік байтів вже активовано"}.
|
||||
{"Cannot remove active list","Неможливо видалити активний список"}.
|
||||
{"Cannot remove default list","Неможливо видалити список за промовчанням"}.
|
||||
{"CAPTCHA web page","Адреса капчі"}.
|
||||
{"Cannot remove default list","Неможливо видалити список за замовчуванням"}.
|
||||
{"CAPTCHA web page","Веб-сторінка CAPTCHA"}.
|
||||
{"Challenge ID","ID виклику"}.
|
||||
{"Change Password","Змінити пароль"}.
|
||||
{"Change User Password","Змінити Пароль Користувача"}.
|
||||
{"Change User Password","Змінити пароль користувача"}.
|
||||
{"Changing password is not allowed","Зміна пароля заборонена"}.
|
||||
{"Changing role/affiliation is not allowed","Зміна ролі/рангу заборонена"}.
|
||||
{"Channel already exists","Канал уже існує"}.
|
||||
{"Channel does not exist","Канал не існує"}.
|
||||
{"Channel already exists","Канал вже існує"}.
|
||||
{"Channel does not exist","Каналу не існує"}.
|
||||
{"Channel JID","Канали JID"}.
|
||||
{"Channels","Канали"}.
|
||||
{"Characters not allowed:","Заборонені символи:"}.
|
||||
{"Chatroom configuration modified","Конфігурація кімнати змінилась"}.
|
||||
{"Chatroom is created","Створено кімнату"}.
|
||||
{"Chatroom is destroyed","Знищено кімнату"}.
|
||||
{"Chatroom is started","Запущено кімнату"}.
|
||||
{"Chatroom is stopped","Зупинено кімнату"}.
|
||||
{"Chatroom configuration modified","Конфігурацію кімнати змінено"}.
|
||||
{"Chatroom is created","Кімнату створено"}.
|
||||
{"Chatroom is destroyed","Кімнату видалено"}.
|
||||
{"Chatroom is started","Кімнату запущено"}.
|
||||
{"Chatroom is stopped","Кімнату зупинено"}.
|
||||
{"Chatrooms","Кімнати"}.
|
||||
{"Choose a username and password to register with this server","Виберіть назву користувача та пароль для реєстрації на цьому сервері"}.
|
||||
{"Choose a username and password to register with this server","Виберіть логін і пароль для реєстрації на цьому сервері"}.
|
||||
{"Choose storage type of tables","Оберіть тип збереження таблиць"}.
|
||||
{"Choose whether to approve this entity's subscription.","Вирішіть, чи задовольнити запит цього об'єкту на підписку."}.
|
||||
{"Choose whether to approve this entity's subscription.","Виберіть, чи підтверджувати підписку."}.
|
||||
{"City","Місто"}.
|
||||
{"Client acknowledged more stanzas than sent by server","Клієнт підтвердив більше повідомлень, ніж було відправлено сервером"}.
|
||||
{"Commands","Команди"}.
|
||||
{"Conference room does not exist","Конференція не існує"}.
|
||||
{"Conference room does not exist","Кімната для переговорів відсутня"}.
|
||||
{"Configuration of room ~s","Конфігурація кімнати ~s"}.
|
||||
{"Configuration","Конфігурація"}.
|
||||
{"Connected Resources:","Підключені ресурси:"}.
|
||||
{"Contact Addresses (normally, room owner or owners)","Контактні адреси (зазвичай, власника або власників кімнати)"}.
|
||||
{"Country","Країна"}.
|
||||
{"CPU Time:","Процесорний час:"}.
|
||||
{"CPU Time:","Час роботи процесора:"}.
|
||||
{"Current Discussion Topic","Поточна тема обговорення"}.
|
||||
{"Database failure","Помилка база даних"}.
|
||||
{"Database failure","Збій бази даних"}.
|
||||
{"Database Tables at ~p","Таблиці бази даних на ~p"}.
|
||||
{"Database Tables Configuration at ","Конфігурація таблиць бази даних на "}.
|
||||
{"Database","База даних"}.
|
||||
{"December","грудня"}.
|
||||
{"Default users as participants","Зробити користувачів учасниками за замовчуванням"}.
|
||||
{"December","Грудень"}.
|
||||
{"Default users as participants","Користувачі за замовчуванням як учасники"}.
|
||||
{"Delete content","Видалити вміст"}.
|
||||
{"Delete message of the day on all hosts","Видалити повідомлення дня на усіх хостах"}.
|
||||
{"Delete message of the day","Видалити повідомлення дня"}.
|
||||
{"Delete Selected","Видалити виділені"}.
|
||||
{"Delete Selected","Видалити вибране"}.
|
||||
{"Delete table","Видалити таблицю"}.
|
||||
{"Delete User","Видалити Користувача"}.
|
||||
{"Delete User","Видалити користувача"}.
|
||||
{"Deliver event notifications","Доставляти сповіщення про події"}.
|
||||
{"Deliver payloads with event notifications","Доставляти разом з повідомленнями про публікації самі публікації"}.
|
||||
{"Description:","Опис:"}.
|
||||
@@ -207,7 +213,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не дозволяється відправляти помилкові повідомлення в кімнату. Учасник (~s) відправив помилкове повідомлення (~s), та був виганий з кімнати"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Не дозволяється надсилати приватні повідомлення типу \"groupchat\""}.
|
||||
{"It is not allowed to send private messages to the conference","Не дозволяється надсилати приватні повідомлення в конференцію"}.
|
||||
{"It is not allowed to send private messages","Приватні повідомлення не дозволені"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","січня"}.
|
||||
{"JID normalization failed","Помилка нормалізації JID"}.
|
||||
|
||||
@@ -132,7 +132,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","On n' pout nén evoyî des messaedjes d' aroke sol såle. Li pårticipan (~s) a-st evoyî on messaedje d' aroke (~s) ey a stî tapé foû."}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","C' est nén possibe d' evoyî des messaedjes privés del sôre «groupchat»"}.
|
||||
{"It is not allowed to send private messages to the conference","On n' pout nén evoyî des messaedjes privés dins cisse conferince ci"}.
|
||||
{"It is not allowed to send private messages","Ci n' est nén permetou d' evoyî des messaedjes privés"}.
|
||||
{"Jabber ID","ID Jabber"}.
|
||||
{"January","djanvî"}.
|
||||
{"joins the room","arive sol såle"}.
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
{"A Web Page","网页"}.
|
||||
{"Accept","接受"}.
|
||||
{"Access denied by service policy","访问被服务策略拒绝"}.
|
||||
{"Access model of authorize","授权的访问模型"}.
|
||||
{"Access model of open","开启的访问模型"}.
|
||||
{"Access model of presence","状态的访问模型"}.
|
||||
{"Access model of roster","花名册的访问模型"}.
|
||||
{"Access model of whitelist","白名单的访问模型"}.
|
||||
{"Access model","访问模型"}.
|
||||
{"Account doesn't exist","账号不存在"}.
|
||||
{"Action on user","对用户的动作"}.
|
||||
@@ -230,7 +225,6 @@
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","不允许将错误消息发送到该房间. 参与者(~s)已发送过一条消息(~s)并已被踢出房间"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","\"群组聊天\"类型不允许发送私聊消息"}.
|
||||
{"It is not allowed to send private messages to the conference","不允许向会议发送私聊消息"}.
|
||||
{"It is not allowed to send private messages","不可以发送私聊消息"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","一月"}.
|
||||
{"JID normalization denied by service policy","JID规范化被服务策略拒绝"}.
|
||||
|
||||
+59
-18
@@ -18,6 +18,10 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%%
|
||||
%%% Dependencies
|
||||
%%%
|
||||
|
||||
{deps, [{base64url, ".*", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.30"}}},
|
||||
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.22"}}},
|
||||
@@ -30,7 +34,7 @@
|
||||
{if_var_true, redis,
|
||||
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
|
||||
{if_var_true, sip,
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.49"}}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.50"}}}},
|
||||
{if_var_true, zlib,
|
||||
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.12"}}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.16"}}},
|
||||
@@ -59,10 +63,10 @@
|
||||
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.15"}}},
|
||||
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.22"}}},
|
||||
{if_var_true, mysql,
|
||||
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.21"}}}},
|
||||
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.22"}}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.11"}}},
|
||||
{if_var_true, pgsql,
|
||||
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.22"}}}},
|
||||
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.23"}}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.25"}}},
|
||||
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.9"}}},
|
||||
{if_not_rebar3, %% Needed because modules are not fully migrated to new structure and mix
|
||||
@@ -72,8 +76,8 @@
|
||||
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.14"}}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.29"}}},
|
||||
{if_var_true, stun,
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.7"}}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.6.2"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.10"}}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.7.0"}}},
|
||||
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.15"}}}
|
||||
]}.
|
||||
|
||||
@@ -101,6 +105,21 @@
|
||||
xmpp,
|
||||
yconf]}}.
|
||||
|
||||
%%%
|
||||
%%% Compile
|
||||
%%%
|
||||
|
||||
{recursive_cmds, ['configure-deps']}.
|
||||
|
||||
{post_hook_configure, [{"eimp", []},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, sip, {"esip", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
{"fast_tls", []},
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{"fast_yaml", []},
|
||||
{"stringprep", []}]}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl",
|
||||
"src/gen_mod.erl", "src/mod_muc_room.erl",
|
||||
"src/mod_push.erl", "src/xmpp_socket.erl"]}.
|
||||
@@ -149,9 +168,22 @@
|
||||
|
||||
{keep_build_info, true}.
|
||||
|
||||
%%%
|
||||
%%% Test
|
||||
%%%
|
||||
|
||||
{xref_warnings, false}.
|
||||
|
||||
{xref_checks, [deprecated_function_calls]}.
|
||||
{if_rebar3,
|
||||
{xref_checks,
|
||||
[deprecated_function_calls, deprecated_functions, locals_not_used,
|
||||
undefined_function_calls, undefined_functions]}
|
||||
}.
|
||||
{if_not_rebar3,
|
||||
{xref_checks,
|
||||
[deprecated_function_calls, deprecated_functions,
|
||||
undefined_function_calls, undefined_functions]}
|
||||
}.
|
||||
|
||||
{xref_exclusions, [
|
||||
"(\"gen_transport\":_/_)",
|
||||
@@ -166,26 +198,35 @@
|
||||
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
|
||||
{if_var_false, zlib, "(\"ezlib\":_/_)"}]}.
|
||||
|
||||
{xref_ignores, [{eldap_filter_yecc, return_error, 2} ]}.
|
||||
|
||||
{eunit_compile_opts, [{i, "tools"},
|
||||
{i, "include"}]}.
|
||||
|
||||
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies
|
||||
{if_version_above, "25",
|
||||
{plt_extra_apps,
|
||||
[asn1, odbc, public_key, stdlib, syntax_tools,
|
||||
eredis, idna, jiffy, luerl, jose,
|
||||
cache_tab, eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
|
||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
||||
sqlite3, stringprep, stun, xmpp, yconf]},
|
||||
{plt_extra_apps, % For Erlang/OTP 25 and older
|
||||
[cache_tab, eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
|
||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
||||
sqlite3, stringprep, stun, xmpp, yconf]}
|
||||
} ]}.
|
||||
|
||||
{ct_opts, [{keep_logs, 20}]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
{cover_export_enabled, true}.
|
||||
{coveralls_coverdata, "_build/test/cover/ct.coverdata"}.
|
||||
{coveralls_service_name, "github"}.
|
||||
{recursive_cmds, ['configure-deps']}.
|
||||
|
||||
{overrides, [
|
||||
{del, [{erl_opts, [warnings_as_errors]}]}]}.
|
||||
|
||||
{post_hook_configure, [{"eimp", []},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, sip, {"esip", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
{"fast_tls", []},
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{"fast_yaml", []},
|
||||
{"stringprep", []}]}.
|
||||
%%%
|
||||
%%% OTP Release
|
||||
%%%
|
||||
|
||||
{relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}},
|
||||
[ejabberd]},
|
||||
|
||||
+4
-2
@@ -232,7 +232,8 @@ LibDir = fun(Name, Suffix) ->
|
||||
GlobalDepsFilter =
|
||||
fun(Deps) ->
|
||||
DepNames = lists:map(fun({DepName, _, _}) -> DepName;
|
||||
({DepName, _}) -> DepName
|
||||
({DepName, _}) -> DepName;
|
||||
(DepName) -> DepName
|
||||
end, Deps),
|
||||
lists:filtermap(fun(Dep) ->
|
||||
case LibDir(atom_to_list(Dep), "") of
|
||||
@@ -357,7 +358,8 @@ ProcessRelx = fun(Relx, Deps) ->
|
||||
_ -> []
|
||||
end,
|
||||
DepApps = lists:map(fun({DepName, _, _}) -> DepName;
|
||||
({DepName, _}) -> DepName
|
||||
({DepName, _}) -> DepName;
|
||||
(DepName) -> DepName
|
||||
end, Deps),
|
||||
[{release, NameVersion, DefaultApps ++ VarsApps ++ ProfileApps ++ DepApps} | RelxTail]
|
||||
end,
|
||||
|
||||
+9
-9
@@ -39,14 +39,14 @@
|
||||
-type acl_rule() :: {user, {binary(), binary()} | binary()} |
|
||||
{server, binary()} |
|
||||
{resource, binary()} |
|
||||
{user_regexp, {re:mp(), binary()} | re:mp()} |
|
||||
{server_regexp, re:mp()} |
|
||||
{resource_regexp, re:mp()} |
|
||||
{node_regexp, {re:mp(), re:mp()}} |
|
||||
{user_glob, {re:mp(), binary()} | re:mp()} |
|
||||
{server_glob, re:mp()} |
|
||||
{resource_glob, re:mp()} |
|
||||
{node_glob, {re:mp(), re:mp()}} |
|
||||
{user_regexp, {re_mp(), binary()} | re_mp()} |
|
||||
{server_regexp, re_mp()} |
|
||||
{resource_regexp, re_mp()} |
|
||||
{node_regexp, {re_mp(), re_mp()}} |
|
||||
{user_glob, {re_mp(), binary()} | re_mp()} |
|
||||
{server_glob, re_mp()} |
|
||||
{resource_glob, re_mp()} |
|
||||
{node_glob, {re_mp(), re_mp()}} |
|
||||
{shared_group, {binary(), binary()} | binary()} |
|
||||
{ip, ip_mask()}.
|
||||
-type access() :: [{action(), [access_rule()]}].
|
||||
@@ -348,7 +348,7 @@ node_validator(UV, SV) ->
|
||||
%%%===================================================================
|
||||
%%% Aux
|
||||
%%%===================================================================
|
||||
-spec match_regexp(iodata(), re:mp()) -> boolean().
|
||||
-spec match_regexp(iodata(), re_mp()) -> boolean().
|
||||
match_regexp(Data, RegExp) ->
|
||||
re:run(Data, RegExp) /= nomatch.
|
||||
|
||||
|
||||
+17
-10
@@ -116,6 +116,11 @@ get_commands_spec() ->
|
||||
desc = "Stop ejabberd gracefully",
|
||||
module = ?MODULE, function = stop,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = halt, tags = [server],
|
||||
desc = "Halt ejabberd abruptly with status code 1",
|
||||
note = "added in 23.10",
|
||||
module = ejabberd, function = halt,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = restart, tags = [server],
|
||||
desc = "Restart ejabberd gracefully",
|
||||
module = ?MODULE, function = restart,
|
||||
@@ -136,8 +141,8 @@ get_commands_spec() ->
|
||||
desc = "Inform users and rooms, wait, and stop the server",
|
||||
longdesc = "Provide the delay in seconds, and the "
|
||||
"announcement quoted, for example: \n"
|
||||
"ejabberdctl stop_kindly 60 "
|
||||
"\\\"The server will stop in one minute.\\\"",
|
||||
"`ejabberdctl stop_kindly 60 "
|
||||
"\\\"The server will stop in one minute.\\\"`",
|
||||
module = ?MODULE, function = stop_kindly,
|
||||
args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
|
||||
args_example = [60, <<"Server will stop now.">>],
|
||||
@@ -288,8 +293,9 @@ get_commands_spec() ->
|
||||
args = [{host, binary}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql],
|
||||
desc = "Import data from Prosody",
|
||||
longdesc = "Note: this requires ejabberd compiled with --enable-lua "
|
||||
"and include the optional 'luerl' library.",
|
||||
longdesc = "Note: this requires ejabberd to be "
|
||||
"[compiled with `--enable-lua`](http://localhost:8098/admin/installation/#configure) "
|
||||
"(which installs the `luerl` library).",
|
||||
module = prosody2ejabberd, function = from_dir,
|
||||
args_desc = ["Full path to the Prosody data directory"],
|
||||
args_example = ["/var/lib/prosody/datadump/"],
|
||||
@@ -367,7 +373,7 @@ get_commands_spec() ->
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [cluster],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
longdesc = "If you provide as nodename \"self\", this "
|
||||
longdesc = "If you provide as nodename `self`, this "
|
||||
"node will be set as its own master.",
|
||||
module = ?MODULE, function = set_master,
|
||||
args_desc = ["Name of the erlang node that will be considered master of this node"],
|
||||
@@ -395,7 +401,7 @@ get_commands_spec() ->
|
||||
"binary backup file the internal Mnesia "
|
||||
"database. This will consume a lot of memory if "
|
||||
"you have a large database, you may prefer "
|
||||
"'install_fallback'.",
|
||||
"http://./#install-fallback[install_fallback].",
|
||||
module = ?MODULE, function = restore_mnesia,
|
||||
args_desc = ["Full path to the backup file"],
|
||||
args_example = ["/var/lib/ejabberd/database.backup"],
|
||||
@@ -417,8 +423,9 @@ get_commands_spec() ->
|
||||
longdesc = "Restore immediately. This is not "
|
||||
"recommended for big databases, as it will "
|
||||
"consume much time, memory and processor. In "
|
||||
"that case it's preferable to use 'backup' and "
|
||||
"'install_fallback'.",
|
||||
"that case it's preferable to use "
|
||||
"http://./#backup[backup] and "
|
||||
"http://./#install-fallback[install_fallback].",
|
||||
module = ?MODULE, function = load_mnesia,
|
||||
args_desc = ["Full path to the text file"],
|
||||
args_example = ["/var/lib/ejabberd/database.txt"],
|
||||
@@ -440,8 +447,8 @@ get_commands_spec() ->
|
||||
"restore the database at the next ejabberd "
|
||||
"start. This means that, after running this "
|
||||
"command, you have to restart ejabberd. This "
|
||||
"command requires less memory than
|
||||
'restore'.",
|
||||
"command requires less memory than "
|
||||
"http://./#restore[restore].",
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args_desc = ["Full path to the fallback file"],
|
||||
args_example = ["/var/lib/ejabberd/database.fallback"],
|
||||
|
||||
+34
-20
@@ -398,15 +398,29 @@ user_exists(_User, <<"">>) ->
|
||||
user_exists(User, Server) ->
|
||||
case validate_credentials(User, Server) of
|
||||
{ok, LUser, LServer} ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
case db_user_exists(LUser, LServer, M) of
|
||||
{error, _} ->
|
||||
false;
|
||||
Else ->
|
||||
Else
|
||||
end
|
||||
end, auth_modules(LServer));
|
||||
{Exists, PerformExternalUserCheck} =
|
||||
lists:foldl(
|
||||
fun(M, {Exists0, PerformExternalUserCheck0}) ->
|
||||
case db_user_exists(LUser, LServer, M) of
|
||||
{{error, _}, Check} ->
|
||||
{Exists0, PerformExternalUserCheck0 orelse Check};
|
||||
{Else, Check2} ->
|
||||
{Exists0 orelse Else, PerformExternalUserCheck0 orelse Check2}
|
||||
end
|
||||
end, {false, false}, auth_modules(LServer)),
|
||||
case (not Exists) andalso PerformExternalUserCheck andalso
|
||||
ejabberd_option:auth_external_user_exists_check(Server) andalso
|
||||
gen_mod:is_loaded(Server, mod_last) of
|
||||
true ->
|
||||
case mod_last:get_last_info(User, Server) of
|
||||
not_found ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
_ ->
|
||||
Exists
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
@@ -420,11 +434,11 @@ user_exists_in_other_modules_loop([], _User, _Server) ->
|
||||
false;
|
||||
user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
|
||||
case db_user_exists(User, Server, AuthModule) of
|
||||
true ->
|
||||
{true, _} ->
|
||||
true;
|
||||
false ->
|
||||
{false, _} ->
|
||||
user_exists_in_other_modules_loop(AuthModules, User, Server);
|
||||
{error, _} ->
|
||||
{{error, _}, _} ->
|
||||
maybe
|
||||
end.
|
||||
|
||||
@@ -628,9 +642,9 @@ db_get_password(User, Server, Mod) ->
|
||||
db_user_exists(User, Server, Mod) ->
|
||||
case db_get_password(User, Server, Mod) of
|
||||
{ok, _} ->
|
||||
true;
|
||||
{true, false};
|
||||
not_found ->
|
||||
false;
|
||||
{false, false};
|
||||
error ->
|
||||
case {Mod:store_type(Server), use_cache(Mod, Server)} of
|
||||
{external, true} ->
|
||||
@@ -649,18 +663,18 @@ db_user_exists(User, Server, Mod) ->
|
||||
end,
|
||||
case Val of
|
||||
{ok, _} ->
|
||||
true;
|
||||
{true, Mod /= ejabberd_auth_anonymous} ;
|
||||
not_found ->
|
||||
false;
|
||||
{false, Mod /= ejabberd_auth_anonymous};
|
||||
error ->
|
||||
false;
|
||||
{false, Mod /= ejabberd_auth_anonymous};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
{Err, Mod /= ejabberd_auth_anonymous}
|
||||
end;
|
||||
{external, false} ->
|
||||
ets_cache:untag(Mod:user_exists(User, Server));
|
||||
{ets_cache:untag(Mod:user_exists(User, Server)), Mod /= ejabberd_auth_anonymous};
|
||||
_ ->
|
||||
false
|
||||
{false, false}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
@@ -45,7 +45,31 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(_Host) -> ok.
|
||||
start(Host) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"users">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"password">>, type = text},
|
||||
#sql_column{name = <<"serverkey">>, type = {text, 128},
|
||||
default = true},
|
||||
#sql_column{name = <<"salt">>, type = {text, 128},
|
||||
default = true},
|
||||
#sql_column{name = <<"iterationcount">>, type = integer,
|
||||
default = true},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
stop(_Host) -> ok.
|
||||
|
||||
@@ -161,7 +185,10 @@ set_password_t(LUser, LServer, Password) ->
|
||||
"users",
|
||||
["!username=%(LUser)s",
|
||||
"!server_host=%(LServer)s",
|
||||
"password=%(Password)s"]).
|
||||
"password=%(Password)s",
|
||||
"serverkey=''",
|
||||
"salt=''",
|
||||
"iterationcount=0"]).
|
||||
|
||||
get_password_scram(LServer, LUser) ->
|
||||
ejabberd_sql:sql_query(
|
||||
|
||||
@@ -73,7 +73,7 @@ get_commands_spec() ->
|
||||
"documentation should be stored",
|
||||
"Regexp matching names of commands or modules "
|
||||
"that will be included inside generated document",
|
||||
"Comma separated list of languages (chosen from java, perl, xmlrpc, json)"
|
||||
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
|
||||
"that will have example invocation include in markdown document"],
|
||||
result_desc = "0 if command failed, 1 when succeeded",
|
||||
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
|
||||
@@ -87,7 +87,7 @@ get_commands_spec() ->
|
||||
"documentation should be stored",
|
||||
"Regexp matching names of commands or modules "
|
||||
"that will be included inside generated document",
|
||||
"Comma separated list of languages (chosen from java, perl, xmlrpc, json)"
|
||||
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
|
||||
"that will have example invocation include in markdown document"],
|
||||
result_desc = "0 if command failed, 1 when succeeded",
|
||||
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
|
||||
|
||||
@@ -472,7 +472,7 @@ generate_html_output(File, RegExp, Languages) ->
|
||||
ok.
|
||||
|
||||
maybe_add_policy_arguments(#ejabberd_commands{args=Args1, policy=user}=Cmd) ->
|
||||
Args2 = [{user, binary}, {server, binary} | Args1],
|
||||
Args2 = [{user, binary}, {host, binary} | Args1],
|
||||
Cmd#ejabberd_commands{args = Args2};
|
||||
maybe_add_policy_arguments(Cmd) ->
|
||||
Cmd.
|
||||
|
||||
+19
-1
@@ -502,7 +502,13 @@ read_file(File, Opts) ->
|
||||
end,
|
||||
case Ret of
|
||||
{ok, Y} ->
|
||||
validate(Y);
|
||||
InstalledModules = maybe_install_contrib_modules(Y),
|
||||
ValResult = validate(Y),
|
||||
case InstalledModules of
|
||||
[] -> ok;
|
||||
_ -> spawn(fun() -> timer:sleep(5000), ?MODULE:reload() end)
|
||||
end,
|
||||
ValResult;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
@@ -527,6 +533,18 @@ read_erlang_file(File, _) ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec maybe_install_contrib_modules(term()) -> [atom()].
|
||||
maybe_install_contrib_modules(Options) ->
|
||||
case {lists:keysearch(allow_contrib_modules, 1, Options),
|
||||
lists:keysearch(install_contrib_modules, 1, Options)} of
|
||||
{Allow, {value, {_, InstallContribModules}}}
|
||||
when (Allow == false) or
|
||||
(Allow == {value, {allow_contrib_modules, true}}) ->
|
||||
ext_mod:install_contrib_modules(InstallContribModules, Options);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec validate(term()) -> {ok, [{atom(), term()}]} | error_return().
|
||||
validate(Y1) ->
|
||||
case pre_validate(Y1) of
|
||||
|
||||
+30
-5
@@ -335,8 +335,8 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
|
||||
ArgsFormatted,
|
||||
CI2,
|
||||
Version),
|
||||
format_result(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
|
||||
format_result_preliminary(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1,A2|_], _} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
@@ -385,6 +385,11 @@ format_arg2(Arg, Parse)->
|
||||
%% Format result
|
||||
%%-----------------------------
|
||||
|
||||
format_result_preliminary(Result, {A, {list, B}}) ->
|
||||
format_result(Result, {A, {top_result_list, B}});
|
||||
format_result_preliminary(Result, ResultFormat) ->
|
||||
format_result(Result, ResultFormat).
|
||||
|
||||
format_result({error, ErrorAtom}, _) ->
|
||||
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
|
||||
|
||||
@@ -421,6 +426,16 @@ format_result(Code, {_Name, rescode}) ->
|
||||
format_result({Code, Text}, {_Name, restuple}) ->
|
||||
{io_lib:format("~ts", [Text]), make_status(Code)};
|
||||
|
||||
format_result([], {_Name, {top_result_list, _ElementsDef}}) ->
|
||||
"";
|
||||
format_result([FirstElement | Elements], {_Name, {top_result_list, ElementsDef}}) ->
|
||||
[format_result(FirstElement, ElementsDef) |
|
||||
lists:map(
|
||||
fun(Element) ->
|
||||
["\n" | format_result(Element, ElementsDef)]
|
||||
end,
|
||||
Elements)];
|
||||
|
||||
%% The result is a list of something: [something()]
|
||||
format_result([], {_Name, {list, _ElementsDef}}) ->
|
||||
"";
|
||||
@@ -430,7 +445,7 @@ format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
|
||||
%% If there are more elements, put always first a newline character
|
||||
lists:map(
|
||||
fun(Element) ->
|
||||
["\n" | format_result(Element, ElementsDef)]
|
||||
[";" | format_result(Element, ElementsDef)]
|
||||
end,
|
||||
Elements)];
|
||||
|
||||
@@ -755,7 +770,10 @@ print_usage_help(MaxC, ShCode) ->
|
||||
"\n",
|
||||
"Please note that 'ejabberdctl' shows all ejabberd commands,\n",
|
||||
"even those that cannot be used in the shell with ejabberdctl.\n",
|
||||
"Those commands can be identified because their description starts with: *"],
|
||||
"Those commands can be identified because their description starts with: *\n",
|
||||
"\n",
|
||||
"Some commands return lists, like get_roster and get_user_subscriptions.\n",
|
||||
"In those commands, the elements in the list are separated with: ;\n"],
|
||||
ArgsDef = [],
|
||||
C = #ejabberd_commands{
|
||||
name = help,
|
||||
@@ -817,6 +835,11 @@ filter_commands_regexp(All, Glob) ->
|
||||
end,
|
||||
All).
|
||||
|
||||
maybe_add_policy_arguments(Args, user) ->
|
||||
[{user, binary}, {host, binary} | Args];
|
||||
maybe_add_policy_arguments(Args, _) ->
|
||||
Args.
|
||||
|
||||
-spec print_usage_command(Cmd::string(), MaxC::integer(),
|
||||
ShCode::boolean(), Version::integer()) -> ok.
|
||||
print_usage_command(Cmd, MaxC, ShCode, Version) ->
|
||||
@@ -829,13 +852,15 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
|
||||
tags = TagsAtoms,
|
||||
definer = Definer,
|
||||
desc = Desc,
|
||||
args = ArgsDef,
|
||||
args = ArgsDefPreliminary,
|
||||
policy = Policy,
|
||||
longdesc = LongDesc,
|
||||
result = ResultDef} = C,
|
||||
|
||||
NameFmt = [" ", ?B("Command Name"), ": ", ?C(Cmd), "\n"],
|
||||
|
||||
%% Initial indentation of result is 13 = length(" Arguments: ")
|
||||
ArgsDef = maybe_add_policy_arguments(ArgsDefPreliminary, Policy),
|
||||
Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef],
|
||||
ArgsMargin = lists:duplicate(13, $\s),
|
||||
ArgsListFmt = case Args of
|
||||
|
||||
@@ -108,6 +108,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
{Port2, ExtraOpts} = case Port of
|
||||
<<"unix:", Path/binary>> ->
|
||||
SO = lists:keydelete(ip, 1, SockOpts),
|
||||
setup_provisional_udsocket_dir(Path),
|
||||
file:delete(Path),
|
||||
{0, [{ip, {local, Path}} | SO]};
|
||||
_ ->
|
||||
@@ -119,6 +120,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
{reuseaddr, true} |
|
||||
ExtraOpts2]) of
|
||||
{ok, Socket} ->
|
||||
set_definitive_udsocket(Port, Opts),
|
||||
case inet:sockname(Socket) of
|
||||
{ok, {Addr, Port1}} ->
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
@@ -149,6 +151,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
|
||||
case listen_tcp(Port, SockOpts) of
|
||||
{ok, ListenSocket} ->
|
||||
set_definitive_udsocket(Port, Opts),
|
||||
case inet:sockname(ListenSocket) of
|
||||
{ok, {Addr, Port1}} ->
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
@@ -186,8 +189,10 @@ listen_tcp(Port, SockOpts) ->
|
||||
{Port2, ExtraOpts} = case Port of
|
||||
<<"unix:", Path/binary>> ->
|
||||
SO = lists:keydelete(ip, 1, SockOpts),
|
||||
Prov = setup_provisional_udsocket_dir(Path),
|
||||
file:delete(Path),
|
||||
{0, [{ip, {local, Path}} | SO]};
|
||||
file:delete(Prov),
|
||||
{0, [{ip, {local, Prov}} | SO]};
|
||||
_ ->
|
||||
{Port, SockOpts}
|
||||
end,
|
||||
@@ -205,6 +210,72 @@ listen_tcp(Port, SockOpts) ->
|
||||
Err
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Unix Domain Socket utility functions
|
||||
%%%
|
||||
|
||||
setup_provisional_udsocket_dir(DefinitivePath) ->
|
||||
ProvisionalPath = get_provisional_udsocket_path(DefinitivePath),
|
||||
SocketDir = filename:dirname(ProvisionalPath),
|
||||
file:make_dir(SocketDir),
|
||||
file:change_mode(SocketDir, 8#00700),
|
||||
?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
|
||||
[ProvisionalPath, DefinitivePath]),
|
||||
ProvisionalPath.
|
||||
|
||||
get_provisional_udsocket_path(Path) ->
|
||||
MnesiaDir = mnesia:system_info(directory),
|
||||
SocketDir = filename:join(MnesiaDir, "socket"),
|
||||
PathBase64 = misc:term_to_base64(Path),
|
||||
PathBuild = filename:join(SocketDir, PathBase64),
|
||||
%% Shorthen the path, a long path produces a crash when opening the socket.
|
||||
binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}).
|
||||
|
||||
get_definitive_udsocket_path(<<"unix", _>> = Unix) ->
|
||||
Unix;
|
||||
get_definitive_udsocket_path(ProvisionalPath) ->
|
||||
PathBase64 = filename:basename(ProvisionalPath),
|
||||
{term, Path} = misc:base64_to_term(PathBase64),
|
||||
Path.
|
||||
|
||||
set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
|
||||
Prov = get_provisional_udsocket_path(Path),
|
||||
timer:sleep(5000),
|
||||
Usd = maps:get(unix_socket, Opts),
|
||||
case maps:get(mode, Usd, undefined) of
|
||||
undefined -> ok;
|
||||
Mode -> ok = file:change_mode(Prov, Mode)
|
||||
end,
|
||||
case maps:get(owner, Usd, undefined) of
|
||||
undefined -> ok;
|
||||
Owner ->
|
||||
try
|
||||
ok = file:change_owner(Prov, Owner)
|
||||
catch
|
||||
error:{badmatch, {error, eperm}} ->
|
||||
?ERROR_MSG("Error trying to set owner ~p for socket ~p", [Owner, Prov]),
|
||||
throw({error_setting_socket_owner, Owner, Prov})
|
||||
end
|
||||
end,
|
||||
case maps:get(group, Usd, undefined) of
|
||||
undefined -> ok;
|
||||
Group ->
|
||||
try
|
||||
ok = file:change_group(Prov, Group)
|
||||
catch
|
||||
error:{badmatch, {error, eperm}} ->
|
||||
?ERROR_MSG("Error trying to set group ~p for socket ~p", [Group, Prov]),
|
||||
throw({error_setting_socket_group, Group, Prov})
|
||||
end
|
||||
end,
|
||||
file:rename(Prov, Path);
|
||||
set_definitive_udsocket(_Port, _Opts) ->
|
||||
ok.
|
||||
|
||||
%%%
|
||||
%%%
|
||||
%%%
|
||||
|
||||
-spec split_opts(transport(), opts()) -> {opts(), [gen_tcp:option()]}.
|
||||
split_opts(Transport, Opts) ->
|
||||
maps:fold(
|
||||
@@ -493,8 +564,11 @@ format_error(Reason) ->
|
||||
-spec format_endpoint(endpoint()) -> string().
|
||||
format_endpoint({Port, IP, _Transport}) ->
|
||||
case Port of
|
||||
<<"unix:", _/binary>> ->
|
||||
Port;
|
||||
Unix when is_binary(Unix) ->
|
||||
<<"unix:", Unix/binary>>;
|
||||
Def = get_definitive_udsocket_path(Unix),
|
||||
<<"unix:", Def/binary>>;
|
||||
_ ->
|
||||
IPStr = case tuple_size(IP) of
|
||||
4 -> inet:ntoa(IP);
|
||||
@@ -699,6 +773,12 @@ listen_opt_type(shaper) ->
|
||||
econf:shaper();
|
||||
listen_opt_type(access) ->
|
||||
econf:acl();
|
||||
listen_opt_type(unix_socket) ->
|
||||
econf:options(
|
||||
#{group => econf:non_neg_int(),
|
||||
owner => econf:non_neg_int(),
|
||||
mode => econf:octal()},
|
||||
[unique, {return, map}]);
|
||||
listen_opt_type(use_proxy_protocol) ->
|
||||
econf:bool().
|
||||
|
||||
@@ -708,6 +788,7 @@ listen_options() ->
|
||||
{ip, {0,0,0,0}},
|
||||
{accept_interval, 0},
|
||||
{send_timeout, 15000},
|
||||
{backlog, 5},
|
||||
{backlog, 128},
|
||||
{unix_socket, #{}},
|
||||
{use_proxy_protocol, false},
|
||||
{supervisor, true}].
|
||||
|
||||
@@ -244,10 +244,9 @@ terminate(_Reason, _State) ->
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
|
||||
get_client_identity(<<"">>, Ctx) ->
|
||||
{ok, {Ctx, {client, unknown_client}}};
|
||||
get_client_identity({client, ClientID}, Ctx) ->
|
||||
get_client_identity(ClientID, Ctx) when is_binary(ClientID) ->
|
||||
{ok, {Ctx, {client, ClientID}}}.
|
||||
|
||||
verify_redirection_uri(_ClientID, RedirectURI, Ctx) ->
|
||||
@@ -753,6 +752,7 @@ json_error(Code, Error, Reason) ->
|
||||
json_response(Code, Body).
|
||||
|
||||
json_error_desc(access_denied) -> <<"Access denied">>;
|
||||
json_error_desc(badpass) -> <<"Bad password">>;
|
||||
json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>;
|
||||
json_error_desc(invalid_scope) -> <<"Invalid scope">>.
|
||||
|
||||
|
||||
@@ -41,8 +41,35 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
init() ->
|
||||
ejabberd_sql_schema:update_schema(
|
||||
ejabberd_config:get_myname(), ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"oauth_token">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"token">>, type = text},
|
||||
#sql_column{name = <<"jid">>, type = text},
|
||||
#sql_column{name = <<"scope">>, type = text},
|
||||
#sql_column{name = <<"expire">>, type = bigint}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"token">>],
|
||||
unique = true}]},
|
||||
#sql_table{
|
||||
name = <<"oauth_client">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"client_id">>, type = text},
|
||||
#sql_column{name = <<"client_name">>, type = text},
|
||||
#sql_column{name = <<"grant_type">>, type = text},
|
||||
#sql_column{name = <<"options">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"client_id">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
store(R) ->
|
||||
Token = R#oauth_token.token,
|
||||
{User, Server} = R#oauth_token.us,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
-export([auth_cache_life_time/0]).
|
||||
-export([auth_cache_missed/0]).
|
||||
-export([auth_cache_size/0]).
|
||||
-export([auth_external_user_exists_check/0, auth_external_user_exists_check/1]).
|
||||
-export([auth_method/0, auth_method/1]).
|
||||
-export([auth_opts/0, auth_opts/1]).
|
||||
-export([auth_password_format/0, auth_password_format/1]).
|
||||
@@ -52,6 +53,7 @@
|
||||
-export([host_config/0]).
|
||||
-export([hosts/0]).
|
||||
-export([include_config_file/0, include_config_file/1]).
|
||||
-export([install_contrib_modules/0]).
|
||||
-export([jwt_auth_only_rule/0, jwt_auth_only_rule/1]).
|
||||
-export([jwt_jid_field/0, jwt_jid_field/1]).
|
||||
-export([jwt_key/0, jwt_key/1]).
|
||||
@@ -142,6 +144,7 @@
|
||||
-export([sm_use_cache/0, sm_use_cache/1]).
|
||||
-export([sql_connect_timeout/0, sql_connect_timeout/1]).
|
||||
-export([sql_database/0, sql_database/1]).
|
||||
-export([sql_flags/0, sql_flags/1]).
|
||||
-export([sql_keepalive_interval/0, sql_keepalive_interval/1]).
|
||||
-export([sql_odbc_driver/0, sql_odbc_driver/1]).
|
||||
-export([sql_password/0, sql_password/1]).
|
||||
@@ -159,6 +162,7 @@
|
||||
-export([sql_type/0, sql_type/1]).
|
||||
-export([sql_username/0, sql_username/1]).
|
||||
-export([trusted_proxies/0]).
|
||||
-export([update_sql_schema/0]).
|
||||
-export([use_cache/0, use_cache/1]).
|
||||
-export([validate_stream/0]).
|
||||
-export([version/0]).
|
||||
@@ -222,6 +226,13 @@ auth_cache_missed() ->
|
||||
auth_cache_size() ->
|
||||
ejabberd_config:get_option({auth_cache_size, global}).
|
||||
|
||||
-spec auth_external_user_exists_check() -> boolean().
|
||||
auth_external_user_exists_check() ->
|
||||
auth_external_user_exists_check(global).
|
||||
-spec auth_external_user_exists_check(global | binary()) -> boolean().
|
||||
auth_external_user_exists_check(Host) ->
|
||||
ejabberd_config:get_option({auth_external_user_exists_check, Host}).
|
||||
|
||||
-spec auth_method() -> [atom()].
|
||||
auth_method() ->
|
||||
auth_method(global).
|
||||
@@ -449,6 +460,10 @@ include_config_file() ->
|
||||
include_config_file(Host) ->
|
||||
ejabberd_config:get_option({include_config_file, Host}).
|
||||
|
||||
-spec install_contrib_modules() -> [atom()].
|
||||
install_contrib_modules() ->
|
||||
ejabberd_config:get_option({install_contrib_modules, global}).
|
||||
|
||||
-spec jwt_auth_only_rule() -> atom().
|
||||
jwt_auth_only_rule() ->
|
||||
jwt_auth_only_rule(global).
|
||||
@@ -959,6 +974,13 @@ sql_database() ->
|
||||
sql_database(Host) ->
|
||||
ejabberd_config:get_option({sql_database, Host}).
|
||||
|
||||
-spec sql_flags() -> ['mysql_alternative_upsert'].
|
||||
sql_flags() ->
|
||||
sql_flags(global).
|
||||
-spec sql_flags(global | binary()) -> ['mysql_alternative_upsert'].
|
||||
sql_flags(Host) ->
|
||||
ejabberd_config:get_option({sql_flags, Host}).
|
||||
|
||||
-spec sql_keepalive_interval() -> 'undefined' | pos_integer().
|
||||
sql_keepalive_interval() ->
|
||||
sql_keepalive_interval(global).
|
||||
@@ -1075,6 +1097,10 @@ sql_username(Host) ->
|
||||
trusted_proxies() ->
|
||||
ejabberd_config:get_option({trusted_proxies, global}).
|
||||
|
||||
-spec update_sql_schema() -> boolean().
|
||||
update_sql_schema() ->
|
||||
ejabberd_config:get_option({update_sql_schema, global}).
|
||||
|
||||
-spec use_cache() -> boolean().
|
||||
use_cache() ->
|
||||
use_cache(global).
|
||||
|
||||
@@ -81,6 +81,8 @@ opt_type(auth_password_format) ->
|
||||
econf:enum([plain, scram]);
|
||||
opt_type(auth_scram_hash) ->
|
||||
econf:enum([sha, sha256, sha512]);
|
||||
opt_type(auth_external_user_exists_check) ->
|
||||
econf:bool();
|
||||
opt_type(auth_use_cache) ->
|
||||
econf:bool();
|
||||
opt_type(c2s_cafile) ->
|
||||
@@ -183,6 +185,8 @@ opt_type(hosts) ->
|
||||
econf:non_empty(econf:list(econf:domain(), [unique]));
|
||||
opt_type(include_config_file) ->
|
||||
econf:any();
|
||||
opt_type(install_contrib_modules) ->
|
||||
econf:list(econf:atom());
|
||||
opt_type(language) ->
|
||||
econf:lang();
|
||||
opt_type(ldap_backups) ->
|
||||
@@ -256,6 +260,8 @@ opt_type(net_ticktime) ->
|
||||
econf:timeout(second);
|
||||
opt_type(new_sql_schema) ->
|
||||
econf:bool();
|
||||
opt_type(update_sql_schema) ->
|
||||
econf:bool();
|
||||
opt_type(oauth_access) ->
|
||||
econf:acl();
|
||||
opt_type(oauth_cache_life_time) ->
|
||||
@@ -422,6 +428,8 @@ opt_type(sql_username) ->
|
||||
econf:binary();
|
||||
opt_type(sql_prepared_statements) ->
|
||||
econf:bool();
|
||||
opt_type(sql_flags) ->
|
||||
econf:list_or_single(econf:enum([mysql_alternative_upsert]), [sorted, unique]);
|
||||
opt_type(trusted_proxies) ->
|
||||
econf:either(all, econf:list(econf:ip_mask()));
|
||||
opt_type(use_cache) ->
|
||||
@@ -517,6 +525,7 @@ options() ->
|
||||
{access_rules, []},
|
||||
{acme, #{}},
|
||||
{allow_contrib_modules, true},
|
||||
{install_contrib_modules, []},
|
||||
{allow_multiple_connections, false},
|
||||
{anonymous_protocol, sasl_anon},
|
||||
{api_permissions,
|
||||
@@ -537,6 +546,7 @@ options() ->
|
||||
{auth_opts, []},
|
||||
{auth_password_format, plain},
|
||||
{auth_scram_hash, sha},
|
||||
{auth_external_user_exists_check, true},
|
||||
{auth_use_cache,
|
||||
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
|
||||
{c2s_cafile, undefined},
|
||||
@@ -599,6 +609,7 @@ options() ->
|
||||
{negotiation_timeout, timer:seconds(30)},
|
||||
{net_ticktime, timer:seconds(60)},
|
||||
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
|
||||
{update_sql_schema, false},
|
||||
{oauth_access, none},
|
||||
{oauth_cache_life_time,
|
||||
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
|
||||
@@ -705,6 +716,7 @@ options() ->
|
||||
{sql_start_interval, timer:seconds(30)},
|
||||
{sql_username, <<"ejabberd">>},
|
||||
{sql_prepared_statements, true},
|
||||
{sql_flags, []},
|
||||
{trusted_proxies, []},
|
||||
{validate_stream, false},
|
||||
{websocket_origin, []},
|
||||
@@ -736,6 +748,7 @@ globals() ->
|
||||
fqdn,
|
||||
hosts,
|
||||
host_config,
|
||||
install_contrib_modules,
|
||||
listen,
|
||||
loglevel,
|
||||
log_rotate_count,
|
||||
@@ -746,6 +759,7 @@ globals() ->
|
||||
negotiation_timeout,
|
||||
net_ticktime,
|
||||
new_sql_schema,
|
||||
update_sql_schema,
|
||||
node_start,
|
||||
oauth_cache_life_time,
|
||||
oauth_cache_missed,
|
||||
|
||||
@@ -303,6 +303,8 @@ doc() ->
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("Whether to allow installation of third-party modules or not. "
|
||||
"See https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
|
||||
"[ejabberd-contrib] documentation section. "
|
||||
"The default value is 'true'.")}},
|
||||
{allow_multiple_connections,
|
||||
#{value => "true | false",
|
||||
@@ -393,6 +395,14 @@ doc() ->
|
||||
"You shouldn't change this if you already have passwords generated with "
|
||||
"a different algorithm - users that have such passwords will not be able "
|
||||
"to authenticate. The default value is 'sha'.")}},
|
||||
{auth_external_user_exists_check,
|
||||
#{value => "true | false",
|
||||
note => "added in 23.10",
|
||||
desc =>
|
||||
?T("Supplement check for user existence based on 'mod_last' data, for authentication "
|
||||
"methods that don't have a way to reliable tell if user exists (like is the case for "
|
||||
"'jwt' and certificate based authentication). This helps with processing offline message "
|
||||
"for those users. The default value is 'true'.")}},
|
||||
{auth_use_cache,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
@@ -670,6 +680,14 @@ doc() ->
|
||||
"file 'Filename'. The options that do not match this "
|
||||
"criteria are not accepted. The default value is to include "
|
||||
"all options.")}}]},
|
||||
{install_contrib_modules,
|
||||
#{value => "[Module, ...]",
|
||||
note => "added in 23.10",
|
||||
desc =>
|
||||
?T("Modules to install from "
|
||||
"https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
|
||||
"[ejabberd-contrib] at start time. "
|
||||
"The default value is an empty list of modules: '[]'.")}},
|
||||
{jwt_auth_only_rule,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
@@ -893,6 +911,11 @@ doc() ->
|
||||
"configuration flag '--enable-new-sql-schema' which is set "
|
||||
"at compile time."),
|
||||
[binary:part(ejabberd_config:version(), {0,5})]}}},
|
||||
{update_sql_schema,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("Allow ejabberd to update SQL schema. "
|
||||
"The default value is 'true'.")}},
|
||||
{oauth_access,
|
||||
#{value => ?T("AccessName"),
|
||||
desc => ?T("By default creating OAuth tokens is not allowed. "
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
ejabberd_sql_schema:update_schema(
|
||||
ejabberd_config:get_myname(), ?MODULE, schemas()),
|
||||
Node = erlang:atom_to_binary(node(), latin1),
|
||||
?DEBUG("Cleaning SQL 'route' table...", []),
|
||||
case ejabberd_sql:sql_query(
|
||||
@@ -48,6 +50,23 @@ init() ->
|
||||
Err
|
||||
end.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"route">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"domain">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"node">>, type = text},
|
||||
#sql_column{name = <<"pid">>, type = text},
|
||||
#sql_column{name = <<"local_hint">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"domain">>, <<"server_host">>,
|
||||
<<"node">>, <<"pid">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
|
||||
PidS = misc:encode_pid(Pid),
|
||||
LocalHintS = enc_local_hint(LocalHint),
|
||||
|
||||
@@ -48,6 +48,7 @@ init() ->
|
||||
?DEBUG("Cleaning SQL SM table...", []),
|
||||
lists:foldl(
|
||||
fun(Host, ok) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
case ejabberd_sql:sql_query(
|
||||
Host, ?SQL("delete from sm where node=%(Node)s")) of
|
||||
{updated, _} ->
|
||||
@@ -60,6 +61,29 @@ init() ->
|
||||
Err
|
||||
end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"sm">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"usec">>, type = bigint},
|
||||
#sql_column{name = <<"pid">>, type = text},
|
||||
#sql_column{name = <<"node">>, type = text},
|
||||
#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"resource">>, type = text},
|
||||
#sql_column{name = <<"priority">>, type = text},
|
||||
#sql_column{name = <<"info">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"usec">>, <<"pid">>],
|
||||
unique = true},
|
||||
#sql_index{
|
||||
columns = [<<"node">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>]}]}]}].
|
||||
|
||||
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
|
||||
priority = Priority, info = Info}) ->
|
||||
InfoS = misc:term_to_expr(Info),
|
||||
|
||||
+82
-15
@@ -73,7 +73,7 @@
|
||||
-record(state,
|
||||
{db_ref :: undefined | pid(),
|
||||
db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql,
|
||||
db_version :: undefined | non_neg_integer(),
|
||||
db_version :: undefined | non_neg_integer() | {non_neg_integer(), atom(), non_neg_integer()},
|
||||
reconnect_count = 0 :: non_neg_integer(),
|
||||
host :: binary(),
|
||||
pending_requests :: p1_queue:queue(),
|
||||
@@ -93,15 +93,16 @@
|
||||
-endif.
|
||||
|
||||
-type state() :: #state{}.
|
||||
-type sql_query_simple() :: [sql_query() | binary()] | #sql_query{} |
|
||||
fun(() -> any()) | fun((atom(), _) -> any()).
|
||||
-type sql_query() :: sql_query_simple() |
|
||||
[{atom() | {atom(), any()}, sql_query_simple()}].
|
||||
-type sql_query_result() :: {updated, non_neg_integer()} |
|
||||
{error, binary() | atom()} |
|
||||
{selected, [binary()], [[binary()]]} |
|
||||
{selected, [any()]} |
|
||||
ok.
|
||||
-type sql_query_simple(T) :: [sql_query(T) | binary()] | binary() |
|
||||
#sql_query{} |
|
||||
fun(() -> T) | fun((atom(), _) -> T).
|
||||
-type sql_query(T) :: sql_query_simple(T) |
|
||||
[{atom() | {atom(), any()}, sql_query_simple(T)}].
|
||||
-type sql_query_result(T) :: {updated, non_neg_integer()} |
|
||||
{error, binary() | atom()} |
|
||||
{selected, [binary()], [[binary()]]} |
|
||||
{selected, [any()]} |
|
||||
T.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@@ -112,14 +113,14 @@ start_link(Host, I) ->
|
||||
p1_fsm:start_link({local, Proc}, ?MODULE, [Host],
|
||||
fsm_limit_opts() ++ ?FSMOPTS).
|
||||
|
||||
-spec sql_query(binary(), sql_query()) -> sql_query_result().
|
||||
-spec sql_query(binary(), sql_query(T)) -> sql_query_result(T).
|
||||
sql_query(Host, Query) ->
|
||||
sql_call(Host, {sql_query, Query}).
|
||||
|
||||
%% SQL transaction based on a list of queries
|
||||
%% This function automatically
|
||||
-spec sql_transaction(binary(), [sql_query()] | fun(() -> any())) ->
|
||||
{atomic, any()} |
|
||||
-spec sql_transaction(binary(), [sql_query(T)] | fun(() -> T)) ->
|
||||
{atomic, T} |
|
||||
{aborted, any()}.
|
||||
sql_transaction(Host, Queries)
|
||||
when is_list(Queries) ->
|
||||
@@ -177,7 +178,7 @@ sync_send_event(Proc, Msg, Timeout) ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec sql_query_t(sql_query()) -> sql_query_result().
|
||||
-spec sql_query_t(sql_query(T)) -> sql_query_result(T).
|
||||
%% This function is intended to be used from inside an sql_transaction:
|
||||
sql_query_t(Query) ->
|
||||
QRes = sql_query_internal(Query),
|
||||
@@ -683,7 +684,14 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
pgsql_sql_query(Query)
|
||||
end;
|
||||
mysql ->
|
||||
generic_sql_query(Query);
|
||||
case {Query#sql_query.flags, ejabberd_option:sql_prepared_statements(State#state.host)} of
|
||||
{1, _} ->
|
||||
generic_sql_query(Query);
|
||||
{_, false} ->
|
||||
generic_sql_query(Query);
|
||||
_ ->
|
||||
mysql_prepared_execute(Query, State)
|
||||
end;
|
||||
sqlite ->
|
||||
sqlite_sql_query(Query)
|
||||
end
|
||||
@@ -862,6 +870,24 @@ pgsql_execute_sql_query(SQLQuery, State) ->
|
||||
Res = pgsql_execute_to_odbc(ExecuteRes),
|
||||
sql_query_format_res(Res, SQLQuery).
|
||||
|
||||
mysql_prepared_execute(#sql_query{hash = Hash} = Query, State) ->
|
||||
ValEsc = #sql_escape{like_escape = fun() -> ignore end, _ = fun(X) -> X end},
|
||||
TypesEsc = #sql_escape{string = fun(_) -> string end,
|
||||
integer = fun(_) -> integer end,
|
||||
boolean = fun(_) -> bool end,
|
||||
in_array_string = fun(_) -> string end,
|
||||
like_escape = fun() -> ignore end},
|
||||
Val = [X || X <- (Query#sql_query.args)(ValEsc), X /= ignore],
|
||||
Types = [X || X <- (Query#sql_query.args)(TypesEsc), X /= ignore],
|
||||
QueryFn = fun() ->
|
||||
PrepEsc = #sql_escape{like_escape = fun() -> <<>> end, _ = fun(_) -> <<"?">> end},
|
||||
(Query#sql_query.format_query)((Query#sql_query.args)(PrepEsc))
|
||||
end,
|
||||
QueryTimeout = query_timeout(State#state.host),
|
||||
Res = p1_mysql_conn:prepared_query(State#state.db_ref, QueryFn, Hash, Val, Types,
|
||||
self(), [{timeout, QueryTimeout - 1000}]),
|
||||
Res2 = mysql_to_odbc(Res),
|
||||
sql_query_format_res(Res2, Query).
|
||||
|
||||
sql_query_format_res({selected, _, Rows}, SQLQuery) ->
|
||||
Res =
|
||||
@@ -1123,9 +1149,50 @@ get_db_version(#state{db_type = pgsql} = State) ->
|
||||
?WARNING_MSG("Error getting pgsql version: ~p", [Res]),
|
||||
State
|
||||
end;
|
||||
get_db_version(#state{db_type = mysql, host = Host} = State) ->
|
||||
DefaultUpsert = case lists:member(mysql_alternative_upsert, ejabberd_option:sql_flags(Host)) of
|
||||
true -> 1;
|
||||
_ -> 0
|
||||
end,
|
||||
case mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
|
||||
[<<"select version();">>], self(),
|
||||
[{timeout, 5000},
|
||||
{result_type, binary}])) of
|
||||
{selected, _, [SVersion]} ->
|
||||
case re:run(SVersion, <<"(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-([^-]*))?">>,
|
||||
[{capture, all_but_first, binary}]) of
|
||||
{match, [V1, V2, V3, Type]} ->
|
||||
V = ((bin_to_int(V1)*1000)+bin_to_int(V2))*1000+bin_to_int(V3),
|
||||
TypeA = binary_to_atom(Type, utf8),
|
||||
Flags = case TypeA of
|
||||
'MariaDB' -> DefaultUpsert;
|
||||
_ when V >= 5007026 andalso V < 8000000 -> 1;
|
||||
_ when V >= 8000020 -> 1;
|
||||
_ -> DefaultUpsert
|
||||
end,
|
||||
State#state{db_version = {V, TypeA, Flags}};
|
||||
{match, [V1, V2, V3]} ->
|
||||
V = ((bin_to_int(V1)*1000)+bin_to_int(V2))*1000+bin_to_int(V3),
|
||||
Flags = case V of
|
||||
_ when V >= 5007026 andalso V < 8000000 -> 1;
|
||||
_ when V >= 8000020 -> 1;
|
||||
_ -> DefaultUpsert
|
||||
end,
|
||||
State#state{db_version = {V, unknown, Flags}};
|
||||
_ ->
|
||||
?WARNING_MSG("Error parsing mysql version: ~p", [SVersion]),
|
||||
State
|
||||
end;
|
||||
Res ->
|
||||
?WARNING_MSG("Error getting mysql version: ~p", [Res]),
|
||||
State
|
||||
end;
|
||||
get_db_version(State) ->
|
||||
State.
|
||||
|
||||
bin_to_int(<<>>) -> 0;
|
||||
bin_to_int(V) -> binary_to_integer(V).
|
||||
|
||||
log(Level, Format, Args) ->
|
||||
case Level of
|
||||
debug -> ?DEBUG(Format, Args);
|
||||
|
||||
+78
-5
@@ -42,7 +42,8 @@
|
||||
used_vars = [],
|
||||
use_new_schema,
|
||||
need_timestamp_pass = false,
|
||||
need_array_pass = false}).
|
||||
need_array_pass = false,
|
||||
has_list = false}).
|
||||
|
||||
-define(QUERY_RECORD, "sql_query").
|
||||
|
||||
@@ -268,9 +269,12 @@ parse1([$@, $( | S], Acc, State) ->
|
||||
Convert =
|
||||
case Type of
|
||||
integer ->
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(binary_to_integer),
|
||||
[EVar]);
|
||||
erl_syntax:if_expr([
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:application(erl_syntax:atom(is_binary), [EVar])],
|
||||
[erl_syntax:application(erl_syntax:atom(binary_to_integer), [EVar])]),
|
||||
erl_syntax:clause([erl_syntax:atom(true)], [EVar])
|
||||
]);
|
||||
string ->
|
||||
EVar;
|
||||
timestamp ->
|
||||
@@ -339,6 +343,7 @@ parse1([$%, $( | S], Acc, State) ->
|
||||
erl_syntax:variable(Name)]),
|
||||
State2#state{'query' = [[{var, Var, Type}] | State2#state.'query'],
|
||||
need_array_pass = true,
|
||||
has_list = true,
|
||||
args = [[Convert, ConvertArr] | State2#state.args],
|
||||
params = [Var | State2#state.params],
|
||||
param_pos = State2#state.param_pos + 1,
|
||||
@@ -467,6 +472,7 @@ make_sql_query(State, Type) ->
|
||||
Hash = erlang:phash2(State#state{loc = undefined, use_new_schema = true}),
|
||||
SHash = <<"Q", (integer_to_binary(Hash))/binary>>,
|
||||
Query = pack_query(State#state.'query'),
|
||||
Flags = case State#state.has_list of true -> 1; _ -> 0 end,
|
||||
EQuery =
|
||||
lists:flatmap(
|
||||
fun({str, S}) ->
|
||||
@@ -515,6 +521,9 @@ make_sql_query(State, Type) ->
|
||||
none,
|
||||
[erl_syntax:tuple(State#state.res)]
|
||||
)])),
|
||||
erl_syntax:record_field(
|
||||
erl_syntax:atom(flags),
|
||||
erl_syntax:abstract(Flags)),
|
||||
erl_syntax:record_field(
|
||||
erl_syntax:atom(loc),
|
||||
erl_syntax:abstract({get(?MOD), State#state.loc}))
|
||||
@@ -567,7 +576,6 @@ parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
|
||||
parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
|
||||
parse_upsert_field1(S, [C | Acc], ParamPos, Loc).
|
||||
|
||||
|
||||
make_sql_upsert(Table, ParseRes, Pos) ->
|
||||
check_upsert(ParseRes, Pos),
|
||||
erl_syntax:fun_expr(
|
||||
@@ -587,6 +595,11 @@ make_sql_upsert(Table, ParseRes, Pos) ->
|
||||
erl_syntax:integer(90100))],
|
||||
[make_sql_upsert_pgsql901(Table, ParseRes),
|
||||
erl_syntax:atom(ok)]),
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:atom(mysql), erl_syntax:tuple([erl_syntax:underscore(), erl_syntax:underscore(), erl_syntax:integer(1)])],
|
||||
[],
|
||||
[make_sql_upsert_mysql_select(Table, ParseRes),
|
||||
erl_syntax:atom(ok)]),
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:atom(mysql), erl_syntax:underscore()],
|
||||
[],
|
||||
@@ -682,6 +695,66 @@ make_sql_upsert_insert(Table, ParseRes) ->
|
||||
]),
|
||||
State.
|
||||
|
||||
make_sql_upsert_select(Table, ParseRes) ->
|
||||
{Fields0, Where0} =
|
||||
lists:foldl(
|
||||
fun({Field, key, ST}, {Fie, Whe}) ->
|
||||
{Fie, [ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'}] ++ Whe};
|
||||
({Field, {true}, ST}, {Fie, Whe}) ->
|
||||
{[ST#state{
|
||||
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'}] ++ Fie, Whe};
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, {[], []}, ParseRes),
|
||||
Fields = join_states(Fields0, " AND "),
|
||||
Where = join_states(Where0, " AND "),
|
||||
State =
|
||||
concat_states(
|
||||
[#state{'query' = [{str, "SELECT "}],
|
||||
res_vars = [erl_syntax:variable("__VSel")],
|
||||
res = [erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(to_bool),
|
||||
[erl_syntax:variable("__VSel")])]},
|
||||
Fields,
|
||||
#state{'query' = [{str, " FROM "}, {str, Table}, {str, " WHERE "}]},
|
||||
Where
|
||||
]),
|
||||
State.
|
||||
|
||||
make_sql_upsert_mysql_select(Table, ParseRes) ->
|
||||
Select = make_sql_query(make_sql_upsert_select(Table, ParseRes)),
|
||||
Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)),
|
||||
Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)),
|
||||
erl_syntax:case_expr(
|
||||
erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Select]),
|
||||
[erl_syntax:clause(
|
||||
[erl_syntax:tuple([erl_syntax:atom(selected), erl_syntax:list([])])],
|
||||
none,
|
||||
[erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Insert])]),
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:abstract({selected, [{true}]})],
|
||||
[],
|
||||
[erl_syntax:atom(ok)]),
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:tuple([erl_syntax:atom(selected), erl_syntax:underscore()])],
|
||||
none,
|
||||
[erl_syntax:application(
|
||||
erl_syntax:atom(ejabberd_sql),
|
||||
erl_syntax:atom(sql_query_t),
|
||||
[Update])]),
|
||||
erl_syntax:clause(
|
||||
[erl_syntax:variable("__SelectRes")],
|
||||
none,
|
||||
[erl_syntax:variable("__SelectRes")])]).
|
||||
|
||||
make_sql_upsert_mysql(Table, ParseRes) ->
|
||||
Vals =
|
||||
lists:map(
|
||||
|
||||
@@ -0,0 +1,936 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_sql.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : SQL schema versioning
|
||||
%%% Created : 15 Aug 2023 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2023 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.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_sql_schema).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, update_schema/3,
|
||||
get_table_schema/2, get_table_indices/2, test/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
start(Host) ->
|
||||
case should_update_schema(Host) of
|
||||
true ->
|
||||
case table_exists(Host, <<"schema_version">>) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
Table = filter_table_sh(schema_table()),
|
||||
Res = create_table(Host, Table),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to create table ~s: ~p",
|
||||
[Table#sql_table.name, Error]),
|
||||
{error, Error};
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
schema_table() ->
|
||||
#sql_table{
|
||||
name = <<"schema_version">>,
|
||||
columns = [#sql_column{name = <<"module">>, type = text},
|
||||
#sql_column{name = <<"version">>, type = bigint}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"module">>],
|
||||
unique = true}]}.
|
||||
|
||||
get_table_schema(Host, Table) ->
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(pgsql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select "
|
||||
" @(a.attname)s, "
|
||||
" @(pg_catalog.format_type(a.atttypid, a.atttypmod))s "
|
||||
" from "
|
||||
" pg_class t, "
|
||||
" pg_attribute a "
|
||||
" where "
|
||||
" a.attrelid = t.oid and "
|
||||
" a.attnum > 0 and "
|
||||
" a.atttypid > 0 and "
|
||||
" t.relkind = 'r' and "
|
||||
" t.relname=%(Table)s"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
|
||||
end;
|
||||
(sqlite, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(i.name)s, @(i.type)s"
|
||||
" from pragma_table_info(%(Table)s) as i"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
|
||||
end;
|
||||
(mysql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(column_name)s, @(column_type)s"
|
||||
" from information_schema.columns"
|
||||
" where table_name=%(Table)s and"
|
||||
" table_schema=schema()"
|
||||
" order by ordinal_position"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
|
||||
end
|
||||
end).
|
||||
|
||||
get_table_indices(Host, Table) ->
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(pgsql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select "
|
||||
" @(i.relname)s, "
|
||||
" @(a.attname)s "
|
||||
" from "
|
||||
" pg_class t, "
|
||||
" pg_class i, "
|
||||
" pg_index ix, "
|
||||
" pg_attribute a "
|
||||
" where "
|
||||
" t.oid = ix.indrelid and "
|
||||
" i.oid = ix.indexrelid and "
|
||||
" a.attrelid = t.oid and "
|
||||
" a.attnum = ANY(ix.indkey) and "
|
||||
" t.relkind = 'r' and "
|
||||
" t.relname=%(Table)s "
|
||||
" order by "
|
||||
" i.relname, "
|
||||
" array_position(ix.indkey, a.attnum)"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
Indices =
|
||||
lists:foldr(
|
||||
fun({IdxName, ColName}, Acc) ->
|
||||
maps:update_with(
|
||||
IdxName,
|
||||
fun(Cs) -> [ColName | Cs] end,
|
||||
[ColName],
|
||||
Acc)
|
||||
end, #{}, Cols),
|
||||
maps:to_list(Indices)
|
||||
end;
|
||||
(sqlite, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(i.name)s, @(c.name)s "
|
||||
" from pragma_index_list(%(Table)s) as i,"
|
||||
" pragma_index_xinfo(i.name) as c"
|
||||
" where c.cid >= 0"
|
||||
" order by i.name, c.seqno"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
Indices =
|
||||
lists:foldr(
|
||||
fun({IdxName, ColName}, Acc) ->
|
||||
maps:update_with(
|
||||
IdxName,
|
||||
fun(Cs) -> [ColName | Cs] end,
|
||||
[ColName],
|
||||
Acc)
|
||||
end, #{}, Cols),
|
||||
maps:to_list(Indices)
|
||||
end;
|
||||
(mysql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(index_name)s, @(column_name)s"
|
||||
" from information_schema.statistics"
|
||||
" where table_name=%(Table)s and"
|
||||
" table_schema=schema()"
|
||||
" order by index_name, seq_in_index"))
|
||||
of
|
||||
{selected, Cols} ->
|
||||
Indices =
|
||||
lists:foldr(
|
||||
fun({IdxName, ColName}, Acc) ->
|
||||
maps:update_with(
|
||||
IdxName,
|
||||
fun(Cs) -> [ColName | Cs] end,
|
||||
[ColName],
|
||||
Acc)
|
||||
end, #{}, Cols),
|
||||
maps:to_list(Indices)
|
||||
end
|
||||
end).
|
||||
|
||||
find_index_name(Host, Table, Columns) ->
|
||||
Indices = get_table_indices(Host, Table),
|
||||
case lists:keyfind(Columns, 2, Indices) of
|
||||
false ->
|
||||
false;
|
||||
{Name, _} ->
|
||||
{ok, Name}
|
||||
end.
|
||||
|
||||
get_version(Host, Module) ->
|
||||
SModule = misc:atom_to_binary(Module),
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
?SQL("select @(version)d"
|
||||
" from schema_version"
|
||||
" where module=%(SModule)s")).
|
||||
|
||||
store_version(Host, Module, Version) ->
|
||||
SModule = misc:atom_to_binary(Module),
|
||||
?SQL_UPSERT(
|
||||
Host,
|
||||
"schema_version",
|
||||
["!module=%(SModule)s",
|
||||
"version=%(Version)d"]).
|
||||
|
||||
table_exists(Host, Table) ->
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(pgsql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @()b exists (select * from pg_tables "
|
||||
" where tablename=%(Table)s)"))
|
||||
of
|
||||
{selected, [{Res}]} ->
|
||||
Res
|
||||
end;
|
||||
(sqlite, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @()b exists"
|
||||
" (select 0 from pragma_table_info(%(Table)s))"))
|
||||
of
|
||||
{selected, [{Res}]} ->
|
||||
Res
|
||||
end;
|
||||
(mysql, _) ->
|
||||
case
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @()b exists"
|
||||
" (select 0 from information_schema.tables"
|
||||
" where table_name=%(Table)s and"
|
||||
" table_schema=schema())"))
|
||||
of
|
||||
{selected, [{Res}]} ->
|
||||
Res
|
||||
end
|
||||
end).
|
||||
|
||||
filter_table_sh(Table) ->
|
||||
case {ejabberd_sql:use_new_schema(), Table#sql_table.name} of
|
||||
{true, _} ->
|
||||
Table;
|
||||
{_, <<"route">>} ->
|
||||
Table;
|
||||
{false, _} ->
|
||||
Table#sql_table{
|
||||
columns =
|
||||
lists:keydelete(
|
||||
<<"server_host">>, #sql_column.name, Table#sql_table.columns),
|
||||
indices =
|
||||
lists:map(
|
||||
fun(Idx) ->
|
||||
Idx#sql_index{
|
||||
columns =
|
||||
lists:delete(
|
||||
<<"server_host">>, Idx#sql_index.columns)
|
||||
}
|
||||
end, Table#sql_table.indices)
|
||||
}
|
||||
end.
|
||||
|
||||
string_to_type(SType) ->
|
||||
case string:lowercase(SType) of
|
||||
<<"text">> -> text;
|
||||
<<"mediumtext">> -> text;
|
||||
<<"bigint">> -> bigint;
|
||||
<<"bigint ", _/binary>> -> bigint;
|
||||
<<"bigint(", _/binary>> -> bigint;
|
||||
<<"integer">> -> integer;
|
||||
<<"int">> -> integer;
|
||||
<<"int(", _/binary>> -> integer;
|
||||
<<"smallint">> -> smallint;
|
||||
<<"smallint(", _/binary>> -> smallint;
|
||||
<<"numeric">> -> numeric;
|
||||
<<"decimal", _/binary>> -> numeric;
|
||||
<<"bigserial">> -> bigserial;
|
||||
<<"boolean">> -> boolean;
|
||||
<<"tinyint(1)">> -> boolean;
|
||||
<<"bytea">> -> blob;
|
||||
<<"blob">> -> blob;
|
||||
<<"timestamp", _/binary>> -> timestamp;
|
||||
<<"character(", R/binary>> ->
|
||||
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
|
||||
{char, N};
|
||||
<<"char(", R/binary>> ->
|
||||
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
|
||||
{char, N};
|
||||
<<"varchar(", _/binary>> -> text;
|
||||
<<"character varying(", _/binary>> -> text;
|
||||
T ->
|
||||
?ERROR_MSG("Unknown SQL type '~s'", [T]),
|
||||
{undefined, T}
|
||||
end.
|
||||
|
||||
check_columns_compatibility(RequiredColumns, Columns) ->
|
||||
lists:all(
|
||||
fun(#sql_column{name = Name, type = Type}) ->
|
||||
%io:format("col ~p~n", [{Name, Type}]),
|
||||
case lists:keyfind(Name, 1, Columns) of
|
||||
false ->
|
||||
false;
|
||||
{_, Type2} ->
|
||||
%io:format("tt ~p~n", [{Type, Type2}]),
|
||||
case {Type, Type2} of
|
||||
{T, T} -> true;
|
||||
{text, blob} -> true;
|
||||
{{text, _}, blob} -> true;
|
||||
{{text, _}, text} -> true;
|
||||
{{text, _}, {varchar, _}} -> true;
|
||||
{text, {varchar, _}} -> true;
|
||||
{{char, _}, text} -> true;
|
||||
{{varchar, _}, text} -> true;
|
||||
{smallint, integer} -> true;
|
||||
{smallint, bigint} -> true;
|
||||
{smallint, numeric} -> true;
|
||||
{integer, bigint} -> true;
|
||||
{integer, numeric} -> true;
|
||||
{bigint, numeric} -> true;
|
||||
{bigserial, integer} -> true;
|
||||
{bigserial, bigint} -> true;
|
||||
{bigserial, numeric} -> true;
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
end, RequiredColumns).
|
||||
|
||||
guess_version(Host, Schemas) ->
|
||||
LastSchema = lists:max(Schemas),
|
||||
SomeTablesExist =
|
||||
lists:any(
|
||||
fun(Table) ->
|
||||
table_exists(Host, Table#sql_table.name)
|
||||
end, LastSchema#sql_schema.tables),
|
||||
if
|
||||
SomeTablesExist ->
|
||||
CompatibleSchemas =
|
||||
lists:filter(
|
||||
fun(Schema) ->
|
||||
lists:all(
|
||||
fun(Table) ->
|
||||
Table2 = filter_table_sh(Table),
|
||||
CurrentColumns =
|
||||
get_table_schema(
|
||||
Host, Table2#sql_table.name),
|
||||
check_columns_compatibility(
|
||||
Table2#sql_table.columns,
|
||||
CurrentColumns)
|
||||
end, Schema#sql_schema.tables)
|
||||
end, Schemas),
|
||||
case CompatibleSchemas of
|
||||
[] -> -1;
|
||||
_ ->
|
||||
(lists:max(CompatibleSchemas))#sql_schema.version
|
||||
end;
|
||||
true ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_current_version(Host, Module, Schemas) ->
|
||||
case get_version(Host, Module) of
|
||||
{selected, [{Version}]} ->
|
||||
Version;
|
||||
{selected, []} ->
|
||||
Version = guess_version(Host, Schemas),
|
||||
if
|
||||
Version > 0 ->
|
||||
store_version(Host, Module, Version);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
Version
|
||||
end.
|
||||
|
||||
format_type(pgsql, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"text">>;
|
||||
{text, _} -> <<"text">>;
|
||||
bigint -> <<"bigint">>;
|
||||
integer -> <<"integer">>;
|
||||
smallint -> <<"smallint">>;
|
||||
numeric -> <<"numeric">>;
|
||||
boolean -> <<"boolean">>;
|
||||
blob -> <<"bytea">>;
|
||||
timestamp -> <<"timestamp">>;
|
||||
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
|
||||
bigserial -> <<"bigserial">>
|
||||
end;
|
||||
format_type(sqlite, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"text">>;
|
||||
{text, _} -> <<"text">>;
|
||||
bigint -> <<"bigint">>;
|
||||
integer -> <<"integer">>;
|
||||
smallint -> <<"smallint">>;
|
||||
numeric -> <<"numeric">>;
|
||||
boolean -> <<"boolean">>;
|
||||
blob -> <<"blob">>;
|
||||
timestamp -> <<"timestamp">>;
|
||||
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
|
||||
bigserial -> <<"integer primary key autoincrement">>
|
||||
end;
|
||||
format_type(mysql, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"text">>;
|
||||
{text, big} -> <<"mediumtext">>;
|
||||
{text, N} when is_integer(N), N < 191 ->
|
||||
[<<"varchar(">>, integer_to_binary(N), <<")">>];
|
||||
{text, _} -> <<"text">>;
|
||||
bigint -> <<"bigint">>;
|
||||
integer -> <<"integer">>;
|
||||
smallint -> <<"smallint">>;
|
||||
numeric -> <<"numeric">>;
|
||||
boolean -> <<"boolean">>;
|
||||
blob -> <<"blob">>;
|
||||
timestamp -> <<"timestamp">>;
|
||||
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
|
||||
bigserial -> <<"bigint auto_increment primary key">>
|
||||
end.
|
||||
|
||||
format_default(pgsql, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"''">>;
|
||||
{text, _} -> <<"''">>;
|
||||
bigint -> <<"0">>;
|
||||
integer -> <<"0">>;
|
||||
smallint -> <<"0">>;
|
||||
numeric -> <<"0">>;
|
||||
boolean -> <<"false">>;
|
||||
blob -> <<"''">>;
|
||||
timestamp -> <<"now()">>
|
||||
%{char, N} -> <<"''">>;
|
||||
%bigserial -> <<"0">>
|
||||
end;
|
||||
format_default(sqlite, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"''">>;
|
||||
{text, _} -> <<"''">>;
|
||||
bigint -> <<"0">>;
|
||||
integer -> <<"0">>;
|
||||
smallint -> <<"0">>;
|
||||
numeric -> <<"0">>;
|
||||
boolean -> <<"false">>;
|
||||
blob -> <<"''">>;
|
||||
timestamp -> <<"CURRENT_TIMESTAMP">>
|
||||
%{char, N} -> <<"''">>;
|
||||
%bigserial -> <<"0">>
|
||||
end;
|
||||
format_default(mysql, _DBVersion, Column) ->
|
||||
case Column#sql_column.type of
|
||||
text -> <<"('')">>;
|
||||
{text, _} -> <<"('')">>;
|
||||
bigint -> <<"0">>;
|
||||
integer -> <<"0">>;
|
||||
smallint -> <<"0">>;
|
||||
numeric -> <<"0">>;
|
||||
boolean -> <<"false">>;
|
||||
blob -> <<"('')">>;
|
||||
timestamp -> <<"CURRENT_TIMESTAMP">>
|
||||
%{char, N} -> <<"''">>;
|
||||
%bigserial -> <<"0">>
|
||||
end.
|
||||
|
||||
escape_name(pgsql, _DBVersion, <<"type">>) ->
|
||||
<<"\"type\"">>;
|
||||
escape_name(_DBType, _DBVersion, ColumnName) ->
|
||||
ColumnName.
|
||||
|
||||
format_column_def(DBType, DBVersion, Column) ->
|
||||
[<<" ">>,
|
||||
escape_name(DBType, DBVersion, Column#sql_column.name), <<" ">>,
|
||||
format_type(DBType, DBVersion, Column),
|
||||
<<" NOT NULL">>,
|
||||
case Column#sql_column.default of
|
||||
false -> [];
|
||||
true ->
|
||||
[<<" DEFAULT ">>, format_default(DBType, DBVersion, Column)]
|
||||
end,
|
||||
case lists:keyfind(sql_references, 1, Column#sql_column.opts) of
|
||||
false -> [];
|
||||
#sql_references{table = T, column = C} ->
|
||||
[<<" REFERENCES ">>, T, <<"(">>, C, <<") ON DELETE CASCADE">>]
|
||||
end].
|
||||
|
||||
format_mysql_index_column(Table, ColumnName) ->
|
||||
{value, Column} =
|
||||
lists:keysearch(
|
||||
ColumnName, #sql_column.name, Table#sql_table.columns),
|
||||
NeedsSizeLimit =
|
||||
case Column#sql_column.type of
|
||||
{text, N} when is_integer(N), N < 191 -> false;
|
||||
{text, _} -> true;
|
||||
text -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if
|
||||
NeedsSizeLimit ->
|
||||
[ColumnName, <<"(191)">>];
|
||||
true ->
|
||||
ColumnName
|
||||
end.
|
||||
|
||||
format_create_index(pgsql, _DBVersion, Table, Index) ->
|
||||
TableName = Table#sql_table.name,
|
||||
Unique =
|
||||
case Index#sql_index.unique of
|
||||
true -> <<"UNIQUE ">>;
|
||||
false -> <<"">>
|
||||
end,
|
||||
Name = [<<"i_">>, TableName, <<"_">>,
|
||||
lists:join(
|
||||
<<"_">>,
|
||||
Index#sql_index.columns)],
|
||||
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
|
||||
<<" USING btree (">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
Index#sql_index.columns),
|
||||
<<");">>];
|
||||
format_create_index(sqlite, _DBVersion, Table, Index) ->
|
||||
TableName = Table#sql_table.name,
|
||||
Unique =
|
||||
case Index#sql_index.unique of
|
||||
true -> <<"UNIQUE ">>;
|
||||
false -> <<"">>
|
||||
end,
|
||||
Name = [<<"i_">>, TableName, <<"_">>,
|
||||
lists:join(
|
||||
<<"_">>,
|
||||
Index#sql_index.columns)],
|
||||
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
|
||||
<<"(">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
Index#sql_index.columns),
|
||||
<<");">>];
|
||||
format_create_index(mysql, _DBVersion, Table, Index) ->
|
||||
TableName = Table#sql_table.name,
|
||||
Unique =
|
||||
case Index#sql_index.unique of
|
||||
true -> <<"UNIQUE ">>;
|
||||
false -> <<"">>
|
||||
end,
|
||||
Name = [<<"i_">>, TableName, <<"_">>,
|
||||
lists:join(
|
||||
<<"_">>,
|
||||
Index#sql_index.columns)],
|
||||
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name,
|
||||
<<" USING BTREE ON ">>, TableName,
|
||||
<<"(">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
lists:map(
|
||||
fun(Col) ->
|
||||
format_mysql_index_column(Table, Col)
|
||||
end, Index#sql_index.columns)),
|
||||
<<");">>].
|
||||
|
||||
format_create_table(pgsql = DBType, DBVersion, Table) ->
|
||||
TableName = Table#sql_table.name,
|
||||
[iolist_to_binary(
|
||||
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
|
||||
lists:join(
|
||||
<<",\n">>,
|
||||
lists:map(
|
||||
fun(C) -> format_column_def(DBType, DBVersion, C) end,
|
||||
Table#sql_table.columns)),
|
||||
<<"\n);\n">>])] ++
|
||||
lists:map(
|
||||
fun(I) ->
|
||||
iolist_to_binary(
|
||||
[format_create_index(DBType, DBVersion, Table, I),
|
||||
<<"\n">>])
|
||||
end,
|
||||
Table#sql_table.indices);
|
||||
format_create_table(sqlite = DBType, DBVersion, Table) ->
|
||||
TableName = Table#sql_table.name,
|
||||
[iolist_to_binary(
|
||||
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
|
||||
lists:join(
|
||||
<<",\n">>,
|
||||
lists:map(
|
||||
fun(C) -> format_column_def(DBType, DBVersion, C) end,
|
||||
Table#sql_table.columns)),
|
||||
<<"\n);\n">>])] ++
|
||||
lists:map(
|
||||
fun(I) ->
|
||||
iolist_to_binary(
|
||||
[format_create_index(DBType, DBVersion, Table, I),
|
||||
<<"\n">>])
|
||||
end,
|
||||
Table#sql_table.indices);
|
||||
format_create_table(mysql = DBType, DBVersion, Table) ->
|
||||
TableName = Table#sql_table.name,
|
||||
[iolist_to_binary(
|
||||
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
|
||||
lists:join(
|
||||
<<",\n">>,
|
||||
lists:map(
|
||||
fun(C) -> format_column_def(DBType, DBVersion, C) end,
|
||||
Table#sql_table.columns)),
|
||||
<<"\n) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n">>])] ++
|
||||
lists:map(
|
||||
fun(I) ->
|
||||
iolist_to_binary(
|
||||
[format_create_index(DBType, DBVersion, Table, I),
|
||||
<<"\n">>])
|
||||
end,
|
||||
Table#sql_table.indices).
|
||||
%format_create_table(DBType, _DBVersion, Table) ->
|
||||
% ?ERROR_MSG("Can't create SQL table ~p on ~p",
|
||||
% [Table#sql_table.name, DBType]),
|
||||
% error.
|
||||
|
||||
create_table(Host, Table) ->
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, DBVersion) ->
|
||||
SQLs = format_create_table(DBType, DBVersion, Table),
|
||||
?INFO_MSG("Creating table ~s:~n~s~n",
|
||||
[Table#sql_table.name, SQLs]),
|
||||
lists:foreach(
|
||||
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end, SQLs),
|
||||
case Table#sql_table.post_create of
|
||||
undefined ->
|
||||
ok;
|
||||
F ->
|
||||
F(DBType, DBVersion)
|
||||
end
|
||||
end).
|
||||
|
||||
create_tables(Host, Module, Schema) ->
|
||||
lists:foreach(
|
||||
fun(Table) ->
|
||||
Table2 = filter_table_sh(Table),
|
||||
Res = create_table(Host, Table2),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to create table ~s: ~p",
|
||||
[Table2#sql_table.name, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, Schema#sql_schema.tables),
|
||||
store_version(Host, Module, Schema#sql_schema.version).
|
||||
|
||||
should_update_schema(Host) ->
|
||||
SupportedDB =
|
||||
case ejabberd_option:sql_type(Host) of
|
||||
pgsql -> true;
|
||||
sqlite -> true;
|
||||
mysql -> true;
|
||||
_ -> false
|
||||
end,
|
||||
case ejabberd_option:update_sql_schema() andalso SupportedDB of
|
||||
true ->
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Host == ejabberd_config:get_myname();
|
||||
false ->
|
||||
true
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
update_schema(Host, Module, Schemas) ->
|
||||
case should_update_schema(Host) of
|
||||
true ->
|
||||
Version = get_current_version(Host, Module, Schemas),
|
||||
LastSchema = lists:max(Schemas),
|
||||
LastVersion = LastSchema#sql_schema.version,
|
||||
case Version of
|
||||
_ when Version < 0 ->
|
||||
?ERROR_MSG("Can't update SQL schema for module ~p, please do it manually", [Module]);
|
||||
0 ->
|
||||
create_tables(Host, Module, LastSchema);
|
||||
LastVersion ->
|
||||
ok;
|
||||
_ when LastVersion < Version ->
|
||||
?ERROR_MSG("The current SQL schema for module ~p is ~p, but the latest known schema in the module is ~p", [Module, Version, LastVersion]);
|
||||
_ ->
|
||||
lists:foreach(
|
||||
fun(Schema) ->
|
||||
if
|
||||
Schema#sql_schema.version > Version ->
|
||||
do_update_schema(Host, Module, Schema);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, lists:sort(Schemas))
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
do_update_schema(Host, Module, Schema) ->
|
||||
lists:foreach(
|
||||
fun({add_column, TableName, ColumnName}) ->
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Column} =
|
||||
lists:keysearch(
|
||||
ColumnName, #sql_column.name, Table#sql_table.columns),
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, DBVersion) ->
|
||||
Def = format_column_def(DBType, DBVersion, Column),
|
||||
Default = format_default(DBType, DBVersion, Column),
|
||||
SQLs =
|
||||
[[<<"ALTER TABLE ">>,
|
||||
TableName,
|
||||
<<" ADD COLUMN\n">>,
|
||||
Def,
|
||||
<<" DEFAULT ">>,
|
||||
Default, <<";\n">>]] ++
|
||||
case Column#sql_column.default of
|
||||
false ->
|
||||
[[<<"ALTER TABLE ">>,
|
||||
TableName,
|
||||
<<" ALTER COLUMN ">>,
|
||||
ColumnName,
|
||||
<<" DROP DEFAULT;">>]];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
?INFO_MSG("Add column ~s/~s:~n~s~n",
|
||||
[TableName,
|
||||
ColumnName,
|
||||
SQLs]),
|
||||
lists:foreach(
|
||||
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
|
||||
SQLs)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_column, TableName, ColumnName}) ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(_DBType, _DBVersion) ->
|
||||
SQL = [<<"ALTER TABLE ">>,
|
||||
TableName,
|
||||
<<" DROP COLUMN ">>,
|
||||
ColumnName,
|
||||
<<";">>],
|
||||
?INFO_MSG("Drop column ~s/~s:~n~s~n",
|
||||
[TableName,
|
||||
ColumnName,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({create_index, TableName, Columns}) ->
|
||||
{value, Table1} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Index1} =
|
||||
lists:keysearch(
|
||||
Columns, #sql_index.columns, Table1#sql_table.indices),
|
||||
Table = filter_table_sh(Table1),
|
||||
Index =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Index1;
|
||||
false ->
|
||||
Index1#sql_index{
|
||||
columns =
|
||||
lists:delete(
|
||||
<<"server_host">>, Index1#sql_index.columns)
|
||||
}
|
||||
end,
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, DBVersion) ->
|
||||
SQL1 = format_create_index(
|
||||
DBType, DBVersion, Table, Index),
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Create index ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_index, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
case find_index_name(Host, TableName, Columns) of
|
||||
false ->
|
||||
?ERROR_MSG("Can't find an index to drop for ~s/~p",
|
||||
[TableName, Columns]);
|
||||
{ok, IndexName} ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, _DBVersion) ->
|
||||
SQL =
|
||||
case DBType of
|
||||
mysql ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName,
|
||||
<<" ON ">>,
|
||||
TableName,
|
||||
<<";">>];
|
||||
_ ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName, <<";">>]
|
||||
end,
|
||||
?INFO_MSG("Drop index ~s/~p:~n~s~n",
|
||||
[TableName,
|
||||
Columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end, Schema#sql_schema.update),
|
||||
store_version(Host, Module, Schema#sql_schema.version).
|
||||
|
||||
test() ->
|
||||
Schemas =
|
||||
[#sql_schema{
|
||||
version = 2,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"archive2">>,
|
||||
columns = [#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"timestamp">>, type = bigint},
|
||||
#sql_column{name = <<"peer">>, type = text},
|
||||
#sql_column{name = <<"bare_peer">>, type = text},
|
||||
#sql_column{name = <<"xml">>, type = {text, big}},
|
||||
#sql_column{name = <<"txt">>, type = {text, big}},
|
||||
#sql_column{name = <<"id">>, type = bigserial},
|
||||
#sql_column{name = <<"kind">>, type = text},
|
||||
#sql_column{name = <<"nick">>, type = text},
|
||||
#sql_column{name = <<"origin_id">>, type = text},
|
||||
#sql_column{name = <<"type">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"origin_id">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"timestamp">>]}
|
||||
]}],
|
||||
update =
|
||||
[{add_column, <<"archive2">>, <<"origin_id">>},
|
||||
{create_index, <<"archive2">>,
|
||||
[<<"server_host">>, <<"origin_id">>]},
|
||||
{drop_index, <<"archive2">>,
|
||||
[<<"server_host">>, <<"origin_id">>]},
|
||||
{drop_column, <<"archive2">>, <<"origin_id">>}
|
||||
]},
|
||||
#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"archive2">>,
|
||||
columns = [#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"timestamp">>, type = bigint},
|
||||
#sql_column{name = <<"peer">>, type = text},
|
||||
#sql_column{name = <<"bare_peer">>, type = text},
|
||||
#sql_column{name = <<"xml">>, type = {text, big}},
|
||||
#sql_column{name = <<"txt">>, type = {text, big}},
|
||||
#sql_column{name = <<"id">>, type = bigserial},
|
||||
#sql_column{name = <<"kind">>, type = {text, 10}},
|
||||
#sql_column{name = <<"nick">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"timestamp">>]}
|
||||
]}]}],
|
||||
update_schema(<<"localhost">>, mod_foo, Schemas).
|
||||
@@ -51,7 +51,9 @@ start(Host) ->
|
||||
type => supervisor,
|
||||
modules => [?MODULE]},
|
||||
case supervisor:start_child(ejabberd_db_sup, Spec) of
|
||||
{ok, _} -> ok;
|
||||
{ok, _} ->
|
||||
ejabberd_sql_schema:start(Host),
|
||||
ok;
|
||||
{error, {already_started, Pid}} ->
|
||||
%% Wait for the supervisor to fully start
|
||||
_ = supervisor:count_children(Pid),
|
||||
|
||||
+40
-47
@@ -44,9 +44,8 @@
|
||||
{socket :: gen_udp:socket() | undefined,
|
||||
destination :: inet:local_address() | undefined,
|
||||
interval :: pos_integer() | undefined,
|
||||
last_ping :: integer() | undefined}).
|
||||
timer :: reference() | undefined}).
|
||||
|
||||
-type watchdog_timeout() :: pos_integer() | hibernate.
|
||||
-type state() :: #state{}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -71,8 +70,7 @@ stopping() ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init(any())
|
||||
-> {ok, state()} | {ok, state(), watchdog_timeout()} | {stop, term()}.
|
||||
-spec init(any()) -> {ok, state()} | {stop, term()}.
|
||||
init(_Opts) ->
|
||||
process_flag(trap_exit, true),
|
||||
case os:getenv("NOTIFY_SOCKET") of
|
||||
@@ -84,17 +82,10 @@ init(_Opts) ->
|
||||
Destination = {local, Path},
|
||||
case gen_udp:open(0, [local]) of
|
||||
{ok, Socket} ->
|
||||
Interval = get_watchdog_interval(),
|
||||
State = #state{socket = Socket,
|
||||
destination = Destination,
|
||||
interval = Interval},
|
||||
if is_integer(Interval), Interval > 0 ->
|
||||
?INFO_MSG("Watchdog notifications enabled", []),
|
||||
{ok, set_last_ping(State), Interval};
|
||||
true ->
|
||||
?INFO_MSG("Watchdog notifications disabled", []),
|
||||
{ok, State}
|
||||
end;
|
||||
interval = get_watchdog_interval()},
|
||||
{ok, maybe_start_timer(State)};
|
||||
{error, Reason} ->
|
||||
?CRITICAL_MSG("Cannot open IPC socket: ~p", [Reason]),
|
||||
{stop, Reason}
|
||||
@@ -105,47 +96,48 @@ init(_Opts) ->
|
||||
end.
|
||||
|
||||
-spec handle_call(term(), {pid(), term()}, state())
|
||||
-> {reply, {error, badarg}, state(), watchdog_timeout()}.
|
||||
-> {reply, {error, badarg}, state()}.
|
||||
handle_call(Request, From, State) ->
|
||||
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
|
||||
{reply, {error, badarg}, State, get_timeout(State)}.
|
||||
{reply, {error, badarg}, State}.
|
||||
|
||||
-spec handle_cast({notify, binary()} | term(), state())
|
||||
-> {noreply, state(), watchdog_timeout()}.
|
||||
-spec handle_cast({notify, binary()} | term(), state()) -> {noreply, state()}.
|
||||
handle_cast({notify, Notification},
|
||||
#state{destination = undefined} = State) ->
|
||||
?DEBUG("No NOTIFY_SOCKET, dropping ~s notification", [Notification]),
|
||||
{noreply, State, get_timeout(State)};
|
||||
{noreply, State};
|
||||
handle_cast({notify, Notification}, State) ->
|
||||
try notify(State, Notification)
|
||||
catch _:Err ->
|
||||
?ERROR_MSG("Cannot send ~s notification: ~p", [Notification, Err])
|
||||
end,
|
||||
{noreply, State, get_timeout(State)};
|
||||
{noreply, State};
|
||||
handle_cast(Msg, State) ->
|
||||
?ERROR_MSG("Got unexpected message: ~p", [Msg]),
|
||||
{noreply, State, get_timeout(State)}.
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(timeout | term(), state())
|
||||
-> {noreply, state(), watchdog_timeout()}.
|
||||
handle_info(timeout, #state{interval = Interval} = State)
|
||||
-spec handle_info(ping_watchdog | term(), state()) -> {noreply, state()}.
|
||||
handle_info(ping_watchdog, #state{interval = Interval} = State)
|
||||
when is_integer(Interval), Interval > 0 ->
|
||||
try notify(State, <<"WATCHDOG=1">>)
|
||||
catch _:Err ->
|
||||
?ERROR_MSG("Cannot ping watchdog: ~p", [Err])
|
||||
end,
|
||||
{noreply, set_last_ping(State), Interval};
|
||||
{noreply, start_timer(State)};
|
||||
handle_info(Info, State) ->
|
||||
?ERROR_MSG("Got unexpected info: ~p", [Info]),
|
||||
{noreply, State, get_timeout(State)}.
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
|
||||
terminate(Reason, #state{socket = undefined}) ->
|
||||
terminate(Reason, #state{socket = Socket} = State) ->
|
||||
?DEBUG("Terminating ~s (~p)", [?MODULE, Reason]),
|
||||
ok;
|
||||
terminate(Reason, #state{socket = Socket}) ->
|
||||
?DEBUG("Closing socket and terminating ~s (~p)", [?MODULE, Reason]),
|
||||
ok = gen_udp:close(Socket).
|
||||
cancel_timer(State),
|
||||
case Socket of
|
||||
undefined ->
|
||||
ok;
|
||||
_Socket ->
|
||||
gen_udp:close(Socket)
|
||||
end.
|
||||
|
||||
-spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
@@ -166,24 +158,24 @@ get_watchdog_interval() ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec get_timeout(state()) -> watchdog_timeout().
|
||||
get_timeout(#state{interval = undefined}) ->
|
||||
?DEBUG("Watchdog interval is undefined, hibernating", []),
|
||||
hibernate;
|
||||
get_timeout(#state{interval = Interval, last_ping = LastPing}) ->
|
||||
case Interval - (erlang:monotonic_time(millisecond) - LastPing) of
|
||||
Timeout when Timeout > 0 ->
|
||||
?DEBUG("Calculated new timeout value: ~B", [Timeout]),
|
||||
Timeout;
|
||||
_ ->
|
||||
?DEBUG("Calculated new timeout value: 1", []),
|
||||
1
|
||||
end.
|
||||
-spec maybe_start_timer(state()) -> state().
|
||||
maybe_start_timer(#state{interval = Interval} = State)
|
||||
when is_integer(Interval), Interval > 0 ->
|
||||
?INFO_MSG("Watchdog notifications enabled", []),
|
||||
start_timer(State);
|
||||
maybe_start_timer(State) ->
|
||||
?INFO_MSG("Watchdog notifications disabled", []),
|
||||
State.
|
||||
|
||||
-spec set_last_ping(state()) -> state().
|
||||
set_last_ping(State) ->
|
||||
LastPing = erlang:monotonic_time(millisecond),
|
||||
State#state{last_ping = LastPing}.
|
||||
-spec start_timer(state()) -> state().
|
||||
start_timer(#state{interval = Interval} = State) ->
|
||||
?DEBUG("Pinging watchdog in ~B milliseconds", [Interval]),
|
||||
State#state{timer = erlang:send_after(Interval, self(), ping_watchdog)}.
|
||||
|
||||
-spec cancel_timer(state()) -> ok.
|
||||
cancel_timer(#state{timer = Timer}) ->
|
||||
?DEBUG("Cancelling watchdog timer", []),
|
||||
misc:cancel_timer(Timer).
|
||||
|
||||
-spec notify(state(), binary()) -> ok.
|
||||
notify(#state{socket = Socket, destination = Destination},
|
||||
@@ -193,4 +185,5 @@ notify(#state{socket = Socket, destination = Destination},
|
||||
|
||||
-spec cast_notification(binary()) -> ok.
|
||||
cast_notification(Notification) ->
|
||||
?DEBUG("Closing NOTIFY_SOCKET", []),
|
||||
gen_server:cast(?MODULE, {notify, Notification}).
|
||||
|
||||
@@ -397,13 +397,15 @@ logo_fill() ->
|
||||
%%%% process_admin
|
||||
|
||||
process_admin(global, #request{path = [], lang = Lang}, AJID) ->
|
||||
MenuItems = get_menu_items(global, cluster, Lang, AJID, 0),
|
||||
Disclaimer = maybe_disclaimer_not_admin(MenuItems, AJID, Lang),
|
||||
make_xhtml((?H1GL((translate:translate(Lang, ?T("Administration"))), <<"">>,
|
||||
<<"Contents">>))
|
||||
++
|
||||
++ Disclaimer ++
|
||||
[?XE(<<"ul">>,
|
||||
[?LI([?ACT(MIU, MIN)])
|
||||
|| {MIU, MIN}
|
||||
<- get_menu_items(global, cluster, Lang, AJID, 0)])],
|
||||
<- MenuItems])],
|
||||
global, Lang, AJID, 0);
|
||||
process_admin(Host, #request{path = [], lang = Lang}, AJID) ->
|
||||
make_xhtml([?XCT(<<"h1">>, ?T("Administration")),
|
||||
@@ -573,14 +575,16 @@ term_to_id(T) -> base64:encode((term_to_binary(T))).
|
||||
%%%% list_vhosts
|
||||
|
||||
list_vhosts(Lang, JID) ->
|
||||
list_vhosts2(Lang, list_vhosts_allowed(JID)).
|
||||
|
||||
list_vhosts_allowed(JID) ->
|
||||
Hosts = ejabberd_option:hosts(),
|
||||
HostsAllowed = lists:filter(fun (Host) ->
|
||||
lists:filter(fun (Host) ->
|
||||
any_rules_allowed(Host,
|
||||
[configure, webadmin_view],
|
||||
JID)
|
||||
end,
|
||||
Hosts),
|
||||
list_vhosts2(Lang, HostsAllowed).
|
||||
Hosts).
|
||||
|
||||
list_vhosts2(Lang, Hosts) ->
|
||||
SHosts = lists:sort(Hosts),
|
||||
@@ -616,6 +620,17 @@ list_vhosts2(Lang, Hosts) ->
|
||||
end,
|
||||
SHosts)))])].
|
||||
|
||||
maybe_disclaimer_not_admin(MenuItems, AJID, Lang) ->
|
||||
case {MenuItems, list_vhosts_allowed(AJID)} of
|
||||
{[_], []} ->
|
||||
[?XREST(?T("Apparently your account has no administration rights in this server. "
|
||||
"Please check how to grant admin rights in: "
|
||||
"https://docs.ejabberd.im/admin/installation/#administration-account"))
|
||||
];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% list_users
|
||||
|
||||
|
||||
+111
-29
@@ -33,6 +33,7 @@
|
||||
installed_command/0, installed/0, installed/1,
|
||||
install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0,
|
||||
add_sources/1, add_sources/2, del_sources/1, modules_dir/0,
|
||||
install_contrib_modules/2,
|
||||
config_dir/0, get_commands_spec/0]).
|
||||
-export([modules_configs/0, module_ebin_dir/1]).
|
||||
-export([compile_erlang_file/2, compile_elixir_file/2]).
|
||||
@@ -48,7 +49,7 @@
|
||||
-include("translate.hrl").
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
|
||||
-define(REPOS, "https://github.com/processone/ejabberd-contrib").
|
||||
-define(REPOS, "git@github.com:processone/ejabberd-contrib.git").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@@ -215,10 +216,13 @@ installed_command() ->
|
||||
[short_spec(Item) || Item <- installed()].
|
||||
|
||||
install(Module) when is_atom(Module) ->
|
||||
install(misc:atom_to_binary(Module));
|
||||
install(misc:atom_to_binary(Module), undefined);
|
||||
install(Package) when is_binary(Package) ->
|
||||
install(Package, undefined).
|
||||
|
||||
install(Package, Config) when is_binary(Package) ->
|
||||
Spec = [S || {Mod, S} <- available(), misc:atom_to_binary(Mod)==Package],
|
||||
case {Spec, installed(Package), is_contrib_allowed()} of
|
||||
case {Spec, installed(Package), is_contrib_allowed(Config)} of
|
||||
{_, _, false} ->
|
||||
{error, not_allowed};
|
||||
{[], _, _} ->
|
||||
@@ -227,13 +231,14 @@ install(Package) when is_binary(Package) ->
|
||||
{error, conflict};
|
||||
{[Attrs], _, _} ->
|
||||
Module = misc:binary_to_atom(Package),
|
||||
case compile_and_install(Module, Attrs) of
|
||||
case compile_and_install(Module, Attrs, Config) of
|
||||
ok ->
|
||||
code:add_pathsz([module_ebin_dir(Module)|module_deps_dirs(Module)]),
|
||||
ejabberd_config:reload(),
|
||||
ejabberd_config_reload(Config),
|
||||
copy_commit_json(Package, Attrs),
|
||||
case erlang:function_exported(Module, post_install, 0) of
|
||||
true -> Module:post_install();
|
||||
ModuleRuntime = get_runtime_module_name(Module),
|
||||
case erlang:function_exported(ModuleRuntime, post_install, 0) of
|
||||
true -> ModuleRuntime:post_install();
|
||||
_ -> ok
|
||||
end;
|
||||
Error ->
|
||||
@@ -242,20 +247,29 @@ install(Package) when is_binary(Package) ->
|
||||
end
|
||||
end.
|
||||
|
||||
ejabberd_config_reload(Config) when is_list(Config) ->
|
||||
%% Don't reload config when ejabberd is starting
|
||||
%% because it will be reloaded after installing
|
||||
%% all the external modules from install_contrib_modules
|
||||
ok;
|
||||
ejabberd_config_reload(undefined) ->
|
||||
ejabberd_config:reload().
|
||||
|
||||
uninstall(Module) when is_atom(Module) ->
|
||||
uninstall(misc:atom_to_binary(Module));
|
||||
uninstall(Package) when is_binary(Package) ->
|
||||
case installed(Package) of
|
||||
true ->
|
||||
Module = misc:binary_to_atom(Package),
|
||||
case erlang:function_exported(Module, pre_uninstall, 0) of
|
||||
true -> Module:pre_uninstall();
|
||||
ModuleRuntime = get_runtime_module_name(Module),
|
||||
case erlang:function_exported(ModuleRuntime, pre_uninstall, 0) of
|
||||
true -> ModuleRuntime:pre_uninstall();
|
||||
_ -> ok
|
||||
end,
|
||||
[catch gen_mod:stop_module(Host, Module)
|
||||
[catch gen_mod:stop_module(Host, ModuleRuntime)
|
||||
|| Host <- ejabberd_option:hosts()],
|
||||
code:purge(Module),
|
||||
code:delete(Module),
|
||||
code:purge(ModuleRuntime),
|
||||
code:delete(ModuleRuntime),
|
||||
[code:del_path(PathDelete) || PathDelete <- [module_ebin_dir(Module)|module_deps_dirs(Module)]],
|
||||
delete_path(module_lib_dir(Module)),
|
||||
ejabberd_config:reload();
|
||||
@@ -280,7 +294,7 @@ add_sources(Module, Path) when is_atom(Module), is_list(Path) ->
|
||||
add_sources(Package, Path) when is_binary(Package), is_list(Path) ->
|
||||
DestDir = sources_dir(),
|
||||
RepDir = filename:join(DestDir, module_name(Path)),
|
||||
delete_path(RepDir),
|
||||
delete_path(RepDir, binary_to_list(Package)),
|
||||
case filelib:ensure_dir(RepDir) of
|
||||
ok ->
|
||||
case {string:left(Path, 4), string:right(Path, 2)} of
|
||||
@@ -343,6 +357,8 @@ geturl(Url) ->
|
||||
case httpc:request(get, {Url, [UA]}, User, [{body_format, binary}], ext_mod) of
|
||||
{ok, {{_, 200, _}, Headers, Response}} ->
|
||||
{ok, Headers, Response};
|
||||
{ok, {{_, 403, Reason}, _Headers, _Response}} ->
|
||||
{error, Reason};
|
||||
{ok, {{_, Code, _}, _Headers, Response}} ->
|
||||
{error, {Code, Response}};
|
||||
{error, Reason} ->
|
||||
@@ -390,8 +406,9 @@ extract_github_master(Repos, DestDir) ->
|
||||
case extract(zip, geturl(Url++"/archive/master.zip"), DestDir) of
|
||||
ok ->
|
||||
RepDir = filename:join(DestDir, module_name(Repos)),
|
||||
file:rename(RepDir++"-master", RepDir),
|
||||
maybe_write_commit_json(Url, RepDir);
|
||||
RepDirSpec = filename:join(DestDir, module_spec_name(RepDir)),
|
||||
file:rename(RepDir++"-master", RepDirSpec),
|
||||
maybe_write_commit_json(Url, RepDirSpec);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@@ -426,6 +443,9 @@ delete_path(Path) ->
|
||||
file:delete(Path)
|
||||
end.
|
||||
|
||||
delete_path(Path, Package) ->
|
||||
delete_path(filename:join(filename:dirname(Path), Package)).
|
||||
|
||||
modules_dir() ->
|
||||
DefaultDir = filename:join(getenv("HOME"), ".ejabberd-modules"),
|
||||
getenv("CONTRIB_MODULES_PATH", DefaultDir).
|
||||
@@ -464,6 +484,14 @@ module_src_dir(Package) ->
|
||||
module_name(Id) ->
|
||||
filename:basename(filename:rootname(Id)).
|
||||
|
||||
module_spec_name(Path) ->
|
||||
case filelib:wildcard(filename:join(Path++"-master", "*.spec")) of
|
||||
"" ->
|
||||
module_name(Path);
|
||||
ModuleName ->
|
||||
filename:basename(ModuleName, ".spec")
|
||||
end.
|
||||
|
||||
module(Id) ->
|
||||
misc:binary_to_atom(iolist_to_binary(module_name(Id))).
|
||||
|
||||
@@ -483,7 +511,13 @@ modules_spec(Dir, Path) ->
|
||||
short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) ->
|
||||
{Module, proplists:get_value(summary, Attrs, "")}.
|
||||
|
||||
is_contrib_allowed() ->
|
||||
is_contrib_allowed(Config) when is_list(Config) ->
|
||||
case lists:keyfind(allow_contrib_modules, 1, Config) of
|
||||
false -> true;
|
||||
{_, false} -> false;
|
||||
{_, true} -> true
|
||||
end;
|
||||
is_contrib_allowed(undefined) ->
|
||||
ejabberd_option:allow_contrib_modules().
|
||||
|
||||
%% -- build functions
|
||||
@@ -526,7 +560,7 @@ check_sources(Module) ->
|
||||
_ -> {error, Result}
|
||||
end.
|
||||
|
||||
compile_and_install(Module, Spec) ->
|
||||
compile_and_install(Module, Spec, Config) ->
|
||||
SrcDir = module_src_dir(Module),
|
||||
LibDir = module_lib_dir(Module),
|
||||
case filelib:is_dir(SrcDir) of
|
||||
@@ -534,7 +568,7 @@ compile_and_install(Module, Spec) ->
|
||||
case compile_deps(SrcDir) of
|
||||
ok ->
|
||||
case compile(SrcDir) of
|
||||
ok -> install(Module, Spec, SrcDir, LibDir);
|
||||
ok -> install(Module, Spec, SrcDir, LibDir, Config);
|
||||
Error -> Error
|
||||
end;
|
||||
Error ->
|
||||
@@ -543,7 +577,7 @@ compile_and_install(Module, Spec) ->
|
||||
false ->
|
||||
Path = proplists:get_value(url, Spec, ""),
|
||||
case add_sources(Module, Path) of
|
||||
ok -> compile_and_install(Module, Spec);
|
||||
ok -> compile_and_install(Module, Spec, Config);
|
||||
Error -> Error
|
||||
end
|
||||
end.
|
||||
@@ -648,7 +682,7 @@ compile_elixir_file(_, File) ->
|
||||
{error, {compilation_failed, File}}.
|
||||
-endif.
|
||||
|
||||
install(Module, Spec, SrcDir, LibDir) ->
|
||||
install(Module, Spec, SrcDir, LibDir, Config) ->
|
||||
{ok, CurDir} = file:get_cwd(),
|
||||
file:set_cwd(SrcDir),
|
||||
Files1 = [{File, copy(File, filename:join(LibDir, File))}
|
||||
@@ -660,7 +694,7 @@ install(Module, Spec, SrcDir, LibDir) ->
|
||||
Errors = lists:dropwhile(fun({_, ok}) -> true;
|
||||
(_) -> false
|
||||
end, Files1++Files2++Files3),
|
||||
inform_module_configuration(Module, LibDir, Files1),
|
||||
inform_module_configuration(Module, LibDir, Files1, Config),
|
||||
Result = case Errors of
|
||||
[{F, {error, E}}|_] ->
|
||||
{error, {F, E}};
|
||||
@@ -672,24 +706,37 @@ install(Module, Spec, SrcDir, LibDir) ->
|
||||
file:set_cwd(CurDir),
|
||||
Result.
|
||||
|
||||
inform_module_configuration(Module, LibDir, Files1) ->
|
||||
inform_module_configuration(Module, LibDir, Files1, Config) ->
|
||||
Res = lists:filter(fun({[$c, $o, $n, $f |_], ok}) -> true;
|
||||
(_) -> false
|
||||
end, Files1),
|
||||
case Res of
|
||||
[{ConfigPath, ok}] ->
|
||||
AlreadyConfigured = lists:keymember(Module, 1, get_modules(Config)),
|
||||
case {Res, AlreadyConfigured} of
|
||||
{[{ConfigPath, ok}], false} ->
|
||||
FullConfigPath = filename:join(LibDir, ConfigPath),
|
||||
io:format("Module ~p has been installed and started.~n"
|
||||
"It's configured in the file:~n ~s~n"
|
||||
"Configure the module in that file, or remove it~n"
|
||||
"and configure in your main ejabberd.yml~n",
|
||||
[Module, FullConfigPath]);
|
||||
[] ->
|
||||
{[{ConfigPath, ok}], true} ->
|
||||
FullConfigPath = filename:join(LibDir, ConfigPath),
|
||||
file:rename(FullConfigPath, FullConfigPath++".example"),
|
||||
io:format("Module ~p has been installed and started.~n"
|
||||
"The ~p configuration in your ejabberd.yml is used.~n",
|
||||
[Module, Module]);
|
||||
{[], _} ->
|
||||
io:format("Module ~p has been installed.~n"
|
||||
"Now you can configure it in your ejabberd.yml~n",
|
||||
[Module])
|
||||
end.
|
||||
|
||||
get_modules(Config) when is_list(Config) ->
|
||||
{modules, Modules} = lists:keyfind(modules, 1, Config),
|
||||
Modules;
|
||||
get_modules(undefined) ->
|
||||
ejabberd_config:get_option(modules).
|
||||
|
||||
%% -- minimalist rebar spec parser, only support git
|
||||
|
||||
fetch_rebar_deps(SrcDir) ->
|
||||
@@ -766,10 +813,14 @@ maybe_write_commit_json(Url, RepDir) ->
|
||||
write_commit_json(Url, RepDir) ->
|
||||
Url2 = string_replace(Url, "https://github.com", "https://api.github.com/repos"),
|
||||
BranchUrl = lists:flatten(Url2 ++ "/branches/master"),
|
||||
{ok, _Headers, Body} = geturl(BranchUrl),
|
||||
{ok, F} = file:open(filename:join(RepDir, "COMMIT.json"), [raw, write]),
|
||||
file:write(F, Body),
|
||||
file:close(F).
|
||||
case geturl(BranchUrl) of
|
||||
{ok, _Headers, Body} ->
|
||||
{ok, F} = file:open(filename:join(RepDir, "COMMIT.json"), [raw, write]),
|
||||
file:write(F, Body),
|
||||
file:close(F);
|
||||
{error, Reason} ->
|
||||
Reason
|
||||
end.
|
||||
|
||||
find_commit_json(Attrs) ->
|
||||
{_, FromPath} = lists:keyfind(path, 1, Attrs),
|
||||
@@ -988,6 +1039,26 @@ get_module_status(Module) ->
|
||||
unknown
|
||||
end.
|
||||
|
||||
%% When a module named mod_whatever in ejabberd-modules
|
||||
%% is written in Elixir, its runtime name is 'Elixir.ModWhatever'
|
||||
get_runtime_module_name(Module) ->
|
||||
case is_elixir_module(Module) of
|
||||
true -> elixir_module_name(Module);
|
||||
false -> Module
|
||||
end.
|
||||
|
||||
is_elixir_module(Module) ->
|
||||
LibDir = module_src_dir(Module),
|
||||
Lib = filename:join(LibDir, "lib"),
|
||||
Src = filename:join(LibDir, "src"),
|
||||
case {filelib:wildcard(Lib++"/*.{ex}"),
|
||||
filelib:wildcard(Src++"/*.{erl}")} of
|
||||
{[_ | _], []} ->
|
||||
true;
|
||||
{[], [_ | _]} ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Converts mod_some_thing to Elixir.ModSomeThing
|
||||
elixir_module_name(ModAtom) ->
|
||||
list_to_atom("Elixir." ++ elixir_module_name("_" ++ atom_to_list(ModAtom), [])).
|
||||
@@ -1213,3 +1284,14 @@ list_modules_parse_uninstall(Query) ->
|
||||
end,
|
||||
installed()),
|
||||
ok.
|
||||
|
||||
install_contrib_modules(Modules, Config) ->
|
||||
lists:filter(fun(Module) ->
|
||||
case install(misc:atom_to_binary(Module), Config) of
|
||||
{error, conflict} ->
|
||||
false;
|
||||
ok ->
|
||||
true
|
||||
end
|
||||
end,
|
||||
Modules).
|
||||
|
||||
+68
-8
@@ -49,6 +49,7 @@
|
||||
-record(ejabberd_module,
|
||||
{module_host = {undefined, <<"">>} :: {atom(), binary()},
|
||||
opts = [] :: opts() | '_' | '$2',
|
||||
registrations = [] :: [registration()],
|
||||
order = 0 :: integer()}).
|
||||
|
||||
-type opts() :: #{atom() => term()}.
|
||||
@@ -57,7 +58,17 @@
|
||||
value => string() | binary()}.
|
||||
-type opt_doc() :: {atom(), opt_desc()} | {atom(), opt_desc(), [opt_doc()]}.
|
||||
|
||||
-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
|
||||
-type component() :: ejabberd_sm | ejabberd_local.
|
||||
-type registration() ::
|
||||
{hook, atom(), atom(), integer()} |
|
||||
{hook, atom(), module(), atom(), integer()} |
|
||||
{iq_handler, component(), binary(), atom()} |
|
||||
{iq_handler, component(), binary(), module(), atom()}.
|
||||
-export_type([registration/0]).
|
||||
|
||||
-callback start(binary(), opts()) ->
|
||||
ok | {ok, pid()} |
|
||||
{ok, [registration()]} | {error, term()}.
|
||||
-callback stop(binary()) -> any().
|
||||
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}.
|
||||
-callback mod_opt_type(atom()) -> econf:validator().
|
||||
@@ -155,6 +166,10 @@ start_module(Host, Module, Opts, Order) ->
|
||||
try case Module:start(Host, Opts) of
|
||||
ok -> ok;
|
||||
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
|
||||
{ok, Registrations} when is_list(Registrations) ->
|
||||
store_options(Host, Module, Opts, Registrations, Order),
|
||||
add_registrations(Host, Module, Registrations),
|
||||
ok;
|
||||
Err ->
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
erlang:error({bad_return, Module, Err})
|
||||
@@ -246,9 +261,21 @@ update_module(Host, Module, Opts) ->
|
||||
|
||||
-spec store_options(binary(), module(), opts(), integer()) -> true.
|
||||
store_options(Host, Module, Opts, Order) ->
|
||||
case ets:lookup(ejabberd_modules, {Module, Host}) of
|
||||
[M] ->
|
||||
store_options(
|
||||
Host, Module, Opts, M#ejabberd_module.registrations, Order);
|
||||
[] ->
|
||||
store_options(Host, Module, Opts, [], Order)
|
||||
end.
|
||||
|
||||
-spec store_options(binary(), module(), opts(), [registration()], integer()) -> true.
|
||||
store_options(Host, Module, Opts, Registrations, Order) ->
|
||||
ets:insert(ejabberd_modules,
|
||||
#ejabberd_module{module_host = {Module, Host},
|
||||
opts = Opts, order = Order}).
|
||||
opts = Opts,
|
||||
registrations = Registrations,
|
||||
order = Order}).
|
||||
|
||||
maybe_halt_ejabberd() ->
|
||||
case is_app_running(ejabberd) of
|
||||
@@ -281,16 +308,21 @@ stop_modules(Host) ->
|
||||
stop_module_keep_config(Host, Module)
|
||||
end, Modules).
|
||||
|
||||
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
|
||||
-spec stop_module(binary(), atom()) -> error | ok.
|
||||
stop_module(Host, Module) ->
|
||||
case stop_module_keep_config(Host, Module) of
|
||||
error -> error;
|
||||
ok -> ok
|
||||
end.
|
||||
stop_module_keep_config(Host, Module).
|
||||
|
||||
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
|
||||
stop_module_keep_config(Host, Module) ->
|
||||
?DEBUG("Stopping ~ts at ~ts", [Module, Host]),
|
||||
Registrations =
|
||||
case ets:lookup(ejabberd_modules, {Module, Host}) of
|
||||
[M] ->
|
||||
M#ejabberd_module.registrations;
|
||||
[] ->
|
||||
[]
|
||||
end,
|
||||
del_registrations(Host, Module, Registrations),
|
||||
try Module:stop(Host) of
|
||||
_ ->
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
@@ -303,6 +335,34 @@ stop_module_keep_config(Host, Module) ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec add_registrations(binary(), module(), [registration()]) -> ok.
|
||||
add_registrations(Host, Module, Registrations) ->
|
||||
lists:foreach(
|
||||
fun({hook, Hook, Function, Seq}) ->
|
||||
ejabberd_hooks:add(Hook, Host, Module, Function, Seq);
|
||||
({hook, Hook, Module1, Function, Seq}) ->
|
||||
ejabberd_hooks:add(Hook, Host, Module1, Function, Seq);
|
||||
({iq_handler, Component, NS, Function}) ->
|
||||
gen_iq_handler:add_iq_handler(
|
||||
Component, Host, NS, Module, Function);
|
||||
({iq_handler, Component, NS, Module1, Function}) ->
|
||||
gen_iq_handler:add_iq_handler(
|
||||
Component, Host, NS, Module1, Function)
|
||||
end, Registrations).
|
||||
|
||||
-spec del_registrations(binary(), module(), [registration()]) -> ok.
|
||||
del_registrations(Host, Module, Registrations) ->
|
||||
lists:foreach(
|
||||
fun({hook, Hook, Function, Seq}) ->
|
||||
ejabberd_hooks:delete(Hook, Host, Module, Function, Seq);
|
||||
({hook, Hook, Module1, Function, Seq}) ->
|
||||
ejabberd_hooks:delete(Hook, Host, Module1, Function, Seq);
|
||||
({iq_handler, Component, NS, _Function}) ->
|
||||
gen_iq_handler:remove_iq_handler(Component, Host, NS);
|
||||
({iq_handler, Component, NS, _Module, _Function}) ->
|
||||
gen_iq_handler:remove_iq_handler(Component, Host, NS)
|
||||
end, Registrations).
|
||||
|
||||
-spec get_opt(atom(), opts()) -> any().
|
||||
get_opt(Opt, Opts) ->
|
||||
maps:get(Opt, Opts).
|
||||
@@ -431,7 +491,7 @@ is_equal_opt(Opt, NewOpts, OldOpts) ->
|
||||
%%%===================================================================
|
||||
-spec format_module_error(atom(), start | reload, non_neg_integer(), opts(),
|
||||
error | exit | throw, any(),
|
||||
[erlang:stack_item()]) -> iolist().
|
||||
[tuple()]) -> iolist().
|
||||
format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
|
||||
case {Class, Reason} of
|
||||
{error, {bad_return, Module, {error, _} = Err}} ->
|
||||
|
||||
+13
-42
@@ -42,49 +42,20 @@
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_COMMANDS, ?MODULE, process_local_iq),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_COMMANDS, ?MODULE, process_sm_iq),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||
get_local_identity, 99),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
get_local_features, 99),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
|
||||
get_local_commands, 99),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 99),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 99),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_commands, 99),
|
||||
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
|
||||
ping_item, 100),
|
||||
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
|
||||
ping_command, 100).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{iq_handler, ejabberd_local, ?NS_COMMANDS, process_local_iq},
|
||||
{iq_handler, ejabberd_sm, ?NS_COMMANDS, process_sm_iq},
|
||||
{hook, disco_local_identity, get_local_identity, 99},
|
||||
{hook, disco_local_features, get_local_features, 99},
|
||||
{hook, disco_local_items, get_local_commands, 99},
|
||||
{hook, disco_sm_identity, get_sm_identity, 99},
|
||||
{hook, disco_sm_features, get_sm_features, 99},
|
||||
{hook, disco_sm_items, get_sm_commands, 99},
|
||||
{hook, adhoc_local_items, ping_item, 100},
|
||||
{hook, adhoc_local_commands, ping_command, 100}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(adhoc_local_commands, Host,
|
||||
?MODULE, ping_command, 100),
|
||||
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
|
||||
ping_item, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_commands, 99),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 99),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 99),
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
|
||||
get_local_commands, 99),
|
||||
ejabberd_hooks:delete(disco_local_features, Host,
|
||||
?MODULE, get_local_features, 99),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, get_local_identity, 99),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_COMMANDS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_COMMANDS).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
+38
-38
@@ -169,10 +169,12 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
|
||||
desc = "Delete users that didn't log in last days, or that never logged",
|
||||
longdesc = "To protect admin accounts, configure this for example:\n"
|
||||
"```\n"
|
||||
"access_rules:\n"
|
||||
" protect_old_users:\n"
|
||||
" - allow: admin\n"
|
||||
" - deny: all\n",
|
||||
" - deny: all\n"
|
||||
"```\n",
|
||||
module = ?MODULE, function = delete_old_users,
|
||||
args = [{days, integer}],
|
||||
args_example = [30],
|
||||
@@ -183,10 +185,12 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
|
||||
desc = "Delete users that didn't log in last days in vhost, or that never logged",
|
||||
longdesc = "To protect admin accounts, configure this for example:\n"
|
||||
"```\n"
|
||||
"access_rules:\n"
|
||||
" delete_old_users:\n"
|
||||
" - deny: admin\n"
|
||||
" - allow: all\n",
|
||||
" - allow: all\n"
|
||||
"```\n",
|
||||
module = ?MODULE, function = delete_old_users_vhost,
|
||||
args = [{host, binary}, {days, integer}],
|
||||
args_example = [<<"myserver.com">>, 30],
|
||||
@@ -215,7 +219,8 @@ get_commands_spec() ->
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
#ejabberd_commands{name = check_password_hash, tags = [accounts],
|
||||
desc = "Check if the password hash is correct",
|
||||
longdesc = "Allows hash methods from crypto application",
|
||||
longdesc = "Allows hash methods from the Erlang/OTP "
|
||||
"[crypto](https://www.erlang.org/doc/man/crypto) application.",
|
||||
module = ?MODULE, function = check_password_hash,
|
||||
args = [{user, binary}, {host, binary}, {passwordhash, binary},
|
||||
{hashmethod, binary}],
|
||||
@@ -390,14 +395,14 @@ get_commands_spec() ->
|
||||
"and its presence (show and status message) "
|
||||
"for a given user.",
|
||||
longdesc =
|
||||
"The 'jid' value contains the user jid "
|
||||
"with resource.\nThe 'show' value contains "
|
||||
"The `jid` value contains the user JID "
|
||||
"with resource.\n\nThe `show` value contains "
|
||||
"the user presence flag. It can take "
|
||||
"limited values:\n - available\n - chat "
|
||||
"(Free for chat)\n - away\n - dnd (Do "
|
||||
"not disturb)\n - xa (Not available, "
|
||||
"extended away)\n - unavailable (Not "
|
||||
"connected)\n\n'status' is a free text "
|
||||
"limited values:\n\n - `available`\n - `chat` "
|
||||
"(Free for chat)\n - `away`\n - `dnd` (Do "
|
||||
"not disturb)\n - `xa` (Not available, "
|
||||
"extended away)\n - `unavailable` (Not "
|
||||
"connected)\n\n`status` is a free text "
|
||||
"defined by the user client.",
|
||||
module = ?MODULE, function = get_presence,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
@@ -421,8 +426,8 @@ get_commands_spec() ->
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
|
||||
<<"available">>,<<"away">>,<<"BB">>, <<"7">>],
|
||||
args_desc = ["User name", "Server name", "Resource",
|
||||
"Type: available, error, probe...",
|
||||
"Show: away, chat, dnd, xa.", "Status text",
|
||||
"Type: `available`, `error`, `probe`...",
|
||||
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
|
||||
"Priority, provide this value as an integer"],
|
||||
result = {res, rescode}},
|
||||
|
||||
@@ -485,7 +490,7 @@ get_commands_spec() ->
|
||||
|
||||
#ejabberd_commands{name = add_rosteritem, tags = [roster],
|
||||
desc = "Add an item to a user's roster (supports ODBC)",
|
||||
longdesc = "Group can be several groups separated by ; for example: \"g1;g2;g3\"",
|
||||
longdesc = "Group can be several groups separated by `;` for example: `g1;g2;g3`",
|
||||
module = ?MODULE, function = add_rosteritem,
|
||||
args = [{localuser, binary}, {localhost, binary},
|
||||
{user, binary}, {host, binary},
|
||||
@@ -569,7 +574,11 @@ get_commands_spec() ->
|
||||
]}}
|
||||
}}},
|
||||
#ejabberd_commands{name = get_roster, tags = [roster],
|
||||
desc = "Get roster of a local user",
|
||||
desc = "Get list of contacts in a local user roster",
|
||||
longdesc =
|
||||
"Subscription can be: \"none\", \"from\", \"to\", \"both\". "
|
||||
"Pending can be: \"in\", \"out\", \"none\".",
|
||||
note = "improved in 23.10",
|
||||
policy = user,
|
||||
module = ?MODULE, function = get_roster,
|
||||
args = [],
|
||||
@@ -578,8 +587,8 @@ get_commands_spec() ->
|
||||
{jid, string},
|
||||
{nick, string},
|
||||
{subscription, string},
|
||||
{ask, string},
|
||||
{group, string}
|
||||
{pending, string},
|
||||
{groups, {list, {group, string}}}
|
||||
]}}}}},
|
||||
#ejabberd_commands{name = push_roster, tags = [roster],
|
||||
desc = "Push template roster from file to a user",
|
||||
@@ -616,7 +625,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = get_last, tags = [last],
|
||||
desc = "Get last activity information",
|
||||
longdesc = "Timestamp is UTC and XEP-0082 format, for example: "
|
||||
"2017-02-23T22:25:28.063062Z ONLINE",
|
||||
"`2017-02-23T22:25:28.063062Z ONLINE`",
|
||||
module = ?MODULE, function = get_last,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>],
|
||||
@@ -630,7 +639,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = set_last, tags = [last],
|
||||
desc = "Set last activity information",
|
||||
longdesc = "Timestamp is the seconds since "
|
||||
"1970-01-01 00:00:00 UTC, for example: date +%s",
|
||||
"`1970-01-01 00:00:00 UTC`. For example value see `date +%s`",
|
||||
module = ?MODULE, function = set_last,
|
||||
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
|
||||
@@ -657,11 +666,11 @@ get_commands_spec() ->
|
||||
desc = "Create a Shared Roster Group",
|
||||
longdesc = "If you want to specify several group "
|
||||
"identifiers in the Display argument,\n"
|
||||
"put \\ \" around the argument and\nseparate the "
|
||||
"identifiers with \\ \\ n\n"
|
||||
"put `\\ \"` around the argument and\nseparate the "
|
||||
"identifiers with `\\ \\ n`\n"
|
||||
"For example:\n"
|
||||
" ejabberdctl srg_create group3 myserver.com "
|
||||
"name desc \\\"group1\\\\ngroup2\\\"",
|
||||
" `ejabberdctl srg_create group3 myserver.com "
|
||||
"name desc \\\"group1\\\\ngroup2\\\"`",
|
||||
note = "changed in 21.07",
|
||||
module = ?MODULE, function = srg_create,
|
||||
args = [{group, binary}, {host, binary},
|
||||
@@ -734,7 +743,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = send_message, tags = [stanza],
|
||||
desc = "Send a message to a local or remote bare of full JID",
|
||||
longdesc = "When sending a groupchat message to a MUC room, "
|
||||
"FROM must be the full JID of a room occupant, "
|
||||
"`from` must be the full JID of a room occupant, "
|
||||
"or the bare JID of a MUC service admin, "
|
||||
"or the bare JID of a MUC/Sub subscribed user.",
|
||||
module = ?MODULE, function = send_message,
|
||||
@@ -742,13 +751,13 @@ get_commands_spec() ->
|
||||
{subject, binary}, {body, binary}],
|
||||
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
|
||||
<<"Restart">>, <<"In 5 minutes">>],
|
||||
args_desc = ["Message type: normal, chat, headline, groupchat", "Sender JID",
|
||||
args_desc = ["Message type: `normal`, `chat`, `headline`, `groupchat`", "Sender JID",
|
||||
"Receiver JID", "Subject, or empty string", "Body"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
||||
desc = "Send a stanza from an existing C2S session",
|
||||
longdesc = "USER@HOST/RESOURCE must be an existing C2S session."
|
||||
" As an alternative, use send_stanza instead.",
|
||||
longdesc = "`user`@`host`/`resource` must be an existing C2S session."
|
||||
" As an alternative, use http://./#send-stanza[send_stanza] instead.",
|
||||
module = ?MODULE, function = send_stanza_c2s,
|
||||
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
|
||||
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
|
||||
@@ -1329,25 +1338,16 @@ get_roster(User, Server) ->
|
||||
make_roster_xmlrpc(Items)
|
||||
end.
|
||||
|
||||
%% Note: if a contact is in several groups, the contact is returned
|
||||
%% several times, each one in a different group.
|
||||
make_roster_xmlrpc(Roster) ->
|
||||
lists:foldl(
|
||||
fun(#roster_item{jid = JID, name = Nick, subscription = Sub, ask = Ask} = Item, Res) ->
|
||||
lists:map(
|
||||
fun(#roster_item{jid = JID, name = Nick, subscription = Sub, ask = Ask, groups = Groups}) ->
|
||||
JIDS = jid:encode(JID),
|
||||
Subs = atom_to_list(Sub),
|
||||
Asks = atom_to_list(Ask),
|
||||
Groups = case Item#roster_item.groups of
|
||||
[] -> [<<>>];
|
||||
Gs -> Gs
|
||||
end,
|
||||
ItemsX = [{JIDS, Nick, Subs, Asks, Group} || Group <- Groups],
|
||||
ItemsX ++ Res
|
||||
{JIDS, Nick, Subs, Asks, Groups}
|
||||
end,
|
||||
[],
|
||||
Roster).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Push Roster from file
|
||||
%%-----------------------------
|
||||
|
||||
@@ -40,9 +40,26 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
init(Host, _Opts) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"motd">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"xml">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
set_motd_users(LServer, USRs) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
|
||||
+7
-16
@@ -43,23 +43,14 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE,
|
||||
pubsub_publish_item, 50),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_iq_convert, 30),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_iq_publish, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, pubsub_publish_item, pubsub_publish_item, 50},
|
||||
{hook, vcard_iq_set, vcard_iq_convert, 30},
|
||||
{hook, vcard_iq_set, vcard_iq_publish, 100},
|
||||
{hook, disco_sm_features, get_sm_features, 50}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE,
|
||||
pubsub_publish_item, 50),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_convert, 30),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_publish, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
@@ -45,21 +45,13 @@
|
||||
%%%===================================================================
|
||||
%%% Callbacks and hooks
|
||||
%%%===================================================================
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(user_receive_packet, Host,
|
||||
?MODULE, filter_packet, 25),
|
||||
ejabberd_hooks:add(roster_in_subscription, Host,
|
||||
?MODULE, filter_subscription, 25),
|
||||
ejabberd_hooks:add(offline_message_hook, Host,
|
||||
?MODULE, filter_offline_msg, 25).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, user_receive_packet, filter_packet, 25},
|
||||
{hook, roster_in_subscription, filter_subscription, 25},
|
||||
{hook, offline_message_hook, filter_offline_msg, 25}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(user_receive_packet, Host,
|
||||
?MODULE, filter_packet, 25),
|
||||
ejabberd_hooks:delete(roster_in_subscription, Host,
|
||||
?MODULE, filter_subscription, 25),
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, filter_offline_msg, 25).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
@@ -37,14 +37,12 @@
|
||||
-include("mod_privacy.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_BLOCKING, ?MODULE, process_iq).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, disco_local_features, disco_features, 50},
|
||||
{iq_handler, ejabberd_sm, ?NS_BLOCKING, process_iq}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
ejabberd_sql_schema:update_schema(
|
||||
ejabberd_config:get_myname(), ?MODULE, schemas()),
|
||||
Node = erlang:atom_to_binary(node(), latin1),
|
||||
?DEBUG("Cleaning SQL 'bosh' table...", []),
|
||||
case ejabberd_sql:sql_query(
|
||||
@@ -48,6 +50,20 @@ init() ->
|
||||
Err
|
||||
end.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"bosh">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"sid">>, type = text},
|
||||
#sql_column{name = <<"node">>, type = text},
|
||||
#sql_column{name = <<"pid">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"sid">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
open_session(SID, Pid) ->
|
||||
PidS = misc:encode_pid(Pid),
|
||||
Node = erlang:atom_to_binary(node(Pid), latin1),
|
||||
|
||||
+17
-1
@@ -37,9 +37,25 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
init(Host, _Opts) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"caps_features">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"node">>, type = text},
|
||||
#sql_column{name = <<"subnode">>, type = text},
|
||||
#sql_column{name = <<"feature">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"node">>, <<"subnode">>]}]}]}].
|
||||
|
||||
caps_read(LServer, {Node, SubNode}) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
|
||||
+11
-18
@@ -48,25 +48,18 @@
|
||||
-type direction() :: sent | received.
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
|
||||
ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, disco_local_features, disco_features, 50},
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
{hook, user_send_packet, user_send_packet, 89},
|
||||
{hook, user_receive_packet, user_receive_packet, 89},
|
||||
{hook, c2s_copy_session, c2s_copy_session, 50},
|
||||
{hook, c2s_session_resumed, c2s_session_resumed, 50},
|
||||
{hook, c2s_session_opened, c2s_session_opened, 50},
|
||||
{iq_handler, ejabberd_sm, ?NS_CARBONS_2, iq_handler}]}.
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
|
||||
ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
+13
-43
@@ -44,50 +44,20 @@
|
||||
-include("translate.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
|
||||
get_local_items, 50),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
get_local_features, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||
get_local_identity, 50),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_items, 50),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 50),
|
||||
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
|
||||
adhoc_local_items, 50),
|
||||
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
|
||||
adhoc_local_commands, 50),
|
||||
ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE,
|
||||
adhoc_sm_items, 50),
|
||||
ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE,
|
||||
adhoc_sm_commands, 50),
|
||||
ok.
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, disco_local_items, get_local_items, 50},
|
||||
{hook, disco_local_features, get_local_features, 50},
|
||||
{hook, disco_local_identity, get_local_identity, 50},
|
||||
{hook, disco_sm_items, get_sm_items, 50},
|
||||
{hook, disco_sm_features, get_sm_features, 50},
|
||||
{hook, disco_sm_identity, get_sm_identity, 50},
|
||||
{hook, adhoc_local_items, adhoc_local_items, 50},
|
||||
{hook, adhoc_local_commands, adhoc_local_commands, 50},
|
||||
{hook, adhoc_sm_items, adhoc_sm_items, 50},
|
||||
{hook, adhoc_sm_commands, adhoc_sm_commands, 50}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE,
|
||||
adhoc_sm_commands, 50),
|
||||
ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE,
|
||||
adhoc_sm_items, 50),
|
||||
ejabberd_hooks:delete(adhoc_local_commands, Host,
|
||||
?MODULE, adhoc_local_commands, 50),
|
||||
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
|
||||
adhoc_local_items, 50),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 50),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_items, 50),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, get_local_identity, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host,
|
||||
?MODULE, get_local_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
|
||||
get_local_items, 50).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
+11
-47
@@ -51,16 +51,6 @@
|
||||
-export_type([features_acc/0, items_acc/0]).
|
||||
|
||||
start(Host, Opts) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS, ?MODULE,
|
||||
process_local_iq_items),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, ?MODULE,
|
||||
process_local_iq_info),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, ?MODULE, process_sm_iq_items),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, ?MODULE, process_sm_iq_info),
|
||||
catch ets:new(disco_extra_domains,
|
||||
[named_table, ordered_set, public,
|
||||
{heir, erlang:group_leader(), none}]),
|
||||
@@ -69,45 +59,19 @@ start(Host, Opts) ->
|
||||
register_extra_domain(Host, Domain)
|
||||
end,
|
||||
ExtraDomains),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
|
||||
get_local_services, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
get_local_features, 100),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||
get_local_identity, 100),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_items, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 100),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 100),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info,
|
||||
100),
|
||||
ok.
|
||||
{ok, [{iq_handler, ejabberd_local, ?NS_DISCO_ITEMS, process_local_iq_items},
|
||||
{iq_handler, ejabberd_local, ?NS_DISCO_INFO, process_local_iq_info},
|
||||
{iq_handler, ejabberd_sm, ?NS_DISCO_ITEMS, process_sm_iq_items},
|
||||
{iq_handler, ejabberd_sm, ?NS_DISCO_INFO, process_sm_iq_info},
|
||||
{hook, disco_local_items, get_local_services, 100},
|
||||
{hook, disco_local_features, get_local_features, 100},
|
||||
{hook, disco_local_identity, get_local_identity, 100},
|
||||
{hook, disco_sm_items, get_sm_items, 100},
|
||||
{hook, disco_sm_features, get_sm_features, 100},
|
||||
{hook, disco_sm_identity, get_sm_identity, 100},
|
||||
{hook, disco_info, get_info, 100}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||
get_sm_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
|
||||
get_sm_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, get_local_identity, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host,
|
||||
?MODULE, get_local_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
|
||||
get_local_services, 100),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
|
||||
get_info, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO),
|
||||
catch ets:match_delete(disco_extra_domains,
|
||||
{{'_', Host}}),
|
||||
ok.
|
||||
|
||||
+6
-29
@@ -46,15 +46,14 @@
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, _Opts) ->
|
||||
register_iq_handlers(Host),
|
||||
register_hooks(Host).
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, [gen_mod:registration()]}.
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{iq_handler, ejabberd_local, ?NS_JIDPREP_0, process_iq},
|
||||
{hook, disco_local_features, disco_local_features, 50}]}.
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
unregister_hooks(Host),
|
||||
unregister_iq_handlers(Host).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
@@ -88,19 +87,6 @@ mod_doc() ->
|
||||
"be used to control who is allowed to use this "
|
||||
"service. The default value is 'local'.")}}]}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Register/unregister hooks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_hooks(binary()) -> ok.
|
||||
register_hooks(Host) ->
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
disco_local_features, 50).
|
||||
|
||||
-spec unregister_hooks(binary()) -> ok.
|
||||
unregister_hooks(Host) ->
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
|
||||
disco_local_features, 50).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Service discovery.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -123,15 +109,6 @@ disco_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% IQ handlers.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_iq_handlers(binary()) -> ok.
|
||||
register_iq_handlers(Host) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_JIDPREP_0, ?MODULE, process_iq).
|
||||
|
||||
-spec unregister_iq_handlers(binary()) -> ok.
|
||||
unregister_iq_handlers(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_JIDPREP_0).
|
||||
|
||||
-spec process_iq(iq()) -> iq().
|
||||
process_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
|
||||
|
||||
+8
-25
@@ -62,32 +62,15 @@ start(Host, Opts) ->
|
||||
Mod = gen_mod:db_mod(Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_LAST, ?MODULE, process_local_iq),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST, ?MODULE, process_sm_iq),
|
||||
ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
|
||||
privacy_check_packet, 30),
|
||||
ejabberd_hooks:add(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
|
||||
on_presence_update, 50).
|
||||
{ok, [{iq_handler, ejabberd_local, ?NS_LAST, process_local_iq},
|
||||
{iq_handler, ejabberd_sm, ?NS_LAST, process_sm_iq},
|
||||
{hook, privacy_check_packet, privacy_check_packet, 30},
|
||||
{hook, register_user, register_user, 50},
|
||||
{hook, remove_user, remove_user, 50},
|
||||
{hook, unset_presence_hook, on_presence_update, 50}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(register_user, Host, ?MODULE,
|
||||
register_user, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, Host,
|
||||
?MODULE, on_presence_update, 50),
|
||||
ejabberd_hooks:delete(privacy_check_packet, Host, ?MODULE,
|
||||
privacy_check_packet, 30),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
|
||||
?NS_LAST),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
||||
?NS_LAST).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
|
||||
|
||||
+17
-1
@@ -38,9 +38,25 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
init(Host, _Opts) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"last">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"seconds">>, type = text},
|
||||
#sql_column{name = <<"state">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
get_last(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
|
||||
+5
-10
@@ -37,17 +37,12 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(c2s_unauthenticated_packet, Host, ?MODULE,
|
||||
c2s_unauthenticated_packet, 50),
|
||||
ejabberd_hooks:add(c2s_pre_auth_features, Host, ?MODULE,
|
||||
c2s_stream_features, 50).
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, c2s_unauthenticated_packet, c2s_unauthenticated_packet, 50},
|
||||
{hook, c2s_pre_auth_features, c2s_stream_features, 50}]}.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(c2s_unauthenticated_packet, Host, ?MODULE,
|
||||
c2s_unauthenticated_packet, 50),
|
||||
ejabberd_hooks:delete(c2s_pre_auth_features, Host, ?MODULE,
|
||||
c2s_stream_features, 50).
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
+13
-7
@@ -354,11 +354,15 @@ remove_mam_for_user_with_peer(User, Server, Peer) ->
|
||||
{error, <<"Invalid peer JID">>}
|
||||
end.
|
||||
|
||||
-spec remove_message_from_archive(User :: binary(), Server :: binary(), StanzaId :: integer()) ->
|
||||
-spec remove_message_from_archive(
|
||||
User :: binary() | {User :: binary(), Host :: binary()},
|
||||
Server :: binary(), StanzaId :: integer()) ->
|
||||
ok | {error, binary()}.
|
||||
remove_message_from_archive(User, Server, StanzaId) ->
|
||||
remove_message_from_archive(User, Server, StanzaId) when is_binary(User) ->
|
||||
remove_message_from_archive({User, Server}, Server, StanzaId);
|
||||
remove_message_from_archive({User, Host}, Server, StanzaId) ->
|
||||
Mod = gen_mod:db_mod(Server, ?MODULE),
|
||||
case Mod:remove_from_archive(User, Server, StanzaId) of
|
||||
case Mod:remove_from_archive(User, Host, StanzaId) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, Bin} when is_binary(Bin) ->
|
||||
@@ -461,6 +465,8 @@ offline_message({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
|
||||
|
||||
-spec muc_filter_message(message(), mod_muc_room:state(),
|
||||
binary()) -> message().
|
||||
muc_filter_message(#message{meta = #{mam_ignore := true}} = Pkt, _MUCState, _FromNick) ->
|
||||
Pkt;
|
||||
muc_filter_message(#message{from = From} = Pkt,
|
||||
#state{config = Config, jid = RoomJID} = MUCState,
|
||||
FromNick) ->
|
||||
@@ -1464,9 +1470,9 @@ get_commands_spec() ->
|
||||
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
|
||||
desc = "Delete MAM messages older than DAYS",
|
||||
longdesc = "Valid message TYPEs: "
|
||||
"\"chat\", \"groupchat\", \"all\".",
|
||||
"`chat`, `groupchat`, `all`.",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args_desc = ["Type of messages to delete (chat, groupchat, all)",
|
||||
args_desc = ["Type of messages to delete (`chat`, `groupchat`, `all`)",
|
||||
"Days to keep messages"],
|
||||
args_example = [<<"all">>, 31],
|
||||
args = [{type, binary}, {days, integer}],
|
||||
@@ -1475,10 +1481,10 @@ get_commands_spec() ->
|
||||
desc = "Delete MAM messages older than DAYS",
|
||||
note = "added in 22.05",
|
||||
longdesc = "Valid message TYPEs: "
|
||||
"\"chat\", \"groupchat\", \"all\".",
|
||||
"`chat`, `groupchat`, `all`.",
|
||||
module = ?MODULE, function = delete_old_messages_batch,
|
||||
args_desc = ["Name of host where messages should be deleted",
|
||||
"Type of messages to delete (chat, groupchat, all)",
|
||||
"Type of messages to delete (`chat`, `groupchat`, `all`)",
|
||||
"Days to keep messages",
|
||||
"Number of messages to delete per batch",
|
||||
"Desired rate of messages to delete per minute"],
|
||||
|
||||
+51
-1
@@ -43,9 +43,59 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
init(Host, _Opts) ->
|
||||
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
|
||||
ok.
|
||||
|
||||
schemas() ->
|
||||
[#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"archive">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"timestamp">>, type = bigint},
|
||||
#sql_column{name = <<"peer">>, type = text},
|
||||
#sql_column{name = <<"bare_peer">>, type = text},
|
||||
#sql_column{name = <<"xml">>, type = {text, big}},
|
||||
#sql_column{name = <<"txt">>, type = {text, big}},
|
||||
#sql_column{name = <<"id">>, type = bigserial},
|
||||
#sql_column{name = <<"kind">>, type = {text, 10}},
|
||||
#sql_column{name = <<"nick">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"timestamp">>]}
|
||||
],
|
||||
post_create =
|
||||
fun(mysql, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
<<"CREATE FULLTEXT INDEX i_archive_txt ON archive(txt);">>);
|
||||
(_, _) ->
|
||||
ok
|
||||
end},
|
||||
#sql_table{
|
||||
name = <<"archive_prefs">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"def">>, type = text},
|
||||
#sql_column{name = <<"always">>, type = text},
|
||||
#sql_column{name = <<"never">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>],
|
||||
unique = true}]}]}].
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user