Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d576902bd4 | |||
| a3da27e917 | |||
| 3f015c829c | |||
| 2732c8f6fc | |||
| 3192687334 | |||
| 126653e01b | |||
| ce7acafe37 | |||
| 216a0c97b9 | |||
| 3c8308bb8d | |||
| 1c6aa5e84e | |||
| 609a1d07cf | |||
| 8c026582ab | |||
| 81df1ae3af | |||
| 22435ca562 | |||
| f464189819 | |||
| 89e504c55f | |||
| c1d3d1318e | |||
| d120e0ad91 | |||
| c17ec50e3a | |||
| 368ba3fc55 | |||
| cd098c5adc | |||
| 221e58fff0 | |||
| 9b043ae276 | |||
| abc09054e5 | |||
| 07a193d4dc | |||
| cf09ed2df2 | |||
| 0cc1ae0a6a | |||
| e1efd29156 | |||
| 05feab35c4 | |||
| 2198fbce97 | |||
| b8ab80d1f3 | |||
| 30e5c9bd3e | |||
| 251756de00 | |||
| 3e987d3bae | |||
| 138cc25355 | |||
| a15f186253 | |||
| 59ec3d36f4 | |||
| d7250111ce | |||
| adfb924808 | |||
| 48f2adde98 | |||
| c378ea403e | |||
| 692ccd2e20 | |||
| 5882c9b456 | |||
| db41643bea | |||
| cb076924cc | |||
| cce4056040 | |||
| 7ad525b542 | |||
| 5bf64381cb | |||
| f435d0a103 | |||
| e4d21c1941 | |||
| 5414cbe821 | |||
| e6b1521b29 | |||
| 2a7d9d93c8 | |||
| dfd2045523 | |||
| 9e35af54e0 | |||
| d87151aee6 | |||
| 4ecd8a0780 | |||
| 803c31f760 | |||
| 978c92f5e1 | |||
| 32397aa0c3 | |||
| 1a58a201f8 | |||
| 2acbf4625b | |||
| 7566d254e4 | |||
| 68dee8cbb3 | |||
| f7e8d287d5 | |||
| 30bca124f4 | |||
| d7891a5562 | |||
| 1d396b4716 | |||
| 7f3fceb432 | |||
| 81581f7794 | |||
| 47175adc74 | |||
| 350827f8f4 | |||
| 05c2995c7a | |||
| 2fa6e2fd90 | |||
| ba9a79c89c | |||
| 13ad754ecc | |||
| 0b02d42836 | |||
| ee0a8d2966 | |||
| 793ca45dda | |||
| 6e20e9bcf9 | |||
| b8d2a72333 | |||
| 0760c7273c | |||
| 9bd099013f | |||
| 68fb12153e | |||
| e19d1e9571 | |||
| 1820b4f63b | |||
| 64150cc7c5 | |||
| 63aabed320 | |||
| fd7bf7fed3 | |||
| 7a90cda8ff | |||
| 35eeaa5869 | |||
| 024713a441 | |||
| 32fbfe1981 | |||
| 9c5427e0c2 | |||
| 8f5a1c4a2a | |||
| 7d3609d954 | |||
| fc7ba53c37 | |||
| a96d72330d | |||
| 7d626b4f5c | |||
| e903348dd3 | |||
| bee251d928 | |||
| c658907331 | |||
| 3fec782494 | |||
| 52525eb76d | |||
| 8679cfd2f3 | |||
| 25af3fb029 | |||
| 2bceebc39d | |||
| 92532a0d66 | |||
| b673539a2a | |||
| e1aaa1c99d | |||
| aa9eb001d0 | |||
| 101e808124 | |||
| 766b7c65a6 | |||
| 0516a3dc0e | |||
| 3d185c0fb8 | |||
| 7815164216 | |||
| 65f4094804 | |||
| 06450f4a82 | |||
| ce0beb550c | |||
| 96c0483533 | |||
| 1274bcdba9 | |||
| 5dcc97c85a | |||
| 6deceeec2e | |||
| 9edcbadd60 | |||
| f65492e27f | |||
| 1df61a82c8 | |||
| 73509686f1 | |||
| 93e521d65e | |||
| 50511fcff7 | |||
| 5e26190b98 | |||
| f22bd6eb46 | |||
| 0ba6c78ed0 | |||
| 636d68e0a9 | |||
| 7e6d1c24c2 | |||
| 67918b17d3 | |||
| 51fa438340 | |||
| 35a11526f9 | |||
| 0cfec92c14 | |||
| e5f64bc24a | |||
| 5c48ba4609 | |||
| 3ca62a797a | |||
| b66dab1313 | |||
| 58110e4bc1 | |||
| ea96615460 | |||
| b8c26671c4 | |||
| 2e88d001d6 | |||
| 1a0db3de3a | |||
| 250876ea1a | |||
| 66510c1d78 | |||
| d6f1d3df5b | |||
| 72dbb6e7c1 | |||
| b1082a96c9 | |||
| ed17586cf0 | |||
| 26b8dd75f7 | |||
| c3473c2077 | |||
| e216654c52 | |||
| cc3391cc1c | |||
| b8d56a7c11 | |||
| 1bb9e83501 | |||
| de1a66dfbe | |||
| 95613a11ab | |||
| 2cd193f97c | |||
| 33a9d6a3c3 | |||
| fdb863ce70 | |||
| 43fc29873e | |||
| e216a54ead | |||
| d4cdc3a246 | |||
| f6bdc6fdb2 | |||
| 8f25baada6 | |||
| aaef1a14b4 | |||
| 22e8f5fd51 | |||
| 88ab787ba6 | |||
| eb9faffadd | |||
| 3b0eee785f | |||
| 5ef542a638 | |||
| ffdaff3740 | |||
| 56d273477e | |||
| c69720a1ab |
+1
-1
@@ -27,7 +27,7 @@ before_install:
|
||||
- pip install --user coveralls-merge
|
||||
|
||||
install:
|
||||
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
|
||||
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev libgd-dev libwebp-dev
|
||||
|
||||
before_script:
|
||||
# Ulimit: See Travis-CI issue report: https://github.com/travis-ci/travis-ci/issues/3328
|
||||
|
||||
+13
-6
@@ -1,7 +1,7 @@
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Rafael Römhild <rafael@roemhild.de>
|
||||
|
||||
ENV EJABBERD_BRANCH=17.04 \
|
||||
ENV EJABBERD_BRANCH=17.08 \
|
||||
EJABBERD_USER=ejabberd \
|
||||
EJABBERD_HTTPS=true \
|
||||
EJABBERD_STARTTLS=true \
|
||||
@@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.04 \
|
||||
EJABBERD_HOME=/opt/ejabberd \
|
||||
EJABBERD_DEBUG_MODE=false \
|
||||
HOME=$EJABBERD_HOME \
|
||||
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
XMPP_DOMAIN=localhost \
|
||||
# Set default locale for the environment
|
||||
@@ -38,6 +38,7 @@ RUN set -x \
|
||||
erlang-src erlang-dev \
|
||||
' \
|
||||
&& requiredAptPackages=' \
|
||||
wget \
|
||||
locales \
|
||||
ldnsutils \
|
||||
python2.7 \
|
||||
@@ -47,7 +48,7 @@ RUN set -x \
|
||||
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool \
|
||||
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap \
|
||||
erlang-eunit erlang-ic erlang-odbc erlang-os-mon \
|
||||
erlang-parsetools erlang-percept erlang-typer erlang-inets \
|
||||
erlang-parsetools erlang-percept erlang-typer \
|
||||
python-mysqldb \
|
||||
imagemagick \
|
||||
' \
|
||||
@@ -68,7 +69,6 @@ RUN set -x \
|
||||
&& chmod +x ./autogen.sh \
|
||||
&& ./autogen.sh \
|
||||
&& ./configure --enable-user=$EJABBERD_USER \
|
||||
--prefix=/ \
|
||||
--enable-all \
|
||||
--disable-tools \
|
||||
--disable-pam \
|
||||
@@ -82,12 +82,19 @@ RUN set -x \
|
||||
&& mkdir $EJABBERD_HOME/module_source \
|
||||
&& cd $EJABBERD_HOME \
|
||||
&& rm -rf /tmp/ejabberd \
|
||||
&& rm -rf /etc/ejabberd \
|
||||
&& ln -sf $EJABBERD_HOME/conf /etc/ejabberd \
|
||||
&& rm -rf /usr/local/etc/ejabberd \
|
||||
&& ln -sf $EJABBERD_HOME/conf /usr/local/etc/ejabberd \
|
||||
&& chown -R $EJABBERD_USER: $EJABBERD_HOME \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get purge -y --auto-remove $buildDeps
|
||||
|
||||
RUN wget -P /usr/local/share/ca-certificates/cacert.org http://www.cacert.org/certs/root.crt http://www.cacert.org/certs/class3.crt; \
|
||||
update-ca-certificates
|
||||
|
||||
# Create logging directories
|
||||
RUN mkdir -p /var/log/ejabberd
|
||||
RUN touch /var/log/ejabberd/crash.log /var/log/ejabberd/error.log /var/log/ejabberd/erlang.log
|
||||
|
||||
# Wrapper for setting config on disk from environment
|
||||
# allows setting things like XMPP domain at runtime
|
||||
ADD ./docker/run.sh /sbin/run
|
||||
|
||||
+2
-2
@@ -102,7 +102,7 @@ xref: all
|
||||
|
||||
|
||||
translations:
|
||||
contrib/extract_translations/prepare-translation.sh -updateall
|
||||
tools/prepare-tr.sh
|
||||
|
||||
edoc:
|
||||
$(ERL) -noinput +B -eval \
|
||||
@@ -123,7 +123,7 @@ FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w))))
|
||||
|
||||
ifeq ($(MAKECMDGOALS),copy-files-sub)
|
||||
|
||||
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//'))
|
||||
DEPS:=$(sort $(shell $(REBAR) -q list-deps|$(SED) -e '/[a-z0-9_-]+\s/d;s/ .*//'))
|
||||
|
||||
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
|
||||
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
|
||||
|
||||
@@ -116,11 +116,14 @@ To compile ejabberd you need:
|
||||
needed on systems with GNU Libc.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
|
||||
If your system splits packages in libraries and development headers, you must
|
||||
install the development packages also.
|
||||
|
||||
### 1. Compile and install on *nix systems
|
||||
|
||||
To compile ejabberd, execute the following commands. The first one is only
|
||||
necessary if your source tree didn't come with a `configure` script.
|
||||
necessary if your source tree didn't come with a `configure` script (In this
|
||||
case you need autoconf installed).
|
||||
|
||||
./autogen.sh
|
||||
./configure
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ defmodule Ejabberd.ConfigFile do
|
||||
|
||||
module :mod_client_state do
|
||||
@opts [
|
||||
drop_chat_states: true,
|
||||
queue_chat_states: true,
|
||||
queue_presence: false]
|
||||
end
|
||||
|
||||
|
||||
+1
-1
@@ -560,7 +560,7 @@ modules:
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_chat_states: true
|
||||
queue_presence: false
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
|
||||
+11
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="6.1 (Erlang/OTP 17.1)"
|
||||
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
|
||||
REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)"
|
||||
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
|
||||
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
@@ -236,6 +236,14 @@ AC_ARG_ENABLE(sip,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
|
||||
esac],[if test "x$sip" = "x"; then sip=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(graphics,
|
||||
[AC_HELP_STRING([--enable-graphics], [enable support for graphic images manipulation (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
yes) graphics=true ;;
|
||||
no) graphics=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-graphics) ;;
|
||||
esac],[if test "x$graphics" = "x"; then graphics=true; fi])
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
src/ejabberd.app.src])
|
||||
@@ -280,6 +288,7 @@ AC_SUBST(iconv)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(sip)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(graphics)
|
||||
AC_SUBST(tools)
|
||||
AC_SUBST(latest_deps)
|
||||
AC_SUBST(system_deps)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
extract_translations - auxiliary tool that extracts lines to be translated
|
||||
from ejabberd source tree.
|
||||
|
||||
Building:
|
||||
erlc extract_translations.erl
|
||||
|
||||
Invoking 1:
|
||||
erl -noinput -s extract_translations -extra dirname message_file
|
||||
|
||||
where dirname is the directory "src" in ejabberd's source tree root,
|
||||
message_file is a file with translated messages (src/msgs/*.msg).
|
||||
|
||||
Result is a list of messages from source files which aren't contained in
|
||||
message file.
|
||||
|
||||
Invoking 2:
|
||||
erl -noinput -s extract_translations -extra -unused dirname message_file
|
||||
|
||||
Result is a list of messages from message file which aren't in source
|
||||
files anymore.
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : extract_translations.erl
|
||||
%%% Author : Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Purpose : Auxiliary tool for interface/messages translators
|
||||
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(extract_translations).
|
||||
-author('sgolovan@nes.ru').
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
-define(STATUS_ERROR, 1).
|
||||
-define(STATUS_USAGE, 2).
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
|
||||
start() ->
|
||||
ets:new(translations, [named_table, public]),
|
||||
ets:new(translations_obsolete, [named_table, public]),
|
||||
ets:new(files, [named_table, public]),
|
||||
ets:new(vars, [named_table, public]),
|
||||
case init:get_plain_arguments() of
|
||||
["-srcmsg2po", Dir, File] ->
|
||||
print_po_header(File),
|
||||
Status = process(Dir, File, srcmsg2po),
|
||||
halt(Status);
|
||||
["-unused", Dir, File] ->
|
||||
Status = process(Dir, File, unused),
|
||||
halt(Status);
|
||||
[Dir, File] ->
|
||||
Status = process(Dir, File, used),
|
||||
halt(Status);
|
||||
_ ->
|
||||
print_usage(),
|
||||
halt(?STATUS_USAGE)
|
||||
end.
|
||||
|
||||
|
||||
process(Dir, File, Used) ->
|
||||
case load_file(File) of
|
||||
{error, Reason} ->
|
||||
io:format("~s: ~s~n", [File, file:format_error(Reason)]),
|
||||
?STATUS_ERROR;
|
||||
_ ->
|
||||
FileList = find_src_files(Dir),
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_file(Dir, F, Used)
|
||||
end, FileList),
|
||||
case Used of
|
||||
unused ->
|
||||
ets:foldl(fun({Key, _}, _) ->
|
||||
io:format("~p~n", [Key])
|
||||
end, ok, translations);
|
||||
srcmsg2po ->
|
||||
ets:foldl(fun({Key, Trans}, _) ->
|
||||
print_translation_obsolete(Key, Trans)
|
||||
end, ok, translations_obsolete);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
?STATUS_SUCCESS
|
||||
end.
|
||||
|
||||
parse_file(Dir, File, Used) ->
|
||||
ets:delete_all_objects(vars),
|
||||
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
|
||||
{ok, Forms} ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, Forms);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
parse_form(Dir, File, Form, Used) ->
|
||||
case Form of
|
||||
%%{undefined, Something} ->
|
||||
%% io:format("Undefined: ~p~n", [Something]);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {string, Line, Str}]
|
||||
} ->
|
||||
process_string(Dir, File, Line, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_,
|
||||
{bin,_,
|
||||
[{bin_element,_,
|
||||
{string,Line,Str},
|
||||
default,default}]}]
|
||||
} ->
|
||||
process_string(Dir, File, Line, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {var, _, Name}]
|
||||
} ->
|
||||
case ets:lookup(vars, Name) of
|
||||
[{_Name, Value, Line}] ->
|
||||
process_string(Dir, File, Line, Value, Used);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{string, Line, Value}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value, Line});
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{bin,Line,[{bin_element,_,{string,_,Value},_,_}]}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value, Line});
|
||||
L when is_list(L) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, L);
|
||||
T when is_tuple(T) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, tuple_to_list(T));
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_string(_Dir, _File, _Line, "", _Used) ->
|
||||
ok;
|
||||
|
||||
process_string(_Dir, File, Line, Str, Used) ->
|
||||
case {ets:lookup(translations, Str), Used} of
|
||||
{[{_Key, _Trans}], unused} ->
|
||||
ets:delete(translations, Str);
|
||||
{[{_Key, _Trans}], used} ->
|
||||
ok;
|
||||
{[{_Key, Trans}], srcmsg2po} ->
|
||||
ets:delete(translations_obsolete, Str),
|
||||
print_translation(File, Line, Str, Trans);
|
||||
{_, used} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
io:format("~n% ~s~n", [File]),
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
case Str of
|
||||
[] -> ok;
|
||||
_ -> io:format("{~p, \"\"}.~n", [Str])
|
||||
end,
|
||||
ets:insert(translations, {Str, ""});
|
||||
{_, srcmsg2po} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
ets:insert(translations, {Str, ""}),
|
||||
print_translation(File, Line, Str, "");
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
load_file(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
lists:foreach(
|
||||
fun({Orig, Trans}) ->
|
||||
case Trans of
|
||||
"" ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(translations, {Orig, Trans}),
|
||||
ets:insert(translations_obsolete, {Orig, Trans})
|
||||
end
|
||||
end, Terms);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
find_src_files(Dir) ->
|
||||
case file:list_dir(Dir) of
|
||||
{ok, FileList} ->
|
||||
recurse_filelist(
|
||||
lists:map(
|
||||
fun(F) ->
|
||||
filename:join(Dir, F)
|
||||
end, FileList));
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
recurse_filelist(FileList) ->
|
||||
recurse_filelist(FileList, []).
|
||||
|
||||
recurse_filelist([], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
|
||||
recurse_filelist([H | T], Acc) ->
|
||||
case file:read_file_info(H) of
|
||||
{ok, #file_info{type = directory}} ->
|
||||
recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
|
||||
{ok, #file_info{type = regular}} ->
|
||||
case string:substr(H, string:len(H) - 3) of
|
||||
".erl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
".hrl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end;
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end.
|
||||
|
||||
|
||||
print_usage() ->
|
||||
io:format(
|
||||
"Usage: extract_translations [-unused] dir file~n"
|
||||
"~n"
|
||||
"Example:~n"
|
||||
" extract_translations . ./msgs/ru.msg~n"
|
||||
).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Gettext
|
||||
%%%
|
||||
|
||||
print_po_header(File) ->
|
||||
MsgProps = get_msg_header_props(File),
|
||||
{Language, [LastT | AddT]} = prepare_props(MsgProps),
|
||||
print_po_header(Language, LastT, AddT).
|
||||
|
||||
get_msg_header_props(File) ->
|
||||
{ok, F} = file:open(File, [read]),
|
||||
Lines = get_msg_header_props(F, []),
|
||||
file:close(F),
|
||||
Lines.
|
||||
|
||||
get_msg_header_props(F, Lines) ->
|
||||
String = io:get_line(F, ""),
|
||||
case io_lib:fread("% ", String) of
|
||||
{ok, [], RemString} ->
|
||||
case io_lib:fread("~s", RemString) of
|
||||
{ok, [Key], Value} when Value /= "\n" ->
|
||||
%% The first character in Value is a blankspace:
|
||||
%% And the last characters are 'slash n'
|
||||
ValueClean = string:substr(Value, 2, string:len(Value)-2),
|
||||
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
|
||||
_ ->
|
||||
get_msg_header_props(F, Lines)
|
||||
end;
|
||||
_ ->
|
||||
Lines
|
||||
end.
|
||||
|
||||
prepare_props(MsgProps) ->
|
||||
Language = proplists:get_value("Language:", MsgProps),
|
||||
Authors = proplists:get_all_values("Author:", MsgProps),
|
||||
{Language, Authors}.
|
||||
|
||||
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
|
||||
HeaderString =
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
|
||||
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
|
||||
++ AdditionalTranslatorsString ++
|
||||
"\"MIME-Version: 1.0\\n\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
|
||||
io:format("~s~n", [HeaderString]).
|
||||
|
||||
build_additional_translators(List) ->
|
||||
lists:foldl(
|
||||
fun(T, Str) ->
|
||||
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
|
||||
end,
|
||||
"",
|
||||
List).
|
||||
|
||||
print_translation(File, Line, Str, StrT) ->
|
||||
StrQ = ejabberd_regexp:greplace(list_to_binary(Str), <<"\\\"">>, <<"\\\\\"">>),
|
||||
StrTQ = ejabberd_regexp:greplace(list_to_binary(StrT), <<"\\\"">>, <<"\\\\\"">>),
|
||||
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
print_translation_obsolete(Str, StrT) ->
|
||||
File = "unknown.erl",
|
||||
Line = 1,
|
||||
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
|
||||
StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
|
||||
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Frontend for ejabberd's extract_translations.erl
|
||||
# by Badlop
|
||||
|
||||
# How to create template files for a new language:
|
||||
# NEWLANG=zh
|
||||
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
|
||||
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
|
||||
# ../../extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
prepare_dirs ()
|
||||
{
|
||||
# Where is Erlang binary
|
||||
ERL=`which erl`
|
||||
|
||||
EJA_SRC_DIR=$EJA_DIR/src/
|
||||
EJA_MSGS_DIR=$EJA_DIR/priv/msgs/
|
||||
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
|
||||
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
|
||||
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
|
||||
|
||||
SRC_DIR=$RUN_DIR/src
|
||||
EBIN_DIR=$RUN_DIR/ebin
|
||||
MSGS_DIR=$EJA_DIR/priv/msgs
|
||||
|
||||
if !([[ -n $EJA_DIR ]])
|
||||
then
|
||||
echo "ejabberd dir does not exist: $EJA_DIR"
|
||||
fi
|
||||
|
||||
if !([[ -x $EXTRACT_BEAM ]])
|
||||
then
|
||||
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_lang ()
|
||||
{
|
||||
MSGS_FILE=$1
|
||||
MSGS_FILE2=$MSGS_FILE.translate
|
||||
MSGS_PATH=$MSGS_DIR/$MSGS_FILE
|
||||
MSGS_PATH2=$MSGS_DIR/$MSGS_FILE2
|
||||
|
||||
echo -n "Extracting language strings for '$MSGS_FILE':"
|
||||
|
||||
echo -n " new..."
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra . $MSGS_PATH >$MSGS_PATH.new
|
||||
sed -e 's/^% \.\//% /g;' $MSGS_PATH.new > $MSGS_PATH.new2
|
||||
mv $MSGS_PATH.new2 $MSGS_PATH.new
|
||||
|
||||
echo -n " old..."
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra -unused . $MSGS_PATH >$MSGS_PATH.unused
|
||||
find_unused_full $MSGS_FILE $MSGS_FILE.unused
|
||||
|
||||
echo "" >$MSGS_PATH2
|
||||
echo " ***** Translation file for ejabberd ***** " >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** New strings: Can you please translate them? *** " >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.new >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** Unused strings: They will be removed automatically *** " >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.unused.full >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** Already translated strings: you can also modify any of them if you want *** " >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.old_cleaned >>$MSGS_PATH2
|
||||
|
||||
echo " ok"
|
||||
|
||||
rm $MSGS_PATH.new
|
||||
rm $MSGS_PATH.old_cleaned
|
||||
rm $MSGS_PATH.unused.full
|
||||
}
|
||||
|
||||
extract_lang_all ()
|
||||
{
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
extract_lang $i;
|
||||
done
|
||||
|
||||
echo -e "File\tMissing\tLanguage\t\tLast translator"
|
||||
echo -e "----\t-------\t--------\t\t---------------"
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
|
||||
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
|
||||
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
|
||||
done
|
||||
|
||||
cd $MSGS_DIR
|
||||
REVISION=`git describe --always`
|
||||
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
|
||||
|
||||
rm *.translate
|
||||
}
|
||||
|
||||
find_unused_full ()
|
||||
{
|
||||
DATFILE=$1
|
||||
DATFILEI=$1.old_cleaned
|
||||
DELFILE=$2
|
||||
cd msgs
|
||||
|
||||
DATFILE1=$DATFILE.t1
|
||||
DATFILE2=$DATFILE.t2
|
||||
|
||||
DELFILE1=$DELFILE.t1
|
||||
DELFILE2=$DELFILE.t2
|
||||
DELFILEF=$DATFILE.unused.full
|
||||
echo "" >$DELFILEF
|
||||
|
||||
grep -v "\\\\" $DELFILE >$DELFILE2
|
||||
echo ENDFILEMARK >>$DELFILE2
|
||||
cp $DATFILE $DATFILEI
|
||||
cp $DATFILE $DATFILE2
|
||||
|
||||
cp $DELFILE2 $DELFILE1
|
||||
STRING=`head -1 $DELFILE1`
|
||||
until [[ $STRING == ENDFILEMARK ]]; do
|
||||
cp $DELFILE2 $DELFILE1
|
||||
cp $DATFILE2 $DATFILE1
|
||||
|
||||
STRING=`head -1 $DELFILE1`
|
||||
|
||||
cat $DATFILE1 | grep "$STRING" >>$DELFILEF
|
||||
cat $DATFILE1 | grep -v "$STRING" >$DATFILE2
|
||||
cat $DELFILE1 | grep -v "$STRING" >$DELFILE2
|
||||
done
|
||||
|
||||
mv $DATFILE2 $DATFILEI
|
||||
|
||||
rm -f $MSGS_PATH.t1
|
||||
rm $MSGS_PATH.unused
|
||||
rm -f $MSGS_PATH.unused.t1
|
||||
rm $MSGS_PATH.unused.t2
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
extract_lang_srcmsg2po ()
|
||||
{
|
||||
LANG=$1
|
||||
LANG_CODE=$LANG.$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
|
||||
echo $MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
|
||||
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
|
||||
|
||||
rm $PO_PATH.*
|
||||
}
|
||||
|
||||
extract_lang_src2pot ()
|
||||
{
|
||||
LANG_CODE=$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
|
||||
|
||||
echo -n "" >$MSGS_PATH
|
||||
echo "% Language: Language Name" >>$MSGS_PATH
|
||||
echo "% Author: Translator name and contact method" >>$MSGS_PATH
|
||||
echo "" >>$MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
|
||||
|
||||
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
|
||||
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
|
||||
|
||||
rm $POT_PATH.*
|
||||
rm $MSGS_PATH
|
||||
|
||||
# If the project is a specific module, not the main ejabberd
|
||||
if [[ $PROJECT != ejabberd ]] ; then
|
||||
# Remove from project.pot the strings that are already present in the general ejabberd
|
||||
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
|
||||
POT_PATH_TEMP=$POT_PATH.temp
|
||||
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
|
||||
mv $POT_PATH_TEMP $POT_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
extract_lang_popot2po ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
POT_PATH=$MSGS_DIR/$PROJECT.pot
|
||||
|
||||
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
|
||||
mv $PO_PATH.translate $PO_PATH
|
||||
}
|
||||
|
||||
extract_lang_po2msg ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$LANG_CODE.po
|
||||
MS_PATH=$PO_PATH.ms
|
||||
MSGID_PATH=$PO_PATH.msgid
|
||||
MSGSTR_PATH=$PO_PATH.msgstr
|
||||
MSGS_PATH=$LANG_CODE.msg
|
||||
|
||||
cd $MSGS_DIR
|
||||
|
||||
# Check PO has correct ~
|
||||
# Let's convert to C format so we can use msgfmt
|
||||
PO_TEMP=$LANG_CODE.po.temp
|
||||
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
|
||||
msgfmt $PO_TEMP --check-format
|
||||
result=$?
|
||||
rm $PO_TEMP
|
||||
if [ $result -ne 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
|
||||
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
|
||||
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
|
||||
echo "%% -*- coding: latin-1 -*-" >$MSGS_PATH
|
||||
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >>$MSGS_PATH
|
||||
|
||||
rm $MS_PATH
|
||||
rm $MSGID_PATH
|
||||
rm $MSGSTR_PATH
|
||||
}
|
||||
|
||||
extract_lang_updateall ()
|
||||
{
|
||||
echo "Generating POT"
|
||||
extract_lang_src2pot
|
||||
|
||||
cd $MSGS_DIR
|
||||
echo ""
|
||||
echo -e "File Missing Language Last translator"
|
||||
echo -e "---- ------- -------- ---------------"
|
||||
for i in $( ls *.msg ) ; do
|
||||
LANG_CODE=${i%.msg}
|
||||
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
|
||||
|
||||
# Convert old MSG file to PO
|
||||
PO=$LANG_CODE.po
|
||||
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
|
||||
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
|
||||
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
|
||||
echo -n " $MISSING"
|
||||
|
||||
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
|
||||
echo -n " $LANGUAGE"
|
||||
|
||||
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
|
||||
echo " $LASTAUTH"
|
||||
done
|
||||
echo ""
|
||||
rm messages.mo
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
translation_instructions ()
|
||||
{
|
||||
echo ""
|
||||
echo " A new file has been created for you, with the current, the new and the deprecated strings:"
|
||||
echo " $MSGS_PATH2"
|
||||
echo ""
|
||||
echo " At the end of that file you will find the strings you must update:"
|
||||
echo " - Untranslated strings are like this: {"March", ""}."
|
||||
echo " To translate the string, add the text inside the commas. Example:"
|
||||
echo " {"March", "Marzo"}."
|
||||
echo " - Old strings that are not used: "Woowoa""
|
||||
echo " Search the entire file for those strings and remove them"
|
||||
echo ""
|
||||
echo " Once you have translated all the strings and removed all the old ones,"
|
||||
echo " rename the file to overwrite the previous one:"
|
||||
echo " $MSGS_PATH"
|
||||
}
|
||||
|
||||
EJA_DIR=`pwd`
|
||||
RUN_DIR=`pwd`
|
||||
PROJECT=ejabberd
|
||||
|
||||
while [ $# -ne 0 ] ; do
|
||||
PARAM=$1
|
||||
shift
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
-project)
|
||||
PROJECT=$1
|
||||
shift
|
||||
;;
|
||||
-ejadir)
|
||||
EJA_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-rundir)
|
||||
RUN_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-lang)
|
||||
LANGU=$1
|
||||
prepare_dirs
|
||||
extract_lang $LANGU
|
||||
shift
|
||||
;;
|
||||
-langall)
|
||||
prepare_dirs
|
||||
extract_lang_all
|
||||
;;
|
||||
-srcmsg2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_srcmsg2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-popot2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-src2pot)
|
||||
prepare_dirs
|
||||
extract_lang_src2pot
|
||||
;;
|
||||
-po2msg)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-updateall)
|
||||
prepare_dirs
|
||||
extract_lang_updateall
|
||||
;;
|
||||
*)
|
||||
echo "Options:"
|
||||
echo " -langall"
|
||||
echo " -lang LANGUAGE_FILE"
|
||||
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
|
||||
echo " -src2pot Generate template POT file from source code"
|
||||
echo " -popot2po LANGUAGE Update PO file with template POT file"
|
||||
echo " -po2msg LANGUAGE Export PO file to MSG file"
|
||||
echo " -updateall Generate POT and update all PO"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./prepare-translation.sh -lang es.msg"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
+83
-41
@@ -1,21 +1,23 @@
|
||||
ejabberd container
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Version](#version)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Usage](#usage)
|
||||
- [Persistence](#persistence)
|
||||
- [SSL Certificates](#ssl-certificates)
|
||||
- [Base Image](#base-image)
|
||||
- [Ejabberd Configuration](#ejabberd-configuration)
|
||||
- [Cluster Example](#cluster-example)
|
||||
- [Runtime Configuration](#runtime-configuration)
|
||||
- [Served Hostnames](#served-hostnames)
|
||||
- [Authentication](#authentication)
|
||||
- [Admins](#admins)
|
||||
- [Users](#users)
|
||||
- [SSL](#ssl)
|
||||
- [Erlang](#erlang)
|
||||
- [Modules](#modules)
|
||||
- [Logging](#logging)
|
||||
- [Mount Configurations](#mount-configurations)
|
||||
- [Erlang Configuration](#erlang-configuration)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Register Users](#register-users)
|
||||
- [Creating Backups](#creating-backups)
|
||||
@@ -28,11 +30,29 @@ ejabberd container
|
||||
|
||||
# Introduction
|
||||
|
||||
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
|
||||
This [ejabberd][] docker container is based on the work done by [rroemhild][]. See more [in this blogpost][].
|
||||
This container includes the necessary files to build your own containerized ejabberd,
|
||||
but *IT IS NOT* used to generate official images on the docker [hub][].
|
||||
This container is not maintained by [ProcessOne][].
|
||||
|
||||
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
|
||||
[ProcessOne][] provides and maintain official containers on the docker [hub][], which targets developers for now and will becomes production ready in a near future.
|
||||
These [new containers] allow to build and run ejabberd in a simple and lightweight environment.
|
||||
|
||||
[tags]: https://github.com/rroemhild/ejabberd/tags
|
||||
[ejabberd]: https://www.ejabberd.im/
|
||||
[rroemhild]: https://github.com/rroemhild/docker-ejabberd/
|
||||
[in this blogpost]: https://blog.process-one.net/ejabberd-16-12/
|
||||
[hub]: https://hub.docker.com/r/ejabberd/ecs/
|
||||
[new containers]: https://github.com/processone/docker-ejabberd/
|
||||
[ProcessOne]: https://www.process-one.net/
|
||||
|
||||
## Version
|
||||
|
||||
Current Version: `17.08`
|
||||
|
||||
Docker Tag Names are based on ejabberd versions in git [branches][] and [tags][]. The image tag `:latest` is based on the master branch.
|
||||
|
||||
[tags]: https://github.com/rroemhild/docker-ejabberd/tags
|
||||
[branches]: https://github.com/rroemhild/docker-ejabberd/branches
|
||||
|
||||
# Quick Start
|
||||
|
||||
@@ -46,13 +66,20 @@ docker run -d \
|
||||
-p 5280:5280 \
|
||||
-h 'xmpp.example.de' \
|
||||
-e "XMPP_DOMAIN=example.de" \
|
||||
-e "ERLANG_NODE=nodename" \
|
||||
-e "ERLANG_NODE=ejabberd" \
|
||||
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
|
||||
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
|
||||
-e "TZ=Europe/Berlin" \
|
||||
rroemhild/ejabberd
|
||||
```
|
||||
|
||||
or with the [docker-compose](examples/docker-compose/docker-compose.yml) example
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/rroemhild/docker-ejabberd/master/examples/docker-compose/docker-compose.yml
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## Persistence
|
||||
@@ -100,7 +127,11 @@ ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
|
||||
|
||||
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
|
||||
|
||||
# Ejabberd Configuration
|
||||
## Cluster Example
|
||||
|
||||
The [docker-compose-cluster](examples/docker-compose-cluster) example demonstrates how to extend this container image to setup a multi-master cluster.
|
||||
|
||||
# Runtime Configuration
|
||||
|
||||
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
|
||||
|
||||
@@ -138,6 +169,26 @@ EJABBERD_EXTAUTH_CACHE=600
|
||||
```
|
||||
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
|
||||
|
||||
### Password format
|
||||
|
||||
The variable `EJABBERD_AUTH_PASSWORD_FORMAT` controls in which format user passwords are
|
||||
stored. Possible values are `plain` and `scram`. The default is to store
|
||||
[SCRAM](https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism)bled
|
||||
passwords, meaning that it is impossible to obtain the original plain password from the
|
||||
stored information.
|
||||
|
||||
NOTE: SCRAM does not work with SIP/TURN foreign authentication methods. In this case, you
|
||||
may have to disable the option. More details can be found here:
|
||||
https://docs.ejabberd.im/admin/configuration/#internal
|
||||
|
||||
If using SCRAM with an SQL database that has plaintext passwords stored, use the command
|
||||
|
||||
```
|
||||
ejabberdctl convert_to_scram example.org
|
||||
```
|
||||
|
||||
to convert all your existing plaintext passwords to scrambled format.
|
||||
|
||||
### MySQL Authentication
|
||||
|
||||
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
|
||||
@@ -215,28 +266,38 @@ EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
|
||||
```
|
||||
|
||||
## SSL
|
||||
|
||||
- **EJABBERD_SKIP_MAKE_SSLCERT**: Skip generating ssl certificates. Default: false
|
||||
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
|
||||
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
|
||||
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
|
||||
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
|
||||
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
|
||||
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
|
||||
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
|
||||
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Defaults
|
||||
to `true`.
|
||||
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Defaults to `true`.
|
||||
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Defaults to `true`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Defaults to `false`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Defaults to `true`.
|
||||
- **EJABBERD_CIPHERS**: Cipher suite. Defaults to `HIGH:!aNULL:!3DES`.
|
||||
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Defaults to `false`.
|
||||
- **EJABBERD_SKIP_MAKE_DHPARAM**: Skip generating DH params. Default: false
|
||||
|
||||
## Erlang
|
||||
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `ejabberd` lets erlang add the hostname. Defaults to `ejabberd@localhost`.
|
||||
- **ERLANG_COOKIE**: Set erlang cookie. Defaults to auto-generated cookie.
|
||||
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd.
|
||||
|
||||
## Modules
|
||||
|
||||
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
|
||||
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
|
||||
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
|
||||
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
|
||||
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
|
||||
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Defaults to `false`.
|
||||
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Defaults to `false`.
|
||||
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Defaults to `true`.
|
||||
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Defaults to `true`.
|
||||
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Defaults to `true`.
|
||||
- **EJABBERD_SOURCE_MODULES**: List of modules, which will be installed from sources localized in ${EJABBERD_HOME}/module_source.
|
||||
- **EJABBERD_CONTRIB_MODULES**: List of modules, which will be installed from contrib repository.
|
||||
- **EJABBERD_RESTART_AFTER_MODULE_INSTALL**: If any modules were installed, restart the server, if the option is enabled.
|
||||
- **EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE**: If a custom module was defined for handling auth, we need to override the pre-defined auth methods in the config.
|
||||
|
||||
## Logging
|
||||
|
||||
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
|
||||
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Defaults to `4` (Info).
|
||||
|
||||
```
|
||||
loglevel: Verbosity of log files generated by ejabberd.
|
||||
@@ -276,25 +337,6 @@ Example configuration files can be downloaded from the ejabberd [github](https:/
|
||||
|
||||
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
|
||||
|
||||
## Erlang Configuration
|
||||
|
||||
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
|
||||
|
||||
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
|
||||
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
|
||||
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
|
||||
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
|
||||
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
|
||||
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
|
||||
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
|
||||
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
|
||||
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
|
||||
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
|
||||
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
|
||||
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
|
||||
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
|
||||
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
|
||||
|
||||
# Maintenance
|
||||
|
||||
The `ejabberdctl` command is in the search path and can be run by:
|
||||
|
||||
@@ -123,8 +123,10 @@ auth_method:
|
||||
- {{ auth_method }}
|
||||
{%- endfor %}
|
||||
|
||||
auth_password_format: {{ env.get('EJABBERD_AUTH_PASSWORD_FORMAT', 'scram') }}
|
||||
|
||||
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
anonymous_protocol: login_anon
|
||||
anonymous_protocol: both
|
||||
allow_multiple_connections: true
|
||||
{%- endif %}
|
||||
|
||||
@@ -347,6 +349,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM rroemhild/ejabberd
|
||||
ENV EJABBERD_HOME /opt/ejabberd
|
||||
COPY ./scripts $EJABBERD_HOME/scripts
|
||||
@@ -0,0 +1,23 @@
|
||||
# Ejabberd cluster with docker compose
|
||||
|
||||
This example uses [dnsdocker](https://github.com/tonistiigi/dnsdock) to discover other nodes and setup a multi-master cluster.
|
||||
|
||||
Build the ejabberd cluster image:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/rroemhild/docker-ejabberd.git
|
||||
cd docker-ejabberd/examples/docker-compose-cluster
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
Start dnsdocker and the first ejabberd node:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Wait until the first ejabberd node is up and running `docker-compose logs ejabberd`, then add some ejabberd nodes to the cluster:
|
||||
|
||||
```bash
|
||||
docker-compose scale ejabberd=4
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
dnsdock:
|
||||
image: tonistiigi/dnsdock
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 172.17.42.1:53:53/udp
|
||||
|
||||
ejabberd:
|
||||
build: .
|
||||
ports:
|
||||
- 5222
|
||||
- 5269
|
||||
- 5280
|
||||
environment:
|
||||
- XMPP_DOMAIN=example.com
|
||||
- ERLANG_NODE=ejabberd
|
||||
- EJABBERD_ADMINS=admin@example.com
|
||||
- EJABBERD_USERS=admin@example.com:test321 user@example.com
|
||||
- ERLANG_COOKIE=testCluster
|
||||
- SKIP_MODULES_UPDATE=true
|
||||
- EJABBERD_CLUSTER=true
|
||||
- USE_DNS=true
|
||||
dns: 172.17.42.1
|
||||
domainname: dockercomposecluster_ejabberd.docker
|
||||
tty: true
|
||||
@@ -0,0 +1,37 @@
|
||||
# overwrite get_nodename to discover hostname from DNS
|
||||
get_nodename() {
|
||||
local hostname=${HOSTNAME}
|
||||
|
||||
# get hostname from dns
|
||||
if ( is_true ${USE_DNS} ); then
|
||||
# wait for dns registration
|
||||
sleep 1
|
||||
|
||||
nodename=$(discover_dns_hostname ${HOSTIP})
|
||||
|
||||
is_set ${nodename} \
|
||||
&& hostname=${nodename}
|
||||
fi
|
||||
|
||||
echo $hostname
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# discover hostname from dns with a reverse lookup
|
||||
discover_dns_hostname() {
|
||||
local hostip=$1
|
||||
|
||||
# try to get the hostname from dns
|
||||
local dnsname=$(drill -x ${hostip} \
|
||||
| grep PTR \
|
||||
| awk '{print $5}' \
|
||||
| grep -E "^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*.[a-zA-Z]+\.$" \
|
||||
| cut -d '.' -f1 \
|
||||
| tail -1)
|
||||
|
||||
is_set ${dnsname} \
|
||||
&& echo ${dnsname}
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/scripts/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/functions.sh"
|
||||
|
||||
|
||||
get_cluster_node_from_dns() {
|
||||
local cluster_host=$(drill ${DOMAINNAME} \
|
||||
| grep ${DOMAINNAME} \
|
||||
| grep -v ${HOSTIP} \
|
||||
| awk '{print $5}' \
|
||||
| grep -v "^$" \
|
||||
| head -1)
|
||||
echo $(discover_dns_hostname ${cluster_host})
|
||||
}
|
||||
|
||||
|
||||
file_exist ${FIRST_START_DONE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
|
||||
join_cluster $(get_cluster_node_from_dns)
|
||||
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1 @@
|
||||
# simple docker-compose example
|
||||
@@ -0,0 +1,11 @@
|
||||
ejabberd:
|
||||
image: rroemhild/ejabberd
|
||||
ports:
|
||||
- 5222:5222
|
||||
- 5269:5269
|
||||
- 5280:5280
|
||||
environment:
|
||||
- ERLANG_NODE=ejabberd
|
||||
- XMPP_DOMAIN=example.com xyz.io
|
||||
- EJABBERD_ADMINS=admin@example.com
|
||||
- EJABBERD_USERS=admin@example.com:password4321 user1@xyz.io
|
||||
@@ -3,7 +3,7 @@ readonly HOSTNAME=$(hostname -f)
|
||||
readonly DOMAINNAME=$(hostname -d)
|
||||
|
||||
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
|
||||
readonly EJABBERDCTL="/sbin/ejabberdctl"
|
||||
readonly EJABBERDCTL="/usr/local/sbin/ejabberdctl"
|
||||
readonly CONFIGFILE="${EJABBERD_HOME}/conf/ejabberd.yml"
|
||||
readonly CONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberd.yml.tpl"
|
||||
readonly CTLCONFIGFILE="${EJABBERD_HOME}/conf/ejabberdctl.cfg"
|
||||
@@ -11,7 +11,7 @@ readonly CTLCONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberdctl.cfg.tpl"
|
||||
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
|
||||
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
|
||||
readonly SSLDHPARAM="${SSLCERTDIR}/dh.pem"
|
||||
readonly LOGDIR="/var/log/ejabberd"
|
||||
readonly LOGDIR="/usr/local/var/log/ejabberd"
|
||||
readonly FIRST_START_DONE_FILE="/${EJABBERD_HOME}/first-start-done"
|
||||
readonly CLUSTER_NODE_FILE="/${EJABBERD_HOME}/cluster-done"
|
||||
|
||||
|
||||
+10
-1
@@ -111,8 +111,10 @@ hosts:
|
||||
## 'CERTFILE': "/path/to/xmpp.pem"
|
||||
## 'CIPHERS': "ECDH:DH:!3DES:!aNULL:!eNULL:!MEDIUM@STRENGTH"
|
||||
## 'TLSOPTS':
|
||||
## - "no_sslv2"
|
||||
## - "no_sslv3"
|
||||
## - "no_tlsv1"
|
||||
## - "no_tlsv1_1"
|
||||
## - "cipher_server_preference"
|
||||
## - "no_compression"
|
||||
## 'DHFILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
|
||||
@@ -231,7 +233,7 @@ listen:
|
||||
|
||||
##
|
||||
## s2s_use_starttls: Enable STARTTLS for S2S connections.
|
||||
## Allowed values are: false optional required required_trusted
|
||||
## Allowed values are: false, optional or required
|
||||
## You must specify a certificate file.
|
||||
##
|
||||
## s2s_use_starttls: required
|
||||
@@ -725,6 +727,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
## mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
@@ -764,6 +768,11 @@ modules:
|
||||
mod_time: {}
|
||||
mod_vcard:
|
||||
search: false
|
||||
mod_vcard_xupdate: {}
|
||||
## Convert all avatars posted by Android clients from WebP to JPEG
|
||||
mod_avatar:
|
||||
convert:
|
||||
webp: jpeg
|
||||
mod_version: {}
|
||||
mod_stream_mgmt: {}
|
||||
## Non-SASL Authentication (XEP-0078) is now disabled by default
|
||||
|
||||
+80
-75
@@ -7,33 +7,41 @@ ERL_MAX_PORTS=32000
|
||||
ERL_PROCESSES=250000
|
||||
ERL_MAX_ETS_TABLES=1400
|
||||
FIREWALL_WINDOW=""
|
||||
INET_DIST_INTERFACE=""
|
||||
ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
# define default environment variables
|
||||
ERL="{{erl}}"
|
||||
IEX="{{bindir}}/iex"
|
||||
EPMD="{{epmd}}"
|
||||
INSTALLUSER={{installuser}}
|
||||
INSTALLUSER="{{installuser}}"
|
||||
|
||||
# check the proper system user is used if defined
|
||||
EXEC_CMD="false"
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
|
||||
# check the proper system user is used
|
||||
case $(id -un) in
|
||||
"$INSTALLUSER")
|
||||
EXEC_CMD="as_current_user"
|
||||
else
|
||||
id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
|
||||
fi
|
||||
else
|
||||
EXEC_CMD="as_current_user"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 7
|
||||
fi
|
||||
;;
|
||||
root)
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
EXEC_CMD="as_install_user"
|
||||
else
|
||||
EXEC_CMD="as_current_user"
|
||||
echo "WARNING: This is not recommended to run ejabberd as root" >&2
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 7
|
||||
else
|
||||
EXEC_CMD="as_current_user"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# parse command line parameters
|
||||
for arg; do
|
||||
case $1 in
|
||||
case $arg in
|
||||
-n|--node) ERLANG_NODE_ARG=$2; shift;;
|
||||
-s|--spool) SPOOL_DIR=$2; shift;;
|
||||
-l|--logs) LOGS_DIR=$2; shift;;
|
||||
@@ -48,28 +56,26 @@ for arg; do
|
||||
done
|
||||
|
||||
# define ejabberd variables if not already defined from the command line
|
||||
: ${ETC_DIR:={{sysconfdir}}/ejabberd}
|
||||
: ${LOGS_DIR:={{localstatedir}}/log/ejabberd}
|
||||
: ${SPOOL_DIR:={{localstatedir}}/lib/ejabberd}
|
||||
: ${EJABBERD_CONFIG_PATH:="$ETC_DIR"/ejabberd.yml}
|
||||
: ${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR"/ejabberdctl.cfg}
|
||||
: "${ETC_DIR:="{{sysconfdir}}/ejabberd"}"
|
||||
: "${LOGS_DIR:="{{localstatedir}}/log/ejabberd"}"
|
||||
: "${SPOOL_DIR:="{{localstatedir}}/lib/ejabberd"}"
|
||||
: "${EJABBERD_CONFIG_PATH:="$ETC_DIR/ejabberd.yml"}"
|
||||
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
|
||||
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
||||
[ "$ERLANG_NODE_ARG" != "" ] && ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
||||
: ${EJABBERD_DOC_PATH:={{docdir}}}
|
||||
: ${EJABBERD_LOG_PATH:="$LOGS_DIR"/ejabberd.log}
|
||||
: "${EJABBERD_DOC_PATH:="{{docdir}}"}"
|
||||
: "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}"
|
||||
|
||||
# define erl parameters
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel " \
|
||||
"inet_dist_listen_min ${FIREWALL_WINDOW%-*} " \
|
||||
"inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
if [ -n "$FIREWALL_WINDOW" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
if [ "$INET_DIST_INTERFACE" != "" ] ; then
|
||||
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)
|
||||
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\""
|
||||
if [ -n "$INET_DIST_INTERFACE2" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||
fi
|
||||
fi
|
||||
ERL_LIBS={{libdir}}
|
||||
@@ -103,19 +109,19 @@ export ERL_LIBS
|
||||
exec_cmd()
|
||||
{
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_install_user) su -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) "$@" ;;
|
||||
esac
|
||||
}
|
||||
exec_erl()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd "$ERL" ${S:--}name $NODE $ERLANG_OPTS "$@"
|
||||
exec_cmd "$ERL" ${S:--}name "$NODE" $ERLANG_OPTS "$@"
|
||||
}
|
||||
exec_iex()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd "$IEX" ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
|
||||
exec_cmd "$IEX" -${S:--}name "$NODE" --erl "$ERLANG_OPTS" "$@"
|
||||
}
|
||||
|
||||
# usage
|
||||
@@ -138,7 +144,7 @@ debugwarning()
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press return to continue"
|
||||
read foo
|
||||
read -r
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
@@ -161,7 +167,7 @@ livewarning()
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press return to continue"
|
||||
read foo
|
||||
read -r
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
@@ -191,12 +197,12 @@ help()
|
||||
uid()
|
||||
{
|
||||
uuid=$(uuidgen 2>/dev/null)
|
||||
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
|
||||
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
|
||||
uuid=${uuid%%-*}
|
||||
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
|
||||
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
|
||||
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
|
||||
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
|
||||
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
|
||||
[ $# -eq 2 ] && echo "${uuid}-${1}@${2}"
|
||||
}
|
||||
|
||||
# stop epmd if there is no other running node
|
||||
@@ -210,25 +216,17 @@ stop_epmd()
|
||||
check_start()
|
||||
{
|
||||
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
|
||||
pgrep -f "$ERLANG_NODE" >/dev/null && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
|
||||
exit 4
|
||||
} || {
|
||||
ps ux | grep -v grep | grep -q beam && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
|
||||
echo " but no related beam process has been found."
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
} || {
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
} || {
|
||||
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
|
||||
cd "$SPOOL_DIR" || {
|
||||
echo "ERROR: ejabberd can not access directory $SPOOL_DIR"
|
||||
exit 6
|
||||
pgrep beam >/dev/null && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
|
||||
echo " but no related beam process has been found."
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
}
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,58 +235,65 @@ wait_status()
|
||||
{
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
timeout=$2
|
||||
timeout="$2"
|
||||
status=4
|
||||
while [ $status -ne $1 ] ; do
|
||||
sleep $3
|
||||
timeout=`expr $timeout - 1`
|
||||
while [ "$status" -ne "$1" ] ; do
|
||||
sleep "$3"
|
||||
timeout=$((timeout - 1))
|
||||
if [ $timeout -eq 0 ] ; then
|
||||
status=$1
|
||||
status="$1"
|
||||
else
|
||||
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
|
||||
-extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null
|
||||
status=$?
|
||||
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
|
||||
status="$?"
|
||||
fi
|
||||
done
|
||||
[ $timeout -gt 0 ]
|
||||
}
|
||||
|
||||
# ensure we can change current directory to SPOOL_DIR
|
||||
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
|
||||
cd "$SPOOL_DIR" || {
|
||||
echo "ERROR: can not access directory $SPOOL_DIR"
|
||||
exit 6
|
||||
}
|
||||
|
||||
# main
|
||||
case $1 in
|
||||
start)
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput -detached
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput -detached
|
||||
;;
|
||||
foreground)
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput
|
||||
;;
|
||||
live)
|
||||
livewarning
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS
|
||||
;;
|
||||
debug)
|
||||
debugwarning
|
||||
exec_erl $(uid debug) -hidden -remsh $ERLANG_NODE
|
||||
exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE"
|
||||
;;
|
||||
etop)
|
||||
exec_erl $(uid top) -hidden -node $ERLANG_NODE -s etop \
|
||||
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
iexdebug)
|
||||
debugwarning
|
||||
exec_iex $(uid debug) --remsh "$ERLANG_NODE"
|
||||
exec_iex "$(uid debug)" --remsh "$ERLANG_NODE"
|
||||
;;
|
||||
iexlive)
|
||||
livewarning
|
||||
exec_iex $ERLANG_NODE --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
;;
|
||||
ping)
|
||||
PEER=${2:-$ERLANG_NODE}
|
||||
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
|
||||
exec_cmd "$ERL" ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
|
||||
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
|
||||
exec_cmd "$ERL" ${PS:--}name "$(uid ping "$(hostname $PS)")" $ERLANG_OPTS \
|
||||
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
started)
|
||||
@@ -298,8 +303,8 @@ case $1 in
|
||||
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
|
||||
;;
|
||||
*)
|
||||
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
|
||||
-extra $ERLANG_NODE $NO_TIMEOUT "$@"
|
||||
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
|
||||
result=$?
|
||||
case $result in
|
||||
2|3) help;;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-define(T(S), <<S>>).
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "17.6.0",
|
||||
version: "17.9.0",
|
||||
description: description(),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: ["lib"],
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
|
||||
"esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.10", "dd6aba8951ba15cab4ad483d997f8eefdb0cb00225971d0629c730d107a2bed6", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []},
|
||||
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
|
||||
"esip": {:hex, :esip, "1.0.15", "82c8b0178618c10b1ac9690841d94025c982d63f8cd6c8f8bf920cf33e301658", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.14", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.15", "96546e6a8b8384fbbcddf435c4c42cf2c0a3dc1858c3c9c2e62a74ae1ddd526a", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
|
||||
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
|
||||
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.3", "e2cc26f2e8d17c3885a9c2fee3ff64fcac5915896f50ab6f6aa9b0da1eed341c", [:rebar3], []},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.3", "ce94c83e9605c88d5f541b8f4b49edff3dc2bbacd1b6409c4cad0fbf7bef2ac4", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
|
||||
"stun": {:hex, :stun, "1.0.14", "6dc2080c25a72f7087301dc7333c1ea7d27ea4d88efaa379fc2b5924f3b17006", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.14", "e186f5208e7a448a4af784a8d2cb87cefe99dd49b24623e25d38115b23a50e12", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
-module(override_opts).
|
||||
-export([preprocess/2]).
|
||||
|
||||
override_opts(override, Config, Opts) ->
|
||||
lists:foldl(fun({Opt, Value}, Conf) ->
|
||||
rebar_config:set(Conf, Opt, Value)
|
||||
end, Config, Opts);
|
||||
override_opts(add, Config, Opts) ->
|
||||
lists:foldl(fun({Opt, Value}, Conf) ->
|
||||
V = rebar_config:get_local(Conf, Opt, []),
|
||||
rebar_config:set(Conf, Opt, [Value | V])
|
||||
end, Config, Opts).
|
||||
|
||||
preprocess(Config, _Dirs) ->
|
||||
Overrides = rebar_config:get_local(Config, overrides, []),
|
||||
TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of
|
||||
[] -> Overrides;
|
||||
Val -> Val
|
||||
end,
|
||||
Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides),
|
||||
Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of
|
||||
{ok, C, AppName, _AppData} ->
|
||||
lists:foldl(fun({Type, AppName2, Opts}, Conf1) when
|
||||
AppName2 == AppName ->
|
||||
override_opts(Type, Conf1, Opts);
|
||||
(_, Conf2) ->
|
||||
Conf2
|
||||
end, C, TopOverrides);
|
||||
_ ->
|
||||
Config2
|
||||
end,
|
||||
{ok, Config3, []}.
|
||||
+23
-18
@@ -20,30 +20,31 @@
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
|
||||
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.9"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.13"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.13"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.10"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.11"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.16"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.10"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.24"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.15"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.11"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
|
||||
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.12"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.13"}}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.15"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.16"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.3"}}}},
|
||||
{tag, "1.0.4"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.1.3"}}}},
|
||||
{tag, "1.1.4"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
{tag, "1.1.5"}}}},
|
||||
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
||||
{tag, "1.0.2"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
||||
{tag, "2.4.1"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client.git",
|
||||
{tag, {if_version_above, "19", "develop", "2.5.3"}}}}},
|
||||
{if_var_true, graphics, {eimp, ".*", {git, "https://github.com/processone/eimp.git", {tag, "1.0.1"}}}},
|
||||
%% Elixir support, needed to run tests
|
||||
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
|
||||
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
|
||||
@@ -51,7 +52,7 @@
|
||||
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
|
||||
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
|
||||
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
|
||||
{tag, "1.0.5"}}}},
|
||||
{tag, "1.0.6"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
|
||||
@@ -71,11 +72,13 @@
|
||||
p1_utils,
|
||||
p1_mysql,
|
||||
p1_pgsql,
|
||||
p1_oauth2,
|
||||
epam,
|
||||
ezlib,
|
||||
eimp,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{i, "include"},
|
||||
@@ -86,11 +89,13 @@
|
||||
{if_var_true, debug, debug_info},
|
||||
{if_var_true, sip, {d, 'SIP'}},
|
||||
{if_var_true, stun, {d, 'STUN'}},
|
||||
{if_var_true, graphics, {d, 'GRAPHICS'}},
|
||||
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
|
||||
{if_var_match, db_type, mssql, {d, 'mssql'}},
|
||||
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
|
||||
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
|
||||
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
|
||||
{if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
|
||||
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
|
||||
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
|
||||
{if_var_true, hipe, native},
|
||||
@@ -102,7 +107,7 @@
|
||||
|
||||
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
|
||||
{if_not_rebar3, {plugins, [
|
||||
deps_erl_opts, override_deps_versions,
|
||||
deps_erl_opts, override_deps_versions, override_opts,
|
||||
{if_var_true, elixir, rebar_elixir_compiler},
|
||||
{if_var_true, elixir, rebar_exunit}
|
||||
]}}.
|
||||
@@ -152,12 +157,12 @@
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
{if_var_true, graphics, {"eimp", []}},
|
||||
{if_var_true, iconv, {"iconv", []}}]}.
|
||||
|
||||
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
|
||||
|
||||
{port_specs, [{"priv/lib/jid.so", ["c_src/jid.c"]}]}.
|
||||
|
||||
%% Local Variables:
|
||||
%% mode: erlang
|
||||
%% End:
|
||||
|
||||
+6
-6
@@ -255,9 +255,9 @@ CtParams = fun(CompileOpts) ->
|
||||
|
||||
GenDepConfigureLine =
|
||||
fun(DepPath, Flags) ->
|
||||
["sh -c 'if test ! -f ",DepPath,"config.status -o ",
|
||||
"config.status -nt ",DepPath,"config.status; ",
|
||||
"then (cd ", DepPath, " && ",
|
||||
["sh -c 'if test ! -f config.status -o ",
|
||||
"../../config.status -nt config.status; ",
|
||||
"then (",
|
||||
"CFLAGS=\"", CFlags,"\" ",
|
||||
"CPPFLAGS=\"", CPPFlags, "\" "
|
||||
"LDFLAGS=\"", LDFlags, "\"",
|
||||
@@ -269,8 +269,8 @@ GenDepsConfigure =
|
||||
fun(Hooks) ->
|
||||
lists:map(fun({Pkg, Flags}) ->
|
||||
DepPath = ResolveDepPath("deps/" ++ Pkg ++ "/"),
|
||||
{'compile',
|
||||
lists:flatten(GenDepConfigureLine(DepPath, Flags))}
|
||||
{add, list_to_atom(Pkg), [{pre_hooks, {'compile',
|
||||
lists:flatten(GenDepConfigureLine(DepPath, Flags))}}]}
|
||||
end, Hooks)
|
||||
end,
|
||||
|
||||
@@ -326,7 +326,7 @@ Rules = [
|
||||
AppendList([{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}]), []},
|
||||
{[post_hooks], [cover_enabled], os:getenv("TRAVIS") == "true",
|
||||
AppendList2(TravisPostHooks), [], false},
|
||||
{[pre_hooks], [post_hook_configure], true,
|
||||
{[overrides], [post_hook_configure], true,
|
||||
AppendList2(GenDepsConfigure), [], []},
|
||||
{[ct_extra_params], [eunit_compile_opts], true,
|
||||
AppendStr2(CtParams), "", []},
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ CREATE TABLE archive (
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE FULLTEXT INDEX i_text ON archive(txt);
|
||||
CREATE INDEX i_username USING BTREE ON archive(username);
|
||||
CREATE INDEX i_username_timestamp USING BTREE ON archive(username,timestamp);
|
||||
CREATE INDEX i_timestamp USING BTREE ON archive(timestamp);
|
||||
CREATE INDEX i_peer USING BTREE ON archive(peer);
|
||||
CREATE INDEX i_bare_peer USING BTREE ON archive(bare_peer);
|
||||
|
||||
+12
-24
@@ -25,12 +25,11 @@
|
||||
|
||||
-module(ejabberd_app).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, prep_stop/1, stop/1, opt_type/1]).
|
||||
-export([start/2, prep_stop/1, stop/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -49,13 +48,12 @@ start(normal, _Args) ->
|
||||
setup_if_elixir_conf_used(),
|
||||
ejabberd_config:start(),
|
||||
ejabberd_mnesia:start(),
|
||||
set_settings_from_config(),
|
||||
file_queue_init(),
|
||||
maybe_add_nameservers(),
|
||||
connect_nodes(),
|
||||
case ejabberd_sup:start_link() of
|
||||
{ok, SupPid} ->
|
||||
register_elixir_config_hooks(),
|
||||
ejabberd_cluster:wait_for_sync(infinity),
|
||||
{T2, _} = statistics(wall_clock),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
|
||||
[?VERSION, node(), (T2-T1)/1000]),
|
||||
@@ -88,12 +86,6 @@ stop(_State) ->
|
||||
%%% Internal functions
|
||||
%%%
|
||||
|
||||
connect_nodes() ->
|
||||
Nodes = ejabberd_config:get_option(cluster_nodes, []),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes).
|
||||
|
||||
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
|
||||
maybe_add_nameservers() ->
|
||||
case os:type() of
|
||||
@@ -136,10 +128,6 @@ delete_pid_file() ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
set_settings_from_config() ->
|
||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
||||
net_kernel:set_net_ticktime(Ticktime).
|
||||
|
||||
file_queue_init() ->
|
||||
QueueDir = case ejabberd_config:queue_dir() of
|
||||
undefined ->
|
||||
@@ -158,16 +146,8 @@ start_apps() ->
|
||||
ejabberd:start_app(fast_yaml),
|
||||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
-spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
|
||||
(cluster_nodes) -> fun(([node()]) -> [node()]);
|
||||
(atom()) -> atom().
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||
opt_type(_) -> [cluster_nodes, net_ticktime].
|
||||
ejabberd:start_app(cache_tab),
|
||||
start_eimp().
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case ejabberd_config:is_using_elixir_config() of
|
||||
@@ -191,3 +171,11 @@ start_elixir_application() ->
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-ifdef(GRAPHICS).
|
||||
start_eimp() ->
|
||||
ejabberd:start_app(eimp).
|
||||
-else.
|
||||
start_eimp() ->
|
||||
ok.
|
||||
-endif.
|
||||
|
||||
@@ -261,12 +261,12 @@ try_register(User, Server, Password) ->
|
||||
ok;
|
||||
(Mod, _) ->
|
||||
db_try_register(
|
||||
User, Server, Password, Mod)
|
||||
LUser, LServer, Password, Mod)
|
||||
end, {error, not_allowed},
|
||||
auth_modules(LServer)) of
|
||||
ok ->
|
||||
ejabberd_hooks:run(
|
||||
register_user, Server, [User, Server]);
|
||||
register_user, LServer, [LUser, LServer]);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
|
||||
@@ -208,6 +208,8 @@ remove_user(User, Server) ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
need_transform(#reg_users_counter{}) ->
|
||||
false;
|
||||
need_transform(#passwd{us = {U, S}, password = Pass}) ->
|
||||
if is_binary(Pass) ->
|
||||
case store_type(S) of
|
||||
|
||||
+18
-20
@@ -27,9 +27,7 @@
|
||||
-protocol({xep, 124, '1.11'}).
|
||||
-protocol({xep, 206, '1.4'}).
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% API
|
||||
-export([start/2, start/3, start_link/3]).
|
||||
@@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) ->
|
||||
end.
|
||||
|
||||
start(StateName, State) ->
|
||||
(?GEN_FSM):start_link(?MODULE, [StateName, State],
|
||||
p1_fsm:start_link(?MODULE, [StateName, State],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Body, IP, SID) ->
|
||||
(?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
|
||||
p1_fsm:start_link(?MODULE, [Body, IP, SID],
|
||||
?FSMOPTS).
|
||||
|
||||
send({http_bind, FsmRef, IP}, Packet) ->
|
||||
send_xml({http_bind, FsmRef, IP}, Packet).
|
||||
|
||||
send_xml({http_bind, FsmRef, _IP}, Packet) ->
|
||||
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet},
|
||||
?SEND_TIMEOUT)
|
||||
of
|
||||
@@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) ->
|
||||
setopts({http_bind, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{activate, self()});
|
||||
_ ->
|
||||
case lists:member({active, false}, Opts) of
|
||||
true ->
|
||||
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
deactivate_socket)
|
||||
of
|
||||
{'EXIT', _} -> {error, einval};
|
||||
@@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
|
||||
{receiver, ?MODULE, FsmRef}.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{become_controller, C2SPid}).
|
||||
|
||||
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
@@ -190,14 +188,14 @@ change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
reset_stream({http_bind, _FsmRef, _IP}) -> ok.
|
||||
|
||||
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{change_shaper, Shaper}).
|
||||
|
||||
monitor({http_bind, FsmRef, _IP}) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
|
||||
close({http_bind, FsmRef, _IP}) ->
|
||||
catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
close).
|
||||
|
||||
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
|
||||
@@ -269,7 +267,7 @@ process_request(Data, IP, Type) ->
|
||||
end.
|
||||
|
||||
process_request(Pid, Req, _IP, Type) ->
|
||||
case catch (?GEN_FSM):sync_send_event(Pid, Req,
|
||||
case catch p1_fsm:sync_send_event(Pid, Req,
|
||||
infinity)
|
||||
of
|
||||
#body{} = Resp -> bosh_response(Resp, Type);
|
||||
@@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
|
||||
of
|
||||
{{value, {TRef, From, Body}}, Q} ->
|
||||
cancel_timer(TRef),
|
||||
(?GEN_FSM):send_event(self(), {Body, From}),
|
||||
p1_fsm:send_event(self(), {Body, From}),
|
||||
State1#state{shaped_receivers = Q};
|
||||
_ -> State1
|
||||
end,
|
||||
@@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
|
||||
State) ->
|
||||
case p1_queue:out(State#state.shaped_receivers) of
|
||||
{{value, {TRef, From, Req}}, Q} ->
|
||||
(?GEN_FSM):send_event(self(), {Req, From}),
|
||||
p1_fsm:send_event(self(), {Req, From}),
|
||||
{next_state, StateName,
|
||||
State#state{shaped_receivers = Q}};
|
||||
{{value, _}, _} ->
|
||||
@@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) ->
|
||||
mod_bosh:close_session(State#state.sid),
|
||||
case State#state.c2s_pid of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
(?GEN_FSM):send_event(C2SPid, closed);
|
||||
p1_fsm:send_event(C2SPid, closed);
|
||||
_ -> ok
|
||||
end,
|
||||
bounce_receivers(State, closed),
|
||||
@@ -644,7 +642,7 @@ print_state(State) -> State.
|
||||
route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) ->
|
||||
NewBuf = p1_queue:dropwhile(
|
||||
fun(El) ->
|
||||
?GEN_FSM:send_event(C2SPid, El),
|
||||
p1_fsm:send_event(C2SPid, El),
|
||||
true
|
||||
end, Buf),
|
||||
State#state{el_ibuf = NewBuf}.
|
||||
@@ -653,7 +651,7 @@ route_els(State, Els) ->
|
||||
case State#state.c2s_pid of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
lists:foreach(fun (El) ->
|
||||
(?GEN_FSM):send_event(C2SPid, El)
|
||||
p1_fsm:send_event(C2SPid, El)
|
||||
end,
|
||||
Els),
|
||||
State;
|
||||
@@ -676,7 +674,7 @@ reply(State, Body, RID, From) ->
|
||||
case catch gb_trees:take_smallest(Receivers) of
|
||||
{NextRID, {From1, Req}, Receivers1}
|
||||
when NextRID == RID + 1 ->
|
||||
(?GEN_FSM):send_event(self(), {Req, From1}),
|
||||
p1_fsm:send_event(self(), {Req, From1}),
|
||||
State2#state{receivers = Receivers1};
|
||||
_ -> State2#state{receivers = Receivers}
|
||||
end.
|
||||
@@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) ->
|
||||
?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
|
||||
"~p~n** To: ~p~n** State: ~p",
|
||||
[RID, Body, From, State]),
|
||||
(?GEN_FSM):reply(From, Body),
|
||||
p1_fsm:reply(From, Body),
|
||||
Responses = gb_trees:delete_any(RID,
|
||||
State#state.responses),
|
||||
Responses1 = case gb_trees:size(Responses) of
|
||||
@@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) ->
|
||||
end.
|
||||
|
||||
cancel_timer(TRef) when is_reference(TRef) ->
|
||||
(?GEN_FSM):cancel_timer(TRef);
|
||||
p1_fsm:cancel_timer(TRef);
|
||||
cancel_timer(_) -> false.
|
||||
|
||||
restart_timer(TRef, Timeout, Msg) ->
|
||||
|
||||
+28
-12
@@ -46,8 +46,8 @@
|
||||
reject_unauthenticated_packet/2, process_closed/2,
|
||||
process_terminated/2, process_info/2]).
|
||||
%% API
|
||||
-export([get_presence/1, resend_presence/1, resend_presence/2,
|
||||
open_session/1, call/3, send/2, close/1, close/2, stop/1,
|
||||
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
|
||||
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
|
||||
reply/2, copy_state/2, set_timeout/2, route/2,
|
||||
host_up/1, host_down/1]).
|
||||
|
||||
@@ -90,6 +90,10 @@ socket_type() ->
|
||||
call(Ref, Msg, Timeout) ->
|
||||
xmpp_stream_in:call(Ref, Msg, Timeout).
|
||||
|
||||
-spec cast(pid(), term()) -> ok.
|
||||
cast(Ref, Msg) ->
|
||||
xmpp_stream_in:cast(Ref, Msg).
|
||||
|
||||
reply(Ref, Reply) ->
|
||||
xmpp_stream_in:reply(Ref, Reply).
|
||||
|
||||
@@ -97,6 +101,10 @@ reply(Ref, Reply) ->
|
||||
get_presence(Ref) ->
|
||||
call(Ref, get_presence, 1000).
|
||||
|
||||
-spec set_presence(pid(), presence()) -> ok.
|
||||
set_presence(Ref, Pres) ->
|
||||
call(Ref, {set_presence, Pres}, 1000).
|
||||
|
||||
-spec resend_presence(pid()) -> ok.
|
||||
resend_presence(Pid) ->
|
||||
resend_presence(Pid, undefined).
|
||||
@@ -289,14 +297,19 @@ process_terminated(State, _Reason) ->
|
||||
%%%===================================================================
|
||||
%%% xmpp_stream_in callbacks
|
||||
%%%===================================================================
|
||||
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
tls_options(#{lserver := LServer, tls_options := DefaultOpts,
|
||||
stream_encrypted := Encrypted}) ->
|
||||
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
|
||||
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
|
||||
{_, _} ->
|
||||
case ejabberd_config:get_option(
|
||||
{domain_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
end
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{c2s_ciphers, LServer}) of
|
||||
@@ -398,7 +411,7 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
|
||||
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
|
||||
?INFO_MSG("(~s) Forbidden c2s session for ~s",
|
||||
[SockMod:pp(Socket), jid:encode(JID)]),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
{error, xmpp:err_not_allowed(Txt, Lang), State}
|
||||
end
|
||||
end.
|
||||
@@ -525,6 +538,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
|
||||
end,
|
||||
reply(From, Pres),
|
||||
State;
|
||||
handle_call({set_presence, Pres}, From, State) ->
|
||||
reply(From, ok),
|
||||
process_self_presence(State, Pres);
|
||||
handle_call(Request, From, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(
|
||||
c2s_handle_call, LServer, State, [Request, From]).
|
||||
@@ -642,7 +658,7 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
|
||||
MyBareJID = jid:remove_resource(JID),
|
||||
case acl:match_rule(LServer, Access, MyBareJID) of
|
||||
deny ->
|
||||
ErrText = <<"Denied by ACL">>,
|
||||
ErrText = <<"Access denied by service policy">>,
|
||||
Err = xmpp:err_forbidden(ErrText, Lang),
|
||||
send_error(State, Pres, Err);
|
||||
allow ->
|
||||
|
||||
+11
-16
@@ -388,29 +388,24 @@ get_transfer_protocol(PortString) ->
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_option(listen, []),
|
||||
lists:filter(fun (Listener) when is_list(Listener) ->
|
||||
case proplists:get_value(port, Listener) of
|
||||
PortNumber -> true;
|
||||
_ -> false
|
||||
end;
|
||||
(_) -> false
|
||||
end,
|
||||
AllListeners).
|
||||
lists:filter(
|
||||
fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
|
||||
Port == PortNumber
|
||||
end, AllListeners).
|
||||
|
||||
get_captcha_transfer_protocol([]) ->
|
||||
throw(<<"The port number mentioned in captcha_host "
|
||||
"is not a ejabberd_http listener with "
|
||||
"'captcha' option. Change the port number "
|
||||
"or specify http:// in that option.">>);
|
||||
get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) ->
|
||||
case proplists:get_value(module, Listener) == ejabberd_http andalso
|
||||
proplists:get_bool(captcha, Listener) of
|
||||
get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
|
||||
case proplists:get_bool(captcha, Opts) of
|
||||
true ->
|
||||
case proplists:get_bool(tls, Listener) of
|
||||
true -> https;
|
||||
false -> http
|
||||
end;
|
||||
false -> get_captcha_transfer_protocol(Listeners)
|
||||
case proplists:get_bool(tls, Opts) of
|
||||
true -> https;
|
||||
false -> http
|
||||
end;
|
||||
false -> get_captcha_transfer_protocol(Listeners)
|
||||
end;
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
+142
-88
@@ -1,8 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Ejabberd clustering management
|
||||
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 5 Jul 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
@@ -21,132 +19,188 @@
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_cluster).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([get_nodes/0, call/4, multicall/3, multicall/4,
|
||||
eval_everywhere/3, eval_everywhere/4]).
|
||||
-export([join/1, leave/1, get_known_nodes/0]).
|
||||
-export([node_id/0, get_node_by_id/1]).
|
||||
-export([start_link/0, call/4, multicall/3, multicall/4, eval_everywhere/3,
|
||||
eval_everywhere/4]).
|
||||
%% Backend dependent API
|
||||
-export([get_nodes/0, get_known_nodes/0, join/1, leave/1, subscribe/0,
|
||||
subscribe/1, node_id/0, get_node_by_id/1, send/2, wait_for_sync/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-spec get_nodes() -> [node()].
|
||||
-type dst() :: pid() | atom() | {atom(), node()}.
|
||||
|
||||
get_nodes() ->
|
||||
mnesia:system_info(running_db_nodes).
|
||||
-callback init() -> ok | {error, any()}.
|
||||
-callback get_nodes() -> [node()].
|
||||
-callback get_known_nodes() -> [node()].
|
||||
-callback join(node()) -> ok | {error, any()}.
|
||||
-callback leave(node()) -> ok | {error, any()}.
|
||||
-callback node_id() -> binary().
|
||||
-callback get_node_by_id(binary()) -> node().
|
||||
-callback send({atom(), node()}, term()) -> boolean().
|
||||
-callback wait_for_sync(timeout()) -> ok | {error, any()}.
|
||||
-callback subscribe(dst()) -> ok.
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
-record(state, {}).
|
||||
|
||||
get_known_nodes() ->
|
||||
lists:usort(mnesia:system_info(db_nodes)
|
||||
++ mnesia:system_info(extra_db_nodes)).
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec call(node(), module(), atom(), [any()]) -> any().
|
||||
|
||||
call(Node, Module, Function, Args) ->
|
||||
rpc:call(Node, Module, Function, Args, 5000).
|
||||
rpc:call(Node, Module, Function, Args, rpc_timeout()).
|
||||
|
||||
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
|
||||
|
||||
multicall(Module, Function, Args) ->
|
||||
multicall(get_nodes(), Module, Function, Args).
|
||||
|
||||
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
|
||||
|
||||
multicall(Nodes, Module, Function, Args) ->
|
||||
rpc:multicall(Nodes, Module, Function, Args, 5000).
|
||||
rpc:multicall(Nodes, Module, Function, Args, rpc_timeout()).
|
||||
|
||||
-spec eval_everywhere(module(), atom(), [any()]) -> ok.
|
||||
|
||||
eval_everywhere(Module, Function, Args) ->
|
||||
eval_everywhere(get_nodes(), Module, Function, Args),
|
||||
ok.
|
||||
|
||||
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
|
||||
|
||||
eval_everywhere(Nodes, Module, Function, Args) ->
|
||||
rpc:eval_everywhere(Nodes, Module, Function, Args),
|
||||
ok.
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
%%%===================================================================
|
||||
%%% Backend dependent API
|
||||
%%%===================================================================
|
||||
-spec get_nodes() -> [node()].
|
||||
get_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_nodes().
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
get_known_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_known_nodes().
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
join(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
{error, {not_master, Node}};
|
||||
{_, pong} ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
mnesia:delete_schema([node()]),
|
||||
application:start(mnesia),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(Table) ->
|
||||
Type = call(Node, mnesia, table_info, [Table, storage_type]),
|
||||
mnesia:add_table_copy(Table, node(), Type)
|
||||
end, mnesia:system_info(tables)--[schema])
|
||||
end),
|
||||
application:start(ejabberd);
|
||||
_ ->
|
||||
{error, {no_ping, Node}}
|
||||
end.
|
||||
Mod = get_mod(),
|
||||
Mod:join(Node).
|
||||
|
||||
-spec leave(node()) -> ok | {error, any()}.
|
||||
|
||||
leave(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
Cluster = get_nodes()--[Node],
|
||||
leave(Cluster, Node);
|
||||
{_, pong} ->
|
||||
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
||||
{_, pang} ->
|
||||
case mnesia:del_table_copy(schema, Node) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end
|
||||
end.
|
||||
leave([], Node) ->
|
||||
{error, {no_cluster, Node}};
|
||||
leave([Master|_], Node) ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
call(Master, mnesia, del_table_copy, [schema, Node]),
|
||||
spawn(fun() ->
|
||||
mnesia:delete_schema([node()]),
|
||||
erlang:halt(0)
|
||||
end),
|
||||
ok.
|
||||
Mod = get_mod(),
|
||||
Mod:leave(Node).
|
||||
|
||||
-spec node_id() -> binary().
|
||||
node_id() ->
|
||||
integer_to_binary(erlang:phash2(node())).
|
||||
Mod = get_mod(),
|
||||
Mod:node_id().
|
||||
|
||||
-spec get_node_by_id(binary()) -> node().
|
||||
get_node_by_id(Hash) ->
|
||||
try binary_to_integer(Hash) of
|
||||
I -> match_node_id(I)
|
||||
catch _:_ ->
|
||||
node()
|
||||
get_node_by_id(ID) ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_node_by_id(ID).
|
||||
|
||||
-spec send(dst(), term()) -> boolean().
|
||||
send(Dst, Msg) ->
|
||||
IsLocal = case Dst of
|
||||
{_, Node} -> Node == node();
|
||||
Pid when is_pid(Pid) -> node(Pid) == node();
|
||||
Name when is_atom(Name) -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if IsLocal ->
|
||||
erlang:send(Dst, Msg),
|
||||
true;
|
||||
true ->
|
||||
Mod = get_mod(),
|
||||
Mod:send(Dst, Msg)
|
||||
end.
|
||||
|
||||
-spec wait_for_sync(timeout()) -> ok | {error, any()}.
|
||||
wait_for_sync(Timeout) ->
|
||||
Mod = get_mod(),
|
||||
Mod:wait_for_sync(Timeout).
|
||||
|
||||
-spec subscribe() -> ok.
|
||||
subscribe() ->
|
||||
subscribe(self()).
|
||||
|
||||
-spec subscribe(dst()) -> ok.
|
||||
subscribe(Proc) ->
|
||||
Mod = get_mod(),
|
||||
Mod:subscribe(Proc).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server API
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
||||
Nodes = ejabberd_config:get_option(cluster_nodes, []),
|
||||
net_kernel:set_net_ticktime(Ticktime),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes),
|
||||
Mod = get_mod(),
|
||||
case Mod:init() of
|
||||
ok ->
|
||||
Mod:subscribe(?MODULE),
|
||||
{ok, #state{}};
|
||||
{error, Reason} ->
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({node_up, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has joined", [Node]),
|
||||
{noreply, State};
|
||||
handle_info({node_down, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has left", [Node]),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec match_node_id(integer()) -> node().
|
||||
match_node_id(I) ->
|
||||
match_node_id(I, get_nodes()).
|
||||
get_mod() ->
|
||||
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
|
||||
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
|
||||
|
||||
-spec match_node_id(integer(), [node()]) -> node().
|
||||
match_node_id(I, [Node|Nodes]) ->
|
||||
case erlang:phash2(Node) of
|
||||
I -> Node;
|
||||
_ -> match_node_id(I, Nodes)
|
||||
end;
|
||||
match_node_id(_I, []) ->
|
||||
node().
|
||||
rpc_timeout() ->
|
||||
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||
opt_type(rpc_timeout) ->
|
||||
fun (T) when is_integer(T), T > 0 -> T end;
|
||||
opt_type(cluster_backend) ->
|
||||
fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) ->
|
||||
[rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster_mnesia.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Ejabberd clustering management via Mnesia
|
||||
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 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_cluster_mnesia).
|
||||
-behaviour(ejabberd_cluster).
|
||||
|
||||
%% API
|
||||
-export([init/0, get_nodes/0, join/1, leave/1,
|
||||
get_known_nodes/0, node_id/0, get_node_by_id/1,
|
||||
send/2, wait_for_sync/1, subscribe/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-spec init() -> ok.
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
-spec get_nodes() -> [node()].
|
||||
|
||||
get_nodes() ->
|
||||
mnesia:system_info(running_db_nodes).
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
|
||||
get_known_nodes() ->
|
||||
lists:usort(mnesia:system_info(db_nodes)
|
||||
++ mnesia:system_info(extra_db_nodes)).
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
|
||||
join(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
{error, {not_master, Node}};
|
||||
{_, pong} ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
mnesia:delete_schema([node()]),
|
||||
application:start(mnesia),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(Table) ->
|
||||
Type = ejabberd_cluster:call(
|
||||
Node, mnesia, table_info, [Table, storage_type]),
|
||||
mnesia:add_table_copy(Table, node(), Type)
|
||||
end, mnesia:system_info(tables)--[schema])
|
||||
end),
|
||||
application:start(ejabberd);
|
||||
_ ->
|
||||
{error, {no_ping, Node}}
|
||||
end.
|
||||
|
||||
-spec leave(node()) -> ok | {error, any()}.
|
||||
|
||||
leave(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
Cluster = get_nodes()--[Node],
|
||||
leave(Cluster, Node);
|
||||
{_, pong} ->
|
||||
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
||||
{_, pang} ->
|
||||
case mnesia:del_table_copy(schema, Node) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end
|
||||
end.
|
||||
leave([], Node) ->
|
||||
{error, {no_cluster, Node}};
|
||||
leave([Master|_], Node) ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
ejabberd_cluster:call(Master, mnesia, del_table_copy, [schema, Node]),
|
||||
spawn(fun() ->
|
||||
mnesia:delete_schema([node()]),
|
||||
erlang:halt(0)
|
||||
end),
|
||||
ok.
|
||||
|
||||
-spec node_id() -> binary().
|
||||
node_id() ->
|
||||
integer_to_binary(erlang:phash2(node())).
|
||||
|
||||
-spec get_node_by_id(binary()) -> node().
|
||||
get_node_by_id(Hash) ->
|
||||
try binary_to_integer(Hash) of
|
||||
I -> match_node_id(I)
|
||||
catch _:_ ->
|
||||
node()
|
||||
end.
|
||||
|
||||
-spec send({atom(), node()}, term()) -> boolean().
|
||||
send(Dst, Msg) ->
|
||||
erlang:send(Dst, Msg).
|
||||
|
||||
-spec wait_for_sync(timeout()) -> ok.
|
||||
wait_for_sync(Timeout) ->
|
||||
?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
|
||||
ok.
|
||||
|
||||
-spec subscribe(_) -> ok.
|
||||
subscribe(_) ->
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec match_node_id(integer()) -> node().
|
||||
match_node_id(I) ->
|
||||
match_node_id(I, get_nodes()).
|
||||
|
||||
-spec match_node_id(integer(), [node()]) -> node().
|
||||
match_node_id(I, [Node|Nodes]) ->
|
||||
case erlang:phash2(Node) of
|
||||
I -> Node;
|
||||
_ -> match_node_id(I, Nodes)
|
||||
end;
|
||||
match_node_id(_I, []) ->
|
||||
node().
|
||||
+2
-273
@@ -221,7 +221,6 @@
|
||||
get_command_format/1,
|
||||
get_command_format/2,
|
||||
get_command_format/3,
|
||||
get_command_policy_and_scope/1,
|
||||
get_command_definition/1,
|
||||
get_command_definition/2,
|
||||
get_tags_commands/0,
|
||||
@@ -230,11 +229,6 @@
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
expose_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
execute_command/6,
|
||||
opt_type/1,
|
||||
get_commands_spec/0,
|
||||
get_commands_definition/0,
|
||||
@@ -361,6 +355,8 @@ expose_commands(Commands) ->
|
||||
Commands),
|
||||
|
||||
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
|
||||
ok ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason};
|
||||
{atomic, Result} ->
|
||||
@@ -427,17 +423,6 @@ get_command_format(Name, Auth, Version) ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
|
||||
|
||||
%% @doc return command policy.
|
||||
get_command_policy_and_scope(Name) ->
|
||||
case get_command_definition(Name) of
|
||||
#ejabberd_commands{policy = Policy} = Cmd ->
|
||||
{ok, Policy, cmd_scope(Cmd)};
|
||||
command_not_found ->
|
||||
{error, command_not_found}
|
||||
end.
|
||||
|
||||
%% The oauth scopes for a command are the command name itself,
|
||||
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
|
||||
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
|
||||
@@ -503,129 +488,6 @@ execute_command2(Name, Arguments, CallerInfo, Version) ->
|
||||
throw({error, access_rules_unauthorized})
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm
|
||||
%% where
|
||||
%% Arguments = [any()]
|
||||
%% @doc Execute a command.
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided | access_rules_unauthorized
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command(atom(),
|
||||
[any()],
|
||||
integer() |
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin
|
||||
) -> any().
|
||||
|
||||
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
|
||||
%% where
|
||||
%% Auth = {User::string(), Server::string(), Password::string(),
|
||||
%% Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments, Version) when is_integer(Version) ->
|
||||
execute_command([], noauth, Name, Arguments, Version);
|
||||
execute_command(Name, Arguments, Auth) ->
|
||||
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
|
||||
%% ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()],
|
||||
integer()
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
|
||||
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
|
||||
Auth = case is_admin(Name, Auth1, CallerInfo) of
|
||||
true -> admin;
|
||||
false -> Auth1
|
||||
end,
|
||||
TokenJID = oauth_token_user(Auth1),
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_all_access_commands(AccessCommands1),
|
||||
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
|
||||
ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
|
||||
end.
|
||||
|
||||
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
noauth, _JID, Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, [User, Server | Arguments]).
|
||||
|
||||
execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_access(undefined, _Command, _Arguments) ->
|
||||
throw({error, access_rules_unauthorized});
|
||||
execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
|
||||
%% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
|
||||
Host = global,
|
||||
Rules = lists:map(
|
||||
fun({Mod, AccessName, Default}) ->
|
||||
gen_mod:get_module_opt(Host, Mod, AccessName, Default);
|
||||
(Default) ->
|
||||
Default
|
||||
end, AccessRefs),
|
||||
case acl:any_rules_allowed(Host, Rules, FromJID) of
|
||||
true ->
|
||||
do_execute_command(Command, Arguments);
|
||||
false ->
|
||||
throw({error, access_rules_unauthorized})
|
||||
end.
|
||||
|
||||
do_execute_command(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
@@ -672,58 +534,6 @@ get_tags_commands(Version) ->
|
||||
%% Access verification
|
||||
%% -----------------------------
|
||||
|
||||
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
|
||||
%% where
|
||||
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% @doc Check access is allowed to that command.
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
|
||||
Command =
|
||||
case {Command1#ejabberd_commands.policy, Auth} of
|
||||
{user, {_, _, _, _}} ->
|
||||
Command1;
|
||||
{user, _} ->
|
||||
Command1#ejabberd_commands{
|
||||
args = [{user, binary}, {server, binary} |
|
||||
Command1#ejabberd_commands.args]};
|
||||
_ ->
|
||||
Command1
|
||||
end,
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
({Access, Commands}) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
AccessCommands),
|
||||
case AccessCommandsAllowed of
|
||||
[] -> throw({error, account_unprivileged});
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
|
||||
(ejabberd_commands(),
|
||||
{binary(), binary(), binary(), boolean()}) ->
|
||||
@@ -746,80 +556,6 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
check_access(Command, ?POLICY_ACCESS, _, _)
|
||||
when Command#ejabberd_commands.policy == open ->
|
||||
true;
|
||||
check_access(_Command, _Access, admin, _) ->
|
||||
true;
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
|
||||
false;
|
||||
check_access(Command, Access, Auth, CallerInfo)
|
||||
when Access =/= ?POLICY_ACCESS;
|
||||
Command#ejabberd_commands.policy == open;
|
||||
Command#ejabberd_commands.policy == user ->
|
||||
case check_auth(Command, Auth) of
|
||||
{ok, User, Server} ->
|
||||
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server))}, Server);
|
||||
no_auth_provided ->
|
||||
case Command#ejabberd_commands.policy of
|
||||
user ->
|
||||
false;
|
||||
_ ->
|
||||
check_access2(Access, CallerInfo, global)
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
check_access(_Command, _Access, _Auth, _CallerInfo) ->
|
||||
false.
|
||||
|
||||
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
|
||||
true;
|
||||
check_access2(Access, AccessInfo, Server) ->
|
||||
%% Check this user has access permission
|
||||
case acl:access_matches(Access, AccessInfo, Server) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
|
||||
ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
|
||||
lists:all(
|
||||
fun({ArgName, ArgAllowedValue}) ->
|
||||
%% If the call uses the argument, check the value is acceptable
|
||||
case lists:keysearch(ArgName, 1, ArgumentsTagged) of
|
||||
{value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
|
||||
false -> true
|
||||
end
|
||||
end, ArgumentRestrictions).
|
||||
|
||||
tag_arguments(ArgsDefs, Args) ->
|
||||
lists:zipwith(
|
||||
fun({ArgName, _ArgType}, ArgValue) ->
|
||||
{ArgName, ArgValue}
|
||||
end,
|
||||
ArgsDefs,
|
||||
Args).
|
||||
|
||||
|
||||
%% Get commands for all version
|
||||
get_all_access_commands(AccessCommands) ->
|
||||
get_access_commands(AccessCommands, ?DEFAULT_VERSION).
|
||||
|
||||
get_access_commands(undefined, Version) ->
|
||||
Cmds = get_exposed_commands(Version),
|
||||
[{?POLICY_ACCESS, Cmds, []}];
|
||||
get_access_commands(AccessCommands, _Version) ->
|
||||
AccessCommands.
|
||||
|
||||
get_exposed_commands() ->
|
||||
get_exposed_commands(?DEFAULT_VERSION).
|
||||
get_exposed_commands(Version) ->
|
||||
@@ -854,13 +590,6 @@ expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L
|
||||
[Command|Acc]
|
||||
end, [], L).
|
||||
|
||||
oauth_token_user(noauth) ->
|
||||
undefined;
|
||||
oauth_token_user(admin) ->
|
||||
undefined;
|
||||
oauth_token_user({User, Server, _, _}) ->
|
||||
jid:make(User, Server).
|
||||
|
||||
is_admin(_Name, admin, _Extra) ->
|
||||
true;
|
||||
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
|
||||
|
||||
@@ -87,9 +87,6 @@ md_tag(strong, V) ->
|
||||
md_tag(_, V) ->
|
||||
V.
|
||||
|
||||
unbinarize(binary) -> string;
|
||||
unbinarize(Other) -> Other.
|
||||
|
||||
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
||||
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
|
||||
@@ -252,7 +249,7 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
|
||||
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
|
||||
{{Name0, _}, _} ->
|
||||
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), ?OP_L("}")]}
|
||||
end,
|
||||
CodeStr = case Code of
|
||||
200 -> <<" 200 OK">>;
|
||||
@@ -340,48 +337,62 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
|
||||
end
|
||||
end.
|
||||
|
||||
format_type({list, {_, {tuple, Els}}}) ->
|
||||
io_lib:format("[~s]", [format_type({tuple, Els})]);
|
||||
format_type({list, El}) ->
|
||||
io_lib:format("[~s]", [format_type(El)]);
|
||||
format_type({tuple, Els}) ->
|
||||
Args = [format_type(El) || El <- Els],
|
||||
io_lib:format("{~s}", [string:join(Args, ", ")]);
|
||||
format_type({Name, Type}) ->
|
||||
io_lib:format("~s::~s", [Name, format_type(Type)]);
|
||||
format_type(binary) ->
|
||||
"string";
|
||||
format_type(atom) ->
|
||||
"string";
|
||||
format_type(Type) ->
|
||||
io_lib:format("~p", [Type]).
|
||||
|
||||
gen_param(Name, Type, undefined, HTMLOutput) ->
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [unbinarize(Type)]))])];
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
|
||||
gen_param(Name, Type, Desc, HTMLOutput) ->
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [unbinarize(Type)]))]),
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
|
||||
?TAG(dd, ?RAW(Desc))].
|
||||
|
||||
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
|
||||
args=Args, args_desc=ArgsDesc,
|
||||
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||
|| {AName, Type} <- Args])];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||
end,
|
||||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?TAG(dl, [gen_param(res, string,
|
||||
"Raw result string",
|
||||
HTMLOutput)])];
|
||||
{RName, Type} ->
|
||||
case ResultDesc of
|
||||
none ->
|
||||
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
|
||||
_ ->
|
||||
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
|
||||
end
|
||||
end,
|
||||
|
||||
try
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||
|| {AName, Type} <- Args])];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||
end,
|
||||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?TAG(dl, [gen_param(res, string,
|
||||
"Raw result string",
|
||||
HTMLOutput)])];
|
||||
{RName, Type} ->
|
||||
case ResultDesc of
|
||||
none ->
|
||||
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
|
||||
_ ->
|
||||
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
|
||||
end
|
||||
end,
|
||||
|
||||
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||
?TAG(p, ?RAW(LDesc)),
|
||||
?TAG(h2, <<"Arguments:">>), ArgsText,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
-export([start/0, load_file/1, reload_file/0, read_file/1,
|
||||
get_option/1, get_option/2, add_option/2, has_option/1,
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_vh_by_auth_method/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
|
||||
get_ejabberd_config_path/0, is_using_elixir_config/0,
|
||||
prepare_opt_val/4, transform_options/1, collect_options/1,
|
||||
@@ -46,11 +46,12 @@
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3,
|
||||
get_option/3]).
|
||||
-export([is_file_readable/1]).
|
||||
|
||||
-deprecated([{add_global_option, 2}, {add_local_option, 2},
|
||||
{get_global_option, 2}, {get_local_option, 2},
|
||||
{get_global_option, 3}, {get_local_option, 3},
|
||||
{get_option, 3}]).
|
||||
{get_option, 3}, {is_file_readable, 1}]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
-author('ecestari@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-export([start/1, start_link/1, init/1, handle_event/3,
|
||||
handle_sync_event/4, code_change/4, handle_info/3,
|
||||
@@ -75,13 +75,13 @@
|
||||
-export_type([ws_socket/0]).
|
||||
|
||||
start(WS) ->
|
||||
gen_fsm:start(?MODULE, [WS], ?FSMOPTS).
|
||||
p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
|
||||
|
||||
start_link(WS) ->
|
||||
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
||||
p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
||||
|
||||
send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet},
|
||||
15000)
|
||||
of
|
||||
@@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
||||
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
gen_fsm:send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{activate, self()});
|
||||
_ -> ok
|
||||
end.
|
||||
@@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
|
||||
controlling_process(_Socket, _Pid) -> ok.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
gen_fsm:send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{become_controller, C2SPid}).
|
||||
|
||||
close({http_ws, FsmRef, _IP}) ->
|
||||
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
|
||||
socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
|
||||
ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod,
|
||||
@@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
|
||||
StateData2#state{pong_expected = false}};
|
||||
handle_info({timeout, Timer, _}, _StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
|
||||
{stop, normal, StateData};
|
||||
handle_info({timeout, Timer, _}, StateName,
|
||||
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
|
||||
@@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
|
||||
{next_state, StateName,
|
||||
StateData#state{ping_timer = PingTimer, pong_expected = true}};
|
||||
true ->
|
||||
?DEBUG("Closing websocket connection from missing pongs", []),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
handle_info(_, StateName, StateData) ->
|
||||
|
||||
@@ -109,6 +109,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
{ok, Socket} ->
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
application:ensure_started(ejabberd),
|
||||
start_module_sup(Port, Module),
|
||||
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
|
||||
[format_portip(PortIP), Module]),
|
||||
@@ -134,6 +135,7 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
application:ensure_started(ejabberd),
|
||||
start_module_sup(Port, Module),
|
||||
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
|
||||
[format_portip(PortIP), Module]),
|
||||
|
||||
@@ -151,6 +151,9 @@ do_start() ->
|
||||
application:set_env(lager, crash_log_size, LogRotateSize),
|
||||
application:set_env(lager, crash_log_count, LogRotateCount),
|
||||
ejabberd:start_app(lager),
|
||||
lists:foreach(fun(Handler) ->
|
||||
lager:set_loghwm(Handler, LogRateLimit)
|
||||
end, gen_event:which_handlers(lager_event)),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
+3
-25
@@ -50,7 +50,7 @@
|
||||
config_reloaded/0,
|
||||
opt_type/1]).
|
||||
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
@@ -96,14 +96,6 @@ get_commands_spec() ->
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
|
||||
desc = "List scopes that can be granted, and commands",
|
||||
longdesc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
|
||||
module = ?MODULE, function = oauth_list_scopes,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
|
||||
desc = "Revoke authorization for a token (only Mnesia)",
|
||||
module = ?MODULE, function = oauth_revoke_token,
|
||||
@@ -143,9 +135,6 @@ oauth_revoke_token(Token) ->
|
||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||
oauth_list_tokens().
|
||||
|
||||
oauth_list_scopes() ->
|
||||
[ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
|
||||
|
||||
config_reloaded() ->
|
||||
DBMod = get_db_backend(),
|
||||
case init_cache(DBMod) of
|
||||
@@ -240,17 +229,6 @@ verify_resowner_scope(_, _, _) ->
|
||||
{error, badscope}.
|
||||
|
||||
|
||||
get_cmd_scopes() ->
|
||||
ScopeMap = lists:foldl(fun(Cmd, Accum) ->
|
||||
case ejabberd_commands:get_command_policy_and_scope(Cmd) of
|
||||
{ok, Policy, Scopes} when Policy =/= restricted ->
|
||||
lists:foldl(fun(Scope, Accum2) ->
|
||||
dict:append(Scope, Cmd, Accum2)
|
||||
end, Accum, Scopes);
|
||||
_ -> Accum
|
||||
end end, dict:new(), ejabberd_commands:get_exposed_commands()),
|
||||
ScopeMap.
|
||||
|
||||
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
|
||||
%% made available.
|
||||
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
|
||||
@@ -458,7 +436,7 @@ process(_Handlers,
|
||||
?INPUT(<<"hidden">>, <<"scope">>, Scope),
|
||||
?INPUT(<<"hidden">>, <<"state">>, State),
|
||||
?BR,
|
||||
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
|
||||
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]),
|
||||
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
|
||||
[
|
||||
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
|
||||
@@ -755,7 +733,7 @@ css() ->
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container > .section {
|
||||
.container > .section {
|
||||
background: #424A55;
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ terminate(_Reason,
|
||||
State) ->
|
||||
close_stream(XMLStreamState),
|
||||
if C2SPid /= undefined ->
|
||||
gen_fsm:send_event(C2SPid, closed);
|
||||
p1_fsm:send_event(C2SPid, closed);
|
||||
true -> ok
|
||||
end,
|
||||
catch (State#state.sock_mod):close(State#state.socket),
|
||||
@@ -272,7 +272,7 @@ process_data([Element | Els],
|
||||
element(1, Element) == xmlstreamend ->
|
||||
if C2SPid == undefined -> State;
|
||||
true ->
|
||||
catch gen_fsm:send_event(C2SPid,
|
||||
catch p1_fsm:send_event(C2SPid,
|
||||
element_wrapper(Element)),
|
||||
process_data(Els, State)
|
||||
end;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
-module(ejabberd_regexp).
|
||||
|
||||
-compile([export_all]).
|
||||
-export([exec/2, run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
|
||||
|
||||
exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
|
||||
try apply(ReM, ReF, ReA) catch
|
||||
|
||||
@@ -149,7 +149,7 @@ init([]) ->
|
||||
lists:foreach(
|
||||
fun (Pid) -> erlang:monitor(process, Pid) end,
|
||||
mnesia:dirty_select(route,
|
||||
[{{route, '_', '$1', '_'}, [], ['$1']}])),
|
||||
[{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
|
||||
+28
-24
@@ -39,7 +39,7 @@
|
||||
remove_connection/2, start_connection/2, start_connection/3,
|
||||
dirty_get_connections/0, allow_host/2,
|
||||
incoming_s2s_number/0, outgoing_s2s_number/0,
|
||||
stop_all_connections/0,
|
||||
stop_s2s_connections/0,
|
||||
clean_temporarily_blocked_table/0,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1, is_temporarly_blocked/1,
|
||||
@@ -199,9 +199,9 @@ dirty_get_connections() ->
|
||||
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
|
||||
tls_options(LServer, DefaultOpts) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{s2s_certfile, LServer},
|
||||
{domain_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer})) of
|
||||
{s2s_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
@@ -369,7 +369,7 @@ do_route(Packet) ->
|
||||
<<"Server connections to local "
|
||||
"subdomains are forbidden">>, Lang);
|
||||
forbidden ->
|
||||
xmpp:err_forbidden(<<"Denied by ACL">>, Lang);
|
||||
xmpp:err_forbidden(<<"Access denied by service policy">>, Lang);
|
||||
internal_server_error ->
|
||||
xmpp:err_internal_server_error()
|
||||
end,
|
||||
@@ -546,25 +546,23 @@ parent_domains(Domain) ->
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{
|
||||
name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
name = incoming_s2s_number, tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{
|
||||
name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
name = outgoing_s2s_number, tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}},
|
||||
#ejabberd_commands{name = stop_all_connections,
|
||||
tags = [s2s],
|
||||
desc = "Stop all outgoing and incoming connections",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stop_all_connections,
|
||||
args = [], result = {res, rescode}}].
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}},
|
||||
#ejabberd_commands{
|
||||
name = stop_s2s_connections, tags = [s2s],
|
||||
desc = "Stop all s2s outgoing and incoming connections",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stop_s2s_connections,
|
||||
args = [], result = {res, rescode}}].
|
||||
|
||||
%% TODO Move those stats commands to ejabberd stats command ?
|
||||
incoming_s2s_number() ->
|
||||
@@ -580,8 +578,8 @@ supervisor_count(Supervisor) ->
|
||||
length(Result)
|
||||
end.
|
||||
|
||||
-spec stop_all_connections() -> ok.
|
||||
stop_all_connections() ->
|
||||
-spec stop_s2s_connections() -> ok.
|
||||
stop_s2s_connections() ->
|
||||
lists:foreach(
|
||||
fun({_Id, Pid, _Type, _Module}) ->
|
||||
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
|
||||
@@ -684,7 +682,7 @@ complete_s2s_info([Connection | T], Type, Result) ->
|
||||
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
|
||||
|
||||
get_s2s_state(S2sPid) ->
|
||||
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
|
||||
Infos = case p1_fsm:sync_send_all_state_event(S2sPid,
|
||||
get_state_infos)
|
||||
of
|
||||
{state_infos, Is} -> [{status, open} | Is];
|
||||
@@ -728,7 +726,13 @@ opt_type(s2s_use_starttls) ->
|
||||
(false) -> false;
|
||||
(optional) -> optional;
|
||||
(required) -> required;
|
||||
(required_trusted) -> required_trusted
|
||||
(required_trusted) ->
|
||||
?WARNING_MSG("The value 'required_trusted' of option "
|
||||
"'s2s_use_starttls' is deprected and will be "
|
||||
"unsupported in future releases. Instead, "
|
||||
"set it to 'required' and make sure "
|
||||
"mod_s2s_dialback is *NOT* loaded", []),
|
||||
required_trusted
|
||||
end;
|
||||
opt_type(s2s_zlib) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
|
||||
@@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) ->
|
||||
-spec get_delay() -> non_neg_integer().
|
||||
get_delay() ->
|
||||
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
|
||||
crypto:rand_uniform(1, MaxDelay).
|
||||
randoms:uniform(MaxDelay).
|
||||
|
||||
-spec set_idle_timeout(state()) -> state().
|
||||
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
|
||||
|
||||
@@ -199,7 +199,7 @@ handle_info({route, Packet}, #{access := Access} = State) ->
|
||||
xmpp_stream_in:send(State, Packet);
|
||||
deny ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
|
||||
Err = xmpp:err_not_allowed(<<"Access denied by service policy">>, Lang),
|
||||
ejabberd_router:route_error(Packet, Err),
|
||||
State
|
||||
end;
|
||||
|
||||
+41
-12
@@ -66,6 +66,8 @@
|
||||
user_resources/2,
|
||||
kick_user/2,
|
||||
get_session_pid/3,
|
||||
get_session_sid/3,
|
||||
get_session_sids/2,
|
||||
get_user_info/2,
|
||||
get_user_info/3,
|
||||
get_user_ip/3,
|
||||
@@ -292,15 +294,32 @@ close_session_unset_presence(SID, User, Server,
|
||||
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
|
||||
|
||||
get_session_pid(User, Server, Resource) ->
|
||||
case get_session_sid(User, Server, Resource) of
|
||||
{_, PID} -> PID;
|
||||
none -> none
|
||||
end.
|
||||
|
||||
-spec get_session_sid(binary(), binary(), binary()) -> none | sid().
|
||||
|
||||
get_session_sid(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[#session{sid = {_, Pid}}] -> Pid;
|
||||
[#session{sid = SID}] -> SID;
|
||||
_ -> none
|
||||
end.
|
||||
|
||||
-spec get_session_sids(binary(), binary()) -> [sid()].
|
||||
|
||||
get_session_sids(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Sessions = online(get_sessions(Mod, LUser, LServer)),
|
||||
[SID || #session{sid = SID} <- Sessions].
|
||||
|
||||
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
set_offline_info(SID, User, Server, Resource, Info) ->
|
||||
@@ -471,9 +490,13 @@ host_up(Host) ->
|
||||
-spec host_down(binary()) -> ok.
|
||||
host_down(Host) ->
|
||||
Mod = get_sm_backend(Host),
|
||||
Err = case ejabberd_cluster:get_nodes() of
|
||||
[Node] when Node == node() -> xmpp:serr_system_shutdown();
|
||||
_ -> xmpp:serr_reset()
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(#session{sid = {_, Pid}}) when node(Pid) == node() ->
|
||||
ejabberd_c2s:send(Pid, xmpp:serr_system_shutdown()),
|
||||
ejabberd_c2s:send(Pid, Err),
|
||||
ejabberd_c2s:stop(Pid);
|
||||
(_) ->
|
||||
ok
|
||||
@@ -957,30 +980,36 @@ cache_nodes(Mod, LServer) ->
|
||||
%%% ejabberd commands
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
[#ejabberd_commands{name = connected_users, tags = [session],
|
||||
desc = "List all established sessions",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = connected_users, args = [],
|
||||
result_desc = "List of users sessions",
|
||||
result_example = [<<"user1@example.com">>, <<"user2@example.com">>],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
#ejabberd_commands{name = connected_users_number, tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
result_example = 2,
|
||||
args = [], result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
#ejabberd_commands{name = user_resources, tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
policy = user,
|
||||
policy = admin,
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [],
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_desc = ["User name", "Server name"],
|
||||
args_example = [<<"user1">>, <<"example.com">>],
|
||||
result_example = [<<"tka1">>, <<"Gajim">>, <<"mobile-app">>],
|
||||
result = {resources, {list, {resource, string}}}},
|
||||
#ejabberd_commands{name = kick_user,
|
||||
tags = [session],
|
||||
#ejabberd_commands{name = kick_user, tags = [session],
|
||||
desc = "Disconnect user's active sessions",
|
||||
module = ?MODULE, function = kick_user,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_desc = ["User name", "Server name"],
|
||||
args_example = [<<"user1">>, <<"example.com">>],
|
||||
result_desc = "Number of resources that were kicked",
|
||||
result_example = 3,
|
||||
result = {num_resources, integer}}].
|
||||
|
||||
-spec connected_users() -> [binary()].
|
||||
|
||||
+14
-16
@@ -29,9 +29,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, start_link/2,
|
||||
@@ -113,11 +111,11 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
(?GEN_FSM):start(ejabberd_sql, [Host],
|
||||
p1_fsm:start(ejabberd_sql, [Host],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
start_link(Host, StartInterval) ->
|
||||
(?GEN_FSM):start_link(ejabberd_sql,
|
||||
p1_fsm:start_link(ejabberd_sql,
|
||||
[Host, StartInterval],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
@@ -160,7 +158,7 @@ sql_call(Host, Msg) ->
|
||||
case ejabberd_sql_sup:get_random_pid(Host) of
|
||||
none -> {error, <<"Unknown Host">>};
|
||||
Pid ->
|
||||
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_fsm:sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
query_timeout(Host))
|
||||
end;
|
||||
@@ -168,7 +166,7 @@ sql_call(Host, Msg) ->
|
||||
end.
|
||||
|
||||
keep_alive(Host, PID) ->
|
||||
(?GEN_FSM):sync_send_event(PID,
|
||||
p1_fsm:sync_send_event(PID,
|
||||
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
query_timeout(Host)).
|
||||
@@ -280,7 +278,7 @@ init([Host, StartInterval]) ->
|
||||
keep_alive, [Host, self()])
|
||||
end,
|
||||
[DBType | _] = db_opts(Host),
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
ejabberd_sql_sup:add_pid(Host, self()),
|
||||
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
|
||||
undefined ->
|
||||
@@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
PendingRequests =
|
||||
p1_queue:dropwhile(
|
||||
fun(Req) ->
|
||||
?GEN_FSM:send_event(self(), Req),
|
||||
p1_fsm:send_event(self(), Req),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
State1 = State#state{db_ref = Ref,
|
||||
@@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
"Retry after: ~p seconds",
|
||||
[State#state.db_type, Reason,
|
||||
State#state.start_interval div 1000]),
|
||||
(?GEN_FSM):send_event_after(State#state.start_interval,
|
||||
p1_fsm:send_event_after(State#state.start_interval,
|
||||
connect),
|
||||
{next_state, connecting, State}
|
||||
end;
|
||||
@@ -337,7 +335,7 @@ connecting(Event, State) ->
|
||||
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
_Timestamp},
|
||||
From, State) ->
|
||||
(?GEN_FSM):reply(From,
|
||||
p1_fsm:reply(From,
|
||||
{error, <<"SQL connection failed">>}),
|
||||
{next_state, connecting, State};
|
||||
connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
@@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
catch error:full ->
|
||||
Q = p1_queue:dropwhile(
|
||||
fun({sql_cmd, _, To, _Timestamp}) ->
|
||||
(?GEN_FSM):reply(
|
||||
p1_fsm:reply(
|
||||
To, {error, <<"SQL connection failed">>}),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
@@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
|
||||
%% monitoring the connection)
|
||||
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
|
||||
_StateName, State) ->
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
{next_state, connecting, State};
|
||||
handle_info(Info, StateName, State) ->
|
||||
?WARNING_MSG("unexpected info in ~p: ~p",
|
||||
@@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) ->
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, timeout, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"Failed sending data on socket", _/binary>>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, closed, get(?STATE_KEY)};
|
||||
abort_on_driver_error(Reply, From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{next_state, session_established, get(?STATE_KEY)}.
|
||||
|
||||
%% == pure ODBC code
|
||||
|
||||
@@ -41,6 +41,12 @@ init([]) ->
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_hooks]},
|
||||
Cluster = {ejabberd_cluster,
|
||||
{ejabberd_cluster, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[ejabberd_cluster]},
|
||||
SystemMonitor =
|
||||
{ejabberd_system_monitor,
|
||||
{ejabberd_system_monitor, start_link, []},
|
||||
@@ -152,6 +158,7 @@ init([]) ->
|
||||
permanent, 5000, worker, [ejabberd_pkix]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
Cluster,
|
||||
CyrSASL,
|
||||
Translation,
|
||||
AccessPerms,
|
||||
|
||||
+3
-2
@@ -58,6 +58,7 @@ modules() ->
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_pubsub,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard].
|
||||
@@ -80,8 +81,8 @@ export(Server, Output, Module) ->
|
||||
case export(LServer, Table, IO, ConvertFun) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed export for module ~p: ~p",
|
||||
[Module, Reason])
|
||||
?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
|
||||
[Module, Table, Reason])
|
||||
end
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
+15
-15
@@ -63,7 +63,7 @@
|
||||
%%% active_bind - sent bind() request and waiting for response
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -148,7 +148,7 @@
|
||||
start_link(Name) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
|
||||
-spec start_link(binary(), [binary()], inet:port_number(), binary(),
|
||||
binary(), tlsopts()) -> any().
|
||||
@@ -156,7 +156,7 @@ start_link(Name) ->
|
||||
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
[Hosts, Port, Rootdn, Passwd, Opts], []).
|
||||
|
||||
-spec get_status(handle()) -> any().
|
||||
@@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
get_status(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
p1_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Shutdown connection (and process) asynchronous.
|
||||
@@ -175,7 +175,7 @@ get_status(Handle) ->
|
||||
|
||||
close(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:send_all_state_event(Handle1, close).
|
||||
p1_fsm:send_all_state_event(Handle1, close).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Add an entry. The entry field MUST NOT exist for the AddRequest
|
||||
@@ -192,7 +192,7 @@ close(Handle) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
add(Handle, Entry, Attributes) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% Do sanity check !
|
||||
@@ -216,7 +216,7 @@ add_attrs(Attrs) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
delete(Handle, Entry) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
p1_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -234,7 +234,7 @@ delete(Handle, Entry) ->
|
||||
|
||||
modify(Handle, Object, Mods) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%%
|
||||
@@ -274,7 +274,7 @@ m(Operation, Type, Values) ->
|
||||
|
||||
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
|
||||
optional(NewSup)},
|
||||
?CALL_TIMEOUT).
|
||||
@@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
|
||||
modify_passwd(Handle, DN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
|
||||
|
||||
bind(Handle, RootDN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% Sanity checks !
|
||||
@@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) ->
|
||||
|
||||
call_search(Handle, A) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {search, A},
|
||||
p1_fsm:sync_send_event(Handle1, {search, A},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
-spec parse_search_args(search_args()) -> eldap_search().
|
||||
@@ -637,7 +637,7 @@ active(Event, From, S) ->
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_event/3
|
||||
%% Called when gen_fsm:send_all_state_event/2 is invoked.
|
||||
%% Called when p1_fsm:send_all_state_event/2 is invoked.
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
@@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
|
||||
case catch recvd_packet(Data, S) of
|
||||
{response, Response, RequestType} ->
|
||||
NewS = case Response of
|
||||
{reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
|
||||
{reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1;
|
||||
{ok, S1} -> S1
|
||||
end,
|
||||
if StateName == active_bind andalso
|
||||
@@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
|
||||
StateName, S) ->
|
||||
case cmd_timeout(Timer, Id, S) of
|
||||
{reply, To, Reason, NewS} ->
|
||||
gen_fsm:reply(To, Reason),
|
||||
p1_fsm:reply(To, Reason),
|
||||
{next_state, StateName, NewS};
|
||||
{error, _Reason} -> {next_state, StateName, S}
|
||||
end;
|
||||
|
||||
+48
-20
@@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = modules_update_specs,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Update the module source code from Git",
|
||||
longdesc = "A connection to Internet is required",
|
||||
module = ?MODULE, function = update,
|
||||
args = [],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = modules_available,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules available to install",
|
||||
module = ?MODULE, function = available_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -97,9 +99,11 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = modules_installed,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules already installed",
|
||||
module = ?MODULE, function = installed_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -107,46 +111,64 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = module_install,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Compile, install and start an available contributed module",
|
||||
module = ?MODULE, function = install,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_uninstall,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Uninstall a contributed module",
|
||||
module = ?MODULE, function = uninstall,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_upgrade,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Upgrade the running code of an installed module",
|
||||
longdesc = "In practice, this uninstalls and installs the module",
|
||||
module = ?MODULE, function = upgrade,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_check,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Check the contributed module repository compliance",
|
||||
module = ?MODULE, function = check,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}}
|
||||
].
|
||||
%% -- public modules functions
|
||||
|
||||
update() ->
|
||||
add_sources(?REPOS),
|
||||
Contrib = maps:put(?REPOS, [], maps:new()),
|
||||
Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Mods = maps:get(Repo, Acc, []),
|
||||
maps:put(Repo, [Package|Mods], Acc)
|
||||
end, Contrib, modules_spec(sources_dir(), "*/*")),
|
||||
Repos = maps:fold(fun(Repo, _Mods, Acc) ->
|
||||
Update = add_sources(Repo),
|
||||
?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [{repository, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], Jungles),
|
||||
Res = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Path = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Path),
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Repo),
|
||||
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [Error|Acc]
|
||||
Error -> [{Package, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], modules_spec(sources_dir(), "*")),
|
||||
end, Repos, modules_spec(sources_dir(), "*")),
|
||||
case Res of
|
||||
[] -> ok;
|
||||
[Error|_] -> Error
|
||||
@@ -538,7 +560,9 @@ compile_result(Results) ->
|
||||
compile_options() ->
|
||||
[verbose, report_errors, report_warnings]
|
||||
++ [{i, filename:join(app_dir(App), "include")}
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]].
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
|
||||
++ [{i, filename:join(mod_dir(Mod), "include")}
|
||||
|| Mod <- installed()].
|
||||
|
||||
app_dir(App) ->
|
||||
case code:lib_dir(App) of
|
||||
@@ -553,6 +577,10 @@ app_dir(App) ->
|
||||
Dir
|
||||
end.
|
||||
|
||||
mod_dir({Package, Spec}) ->
|
||||
Default = filename:join(modules_dir(), Package),
|
||||
proplists:get_value(path, Spec, Default).
|
||||
|
||||
compile_erlang_file(Dest, File) ->
|
||||
compile_erlang_file(Dest, File, compile_options()).
|
||||
|
||||
|
||||
+16
-1
@@ -34,7 +34,8 @@
|
||||
stop_child/1, stop_child/2, config_reloaded/0]).
|
||||
-export([start_module/2, start_module/3,
|
||||
stop_module/2, stop_module_keep_config/2,
|
||||
get_opt/2, get_opt/3, get_opt_host/3, opt_type/1, is_equal_opt/4,
|
||||
get_opt/2, get_opt/3, get_opt_host/3,
|
||||
get_opt_hosts/3, opt_type/1, is_equal_opt/4,
|
||||
get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
|
||||
loaded_modules/1, loaded_modules_with_opts/1,
|
||||
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
|
||||
@@ -441,6 +442,20 @@ get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
|
||||
|
||||
get_opt_hosts(Host, Opts, Default) ->
|
||||
Vals = case get_opt(host, Opts, undefined) of
|
||||
undefined ->
|
||||
case get_opt(hosts, Opts, []) of
|
||||
[] -> [Default];
|
||||
L -> L
|
||||
end;
|
||||
Val ->
|
||||
[Val]
|
||||
end,
|
||||
[ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
|
||||
|
||||
-spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
|
||||
get_validators(Host, Module, Opts) ->
|
||||
try Module:mod_opt_type('') of
|
||||
|
||||
+25
-17
@@ -32,8 +32,8 @@
|
||||
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
||||
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
|
||||
try_read_file/1]).
|
||||
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
|
||||
compile_exprs/2, join_atoms/2, try_read_file/1, have_eimp/0]).
|
||||
|
||||
%% Deprecated functions
|
||||
-export([decode_base64/1, encode_base64/1]).
|
||||
@@ -127,6 +127,18 @@ expr_to_term(Expr) ->
|
||||
term_to_expr(Term) ->
|
||||
list_to_binary(io_lib:print(Term)).
|
||||
|
||||
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
|
||||
now_to_usec({MSec, Sec, USec}) ->
|
||||
(MSec*1000000 + Sec)*1000000 + USec.
|
||||
|
||||
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
|
||||
usec_to_now(Int) ->
|
||||
Secs = Int div 1000000,
|
||||
USec = Int rem 1000000,
|
||||
MSec = Secs div 1000000,
|
||||
Sec = Secs rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
l2i(I) when is_integer(I) -> I;
|
||||
l2i(L) when is_binary(L) -> binary_to_integer(L).
|
||||
|
||||
@@ -192,25 +204,21 @@ join_atoms(Atoms, Sep) ->
|
||||
%% in configuration validators only.
|
||||
-spec try_read_file(file:filename_all()) -> binary().
|
||||
try_read_file(Path) ->
|
||||
Res = case file:read_file_info(Path) of
|
||||
{ok, #file_info{type = Type, access = Access}} ->
|
||||
case {Type, Access} of
|
||||
{regular, read} -> ok;
|
||||
{regular, read_write} -> ok;
|
||||
{regular, _} -> {error, file:format_error(eaccess)};
|
||||
_ -> {error, "not a regular file"}
|
||||
end;
|
||||
{error, Why} ->
|
||||
{error, file:format_error(Why)}
|
||||
end,
|
||||
case Res of
|
||||
ok ->
|
||||
case file:open(Path, [read]) of
|
||||
{ok, Fd} ->
|
||||
file:close(Fd),
|
||||
iolist_to_binary(Path);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
|
||||
{error, Why} ->
|
||||
?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]),
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-ifdef(GRAPHICS).
|
||||
have_eimp() -> true.
|
||||
-else.
|
||||
have_eimp() -> false.
|
||||
-endif.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
+132
-20
@@ -44,7 +44,7 @@
|
||||
kick_session/4, status_num/2, status_num/1,
|
||||
status_list/2, status_list/1, connected_users_info/0,
|
||||
connected_users_vhost/1, set_presence/7,
|
||||
get_presence/2, user_sessions_info/2, get_last/2,
|
||||
get_presence/2, user_sessions_info/2, get_last/2, set_last/4,
|
||||
|
||||
% Accounts
|
||||
set_password/3, check_password_hash/4, delete_old_users/1,
|
||||
@@ -303,6 +303,9 @@ get_commands_spec() ->
|
||||
desc = "List of users logged in host with their statuses",
|
||||
module = ?MODULE, function = status_list,
|
||||
args = [{host, binary}, {status, binary}],
|
||||
args_example = [<<"myserver.com">>, <<"dnd">>],
|
||||
args_desc = ["Server name", "Status type to check"],
|
||||
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
||||
result = {users, {list,
|
||||
{userstatus, {tuple, [
|
||||
{user, string},
|
||||
@@ -316,6 +319,9 @@ get_commands_spec() ->
|
||||
desc = "List of logged users with this status",
|
||||
module = ?MODULE, function = status_list,
|
||||
args = [{status, binary}],
|
||||
args_example = [<<"dnd">>],
|
||||
args_desc = ["Status type to check"],
|
||||
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
||||
result = {users, {list,
|
||||
{userstatus, {tuple, [
|
||||
{user, string},
|
||||
@@ -330,6 +336,8 @@ get_commands_spec() ->
|
||||
desc = "List all established sessions and their information",
|
||||
module = ?MODULE, function = connected_users_info,
|
||||
args = [],
|
||||
result_example = [{"user1@myserver.com/tka", "c2s", "127.0.0.1",
|
||||
40092, 8, "ejabberd@localhost", 28}],
|
||||
result = {connected_users_info,
|
||||
{list,
|
||||
{sessions, {tuple,
|
||||
@@ -346,6 +354,9 @@ get_commands_spec() ->
|
||||
tags = [session],
|
||||
desc = "Get the list of established sessions in a vhost",
|
||||
module = ?MODULE, function = connected_users_vhost,
|
||||
args_example = [<<"myexample.com">>],
|
||||
args_desc = ["Server name"],
|
||||
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
|
||||
args = [{host, binary}],
|
||||
result = {connected_users_vhost, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = user_sessions_info,
|
||||
@@ -353,6 +364,10 @@ get_commands_spec() ->
|
||||
desc = "Get information about all sessions of a user",
|
||||
module = ?MODULE, function = user_sessions_info,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"peter">>, <<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = [{"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
|
||||
231, <<"dnd">>, <<"tka">>, <<>>}],
|
||||
result = {sessions_info,
|
||||
{list,
|
||||
{session, {tuple,
|
||||
@@ -385,6 +400,9 @@ get_commands_spec() ->
|
||||
"defined by the user client.",
|
||||
module = ?MODULE, function = get_presence,
|
||||
args = [{user, binary}, {server, binary}],
|
||||
args_example = [<<"peter">>, <<"myexample.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>},
|
||||
result =
|
||||
{presence,
|
||||
{tuple,
|
||||
@@ -398,24 +416,40 @@ get_commands_spec() ->
|
||||
{resource, binary}, {type, binary},
|
||||
{show, binary}, {status, binary},
|
||||
{priority, binary}],
|
||||
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",
|
||||
"Priority, provide this value as an integer"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
||||
desc = "Set nickname in a user's vCard",
|
||||
module = ?MODULE, function = set_nickname,
|
||||
args = [{user, binary}, {host, binary}, {nickname, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
|
||||
args_desc = ["User name", "Server name", "Nickname"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_vcard, tags = [vcard],
|
||||
desc = "Get content from a vCard field",
|
||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = get_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>],
|
||||
args_desc = ["User name", "Server name", "Field name"],
|
||||
result_example = "User 1",
|
||||
result_desc = "Field content",
|
||||
result = {content, string}},
|
||||
#ejabberd_commands{name = get_vcard2, tags = [vcard],
|
||||
desc = "Get content from a vCard field",
|
||||
desc = "Get content from a vCard subfield",
|
||||
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = get_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Subfield name"],
|
||||
result_example = "Schubert",
|
||||
result_desc = "Field content",
|
||||
result = {content, string}},
|
||||
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
|
||||
desc = "Get multiple contents from a vCard field",
|
||||
@@ -429,12 +463,16 @@ get_commands_spec() ->
|
||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = set_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Value"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_vcard2, tags = [vcard],
|
||||
desc = "Set content in a vCard subfield",
|
||||
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = set_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
|
||||
desc = "Set multiple contents in a vCard subfield",
|
||||
@@ -451,6 +489,10 @@ get_commands_spec() ->
|
||||
{user, binary}, {server, binary},
|
||||
{nick, binary}, {group, binary},
|
||||
{subs, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
|
||||
<<"User 2">>, <<"Friends">>, <<"both">>],
|
||||
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
|
||||
"Nickname", "Group", "Subscription"],
|
||||
result = {res, rescode}},
|
||||
%%{"", "subs= none, from, to or both"},
|
||||
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
||||
@@ -460,6 +502,8 @@ get_commands_spec() ->
|
||||
module = ?MODULE, function = delete_rosteritem,
|
||||
args = [{localuser, binary}, {localserver, binary},
|
||||
{user, binary}, {server, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
||||
desc = "List/delete rosteritems that match filter (only Mnesia)",
|
||||
@@ -514,8 +558,14 @@ get_commands_spec() ->
|
||||
]}}}}},
|
||||
#ejabberd_commands{name = push_roster, tags = [roster],
|
||||
desc = "Push template roster from file to a user",
|
||||
longdesc = "The text file must contain an erlang term: a list "
|
||||
"of tuples with username, servername, group and nick. Example:\n"
|
||||
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
module = ?MODULE, function = push_roster,
|
||||
args = [{file, binary}, {user, binary}, {host, binary}],
|
||||
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
|
||||
args_desc = ["File path", "User name", "Server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
||||
desc = "Push template roster from file to all those users",
|
||||
@@ -525,11 +575,15 @@ get_commands_spec() ->
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
module = ?MODULE, function = push_roster_all,
|
||||
args = [{file, binary}],
|
||||
args_example = [<<"/home/ejabberd/roster.txt">>],
|
||||
args_desc = ["File path"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = push_alltoall, tags = [roster],
|
||||
desc = "Add all the users to all the users of Host in Group",
|
||||
module = ?MODULE, function = push_alltoall,
|
||||
args = [{host, binary}, {group, binary}],
|
||||
args_example = [<<"myserver.com">>,<<"Everybody">>],
|
||||
args_desc = ["Server name", "Group name"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = get_last, tags = [last],
|
||||
@@ -538,27 +592,38 @@ get_commands_spec() ->
|
||||
"2017-02-23T22:25:28.063062Z ONLINE",
|
||||
module = ?MODULE, function = get_last,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = {<<"2017-06-30T14:32:16.060684Z">>, "ONLINE"},
|
||||
result_desc = "Last activity timestamp and status",
|
||||
result = {last_activity,
|
||||
{tuple, [{timestamp, string},
|
||||
{status, string}
|
||||
]}}},
|
||||
#ejabberd_commands{name = set_last, tags = [last],
|
||||
desc = "Set last activity information",
|
||||
longdesc = "Timestamp is the seconds since"
|
||||
longdesc = "Timestamp is the seconds since "
|
||||
"1970-01-01 00:00:00 UTC, for example: date +%s",
|
||||
module = mod_last, function = store_last_info,
|
||||
module = ?MODULE, function = set_last,
|
||||
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
|
||||
args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = private_get, tags = [private],
|
||||
desc = "Get some information from a user private storage",
|
||||
module = ?MODULE, function = private_get,
|
||||
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>],
|
||||
args_desc = ["User name", "Server name", "Element name", "Namespace"],
|
||||
result = {res, string}},
|
||||
#ejabberd_commands{name = private_set, tags = [private],
|
||||
desc = "Set to the user private storage",
|
||||
module = ?MODULE, function = private_set,
|
||||
args = [{user, binary}, {host, binary}, {element, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,
|
||||
<<"<query xmlns='jabber:iq:private'> <storage xmlns='storage:rosternotes'/></query>">>],
|
||||
args_desc = ["User name", "Server name", "XML storage element"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
||||
@@ -568,41 +633,63 @@ get_commands_spec() ->
|
||||
"put \\ \" around the argument and\nseparate the "
|
||||
"identifiers with \\ \\ n\n"
|
||||
"For example:\n"
|
||||
" ejabberdctl srg_create group3 localhost "
|
||||
" ejabberdctl srg_create group3 myserver.com "
|
||||
"name desc \\\"group1\\\\ngroup2\\\"",
|
||||
module = ?MODULE, function = srg_create,
|
||||
args = [{group, binary}, {host, binary},
|
||||
{name, binary}, {description, binary}, {display, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
|
||||
<<"Third group">>, <<"group1\\\\ngroup2">>],
|
||||
args_desc = ["Group identifier", "Group server name", "Group name",
|
||||
"Group description", "Groups to display"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
|
||||
desc = "Delete a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_delete,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
|
||||
desc = "List the Shared Roster Groups in Host",
|
||||
module = ?MODULE, function = srg_list,
|
||||
args = [{host, binary}],
|
||||
args_example = [<<"myserver.com">>],
|
||||
args_desc = ["Server name"],
|
||||
result_example = [<<"group1">>, <<"group2">>],
|
||||
result_desc = "List of group identifiers",
|
||||
result = {groups, {list, {id, string}}}},
|
||||
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
|
||||
desc = "Get info of a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_get_info,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
|
||||
result_desc = "List of group informations, as key and value",
|
||||
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
|
||||
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
|
||||
desc = "Get members of a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_get_members,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result_example = [<<"user1@localhost">>, <<"user2@localhost">>],
|
||||
result_desc = "List of group identifiers",
|
||||
result = {members, {list, {member, string}}}},
|
||||
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
|
||||
desc = "Add the JID user@host to the Shared Roster Group",
|
||||
module = ?MODULE, function = srg_user_add,
|
||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
|
||||
desc = "Delete this JID user@host from the Shared Roster Group",
|
||||
module = ?MODULE, function = srg_user_del,
|
||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = get_offline_count,
|
||||
@@ -611,27 +698,42 @@ get_commands_spec() ->
|
||||
policy = user,
|
||||
module = mod_offline, function = count_offline_messages,
|
||||
args = [],
|
||||
result_example = 5,
|
||||
result_desc = "Number",
|
||||
result = {value, integer}},
|
||||
#ejabberd_commands{name = send_message, tags = [stanza],
|
||||
desc = "Send a message to a local or remote bare of full JID",
|
||||
module = ?MODULE, function = send_message,
|
||||
args = [{type, binary}, {from, binary}, {to, binary},
|
||||
{subject, binary}, {body, binary}],
|
||||
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
|
||||
<<"Restart">>, <<"In 5 minutes">>],
|
||||
args_desc = ["Message type: normal, chat, headline", "Sender JID",
|
||||
"Receiver JID", "Subject, or empty string", "Body"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
||||
desc = "Send a stanza as if sent from a c2s session",
|
||||
module = ?MODULE, function = send_stanza_c2s,
|
||||
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
|
||||
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
|
||||
<<"<message to='user1@localhost'><ext attr='value'/></message>">>],
|
||||
args_desc = ["Username", "Server name", "Resource", "Stanza"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
||||
desc = "Send a stanza; provide From JID and valid To JID",
|
||||
module = ?MODULE, function = send_stanza,
|
||||
args = [{from, binary}, {to, binary}, {stanza, binary}],
|
||||
args_example = [<<"admin@localhost">>, <<"user1@localhost">>,
|
||||
<<"<message><ext attr='value'/></message>">>],
|
||||
args_desc = ["Sender JID", "Destination JID", "Stanza"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = privacy_set, tags = [stanza],
|
||||
desc = "Send a IQ set privacy stanza for a local account",
|
||||
module = ?MODULE, function = privacy_set,
|
||||
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>,
|
||||
<<"<query xmlns='jabber:iq:privacy'>...">>],
|
||||
args_desc = ["Username", "Server name", "Query XML element"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = stats, tags = [stats],
|
||||
@@ -639,12 +741,20 @@ get_commands_spec() ->
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}],
|
||||
args_example = [<<"registeredusers">>],
|
||||
args_desc = ["Statistic name"],
|
||||
result_example = 6,
|
||||
result_desc = "Integer statistic value",
|
||||
result = {stat, integer}},
|
||||
#ejabberd_commands{name = stats_host, tags = [stats],
|
||||
desc = "Get statistical value for this host: registeredusers onlineusers",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}, {host, binary}],
|
||||
args_example = [<<"registeredusers">>, <<"example.com">>],
|
||||
args_desc = ["Statistic name", "Server JID"],
|
||||
result_example = 6,
|
||||
result_desc = "Integer statistic value",
|
||||
result = {stat, integer}}
|
||||
].
|
||||
|
||||
@@ -952,7 +1062,7 @@ connected_users_info() ->
|
||||
|
||||
connected_users_vhost(Host) ->
|
||||
USRs = ejabberd_sm:get_vh_session_list(Host),
|
||||
[ [U, $@, S, $/, R] || {U, S, R} <- USRs].
|
||||
[ jid:encode(jid:make(USR)) || USR <- USRs].
|
||||
|
||||
%% Code copied from ejabberd_sm.erl and customized
|
||||
dirty_get_sessions_list2() ->
|
||||
@@ -1002,20 +1112,16 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
|
||||
Priority = if is_integer(Priority0) -> Priority0;
|
||||
true -> binary_to_integer(Priority0)
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
||||
none ->
|
||||
error;
|
||||
Pid ->
|
||||
From = jid:make(User, Host, Resource),
|
||||
To = jid:make(User, Host),
|
||||
Presence = #presence{from = From, to = To,
|
||||
type = misc:binary_to_atom(Type),
|
||||
show = misc:binary_to_atom(Show),
|
||||
status = xmpp:mk_text(Status),
|
||||
priority = Priority},
|
||||
Pid ! {route, Presence},
|
||||
ok
|
||||
end.
|
||||
Pres = #presence{
|
||||
from = jid:make(User, Host, Resource),
|
||||
to = jid:make(User, Host),
|
||||
type = misc:binary_to_atom(Type),
|
||||
status = xmpp:mk_text(Status),
|
||||
show = misc:binary_to_atom(Show),
|
||||
priority = Priority,
|
||||
sub_els = []},
|
||||
Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
|
||||
ejabberd_c2s:set_presence(Ref, Pres).
|
||||
|
||||
user_sessions_info(User, Host) ->
|
||||
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
@@ -1331,6 +1437,12 @@ get_last(User, Server) ->
|
||||
end,
|
||||
{xmpp_util:encode_timestamp(Now), Status}.
|
||||
|
||||
set_last(User, Server, Timestamp, Status) ->
|
||||
case mod_last:store_last_info(User, Server, Timestamp, Status) of
|
||||
{ok, _} -> ok;
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Private Storage
|
||||
%%%
|
||||
|
||||
+14
-6
@@ -237,7 +237,7 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
|
||||
-define(INFO_RESULT(Allow, Feats, Lang),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow ->
|
||||
{result, Feats}
|
||||
end).
|
||||
@@ -252,7 +252,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) -
|
||||
case {acl:match_rule(LServer, Access1, From),
|
||||
acl:match_rule(global, Access2, From)} of
|
||||
{deny, deny} ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
{error, xmpp:err_forbidden(Txt, Lang)};
|
||||
_ ->
|
||||
{result, []}
|
||||
@@ -303,7 +303,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
|
||||
-define(ITEMS_RESULT(Allow, Items, Lang),
|
||||
case Allow of
|
||||
deny ->
|
||||
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow ->
|
||||
{result, Items}
|
||||
end).
|
||||
@@ -417,7 +417,7 @@ commands_result(Allow, From, To, Request) ->
|
||||
case Allow of
|
||||
deny ->
|
||||
Lang = Request#adhoc_command.lang,
|
||||
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow ->
|
||||
announce_commands(From, To, Request)
|
||||
end.
|
||||
@@ -843,7 +843,7 @@ add_store_hint(El) ->
|
||||
-spec route_forbidden_error(stanza()) -> ok.
|
||||
route_forbidden_error(Packet) ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
|
||||
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
|
||||
ejabberd_router:route_error(Packet, Err).
|
||||
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
@@ -913,4 +913,12 @@ import(LServer, {sql, _}, DBType, Tab, List) ->
|
||||
|
||||
mod_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [access, db_type].
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun (I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[access, db_type, cache_life_time, cache_size,
|
||||
use_cache, cache_missed].
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 13 Sep 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 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(mod_avatar).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% gen_mod API
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]).
|
||||
%% Hooks
|
||||
-export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("pubsub.hrl").
|
||||
|
||||
-type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(Host, _Opts) ->
|
||||
case misc:have_eimp() of
|
||||
true ->
|
||||
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);
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd is built without "
|
||||
"graphics support: reconfigure it with "
|
||||
"--enable-graphics or disable '~s'",
|
||||
[?MODULE]),
|
||||
{error, graphics_not_compiled}
|
||||
end.
|
||||
|
||||
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).
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_vcard, hard}, {mod_vcard_xupdate, hard}, {mod_pubsub, hard}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Hooks
|
||||
%%%===================================================================
|
||||
pubsub_publish_item(LServer, ?NS_AVATAR_METADATA,
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
#jid{luser = LUser, lserver = LServer} = Host,
|
||||
ItemId, [Payload|_]) ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_meta{info = []} ->
|
||||
delete_vcard_avatar(From);
|
||||
#avatar_meta{info = Info} ->
|
||||
Rules = get_converting_rules(LServer),
|
||||
case get_meta_info(Info, Rules) of
|
||||
#avatar_info{type = MimeType, id = ID, url = <<"">>} = I ->
|
||||
case get_avatar_data(Host, ID) of
|
||||
{ok, Data} ->
|
||||
Meta = #avatar_meta{info = [I]},
|
||||
Photo = #vcard_photo{type = MimeType,
|
||||
binval = Data},
|
||||
set_vcard_avatar(From, Photo,
|
||||
#{avatar_meta => {ID, Meta}});
|
||||
{error, _} ->
|
||||
ok
|
||||
end;
|
||||
#avatar_info{type = MimeType, url = URL} ->
|
||||
Photo = #vcard_photo{type = MimeType,
|
||||
extval = URL},
|
||||
set_vcard_avatar(From, Photo, #{})
|
||||
end;
|
||||
_ ->
|
||||
?WARNING_MSG("invalid avatar metadata of ~s@~s published "
|
||||
"with item id ~s",
|
||||
[LUser, LServer, ItemId])
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode avatar metadata of ~s@~s: ~s",
|
||||
[LUser, LServer, xmpp:format_error(Why)])
|
||||
end;
|
||||
pubsub_publish_item(_, _, _, _, _, _) ->
|
||||
ok.
|
||||
|
||||
-spec vcard_iq_convert(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_convert(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case convert_avatar(LUser, LServer, VCard) of
|
||||
{ok, MimeType, Data} ->
|
||||
VCard1 = VCard#vcard_temp{
|
||||
photo = #vcard_photo{type = MimeType,
|
||||
binval = Data}},
|
||||
IQ#iq{sub_els = [VCard1]};
|
||||
pass ->
|
||||
IQ;
|
||||
{error, Reason} ->
|
||||
stop_with_error(Lang, Reason)
|
||||
end;
|
||||
vcard_iq_convert(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec vcard_iq_publish(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_publish(#iq{sub_els = [#vcard_temp{photo = undefined}]} = IQ) ->
|
||||
publish_avatar(IQ, #avatar_meta{}, <<>>, <<>>, <<>>);
|
||||
vcard_iq_publish(#iq{sub_els = [#vcard_temp{
|
||||
photo = #vcard_photo{
|
||||
type = MimeType,
|
||||
binval = Data}}]} = IQ)
|
||||
when is_binary(Data), Data /= <<>> ->
|
||||
SHA1 = str:sha(Data),
|
||||
M = get_avatar_meta(IQ),
|
||||
case M of
|
||||
{ok, SHA1, _} ->
|
||||
IQ;
|
||||
{ok, _ItemID, #avatar_meta{info = Info} = Meta} ->
|
||||
case lists:keyfind(SHA1, #avatar_info.id, Info) of
|
||||
#avatar_info{} ->
|
||||
IQ;
|
||||
false ->
|
||||
Info1 = lists:filter(
|
||||
fun(#avatar_info{url = URL}) -> URL /= <<"">> end,
|
||||
Info),
|
||||
Meta1 = Meta#avatar_meta{info = Info1},
|
||||
publish_avatar(IQ, Meta1, MimeType, Data, SHA1)
|
||||
end;
|
||||
{error, _} ->
|
||||
publish_avatar(IQ, #avatar_meta{}, MimeType, Data, SHA1)
|
||||
end;
|
||||
vcard_iq_publish(Acc) ->
|
||||
Acc.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info().
|
||||
get_meta_info(Info, Rules) ->
|
||||
case lists:foldl(
|
||||
fun(_, #avatar_info{} = Acc) ->
|
||||
Acc;
|
||||
(#avatar_info{url = URL}, Acc) when URL /= <<"">> ->
|
||||
Acc;
|
||||
(#avatar_info{} = I, _) when Rules == [] ->
|
||||
I;
|
||||
(#avatar_info{type = MimeType} = I, Acc) ->
|
||||
T = decode_mime_type(MimeType),
|
||||
case lists:keymember(T, 2, Rules) of
|
||||
true ->
|
||||
I;
|
||||
false ->
|
||||
case convert_to_type(T, Rules) of
|
||||
undefined ->
|
||||
Acc;
|
||||
_ ->
|
||||
[I|Acc]
|
||||
end
|
||||
end
|
||||
end, [], Info) of
|
||||
#avatar_info{} = I -> I;
|
||||
[] -> hd(Info);
|
||||
Is -> hd(lists:reverse(Is))
|
||||
end.
|
||||
|
||||
-spec get_avatar_data(jid(), binary()) -> {ok, binary()} |
|
||||
{error,
|
||||
notfound | invalid_data | internal_error}.
|
||||
get_avatar_data(JID, ItemID) ->
|
||||
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:get_item(LBJID, ?NS_AVATAR_DATA, ItemID) of
|
||||
#pubsub_item{payload = [Payload|_]} ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_data{data = Data} ->
|
||||
{ok, Data};
|
||||
_ ->
|
||||
?WARNING_MSG("invalid avatar data detected "
|
||||
"for ~s@~s with item id ~s",
|
||||
[LUser, LServer, ItemID]),
|
||||
{error, invalid_data}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode avatar data for "
|
||||
"~s@~s with item id ~s: ~s",
|
||||
[LUser, LServer, ItemID,
|
||||
xmpp:format_error(Why)]),
|
||||
{error, invalid_data}
|
||||
end;
|
||||
{error, #stanza_error{reason = 'item-not-found'}} ->
|
||||
{error, notfound};
|
||||
{error, Reason} ->
|
||||
?WARNING_MSG("failed to get item for ~s@~s at node ~s "
|
||||
"with item id ~s: ~p",
|
||||
[LUser, LServer, ?NS_AVATAR_METADATA, ItemID, Reason]),
|
||||
{error, internal_error}
|
||||
end.
|
||||
|
||||
-spec get_avatar_meta(iq()) -> {ok, binary(), avatar_meta()} |
|
||||
{error,
|
||||
notfound | invalid_metadata | internal_error}.
|
||||
get_avatar_meta(#iq{meta = #{avatar_meta := {ItemID, Meta}}}) ->
|
||||
{ok, ItemID, Meta};
|
||||
get_avatar_meta(#iq{from = JID}) ->
|
||||
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:get_items(LBJID, ?NS_AVATAR_METADATA) of
|
||||
[#pubsub_item{itemid = {ItemID, _}, payload = [Payload|_]}|_] ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_meta{} = Meta ->
|
||||
{ok, ItemID, Meta};
|
||||
_ ->
|
||||
?WARNING_MSG("invalid metadata payload detected "
|
||||
"for ~s@~s with item id ~s",
|
||||
[LUser, LServer, ItemID]),
|
||||
{error, invalid_metadata}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode metadata for "
|
||||
"~s@~s with item id ~s: ~s",
|
||||
[LUser, LServer, ItemID,
|
||||
xmpp:format_error(Why)]),
|
||||
{error, invalid_metadata}
|
||||
end;
|
||||
{error, #stanza_error{reason = 'item-not-found'}} ->
|
||||
{error, notfound};
|
||||
{error, Reason} ->
|
||||
?WARNING_MSG("failed to get items for ~s@~s at node ~s: ~p",
|
||||
[LUser, LServer, ?NS_AVATAR_METADATA, Reason]),
|
||||
{error, internal_error}
|
||||
end.
|
||||
|
||||
-spec publish_avatar(iq(), avatar_meta(), binary(), binary(), binary()) ->
|
||||
iq() | {stop, stanza_error()}.
|
||||
publish_avatar(#iq{from = JID} = IQ, Meta, <<>>, <<>>, <<>>) ->
|
||||
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_METADATA,
|
||||
JID, <<>>, [xmpp:encode(Meta)]) of
|
||||
{result, _} ->
|
||||
IQ;
|
||||
{error, StanzaErr} ->
|
||||
{stop, StanzaErr}
|
||||
end;
|
||||
publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
|
||||
#avatar_meta{info = Info} = Meta,
|
||||
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
Payload = xmpp:encode(#avatar_data{data = Data}),
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_DATA,
|
||||
JID, ItemID, [Payload]) of
|
||||
{result, _} ->
|
||||
{W, H} = case eimp:identify(Data) of
|
||||
{ok, ImgInfo} ->
|
||||
{proplists:get_value(width, ImgInfo),
|
||||
proplists:get_value(height, ImgInfo)};
|
||||
_ ->
|
||||
{undefined, undefined}
|
||||
end,
|
||||
I = #avatar_info{id = ItemID,
|
||||
width = W,
|
||||
height = H,
|
||||
type = MimeType,
|
||||
bytes = size(Data)},
|
||||
Meta1 = Meta#avatar_meta{info = [I|Info]},
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_METADATA,
|
||||
JID, ItemID, [xmpp:encode(Meta1)]) of
|
||||
{result, _} ->
|
||||
IQ;
|
||||
{error, StanzaErr} ->
|
||||
?ERROR_MSG("Failed to publish avatar metadata for ~s: ~p",
|
||||
[jid:encode(JID), StanzaErr]),
|
||||
{stop, StanzaErr}
|
||||
end;
|
||||
{error, StanzaErr} ->
|
||||
?ERROR_MSG("Failed to publish avatar data for ~s: ~p",
|
||||
[jid:encode(JID), StanzaErr]),
|
||||
{stop, StanzaErr}
|
||||
end.
|
||||
|
||||
-spec convert_avatar(binary(), binary(), vcard_temp()) ->
|
||||
{ok, binary(), binary()} |
|
||||
{error, eimp:error_reason() | base64_error} |
|
||||
pass.
|
||||
convert_avatar(LUser, LServer, VCard) ->
|
||||
case get_converting_rules(LServer) of
|
||||
[] ->
|
||||
pass;
|
||||
Rules ->
|
||||
case VCard#vcard_temp.photo of
|
||||
#vcard_photo{binval = Data} when is_binary(Data) ->
|
||||
convert_avatar(LUser, LServer, Data, Rules);
|
||||
_ ->
|
||||
pass
|
||||
end
|
||||
end.
|
||||
|
||||
-spec convert_avatar(binary(), binary(), binary(), convert_rules()) ->
|
||||
{ok, eimp:img_type(), binary()} |
|
||||
{error, eimp:error_reason()} |
|
||||
pass.
|
||||
convert_avatar(LUser, LServer, Data, Rules) ->
|
||||
Type = get_type(Data),
|
||||
NewType = convert_to_type(Type, Rules),
|
||||
if NewType == undefined orelse Type == NewType ->
|
||||
pass;
|
||||
true ->
|
||||
?DEBUG("Converting avatar of ~s@~s: ~s -> ~s",
|
||||
[LUser, LServer, Type, NewType]),
|
||||
case eimp:convert(Data, NewType) of
|
||||
{ok, NewData} ->
|
||||
{ok, encode_mime_type(NewType), NewData};
|
||||
{error, Reason} = Err ->
|
||||
?ERROR_MSG("Failed to convert avatar of "
|
||||
"~s@~s (~s -> ~s): ~s",
|
||||
[LUser, LServer, Type, NewType,
|
||||
eimp:format_error(Reason)]),
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
-spec set_vcard_avatar(jid(), vcard_photo() | undefined, map()) -> ok.
|
||||
set_vcard_avatar(JID, VCardPhoto, Meta) ->
|
||||
case get_vcard(JID) of
|
||||
{ok, #vcard_temp{photo = VCardPhoto}} ->
|
||||
ok;
|
||||
{ok, VCard} ->
|
||||
VCard1 = VCard#vcard_temp{photo = VCardPhoto},
|
||||
IQ = #iq{from = JID, to = JID, id = randoms:get_string(),
|
||||
type = set, sub_els = [VCard1], meta = Meta},
|
||||
LServer = JID#jid.lserver,
|
||||
ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []),
|
||||
ok;
|
||||
{error, _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec delete_vcard_avatar(jid()) -> ok.
|
||||
delete_vcard_avatar(JID) ->
|
||||
set_vcard_avatar(JID, undefined, #{}).
|
||||
|
||||
-spec get_vcard(jid()) -> {ok, vcard_temp()} | {error, invalid_vcard}.
|
||||
get_vcard(#jid{luser = LUser, lserver = LServer}) ->
|
||||
VCardEl = case mod_vcard:get_vcard(LUser, LServer) of
|
||||
[El] -> El;
|
||||
_ -> #vcard_temp{}
|
||||
end,
|
||||
try xmpp:decode(VCardEl, ?NS_VCARD, []) of
|
||||
#vcard_temp{} = VCard ->
|
||||
{ok, VCard};
|
||||
_ ->
|
||||
?ERROR_MSG("invalid vCard of ~s@~s in the database",
|
||||
[LUser, LServer]),
|
||||
{error, invalid_vcard}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?ERROR_MSG("failed to decode vCard of ~s@~s: ~s",
|
||||
[LUser, LServer, xmpp:format_error(Why)]),
|
||||
{error, invalid_vcard}
|
||||
end.
|
||||
|
||||
-spec stop_with_error(binary(), eimp:error_reason()) ->
|
||||
{stop, stanza_error()}.
|
||||
stop_with_error(Lang, Reason) ->
|
||||
Txt = eimp:format_error(Reason),
|
||||
{stop, xmpp:err_internal_server_error(Txt, Lang)}.
|
||||
|
||||
-spec get_converting_rules(binary()) -> convert_rules().
|
||||
get_converting_rules(LServer) ->
|
||||
gen_mod:get_module_opt(LServer, ?MODULE, convert, []).
|
||||
|
||||
-spec get_type(binary()) -> eimp:img_type() | unknown.
|
||||
get_type(Data) ->
|
||||
eimp:get_type(Data).
|
||||
|
||||
-spec convert_to_type(eimp:img_type() | unknown, convert_rules()) ->
|
||||
eimp:img_type() | undefined.
|
||||
convert_to_type(unknown, _Rules) ->
|
||||
undefined;
|
||||
convert_to_type(Type, Rules) ->
|
||||
case proplists:get_value(Type, Rules) of
|
||||
undefined ->
|
||||
proplists:get_value(default, Rules);
|
||||
T ->
|
||||
T
|
||||
end.
|
||||
|
||||
-spec decode_mime_type(binary()) -> eimp:img_type() | unknown.
|
||||
decode_mime_type(MimeType) ->
|
||||
case str:to_lower(MimeType) of
|
||||
<<"image/jpeg">> -> jpeg;
|
||||
<<"image/png">> -> png;
|
||||
<<"image/webp">> -> webp;
|
||||
<<"image/gif">> -> gif;
|
||||
_ -> unknown
|
||||
end.
|
||||
|
||||
-spec encode_mime_type(eimp:img_type()) -> binary().
|
||||
encode_mime_type(Type) ->
|
||||
<<"image/", (atom_to_binary(Type, latin1))/binary>>.
|
||||
|
||||
mod_opt_type({convert, png}) ->
|
||||
fun(jpeg) -> jpeg;
|
||||
(webp) -> webp;
|
||||
(gif) -> gif
|
||||
end;
|
||||
mod_opt_type({convert, webp}) ->
|
||||
fun(jpeg) -> jpeg;
|
||||
(png) -> png;
|
||||
(gif) -> gif
|
||||
end;
|
||||
mod_opt_type({convert, jpeg}) ->
|
||||
fun(png) -> png;
|
||||
(webp) -> webp;
|
||||
(gif) -> gif
|
||||
end;
|
||||
mod_opt_type({convert, gif}) ->
|
||||
fun(png) -> png;
|
||||
(jpeg) -> jpeg;
|
||||
(webp) -> webp
|
||||
end;
|
||||
mod_opt_type({convert, default}) ->
|
||||
fun(png) -> png;
|
||||
(webp) -> webp;
|
||||
(jpeg) -> jpeg;
|
||||
(gif) -> gif
|
||||
end;
|
||||
mod_opt_type(_) ->
|
||||
[{convert, default},
|
||||
{convert, webp},
|
||||
{convert, png},
|
||||
{convert, gif},
|
||||
{convert, jpeg}].
|
||||
+85
-38
@@ -32,7 +32,7 @@
|
||||
-export([start/2, stop/1, reload/3,
|
||||
depends/2, mod_opt_type/1]).
|
||||
|
||||
-export([filter_packet/1]).
|
||||
-export([filter_packet/1, filter_offline_msg/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
@@ -43,54 +43,29 @@
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(user_receive_packet, Host,
|
||||
?MODULE, filter_packet, 25),
|
||||
ok.
|
||||
ejabberd_hooks:add(offline_message_hook, Host,
|
||||
?MODULE, filter_offline_msg, 25).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(user_receive_packet, Host,
|
||||
?MODULE, filter_packet, 25),
|
||||
ok.
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, filter_offline_msg, 25).
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
filter_packet({#message{} = Msg, State} = Acc) ->
|
||||
From = xmpp:get_from(Msg),
|
||||
filter_packet({#message{from = From} = Msg, State} = Acc) ->
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
|
||||
AllowLocalUsers =
|
||||
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
|
||||
case (Msg#message.body == [] andalso
|
||||
Msg#message.subject == [])
|
||||
orelse (AllowLocalUsers andalso
|
||||
ejabberd_router:is_my_route(From#jid.lserver))
|
||||
orelse (?SETS):is_element(LFrom, PresA)
|
||||
orelse (?SETS):is_element(LBFrom, PresA)
|
||||
#{pres_a := PresA} = State,
|
||||
case (?SETS):is_element(LFrom, PresA)
|
||||
orelse (?SETS):is_element(LBFrom, PresA)
|
||||
orelse sets_bare_member(LBFrom, PresA) of
|
||||
false ->
|
||||
{Sub, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer,
|
||||
{none, []}, [JID#jid.luser, LServer, From]),
|
||||
case Sub of
|
||||
none ->
|
||||
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
|
||||
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
|
||||
if
|
||||
Log ->
|
||||
?INFO_MSG("Drop packet: ~s",
|
||||
[fxml:element_to_binary(
|
||||
xmpp:encode(Msg, ?NS_CLIENT))]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
Drop ->
|
||||
{stop, {drop, State}};
|
||||
true ->
|
||||
Acc
|
||||
end;
|
||||
_ ->
|
||||
Acc
|
||||
case check_message(Msg) of
|
||||
allow -> Acc;
|
||||
deny -> {stop, {drop, State}}
|
||||
end;
|
||||
true ->
|
||||
Acc
|
||||
@@ -98,6 +73,76 @@ filter_packet({#message{} = Msg, State} = Acc) ->
|
||||
filter_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
filter_offline_msg({_Action, #message{} = Msg} = Acc) ->
|
||||
case check_message(Msg) of
|
||||
allow -> Acc;
|
||||
deny -> {stop, {drop, Msg}}
|
||||
end.
|
||||
|
||||
check_message(#message{from = From, to = To} = Msg) ->
|
||||
LServer = To#jid.lserver,
|
||||
AllowLocalUsers =
|
||||
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
|
||||
case (Msg#message.body == [] andalso
|
||||
Msg#message.subject == [])
|
||||
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) andalso
|
||||
ejabberd_router:is_my_host(From#jid.lserver)) of
|
||||
false ->
|
||||
case check_subscription(From, To) of
|
||||
none ->
|
||||
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
|
||||
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
|
||||
if
|
||||
Log ->
|
||||
?INFO_MSG("~s message from stranger ~s to ~s",
|
||||
[if Drop -> "Dropping";
|
||||
true -> "Allow"
|
||||
end,
|
||||
jid:encode(From), jid:encode(To)]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
Drop ->
|
||||
deny;
|
||||
true ->
|
||||
allow
|
||||
end;
|
||||
some ->
|
||||
allow
|
||||
end;
|
||||
true ->
|
||||
allow
|
||||
end.
|
||||
|
||||
-spec check_subscription(jid(), jid()) -> none | some.
|
||||
check_subscription(From, To) ->
|
||||
{LocalUser, LocalServer, _} = jid:tolower(To),
|
||||
{RemoteUser, RemoteServer, _} = jid:tolower(From),
|
||||
case ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LocalServer,
|
||||
{none, []}, [LocalUser, LocalServer, From]) of
|
||||
{none, _} when RemoteUser == <<"">> ->
|
||||
none;
|
||||
{none, _} ->
|
||||
case gen_mod:get_module_opt(LocalServer, ?MODULE,
|
||||
allow_transports, true) of
|
||||
true ->
|
||||
%% Check if the contact's server is in the roster
|
||||
case ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LocalServer,
|
||||
{none, []},
|
||||
[LocalUser, LocalServer, jid:make(RemoteServer)]) of
|
||||
{none, _} -> none;
|
||||
_ -> some
|
||||
end;
|
||||
false ->
|
||||
none
|
||||
end;
|
||||
_ ->
|
||||
some
|
||||
end.
|
||||
|
||||
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
|
||||
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
|
||||
{{U, S, _}, _} -> true;
|
||||
@@ -133,4 +178,6 @@ mod_opt_type(log) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(allow_local_users) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) -> [drop, log, allow_local_users].
|
||||
mod_opt_type(allow_transports) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) -> [drop, log, allow_local_users, allow_transports].
|
||||
|
||||
@@ -192,7 +192,7 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
|
||||
|
||||
-define(INFO_RESULT(Allow, Feats, Lang),
|
||||
case Allow of
|
||||
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow -> {result, Feats}
|
||||
end).
|
||||
|
||||
@@ -310,7 +310,7 @@ get_sm_items(Acc, From,
|
||||
Items ++ Nodes ++ get_user_resources(User, Server)};
|
||||
{allow, <<"config">>} -> {result, []};
|
||||
{_, <<"config">>} ->
|
||||
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
_ -> Acc
|
||||
end
|
||||
end.
|
||||
@@ -432,7 +432,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
|
||||
_ ->
|
||||
LNode = tokenize(Node),
|
||||
Allow = acl:match_rule(LServer, configure, From),
|
||||
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
|
||||
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
|
||||
case LNode of
|
||||
[<<"config">>] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, Err});
|
||||
@@ -765,7 +765,7 @@ get_stopped_nodes(_Lang) ->
|
||||
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
|
||||
Request, Lang),
|
||||
case acl:match_rule(LServerOrGlobal, configure, From) of
|
||||
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow -> adhoc_local_commands(From, To, Request)
|
||||
end).
|
||||
|
||||
@@ -1737,7 +1737,7 @@ adhoc_sm_commands(_Acc, From,
|
||||
action = Action, xdata = XData} = Request) ->
|
||||
case acl:match_rule(LServer, configure, From) of
|
||||
deny ->
|
||||
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
allow ->
|
||||
ActionIsExecute = Action == execute orelse Action == complete,
|
||||
if Action == cancel ->
|
||||
|
||||
+24
-17
@@ -43,7 +43,7 @@
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
-record(state, {hosts = [] :: [binary()]}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod API
|
||||
@@ -62,7 +62,9 @@ depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host].
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) -> [host, hosts].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@@ -77,10 +79,13 @@ mod_opt_type(_) -> [host].
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
Hosts = gen_mod:get_opt_hosts(Host, Opts,
|
||||
<<"echo.@HOST@">>),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
{ok, #state{host = MyHost}}.
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:register_route(H, Host)
|
||||
end, Hosts),
|
||||
{ok, #state{hosts = Hosts}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
@@ -101,17 +106,19 @@ handle_call(stop, _From, State) ->
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({reload, Host, NewOpts, OldOpts}, State) ->
|
||||
NewMyHost = gen_mod:get_opt_host(Host, NewOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
OldMyHost = gen_mod:get_opt_host(Host, OldOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
if NewMyHost /= OldMyHost ->
|
||||
ejabberd_router:register_route(NewMyHost, Host),
|
||||
ejabberd_router:unregister_route(OldMyHost);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State#state{host = NewMyHost}};
|
||||
NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:unregister_route(H)
|
||||
end, OldMyHosts -- NewMyHosts),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:register_route(H, Host)
|
||||
end, NewMyHosts -- OldMyHosts),
|
||||
{noreply, State#state{hosts = NewMyHosts}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_router:unregister_route(State#state.host), ok.
|
||||
lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
|
||||
+10
-2
@@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
|
||||
|
||||
log(Call, Args, {Addr, Port}) ->
|
||||
AddrS = misc:ip_to_list({Addr, Port}),
|
||||
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
|
||||
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
|
||||
log(Call, Args, IP) ->
|
||||
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
|
||||
?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
|
||||
|
||||
hide_sensitive_args(Args=[_H|_T]) ->
|
||||
lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
|
||||
({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
|
||||
(E) -> E end,
|
||||
Args);
|
||||
hide_sensitive_args(NonListArgs) ->
|
||||
NonListArgs.
|
||||
|
||||
permission_addon() ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),
|
||||
|
||||
@@ -59,8 +59,13 @@
|
||||
-define(HTTP_ERR_FILE_NOT_FOUND,
|
||||
{-1, 404, [], <<"Not found">>}).
|
||||
|
||||
-define(REQUEST_AUTH_HEADERS,
|
||||
[{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
|
||||
|
||||
-define(HTTP_ERR_FORBIDDEN,
|
||||
{-1, 403, [], <<"Forbidden">>}).
|
||||
-define(HTTP_ERR_REQUEST_AUTH,
|
||||
{-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
|
||||
|
||||
-define(DEFAULT_CONTENT_TYPE,
|
||||
<<"application/octet-stream">>).
|
||||
@@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
|
||||
false
|
||||
end,
|
||||
case CanProceed of
|
||||
false ->
|
||||
?HTTP_ERR_REQUEST_AUTH;
|
||||
true ->
|
||||
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
|
||||
case file:read_file_info(FileName) of
|
||||
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, eacces} -> ?HTTP_ERR_FORBIDDEN;
|
||||
{error, enoent} ->
|
||||
?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, enotdir} ->
|
||||
?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, eacces} ->
|
||||
?HTTP_ERR_FORBIDDEN;
|
||||
{ok, #file_info{type = directory}} -> serve_index(FileName,
|
||||
DirectoryIndices,
|
||||
CustomHeaders,
|
||||
|
||||
+131
-99
@@ -94,7 +94,7 @@
|
||||
|
||||
-record(state,
|
||||
{server_host :: binary(),
|
||||
host :: binary(),
|
||||
hosts :: [binary()],
|
||||
name :: binary(),
|
||||
access :: atom(),
|
||||
max_size :: pos_integer() | infinity,
|
||||
@@ -107,10 +107,11 @@
|
||||
get_url :: binary(),
|
||||
service_url :: binary() | undefined,
|
||||
thumbnail :: boolean(),
|
||||
custom_headers :: [{binary(), binary()}],
|
||||
slots = #{} :: map()}).
|
||||
|
||||
-record(media_info,
|
||||
{type :: binary(),
|
||||
{type :: atom(),
|
||||
height :: integer(),
|
||||
width :: integer()}).
|
||||
|
||||
@@ -151,6 +152,8 @@ stop(ServerHost) ->
|
||||
|
||||
mod_opt_type(host) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(name) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(access) ->
|
||||
@@ -194,7 +197,7 @@ mod_opt_type(rm_on_unregister) ->
|
||||
mod_opt_type(thumbnail) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[host, name, access, max_size, secret_length, jid_in_url, file_mode,
|
||||
[host, hosts, name, access, max_size, secret_length, jid_in_url, file_mode,
|
||||
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
|
||||
rm_on_unregister, thumbnail].
|
||||
|
||||
@@ -211,7 +214,7 @@ depends(_Host, _Opts) ->
|
||||
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>),
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"upload.@HOST@">>),
|
||||
Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>),
|
||||
Access = gen_mod:get_opt(access, Opts, local),
|
||||
MaxSize = gen_mod:get_opt(max_size, Opts, 104857600),
|
||||
@@ -224,6 +227,7 @@ init([ServerHost, Opts]) ->
|
||||
GetURL = gen_mod:get_opt(get_url, Opts, PutURL),
|
||||
ServiceURL = gen_mod:get_opt(service_url, Opts),
|
||||
Thumbnail = gen_mod:get_opt(thumbnail, Opts, true),
|
||||
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
|
||||
DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
|
||||
DocRoot2 = expand_host(DocRoot1, ServerHost),
|
||||
case DirMode of
|
||||
@@ -234,18 +238,23 @@ init([ServerHost, Opts]) ->
|
||||
end,
|
||||
case Thumbnail of
|
||||
true ->
|
||||
case string:str(os:cmd("identify"), "Magick") of
|
||||
0 ->
|
||||
?ERROR_MSG("Cannot find 'identify' command, please install "
|
||||
"ImageMagick or disable thumbnail creation", []);
|
||||
_ ->
|
||||
ok
|
||||
case misc:have_eimp() of
|
||||
false ->
|
||||
?ERROR_MSG("ejabberd is built without graphics support, "
|
||||
"please rebuild it with --enable-graphics or "
|
||||
"set 'thumbnail: false' for module '~s' in "
|
||||
"ejabberd.yml", [?MODULE]);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
{ok, #state{server_host = ServerHost, host = Host, name = Name,
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, ServerHost)
|
||||
end, Hosts),
|
||||
{ok, #state{server_host = ServerHost, hosts = Hosts, name = Name,
|
||||
access = Access, max_size = MaxSize,
|
||||
secret_length = SecretLength, jid_in_url = JIDinURL,
|
||||
file_mode = FileMode, dir_mode = DirMode,
|
||||
@@ -253,7 +262,8 @@ init([ServerHost, Opts]) ->
|
||||
docroot = DocRoot2,
|
||||
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
|
||||
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
|
||||
service_url = ServiceURL}}.
|
||||
service_url = ServiceURL,
|
||||
custom_headers = CustomHeaders}}.
|
||||
|
||||
-spec handle_call(_, {pid(), _}, state())
|
||||
-> {reply, {ok, pos_integer(), binary(),
|
||||
@@ -261,25 +271,30 @@ init([ServerHost, Opts]) ->
|
||||
pos_integer() | undefined}, state()} |
|
||||
{reply, {error, atom()}, state()} | {noreply, state()}.
|
||||
|
||||
handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
|
||||
dir_mode = DirMode,
|
||||
get_url = GetPrefix,
|
||||
thumbnail = Thumbnail,
|
||||
docroot = DocRoot} = State) ->
|
||||
handle_call({use_slot, Slot, Size}, _From,
|
||||
#state{file_mode = FileMode,
|
||||
dir_mode = DirMode,
|
||||
get_url = GetPrefix,
|
||||
thumbnail = Thumbnail,
|
||||
custom_headers = CustomHeaders,
|
||||
docroot = DocRoot} = State) ->
|
||||
case get_slot(Slot, State) of
|
||||
{ok, {Size, Timer}} ->
|
||||
timer:cancel(Timer),
|
||||
NewState = del_slot(Slot, State),
|
||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||
{reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
|
||||
{reply,
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders},
|
||||
NewState};
|
||||
{ok, {_WrongSize, _Timer}} ->
|
||||
{reply, {error, size_mismatch}, State};
|
||||
error ->
|
||||
{reply, {error, invalid_slot}, State}
|
||||
end;
|
||||
handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
|
||||
{reply, {ok, DocRoot}, State};
|
||||
handle_call(get_conf, _From,
|
||||
#state{docroot = DocRoot,
|
||||
custom_headers = CustomHeaders} = State) ->
|
||||
{reply, {ok, DocRoot, CustomHeaders}, State};
|
||||
handle_call(Request, From, State) ->
|
||||
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
@@ -324,10 +339,9 @@ handle_info(Info, State) ->
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
|
||||
terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) ->
|
||||
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok.
|
||||
lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
@@ -349,44 +363,44 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
|
||||
Method == 'HEAD' ->
|
||||
?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
|
||||
[Method, ?ADDR_TO_STR(IP), Host]),
|
||||
http_response(Host, 404);
|
||||
http_response(404);
|
||||
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
data = Data} = Request) ->
|
||||
{Proc, Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
|
||||
?DEBUG("Storing file from ~s for ~s: ~s",
|
||||
[?ADDR_TO_STR(IP), Host, Path]),
|
||||
case store_file(Path, Data, FileMode, DirMode,
|
||||
GetPrefix, Slot, Thumbnail) of
|
||||
ok ->
|
||||
http_response(Host, 201);
|
||||
http_response(201, CustomHeaders);
|
||||
{ok, Headers, OutData} ->
|
||||
http_response(Host, 201, Headers, OutData);
|
||||
http_response(201, Headers ++ CustomHeaders, OutData);
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p",
|
||||
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
|
||||
http_response(Host, 500)
|
||||
http_response(500)
|
||||
end;
|
||||
{error, size_mismatch} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
|
||||
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
|
||||
http_response(Host, 413);
|
||||
http_response(413);
|
||||
{error, invalid_slot} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
|
||||
[?ADDR_TO_STR(IP), Host]),
|
||||
http_response(Host, 403);
|
||||
http_response(403);
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
|
||||
[?ADDR_TO_STR(IP), Host, Error]),
|
||||
http_response(Host, 500)
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
|
||||
when Method == 'GET';
|
||||
Method == 'HEAD' ->
|
||||
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, get_docroot) of
|
||||
{ok, DocRoot} ->
|
||||
case catch gen_server:call(Proc, get_conf) of
|
||||
{ok, DocRoot, CustomHeaders} ->
|
||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||
case file:read_file(Path) of
|
||||
{ok, Data} ->
|
||||
@@ -401,37 +415,47 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
|
||||
$", FileName/binary, $">>}]
|
||||
end,
|
||||
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
|
||||
http_response(Host, 200, Headers2, Data);
|
||||
Headers3 = Headers2 ++ CustomHeaders,
|
||||
http_response(200, Headers3, Data);
|
||||
{error, eacces} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
http_response(Host, 403);
|
||||
http_response(403);
|
||||
{error, enoent} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: No such file",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
http_response(Host, 404);
|
||||
http_response(404);
|
||||
{error, eisdir} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
http_response(Host, 404);
|
||||
http_response(404);
|
||||
{error, Error} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: ~s",
|
||||
[Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]),
|
||||
http_response(Host, 500)
|
||||
http_response(500)
|
||||
end;
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
|
||||
[Method, ?ADDR_TO_STR(IP), Host, Error]),
|
||||
http_response(Host, 500)
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) ->
|
||||
process(_LocalPath, #request{method = 'OPTIONS', host = Host,
|
||||
ip = IP} = Request) ->
|
||||
?DEBUG("Responding to OPTIONS request from ~s for ~s",
|
||||
[?ADDR_TO_STR(IP), Host]),
|
||||
http_response(Host, 200);
|
||||
{Proc, _Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, get_conf) of
|
||||
{ok, _DocRoot, CustomHeaders} ->
|
||||
http_response(200, CustomHeaders);
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
|
||||
[?ADDR_TO_STR(IP), Host, Error]),
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
|
||||
?DEBUG("Rejecting ~s request from ~s for ~s",
|
||||
[Method, ?ADDR_TO_STR(IP), Host]),
|
||||
http_response(Host, 405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
|
||||
http_response(405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Exported utility functions.
|
||||
@@ -518,7 +542,7 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
|
||||
deny ->
|
||||
?DEBUG("Denying HTTP upload slot request from ~s",
|
||||
[jid:encode(From)]),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
|
||||
end.
|
||||
|
||||
@@ -654,7 +678,7 @@ make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1).
|
||||
-spec make_rand_char() -> char().
|
||||
|
||||
make_rand_char() ->
|
||||
map_int_to_char(crypto:rand_uniform(0, 62)).
|
||||
map_int_to_char(randoms:uniform(0, 61)).
|
||||
|
||||
-spec map_int_to_char(0..61) -> char().
|
||||
|
||||
@@ -722,15 +746,15 @@ parse_http_request(#request{host = Host, path = Path}) ->
|
||||
store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
|
||||
case do_store_file(Path, Data, FileMode, DirMode) of
|
||||
ok when Thumbnail ->
|
||||
case identify(Path) of
|
||||
case identify(Path, Data) of
|
||||
{ok, MediaInfo} ->
|
||||
case convert(Path, MediaInfo) of
|
||||
{ok, OutPath} ->
|
||||
case convert(Path, Data, MediaInfo) of
|
||||
{ok, OutPath, OutMediaInfo} ->
|
||||
[UserDir, RandDir | _] = Slot,
|
||||
FileName = filename:basename(OutPath),
|
||||
URL = str:join([GetPrefix, UserDir,
|
||||
RandDir, FileName], <<$/>>),
|
||||
ThumbEl = thumb_el(OutPath, URL),
|
||||
ThumbEl = thumb_el(OutMediaInfo, URL),
|
||||
{ok,
|
||||
[{<<"Content-Type">>,
|
||||
<<"text/xml; charset=utf-8">>}],
|
||||
@@ -786,30 +810,29 @@ guess_content_type(FileName) ->
|
||||
?DEFAULT_CONTENT_TYPE,
|
||||
?CONTENT_TYPES).
|
||||
|
||||
-spec http_response(binary(), 100..599)
|
||||
-spec http_response(100..599)
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Host, Code) ->
|
||||
http_response(Host, Code, []).
|
||||
http_response(Code) ->
|
||||
http_response(Code, []).
|
||||
|
||||
-spec http_response(binary(), 100..599, [{binary(), binary()}])
|
||||
-spec http_response(100..599, [{binary(), binary()}])
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Host, Code, ExtraHeaders) ->
|
||||
http_response(Code, ExtraHeaders) ->
|
||||
Message = <<(code_to_message(Code))/binary, $\n>>,
|
||||
http_response(Host, Code, ExtraHeaders, Message).
|
||||
http_response(Code, ExtraHeaders, Message).
|
||||
|
||||
-spec http_response(binary(), 100..599, [{binary(), binary()}], binary())
|
||||
-spec http_response(100..599, [{binary(), binary()}], binary())
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Host, Code, ExtraHeaders, Body) ->
|
||||
CustomHeaders = gen_mod:get_module_opt(Host, ?MODULE, custom_headers, []),
|
||||
http_response(Code, ExtraHeaders, Body) ->
|
||||
Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
|
||||
true ->
|
||||
ExtraHeaders;
|
||||
false ->
|
||||
[{<<"Content-Type">>, <<"text/plain">>} | ExtraHeaders]
|
||||
end ++ CustomHeaders,
|
||||
end,
|
||||
{Code, Headers, Body}.
|
||||
|
||||
-spec code_to_message(100..599) -> binary().
|
||||
@@ -826,59 +849,68 @@ code_to_message(_Code) -> <<"">>.
|
||||
%% Image manipulation stuff.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec identify(binary()) -> {ok, media_info()} | pass.
|
||||
-spec identify(binary(), binary()) -> {ok, media_info()} | pass.
|
||||
|
||||
identify(Path) ->
|
||||
Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]),
|
||||
Res = string:strip(os:cmd(Cmd), right, $\n),
|
||||
case string:tokens(Res, " ") of
|
||||
["ok", T, H, W] ->
|
||||
{ok, #media_info{type = list_to_binary(string:to_lower(T)),
|
||||
height = list_to_integer(H),
|
||||
width = list_to_integer(W)}};
|
||||
_ ->
|
||||
?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]),
|
||||
identify(Path, Data) ->
|
||||
case misc:have_eimp() of
|
||||
true ->
|
||||
case eimp:identify(Data) of
|
||||
{ok, Info} ->
|
||||
{ok, #media_info{
|
||||
type = proplists:get_value(type, Info),
|
||||
width = proplists:get_value(width, Info),
|
||||
height = proplists:get_value(height, Info)}};
|
||||
{error, Why} ->
|
||||
?DEBUG("Cannot identify type of ~s: ~s",
|
||||
[Path, eimp:format_error(Why)]),
|
||||
pass
|
||||
end;
|
||||
false ->
|
||||
pass
|
||||
end.
|
||||
|
||||
-spec convert(binary(), media_info()) -> {ok, binary()} | pass.
|
||||
-spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass.
|
||||
|
||||
convert(Path, #media_info{type = T, width = W, height = H}) ->
|
||||
convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
|
||||
if W * H >= 25000000 ->
|
||||
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
|
||||
pass;
|
||||
W =< 300, H =< 300 ->
|
||||
{ok, Path};
|
||||
T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> ->
|
||||
Dir = filename:dirname(Path),
|
||||
FileName = <<(randoms:get_string())/binary, $., T/binary>>,
|
||||
OutPath = filename:join(Dir, FileName),
|
||||
Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]),
|
||||
case os:cmd(Cmd) of
|
||||
"" ->
|
||||
{ok, OutPath};
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
|
||||
[Path, OutPath, string:strip(Err, right, $\n)]),
|
||||
pass
|
||||
end;
|
||||
{ok, Path, Info};
|
||||
true ->
|
||||
?DEBUG("Won't call 'convert' for unknown type ~s", [T]),
|
||||
pass
|
||||
Dir = filename:dirname(Path),
|
||||
Ext = atom_to_binary(T, latin1),
|
||||
FileName = <<(randoms:get_string())/binary, $., Ext/binary>>,
|
||||
OutPath = filename:join(Dir, FileName),
|
||||
{W1, H1} = if W > H -> {300, round(H*300/W)};
|
||||
H > W -> {round(W*300/H), 300};
|
||||
true -> {300, 300}
|
||||
end,
|
||||
OutInfo = #media_info{type = T, width = W1, height = H1},
|
||||
case eimp:convert(Data, T, [{scale, {W1, H1}}]) of
|
||||
{ok, OutData} ->
|
||||
case file:write_file(OutPath, OutData) of
|
||||
ok ->
|
||||
{ok, OutPath, OutInfo};
|
||||
{error, Why} ->
|
||||
?ERROR_MSG("Failed to write to ~s: ~s",
|
||||
[OutPath, file:format_error(Why)]),
|
||||
pass
|
||||
end;
|
||||
{error, Why} ->
|
||||
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
|
||||
[Path, OutPath, eimp:format_error(Why)]),
|
||||
pass
|
||||
end
|
||||
end.
|
||||
|
||||
-spec thumb_el(binary(), binary()) -> xmlel().
|
||||
-spec thumb_el(media_info(), binary()) -> xmlel().
|
||||
|
||||
thumb_el(Path, URI) ->
|
||||
ContentType = guess_content_type(Path),
|
||||
xmpp:encode(
|
||||
case identify(Path) of
|
||||
{ok, #media_info{height = H, width = W}} ->
|
||||
#thumbnail{'media-type' = ContentType, uri = URI,
|
||||
height = H, width = W};
|
||||
pass ->
|
||||
#thumbnail{uri = URI, 'media-type' = ContentType}
|
||||
end).
|
||||
thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
|
||||
MimeType = <<"image/", (atom_to_binary(T, latin1))/binary>>,
|
||||
Thumb = #thumbnail{'media-type' = MimeType, uri = URI,
|
||||
height = H, width = W},
|
||||
xmpp:encode(Thumb).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Remove user.
|
||||
|
||||
+39
-25
@@ -58,7 +58,7 @@
|
||||
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
|
||||
<<"utf-8">>, <<"utf-8+latin-1">>]).
|
||||
|
||||
-record(state, {host = <<"">> :: binary(),
|
||||
-record(state, {hosts = [] :: [binary()],
|
||||
server_host = <<"">> :: binary(),
|
||||
access = all :: atom()}).
|
||||
|
||||
@@ -99,8 +99,7 @@ depends(_Host, _Opts) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
ejabberd:start_app(iconv),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"irc.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
@@ -108,10 +107,13 @@ init([Host, Opts]) ->
|
||||
[named_table, public,
|
||||
{keypos, #irc_connection.jid_server_host}]),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
register_hooks(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
register_hooks(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end, MyHosts),
|
||||
{ok,
|
||||
#state{host = MyHost, server_host = Host,
|
||||
#state{hosts = MyHosts, server_host = Host,
|
||||
access = Access}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -133,8 +135,8 @@ handle_call(stop, _From, State) ->
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>),
|
||||
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>),
|
||||
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>),
|
||||
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>),
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
@@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
register_hooks(NewHost, NewIQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_hooks(OldHost);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_hooks(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_hooks(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_hooks(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
Access = gen_mod:get_opt(access, NewOpts, all),
|
||||
{noreply, State#state{host = NewHost, access = Access}};
|
||||
{noreply, State#state{hosts = NewHosts, access = Access}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -170,9 +178,10 @@ handle_cast(Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, Packet},
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
access = Access} =
|
||||
#state{server_host = ServerHost, access = Access} =
|
||||
State) ->
|
||||
To = xmpp:get_to(Packet),
|
||||
Host = To#jid.lserver,
|
||||
case catch do_route(Host, ServerHost, Access, Packet) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
_ -> ok
|
||||
@@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}.
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, #state{host = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_hooks(MyHost).
|
||||
terminate(_Reason, #state{hosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_hooks(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
@@ -250,7 +262,7 @@ do_route(Host, ServerHost, Access, Packet) ->
|
||||
end;
|
||||
deny ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
|
||||
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
|
||||
ejabberd_router:route_error(Packet, Err)
|
||||
end.
|
||||
|
||||
@@ -975,8 +987,10 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_encoding) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) ->
|
||||
[access, db_type, default_encoding, host].
|
||||
[access, db_type, default_encoding, host, hosts].
|
||||
|
||||
-spec extract_ident(stanza()) -> binary().
|
||||
extract_ident(Packet) ->
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start_link/12, start/13, route_chan/4,
|
||||
@@ -91,7 +91,7 @@ start(From, Host, ServerHost, Server, Username,
|
||||
|
||||
start_link(From, Host, Server, Username, Encoding, Port,
|
||||
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
|
||||
gen_fsm:start_link(?MODULE,
|
||||
p1_fsm:start_link(?MODULE,
|
||||
[From, Host, Server, Username, Encoding, Port, Password,
|
||||
Ident, RemoteAddr, RealName, WebircPassword, Mod],
|
||||
?FSMOPTS).
|
||||
@@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port,
|
||||
%%----------------------------------------------------------------------
|
||||
init([From, Host, Server, Username, Encoding, Port,
|
||||
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
|
||||
gen_fsm:send_event(self(), init),
|
||||
p1_fsm:send_event(self(), init),
|
||||
{ok, open_socket,
|
||||
#state{mod = Mod,
|
||||
encoding = Encoding, port = Port, password = Password,
|
||||
@@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName,
|
||||
StateData#state{inbuf = NewBuf}};
|
||||
handle_info({tcp_closed, _Socket}, StateName,
|
||||
StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
p1_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({tcp_error, _Socket, _Reason}, StateName,
|
||||
StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
p1_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
@@ -133,7 +133,7 @@ authenticate(#{stream_id := StreamID, server := Server,
|
||||
Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()),
|
||||
process_auth_failure(State, U, Err, 'jid-malformed');
|
||||
false ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)),
|
||||
process_auth_failure(State, U, Err, 'forbidden')
|
||||
end.
|
||||
|
||||
+21
-15
@@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) ->
|
||||
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
||||
TypeBin == <<"groupchat">>;
|
||||
TypeBin == <<"all">> ->
|
||||
CurrentTime = p1_time_compat:system_time(micro_seconds),
|
||||
Diff = Days * 24 * 60 * 60 * 1000000,
|
||||
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
|
||||
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
||||
Type = misc:binary_to_atom(TypeBin),
|
||||
DBTypes = lists:usort(
|
||||
lists:map(
|
||||
@@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
|
||||
Msgs =
|
||||
lists:flatmap(
|
||||
fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
|
||||
TS = now_to_usec(Now),
|
||||
TS = misc:now_to_usec(Now),
|
||||
case match_interval(Now, Start, End) and
|
||||
match_rsm(Now, RSM) of
|
||||
true ->
|
||||
@@ -862,8 +863,13 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
|
||||
{Msgs, true, L}
|
||||
end;
|
||||
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
|
||||
case might_expose_jid(Query, MsgType) of
|
||||
true ->
|
||||
{[], true, 0};
|
||||
false ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
|
||||
end.
|
||||
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
|
||||
peer = Peer, id = ID},
|
||||
@@ -979,23 +985,20 @@ match_interval(Now, Start, End) ->
|
||||
(Now >= Start) and (Now =< End).
|
||||
|
||||
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
|
||||
Now > Now1;
|
||||
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
|
||||
Now < Now1;
|
||||
match_rsm(_Now, _) ->
|
||||
true.
|
||||
|
||||
now_to_usec({MSec, Sec, USec}) ->
|
||||
(MSec*1000000 + Sec)*1000000 + USec.
|
||||
|
||||
usec_to_now(Int) ->
|
||||
Secs = Int div 1000000,
|
||||
USec = Int rem 1000000,
|
||||
MSec = Secs div 1000000,
|
||||
Sec = Secs rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
might_expose_jid(Query,
|
||||
{groupchat, Role, #state{config = #config{anonymous = true}}})
|
||||
when Role /= moderator ->
|
||||
proplists:is_defined(with, Query);
|
||||
might_expose_jid(_Query, _MsgType) ->
|
||||
false.
|
||||
|
||||
get_jids(undefined) ->
|
||||
[];
|
||||
@@ -1008,6 +1011,9 @@ get_commands_spec() ->
|
||||
longdesc = "Valid message TYPEs: "
|
||||
"\"chat\", \"groupchat\", \"all\".",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args_desc = ["Type of messages to delete (chat, groupchat, all)",
|
||||
"Days to keep messages"],
|
||||
args_example = [<<"all">>, 31],
|
||||
args = [{type, binary}, {days, integer}],
|
||||
result = {res, rescode}}].
|
||||
|
||||
|
||||
+55
-48
@@ -46,7 +46,7 @@
|
||||
?NS_MIX_NODES_CONFIG]).
|
||||
|
||||
-record(state, {server_host :: binary(),
|
||||
host :: binary()}).
|
||||
hosts :: [binary()]}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) ->
|
||||
%%%===================================================================
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
ConfigTab = gen_mod:get_module_proc(Host, config),
|
||||
ets:new(ConfigTab, [named_table]),
|
||||
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
{ok, #state{server_host = ServerHost, host = Host}}.
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ConfigTab = gen_mod:get_module_proc(Host, config),
|
||||
ets:new(ConfigTab, [named_table]),
|
||||
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
|
||||
ejabberd_router:register_route(Host, ServerHost)
|
||||
end, Hosts),
|
||||
{ok, #state{server_host = ServerHost, hosts = Hosts}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
@@ -180,22 +183,24 @@ handle_info({route, Packet}, State) ->
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_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),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok.
|
||||
terminate(_Reason, #state{hosts = Hosts}) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_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),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
||||
ejabberd_router:unregister_route(Host)
|
||||
end, Hosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
@@ -316,4 +321,6 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host, iqdisc].
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) -> [host, hosts, iqdisc].
|
||||
|
||||
+57
-38
@@ -61,6 +61,7 @@
|
||||
count_online_rooms/1,
|
||||
register_online_user/4,
|
||||
unregister_online_user/4,
|
||||
iq_set_register_info/5,
|
||||
count_online_rooms_by_user/3,
|
||||
get_online_rooms_by_user/3,
|
||||
can_use_nick/4]).
|
||||
@@ -75,7 +76,7 @@
|
||||
-include("mod_muc.hrl").
|
||||
|
||||
-record(state,
|
||||
{host = <<"">> :: binary(),
|
||||
{hosts = [] :: [binary()],
|
||||
server_host = <<"">> :: binary(),
|
||||
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
|
||||
history_size = 20 :: non_neg_integer(),
|
||||
@@ -151,8 +152,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
|
||||
%% If Opts = default, the default room options are used.
|
||||
%% Else use the passed options as defined in mod_muc_room.
|
||||
create_room(Host, Name, From, Nick, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
||||
gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
|
||||
|
||||
store_room(ServerHost, Host, Name, Opts) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
@@ -225,22 +227,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
#state{access = Access, host = MyHost,
|
||||
#state{access = Access, hosts = MyHosts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
room_shaper = RoomShaper} = State = init_state(Host, Opts),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, [{host, MyHost}|Opts]),
|
||||
RMod:init(Host, [{host, MyHost}|Opts]),
|
||||
register_iq_handlers(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType),
|
||||
Mod:init(Host, [{hosts, MyHosts}|Opts]),
|
||||
RMod:init(Host, [{hosts, MyHosts}|Opts]),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
register_iq_handlers(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
load_permanent_rooms(MyHost, Host, Access, HistorySize,
|
||||
RoomShaper, QueueType)
|
||||
end, MyHosts),
|
||||
{ok, State}.
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
handle_call({create, Room, From, Nick, Opts}, _From,
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
handle_call({create, Room, Host, From, Nick, Opts}, _From,
|
||||
#state{server_host = ServerHost,
|
||||
access = Access, default_room_opts = DefOpts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
@@ -256,51 +262,59 @@ handle_call({create, Room, From, Nick, Opts}, _From,
|
||||
Nick, NewOpts, QueueType),
|
||||
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
|
||||
RMod:register_online_room(ServerHost, Room, Host, Pid),
|
||||
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) ->
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE),
|
||||
OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE),
|
||||
#state{host = NewHost} = NewState = init_state(ServerHost, NewOpts),
|
||||
#state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(ServerHost, [{host, NewHost}|NewOpts]);
|
||||
NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewRMod /= OldRMod ->
|
||||
NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]);
|
||||
NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
register_iq_handlers(NewHost, NewIQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_iq_handlers(OldHost);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_iq_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_iq_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_iq_handlers(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
{noreply, NewState};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({route, Packet},
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
#state{server_host = ServerHost,
|
||||
access = Access, default_room_opts = DefRoomOpts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
max_rooms_discoitems = MaxRoomsDiscoItems,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
From = xmpp:get_from(Packet),
|
||||
To = xmpp:get_to(Packet),
|
||||
Host = To#jid.lserver,
|
||||
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
|
||||
QueueType) of
|
||||
@@ -319,9 +333,12 @@ handle_info(Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_iq_handlers(MyHost).
|
||||
terminate(_Reason, #state{hosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_iq_handlers(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
@@ -329,8 +346,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
init_state(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"conference.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts,
|
||||
<<"conference.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
|
||||
@@ -341,7 +358,7 @@ init_state(Host, Opts) ->
|
||||
QueueType = gen_mod:get_opt(queue_type, Opts,
|
||||
ejabberd_config:default_queue_type(Host)),
|
||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
||||
#state{host = MyHost,
|
||||
#state{hosts = MyHosts,
|
||||
server_host = Host,
|
||||
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
default_room_opts = DefRoomOpts,
|
||||
@@ -664,17 +681,17 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
|
||||
|
||||
-spec get_room_disco_item({binary(), binary(), pid()},
|
||||
term()) -> {ok, disco_item()} |
|
||||
{error, timeout | notfound}.
|
||||
{error, timeout | notfound}.
|
||||
get_room_disco_item({Name, Host, Pid}, Query) ->
|
||||
RoomJID = jid:make(Name, Host),
|
||||
try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
|
||||
try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
|
||||
{item, Desc} ->
|
||||
{ok, #disco_item{jid = RoomJID, name = Desc}};
|
||||
false ->
|
||||
{error, notfound}
|
||||
catch _:{timeout, _} ->
|
||||
catch _:{timeout, {p1_fsm, _, _}} ->
|
||||
{error, timeout};
|
||||
_:{noproc, _} ->
|
||||
_:{_, {p1_fsm, _, _}} ->
|
||||
{error, notfound}
|
||||
end.
|
||||
|
||||
@@ -683,7 +700,7 @@ get_subscribed_rooms(ServerHost, Host, From) ->
|
||||
BareFrom = jid:remove_resource(From),
|
||||
lists:flatmap(
|
||||
fun({Name, _, Pid}) ->
|
||||
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
|
||||
case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
|
||||
true -> [jid:make(Name, Host)];
|
||||
false -> []
|
||||
end;
|
||||
@@ -765,7 +782,7 @@ process_iq_register_set(ServerHost, Host, From,
|
||||
broadcast_service_message(ServerHost, Host, Msg) ->
|
||||
lists:foreach(
|
||||
fun({_, _, Pid}) ->
|
||||
gen_fsm:send_all_state_event(
|
||||
p1_fsm:send_all_state_event(
|
||||
Pid, {service_message, Msg})
|
||||
end, get_online_rooms(ServerHost, Host)).
|
||||
|
||||
@@ -850,6 +867,8 @@ mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(history_size) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(max_room_desc) ->
|
||||
fun (infinity) -> infinity;
|
||||
(I) when is_integer(I), I > 0 -> I
|
||||
@@ -943,7 +962,7 @@ mod_opt_type({default_room_options, presence_broadcast}) ->
|
||||
end;
|
||||
mod_opt_type(_) ->
|
||||
[access, access_admin, access_create, access_persistent,
|
||||
db_type, ram_db_type, history_size, host,
|
||||
db_type, ram_db_type, history_size, host, hosts,
|
||||
max_room_desc, max_room_id, max_room_name,
|
||||
max_rooms_discoitems, max_user_conferences, max_users,
|
||||
max_users_admin_threshold, max_users_presence,
|
||||
|
||||
+35
-43
@@ -29,7 +29,7 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, reload/3, depends/2, muc_online_rooms/1,
|
||||
muc_register_nick/3, muc_unregister_nick/1,
|
||||
muc_register_nick/3, muc_unregister_nick/2,
|
||||
create_room_with_opts/4, create_room/3, destroy_room/2,
|
||||
create_rooms_file/1, destroy_rooms_file/1,
|
||||
rooms_unused_list/2, rooms_unused_destroy/2,
|
||||
@@ -91,16 +91,18 @@ get_commands_spec() ->
|
||||
args = [{host, binary}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
#ejabberd_commands{name = muc_register_nick, tags = [muc],
|
||||
desc = "Register a nick in the MUC service",
|
||||
desc = "Register a nick to a User JID in the MUC service of a server",
|
||||
module = ?MODULE, function = muc_register_nick,
|
||||
args_desc = ["Nick", "User JID", "MUC service"],
|
||||
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"muc.example.org">>],
|
||||
args = [{nick, binary}, {jid, binary}, {domain, binary}],
|
||||
args_desc = ["Nick", "User JID", "Server Host"],
|
||||
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"example.org">>],
|
||||
args = [{nick, binary}, {jid, binary}, {serverhost, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
|
||||
desc = "Unregister the nick in the MUC service",
|
||||
desc = "Unregister the nick registered by that account in the MUC service",
|
||||
module = ?MODULE, function = muc_unregister_nick,
|
||||
args = [{nick, binary}],
|
||||
args_desc = ["User JID", "MUC service"],
|
||||
args_example = [<<"tim@example.org">>, <<"example.org">>],
|
||||
args = [{jid, binary}, {serverhost, binary}],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = create_room, tags = [muc_room],
|
||||
@@ -305,31 +307,14 @@ muc_online_rooms(ServerHost) ->
|
||||
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)]
|
||||
end, Hosts).
|
||||
|
||||
muc_register_nick(Nick, JIDBinary, Domain) ->
|
||||
try jid:decode(JIDBinary) of
|
||||
JID ->
|
||||
F = fun (MHost, MNick) ->
|
||||
mnesia:write(#muc_registered{us_host=MHost, nick=MNick})
|
||||
end,
|
||||
case mnesia:transaction(F, [{{JID#jid.luser, JID#jid.lserver},
|
||||
Domain}, Nick]) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, _Error} -> error
|
||||
end
|
||||
catch _:{bad_jid, _} -> throw({error, "Malformed JID"})
|
||||
end.
|
||||
muc_register_nick(Nick, FromBinary, ServerHost) ->
|
||||
Host = find_host(ServerHost),
|
||||
From = jid:decode(FromBinary),
|
||||
Lang = <<"en">>,
|
||||
mod_muc:iq_set_register_info(ServerHost, Host, From, Nick, Lang).
|
||||
|
||||
muc_unregister_nick(Nick) ->
|
||||
F2 = fun(N) ->
|
||||
[{_,Key,_}|_] = mnesia:index_read(muc_registered, N, 3),
|
||||
mnesia:delete({muc_registered, Key})
|
||||
end,
|
||||
case mnesia:transaction(F2, [Nick], 1) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, _Error} ->
|
||||
error
|
||||
end.
|
||||
muc_unregister_nick(FromBinary, ServerHost) ->
|
||||
muc_register_nick(<<"">>, FromBinary, ServerHost).
|
||||
|
||||
get_user_rooms(LUser, LServer) ->
|
||||
lists:flatmap(
|
||||
@@ -536,7 +521,8 @@ prepare_room_info(Room_info) ->
|
||||
%% ok | error
|
||||
%% @doc Create a room immediately with the default options.
|
||||
create_room(Name1, Host1, ServerHost) ->
|
||||
create_room_with_opts(Name1, Host1, ServerHost, []).
|
||||
create_room_with_opts(Name1, Host1, ServerHost, []),
|
||||
change_room_option(Name1, Host1, <<"persistent">>, <<"true">>).
|
||||
|
||||
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
|
||||
true = (error /= (Name = jid:nodeprep(Name1))),
|
||||
@@ -597,7 +583,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
|
||||
destroy_room(Name, Service) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
gen_fsm:send_all_state_event(Pid, destroy),
|
||||
p1_fsm:send_all_state_event(Pid, destroy),
|
||||
ok;
|
||||
error ->
|
||||
error
|
||||
@@ -716,11 +702,11 @@ get_rooms(ServerHost) ->
|
||||
end, Hosts).
|
||||
|
||||
get_room_config(Room_pid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config),
|
||||
R.
|
||||
|
||||
get_room_state(Room_pid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state),
|
||||
R.
|
||||
|
||||
%%---------------
|
||||
@@ -786,7 +772,7 @@ find_serverhost(Host, ServerHosts) ->
|
||||
ServerHost.
|
||||
|
||||
act_on_room(destroy, {N, H, Pid}, SH) ->
|
||||
gen_fsm:send_all_state_event(
|
||||
p1_fsm:send_all_state_event(
|
||||
Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
|
||||
mod_muc:room_destroyed(H, N, Pid, SH),
|
||||
mod_muc:forget_room(SH, H, N);
|
||||
@@ -816,7 +802,13 @@ get_room_occupants(Pid) ->
|
||||
dict:to_list(S#state.users)).
|
||||
|
||||
get_room_occupants_number(Room, Host) ->
|
||||
length(get_room_occupants(Room, Host)).
|
||||
case get_room_pid(Room, Host) of
|
||||
room_not_found ->
|
||||
throw({error, room_not_found});
|
||||
Pid ->
|
||||
S = get_room_state(Pid),
|
||||
dict:size(S#state.users)
|
||||
end.
|
||||
|
||||
%%----------------------------
|
||||
%% Send Direct Invitation
|
||||
@@ -888,7 +880,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
|
||||
{Option, Value} = format_room_option(OptionString, ValueString),
|
||||
Config = get_room_config(Pid),
|
||||
Config2 = change_option(Option, Value, Config),
|
||||
{ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
|
||||
{ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -983,7 +975,7 @@ get_room_affiliations(Name, Service) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
%% Get the PID of the online room, then request its state
|
||||
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
|
||||
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state),
|
||||
Affiliations = ?DICT:to_list(StateData#state.affiliations),
|
||||
lists:map(
|
||||
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
|
||||
@@ -1012,7 +1004,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
%% Get the PID for the online room so we can get the state of the room
|
||||
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
|
||||
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
|
||||
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
|
||||
ok;
|
||||
error ->
|
||||
@@ -1035,7 +1027,7 @@ subscribe_room(User, Nick, Room, Nodes) ->
|
||||
UserJID ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
case gen_fsm:sync_send_all_state_event(
|
||||
case p1_fsm:sync_send_all_state_event(
|
||||
Pid,
|
||||
{muc_subscribe, UserJID, Nick, NodeList}) of
|
||||
{ok, SubscribedNodes} ->
|
||||
@@ -1062,7 +1054,7 @@ unsubscribe_room(User, Room) ->
|
||||
UserJID ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
case gen_fsm:sync_send_all_state_event(
|
||||
case p1_fsm:sync_send_all_state_event(
|
||||
Pid,
|
||||
{muc_unsubscribe, UserJID}) of
|
||||
ok ->
|
||||
@@ -1085,7 +1077,7 @@ unsubscribe_room(User, Room) ->
|
||||
get_subscribers(Name, Host) ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
{ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
|
||||
{ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers),
|
||||
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
|
||||
_ ->
|
||||
throw({error, "The room does not exist"})
|
||||
|
||||
+46
-47
@@ -974,10 +974,9 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
|
||||
Os2 = lists:sort(Os1),
|
||||
Options2 = Title ++ Os2,
|
||||
lists:foldl(fun ({Opt, Val}, R) ->
|
||||
case get_roomconfig_text(Opt) of
|
||||
case get_roomconfig_text(Opt, Lang) of
|
||||
undefined -> R;
|
||||
OptT ->
|
||||
OptText = (?T(OptT)),
|
||||
OptText ->
|
||||
R2 = case Val of
|
||||
false ->
|
||||
<<"<div class=\"rcod\">",
|
||||
@@ -1025,49 +1024,49 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
|
||||
end,
|
||||
<<"">>, Options2).
|
||||
|
||||
get_roomconfig_text(title) -> <<"Room title">>;
|
||||
get_roomconfig_text(persistent) ->
|
||||
<<"Make room persistent">>;
|
||||
get_roomconfig_text(public) ->
|
||||
<<"Make room public searchable">>;
|
||||
get_roomconfig_text(public_list) ->
|
||||
<<"Make participants list public">>;
|
||||
get_roomconfig_text(password_protected) ->
|
||||
<<"Make room password protected">>;
|
||||
get_roomconfig_text(password) -> <<"Password">>;
|
||||
get_roomconfig_text(anonymous) ->
|
||||
<<"This room is not anonymous">>;
|
||||
get_roomconfig_text(members_only) ->
|
||||
<<"Make room members-only">>;
|
||||
get_roomconfig_text(moderated) ->
|
||||
<<"Make room moderated">>;
|
||||
get_roomconfig_text(members_by_default) ->
|
||||
<<"Default users as participants">>;
|
||||
get_roomconfig_text(allow_change_subj) ->
|
||||
<<"Allow users to change the subject">>;
|
||||
get_roomconfig_text(allow_private_messages) ->
|
||||
<<"Allow users to send private messages">>;
|
||||
get_roomconfig_text(allow_private_messages_from_visitors) ->
|
||||
<<"Allow visitors to send private messages to">>;
|
||||
get_roomconfig_text(allow_query_users) ->
|
||||
<<"Allow users to query other users">>;
|
||||
get_roomconfig_text(allow_user_invites) ->
|
||||
<<"Allow users to send invites">>;
|
||||
get_roomconfig_text(logging) -> <<"Enable logging">>;
|
||||
get_roomconfig_text(allow_visitor_nickchange) ->
|
||||
<<"Allow visitors to change nickname">>;
|
||||
get_roomconfig_text(allow_visitor_status) ->
|
||||
<<"Allow visitors to send status text in "
|
||||
"presence updates">>;
|
||||
get_roomconfig_text(captcha_protected) ->
|
||||
<<"Make room captcha protected">>;
|
||||
get_roomconfig_text(description) ->
|
||||
<<"Room description">>;
|
||||
%% get_roomconfig_text(subject) -> "Subject";
|
||||
%% get_roomconfig_text(subject_author) -> "Subject author";
|
||||
get_roomconfig_text(max_users) ->
|
||||
<<"Maximum Number of Occupants">>;
|
||||
get_roomconfig_text(_) -> undefined.
|
||||
get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>);
|
||||
get_roomconfig_text(persistent, Lang) ->
|
||||
?T(<<"Make room persistent">>);
|
||||
get_roomconfig_text(public, Lang) ->
|
||||
?T(<<"Make room public searchable">>);
|
||||
get_roomconfig_text(public_list, Lang) ->
|
||||
?T(<<"Make participants list public">>);
|
||||
get_roomconfig_text(password_protected, Lang) ->
|
||||
?T(<<"Make room password protected">>);
|
||||
get_roomconfig_text(password, Lang) -> ?T(<<"Password">>);
|
||||
get_roomconfig_text(anonymous, Lang) ->
|
||||
?T(<<"This room is not anonymous">>);
|
||||
get_roomconfig_text(members_only, Lang) ->
|
||||
?T(<<"Make room members-only">>);
|
||||
get_roomconfig_text(moderated, Lang) ->
|
||||
?T(<<"Make room moderated">>);
|
||||
get_roomconfig_text(members_by_default, Lang) ->
|
||||
?T(<<"Default users as participants">>);
|
||||
get_roomconfig_text(allow_change_subj, Lang) ->
|
||||
?T(<<"Allow users to change the subject">>);
|
||||
get_roomconfig_text(allow_private_messages, Lang) ->
|
||||
?T(<<"Allow users to send private messages">>);
|
||||
get_roomconfig_text(allow_private_messages_from_visitors, Lang) ->
|
||||
?T(<<"Allow visitors to send private messages to">>);
|
||||
get_roomconfig_text(allow_query_users, Lang) ->
|
||||
?T(<<"Allow users to query other users">>);
|
||||
get_roomconfig_text(allow_user_invites, Lang) ->
|
||||
?T(<<"Allow users to send invites">>);
|
||||
get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>);
|
||||
get_roomconfig_text(allow_visitor_nickchange, Lang) ->
|
||||
?T(<<"Allow visitors to change nickname">>);
|
||||
get_roomconfig_text(allow_visitor_status, Lang) ->
|
||||
?T(<<"Allow visitors to send status text in "
|
||||
"presence updates">>);
|
||||
get_roomconfig_text(captcha_protected, Lang) ->
|
||||
?T(<<"Make room CAPTCHA protected">>);
|
||||
get_roomconfig_text(description, Lang) ->
|
||||
?T(<<"Room description">>);
|
||||
%% get_roomconfig_text(subject, Lang) -> "Subject";
|
||||
%% get_roomconfig_text(subject_author, Lang) -> "Subject author";
|
||||
get_roomconfig_text(max_users, Lang) ->
|
||||
?T(<<"Maximum Number of Occupants">>);
|
||||
get_roomconfig_text(_, _) -> undefined.
|
||||
|
||||
%% Users = [{JID, Nick, Role}]
|
||||
roomoccupants_to_string(Users, _FileFormat) ->
|
||||
@@ -1142,7 +1141,7 @@ get_room_state(RoomName, MucService) ->
|
||||
-spec get_room_state(pid()) -> mod_muc_room:state().
|
||||
|
||||
get_room_state(RoomPid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(RoomPid,
|
||||
get_state),
|
||||
R.
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>,
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([Host, Opts]) ->
|
||||
MyHost = proplists:get_value(host, Opts),
|
||||
MyHosts = proplists:get_value(hosts, Opts),
|
||||
case gen_mod:db_mod(Host, Opts, mod_muc) of
|
||||
?MODULE ->
|
||||
ejabberd_mnesia:create(?MODULE, muc_room,
|
||||
@@ -318,7 +318,10 @@ init([Host, Opts]) ->
|
||||
{type, ordered_set},
|
||||
{attributes, record_info(fields, muc_online_room)}]),
|
||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
clean_table_from_bad_node(node(), MyHost)
|
||||
end, MyHosts),
|
||||
mnesia:subscribe(system);
|
||||
_ ->
|
||||
ok
|
||||
|
||||
@@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
|
||||
forget_room(_LServer, Host, Name) ->
|
||||
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
|
||||
|
||||
can_use_nick(LServer, Host, JID, Nick) ->
|
||||
can_use_nick(_LServer, Host, JID, Nick) ->
|
||||
{LUser, LServer, _} = jid:tolower(JID),
|
||||
LUS = {LUser, LServer},
|
||||
case ejabberd_riak:get_by_index(muc_registered,
|
||||
|
||||
+27
-12
@@ -27,7 +27,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start_link/10,
|
||||
@@ -94,23 +94,23 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, QueueType) ->
|
||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
|
||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Opts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, QueueType) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Opts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
@@ -492,7 +492,7 @@ handle_event({destroy, Reason}, _StateName,
|
||||
handle_event(destroy, StateName, StateData) ->
|
||||
?INFO_MSG("Destroyed MUC room ~s",
|
||||
[jid:encode(StateData#state.jid)]),
|
||||
handle_event({destroy, undefined}, StateName, StateData);
|
||||
handle_event({destroy, <<"">>}, StateName, StateData);
|
||||
handle_event({set_affiliations, Affiliations},
|
||||
StateName, StateData) ->
|
||||
{next_state, StateName,
|
||||
@@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) ->
|
||||
-spec route(pid(), stanza()) -> ok.
|
||||
route(Pid, Packet) ->
|
||||
#jid{lresource = Nick} = xmpp:get_to(Packet),
|
||||
gen_fsm:send_event(Pid, {route, Nick, Packet}).
|
||||
p1_fsm:send_event(Pid, {route, Nick, Packet}).
|
||||
|
||||
-spec process_groupchat_message(message(), state()) -> fsm_next().
|
||||
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
|
||||
@@ -1028,8 +1028,16 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet,
|
||||
end,
|
||||
NewState = add_user_presence_un(From, NewPacket, StateData),
|
||||
case (?DICT):find(Nick, StateData#state.nicks) of
|
||||
{ok, [_, _ | _]} -> ok;
|
||||
_ -> send_new_presence(From, NewState, StateData)
|
||||
{ok, [_, _ | _]} ->
|
||||
Aff = get_affiliation(From, StateData),
|
||||
Item = #muc_item{affiliation = Aff, role = none, jid = From},
|
||||
Pres = xmpp:set_subtag(
|
||||
Packet, #muc_user{items = [Item],
|
||||
status_codes = [110]}),
|
||||
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
|
||||
From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
|
||||
_ ->
|
||||
send_new_presence(From, NewState, StateData)
|
||||
end,
|
||||
Reason = xmpp:get_text(NewPacket#presence.status),
|
||||
remove_online_user(From, NewState, Reason);
|
||||
@@ -1243,7 +1251,12 @@ expulse_participant(Packet, From, StateData, Reason1) ->
|
||||
#presence{type = unavailable,
|
||||
status = xmpp:mk_text(Reason2)},
|
||||
StateData),
|
||||
send_new_presence(From, NewState, StateData),
|
||||
LJID = jid:tolower(From),
|
||||
{ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
|
||||
case (?DICT):find(Nick, StateData#state.nicks) of
|
||||
{ok, [_, _ | _]} -> ok;
|
||||
_ -> send_new_presence(From, NewState, StateData)
|
||||
end,
|
||||
remove_online_user(From, NewState).
|
||||
|
||||
-spec set_affiliation(jid(), affiliation(), state()) -> state().
|
||||
@@ -2697,8 +2710,8 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
Nick /= <<"">> ->
|
||||
case find_jids_by_nick(Nick, StateData) of
|
||||
[] ->
|
||||
ErrText = str:format(<<"Nickname ~s does not exist in the room">>,
|
||||
[Nick]),
|
||||
ErrText = {<<"Nickname ~s does not exist in the room">>,
|
||||
[Nick]},
|
||||
throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
|
||||
JIDList ->
|
||||
JIDList
|
||||
@@ -4004,6 +4017,7 @@ tab_add_online_user(JID, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
|
||||
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
|
||||
|
||||
-spec tab_remove_online_user(jid(), state()) -> any().
|
||||
@@ -4011,6 +4025,7 @@ tab_remove_online_user(JID, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
|
||||
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
|
||||
|
||||
-spec tab_count_user(jid(), state()) -> non_neg_integer().
|
||||
|
||||
@@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) ->
|
||||
build_limit_record(LimitOptsR, remote)}.
|
||||
|
||||
get_from_limitopts(LimitOpts, SenderT) ->
|
||||
{SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts),
|
||||
Result.
|
||||
case lists:keyfind(SenderT, 1, LimitOpts) of
|
||||
false -> [];
|
||||
{SenderT, Result} -> Result
|
||||
end.
|
||||
|
||||
build_remote_limit_record(LimitOpts, SenderT) ->
|
||||
build_limit_record(LimitOpts, SenderT).
|
||||
@@ -1120,10 +1122,10 @@ mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
|
||||
fun(L) ->
|
||||
lists:map(
|
||||
fun ({message, infinite}) -> infinite;
|
||||
({presence, infinite}) -> infinite;
|
||||
({message, I}) when is_integer(I) -> I;
|
||||
({presence, I}) when is_integer(I) -> I
|
||||
fun ({message, infinite} = O) -> O;
|
||||
({presence, infinite} = O) -> O;
|
||||
({message, I} = O) when is_integer(I) -> O;
|
||||
({presence, I} = O) when is_integer(I) -> O
|
||||
end, L)
|
||||
end;
|
||||
mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}].
|
||||
|
||||
+15
-12
@@ -706,22 +706,25 @@ user_queue_parse_query(LUser, LServer, Query) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case lists:keysearch(<<"delete">>, 1, Query) of
|
||||
{value, _} ->
|
||||
case lists:keyfind(<<"selected">>, 1, Query) of
|
||||
{_, Seq} ->
|
||||
case catch binary_to_integer(Seq) of
|
||||
I when is_integer(I), I>=0 ->
|
||||
Mod:remove_message(LUser, LServer, I),
|
||||
ok;
|
||||
_ ->
|
||||
nothing
|
||||
end;
|
||||
false ->
|
||||
nothing
|
||||
end;
|
||||
user_queue_parse_query(LUser, LServer, Query, Mod);
|
||||
_ ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
user_queue_parse_query(LUser, LServer, Query, Mod) ->
|
||||
case lists:keytake(<<"selected">>, 1, Query) of
|
||||
{value, {_, Seq}, Query2} ->
|
||||
case catch binary_to_integer(Seq) of
|
||||
I when is_integer(I), I>=0 ->
|
||||
Mod:remove_message(LUser, LServer, I);
|
||||
_ ->
|
||||
nothing
|
||||
end,
|
||||
user_queue_parse_query(LUser, LServer, Query2, Mod);
|
||||
false ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
jid:encode({User, Server, <<"">>}).
|
||||
|
||||
|
||||
+36
-22
@@ -410,9 +410,11 @@ decode_item(#privacy_item{order = Order,
|
||||
match_presence_out = MatchPresenceOut}
|
||||
end.
|
||||
|
||||
-spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{privacy_active_list := List}) ->
|
||||
State#{privacy_active_list => List}.
|
||||
State#{privacy_active_list => List};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_send_packet({#iq{type = Type,
|
||||
@@ -636,25 +638,29 @@ is_ptype_match(Item, PType) ->
|
||||
ljid(), none | both | from | to, [binary()]) -> boolean().
|
||||
is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
|
||||
true;
|
||||
is_type_match(Type, Value, JID, Subscription, Groups) ->
|
||||
case Type of
|
||||
jid ->
|
||||
case Value of
|
||||
{<<"">>, Server, <<"">>} ->
|
||||
case JID of
|
||||
{_, Server, _} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
{User, Server, <<"">>} ->
|
||||
case JID of
|
||||
{User, Server, _} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
_ -> Value == JID
|
||||
end;
|
||||
subscription -> Value == Subscription;
|
||||
group -> lists:member(Value, Groups)
|
||||
end.
|
||||
is_type_match(jid, Value, JID, _Subscription, _Groups) ->
|
||||
case Value of
|
||||
{<<"">>, Server, <<"">>} ->
|
||||
case JID of
|
||||
{_, Server, _} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
{User, Server, <<"">>} ->
|
||||
case JID of
|
||||
{User, Server, _} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
{<<"">>, Server, Resource} ->
|
||||
case JID of
|
||||
{_, Server, Resource} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
_ -> Value == JID
|
||||
end;
|
||||
is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
|
||||
Value == Subscription;
|
||||
is_type_match(group, Group, _JID, _Subscription, Groups) ->
|
||||
lists:member(Group, Groups).
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
remove_user(User, Server) ->
|
||||
@@ -840,4 +846,12 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun (I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[db_type, iqdisc, cache_life_time, cache_size,
|
||||
use_cache, cache_missed].
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ set_data(LUser, LServer, Data) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:set_data(LUser, LServer, Data) of
|
||||
ok ->
|
||||
delete_cache(Mod, LServer, LServer, Data);
|
||||
delete_cache(Mod, LUser, LServer, Data);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
+3
-1
@@ -112,6 +112,8 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(hostname) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(ip) ->
|
||||
fun (S) ->
|
||||
@@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) ->
|
||||
mod_opt_type(Opt) ->
|
||||
case mod_proxy65_stream:listen_opt_type(Opt) of
|
||||
Opts when is_list(Opts) ->
|
||||
[access, host, hostname, ip, name, port,
|
||||
[access, host, hosts, hostname, ip, name, port,
|
||||
max_connections, ram_db_type] ++ Opts;
|
||||
Fun ->
|
||||
Fun
|
||||
|
||||
+57
-42
@@ -43,7 +43,7 @@
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_proxy65_service).
|
||||
|
||||
-record(state, {myhost = <<"">> :: binary()}).
|
||||
-record(state, {myhosts = [] :: [binary()]}).
|
||||
|
||||
%%%------------------------
|
||||
%%% gen_server callbacks
|
||||
@@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
{ok, #state{myhost = MyHost}}.
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end, MyHosts),
|
||||
{ok, #state{myhosts = MyHosts}}.
|
||||
|
||||
terminate(_Reason, #state{myhost = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS).
|
||||
terminate(_Reason, #state{myhosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_handlers(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
handle_info({route, #iq{} = Packet}, State) ->
|
||||
ejabberd_router:process_iq(Packet),
|
||||
@@ -89,33 +92,29 @@ handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>),
|
||||
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>),
|
||||
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>),
|
||||
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>),
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, NewIQDisc);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State#state{myhost = NewHost}};
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_handlers(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
{noreply, State#state{myhosts = NewHosts}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -184,7 +183,7 @@ process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
|
||||
StreamHost = get_streamhost(Host, ServerHost),
|
||||
xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
|
||||
deny ->
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Denied by ACL">>, Lang))
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang))
|
||||
end;
|
||||
process_bytestreams(#iq{type = set, lang = Lang,
|
||||
sub_els = [#bytestreams{sid = SID}]} = IQ)
|
||||
@@ -233,7 +232,7 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end;
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
|
||||
end.
|
||||
|
||||
@@ -276,3 +275,19 @@ get_my_ip() ->
|
||||
|
||||
max_connections(ServerHost) ->
|
||||
gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity).
|
||||
|
||||
register_handlers(Host, IQDisc) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc).
|
||||
|
||||
unregister_handlers(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% gen_fsm callbacks.
|
||||
-export([init/1, handle_event/3, handle_sync_event/4,
|
||||
@@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) ->
|
||||
[Socket, Host, Opts]).
|
||||
|
||||
start_link(Socket, Host, Opts) ->
|
||||
gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
|
||||
p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
|
||||
|
||||
init([Socket, Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
@@ -106,9 +106,9 @@ socket_type() -> raw.
|
||||
stop(StreamPid) -> StreamPid ! stop.
|
||||
|
||||
activate({P1, J1}, {P2, J2}) ->
|
||||
case catch {gen_fsm:sync_send_all_state_event(P1,
|
||||
case catch {p1_fsm:sync_send_all_state_event(P1,
|
||||
get_socket),
|
||||
gen_fsm:sync_send_all_state_event(P2, get_socket)}
|
||||
p1_fsm:sync_send_all_state_event(P2, get_socket)}
|
||||
of
|
||||
{S1, S2} when is_port(S1), is_port(S2) ->
|
||||
P1 ! {activate, P2, S2, J1, J2},
|
||||
@@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData)
|
||||
when StateName /= wait_for_activation ->
|
||||
erlang:cancel_timer(StateData#state.timer),
|
||||
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
|
||||
gen_fsm:send_event(self(), Data),
|
||||
p1_fsm:send_event(self(), Data),
|
||||
{next_state, StateName, StateData#state{timer = TRef}};
|
||||
%% Activation message.
|
||||
handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
|
||||
|
||||
+288
-335
@@ -52,7 +52,7 @@
|
||||
%% exports for hooks
|
||||
-export([presence_probe/3, caps_add/3, caps_update/3,
|
||||
in_subscription/6, out_subscription/4,
|
||||
on_user_offline/3, remove_user/2,
|
||||
on_user_online/1, on_user_offline/2, remove_user/2,
|
||||
disco_local_identity/5, disco_local_features/5,
|
||||
disco_local_items/5, disco_sm_identity/5,
|
||||
disco_sm_features/5, disco_sm_items/5,
|
||||
@@ -89,11 +89,7 @@
|
||||
%% API and gen_server callbacks
|
||||
-export([start/2, stop/1, init/1,
|
||||
handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3, depends/2]).
|
||||
|
||||
-export([send_loop/1, mod_opt_type/1]).
|
||||
|
||||
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
|
||||
terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@@ -189,7 +185,7 @@
|
||||
-record(state,
|
||||
{
|
||||
server_host,
|
||||
host,
|
||||
hosts,
|
||||
access,
|
||||
pep_mapping = [],
|
||||
ignore_pep_from_offline = true,
|
||||
@@ -205,7 +201,7 @@
|
||||
-type(state() ::
|
||||
#state{
|
||||
server_host :: binary(),
|
||||
host :: mod_pubsub:hostPubsub(),
|
||||
hosts :: [mod_pubsub:hostPubsub()],
|
||||
access :: atom(),
|
||||
pep_mapping :: [{binary(), binary()}],
|
||||
ignore_pep_from_offline :: boolean(),
|
||||
@@ -243,43 +239,65 @@ stop(Host) ->
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access_createnode, Opts, all),
|
||||
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
|
||||
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
|
||||
MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts),
|
||||
case gen_mod:db_type(ServerHost, ?MODULE) of
|
||||
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
|
||||
_ -> ok
|
||||
end,
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
DefaultModule = plugin(Host, hd(Plugins)),
|
||||
BaseOptions = DefaultModule:options(),
|
||||
DefaultNodeCfg = filter_node_options(
|
||||
gen_mod:get_opt(default_node_config, Opts, []),
|
||||
BaseOptions),
|
||||
ejabberd_mnesia:create(?MODULE, pubsub_last_item,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_last_item)}]),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
T = gen_mod:get_module_proc(H, config),
|
||||
ets:new(T, [set, named_table]),
|
||||
ets:insert(T, {nodetree, NodeTree}),
|
||||
ets:insert(T, {plugins, Plugins}),
|
||||
ets:insert(T, {last_item_cache, LastItemCache}),
|
||||
ets:insert(T, {max_items_node, MaxItemsNode}),
|
||||
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
|
||||
ets:insert(T, {default_node_config, DefaultNodeCfg}),
|
||||
ets:insert(T, {pep_mapping, PepMapping}),
|
||||
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(T, {host, Host}),
|
||||
ets:insert(T, {access, Access})
|
||||
end, [Host, ServerHost]),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_last_item)}]),
|
||||
AllPlugins =
|
||||
lists:flatmap(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
case gen_mod:db_type(ServerHost, ?MODULE) of
|
||||
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
|
||||
_ -> ok
|
||||
end,
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
DefaultModule = plugin(Host, hd(Plugins)),
|
||||
DefaultNodeCfg = merge_config(
|
||||
gen_mod:get_opt(default_node_config, Opts, []),
|
||||
DefaultModule:options()),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
T = gen_mod:get_module_proc(H, config),
|
||||
try
|
||||
ets:new(T, [set, named_table]),
|
||||
ets:insert(T, {nodetree, NodeTree}),
|
||||
ets:insert(T, {plugins, Plugins}),
|
||||
ets:insert(T, {last_item_cache, LastItemCache}),
|
||||
ets:insert(T, {max_items_node, MaxItemsNode}),
|
||||
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
|
||||
ets:insert(T, {default_node_config, DefaultNodeCfg}),
|
||||
ets:insert(T, {pep_mapping, PepMapping}),
|
||||
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(T, {host, Host}),
|
||||
ets:insert(T, {access, Access})
|
||||
catch error:badarg when H == ServerHost ->
|
||||
ok
|
||||
end
|
||||
end, [Host, ServerHost]),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
|
||||
?MODULE, process_pubsub, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
|
||||
?MODULE, process_pubsub_owner, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
|
||||
?MODULE, process_commands, IQDisc),
|
||||
Plugins
|
||||
end, Hosts),
|
||||
ejabberd_hooks:add(c2s_session_opened, ServerHost,
|
||||
?MODULE, on_user_online, 75),
|
||||
ejabberd_hooks:add(c2s_terminated, ServerHost,
|
||||
?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, ServerHost,
|
||||
?MODULE, disco_local_identity, 75),
|
||||
@@ -297,19 +315,7 @@ init([ServerHost, Opts]) ->
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(c2s_handle_info, ServerHost,
|
||||
?MODULE, c2s_handle_info, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
|
||||
?MODULE, process_pubsub, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
|
||||
?MODULE, process_pubsub_owner, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
|
||||
?MODULE, process_commands, IQDisc),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
case lists:member(?PEPNODE, AllPlugins) of
|
||||
true ->
|
||||
ejabberd_hooks:add(caps_add, ServerHost,
|
||||
?MODULE, caps_add, 80),
|
||||
@@ -328,35 +334,16 @@ init([ServerHost, Opts]) ->
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
{_, State} = init_send_loop(ServerHost),
|
||||
{ok, State}.
|
||||
|
||||
init_send_loop(ServerHost) ->
|
||||
NodeTree = config(ServerHost, nodetree),
|
||||
Plugins = config(ServerHost, plugins),
|
||||
LastItemCache = config(ServerHost, last_item_cache),
|
||||
MaxItemsNode = config(ServerHost, max_items_node),
|
||||
PepMapping = config(ServerHost, pep_mapping),
|
||||
PepOffline = config(ServerHost, ignore_pep_from_offline),
|
||||
Host = config(ServerHost, host),
|
||||
Access = config(ServerHost, access),
|
||||
DBType = gen_mod:db_type(ServerHost, ?MODULE),
|
||||
State = #state{host = Host, server_host = ServerHost,
|
||||
access = Access, pep_mapping = PepMapping,
|
||||
ignore_pep_from_offline = PepOffline,
|
||||
last_item_cache = LastItemCache,
|
||||
max_items_node = MaxItemsNode, nodetree = NodeTree,
|
||||
plugins = Plugins, db_type = DBType},
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME),
|
||||
Pid = case whereis(Proc) of
|
||||
undefined ->
|
||||
SendLoop = spawn(?MODULE, send_loop, [State]),
|
||||
register(Proc, SendLoop),
|
||||
SendLoop;
|
||||
Loop ->
|
||||
Loop
|
||||
end,
|
||||
{Pid, State}.
|
||||
{ok, #state{hosts = Hosts, server_host = ServerHost,
|
||||
access = Access, pep_mapping = PepMapping,
|
||||
ignore_pep_from_offline = PepOffline,
|
||||
last_item_cache = LastItemCache,
|
||||
max_items_node = MaxItemsNode, nodetree = NodeTree,
|
||||
plugins = Plugins, db_type = DBType}}.
|
||||
|
||||
depends(ServerHost, Opts) ->
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
|
||||
@@ -406,94 +393,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
|
||||
TreePlugin:terminate(Host, ServerHost),
|
||||
ok.
|
||||
|
||||
get_subscribed(User, Server) ->
|
||||
Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
|
||||
lists:filtermap(
|
||||
fun(#roster{jid = LJID, subscription = Sub})
|
||||
when Sub == both orelse Sub == from ->
|
||||
{true, LJID};
|
||||
(_) ->
|
||||
false
|
||||
end, Items).
|
||||
|
||||
send_loop(State) ->
|
||||
receive
|
||||
{presence, JID, _Pid} ->
|
||||
Host = State#state.host,
|
||||
ServerHost = State#state.server_host,
|
||||
DBType = State#state.db_type,
|
||||
LJID = jid:tolower(JID),
|
||||
BJID = jid:remove_resource(LJID),
|
||||
lists:foreach(
|
||||
fun(PType) ->
|
||||
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
|
||||
lists:foreach(
|
||||
fun({NodeRec, _, _, SubJID}) ->
|
||||
{_, Node} = NodeRec#pubsub_node.nodeid,
|
||||
Nidx = NodeRec#pubsub_node.id,
|
||||
Options = NodeRec#pubsub_node.options,
|
||||
[send_items(Host, Node, Nidx, PType, Options, SubJID, last)
|
||||
|| NodeRec#pubsub_node.type == PType]
|
||||
end,
|
||||
lists:usort(Subs))
|
||||
end,
|
||||
State#state.plugins),
|
||||
if not State#state.ignore_pep_from_offline ->
|
||||
{User, Server, Resource} = LJID,
|
||||
Contacts = get_subscribed(User, Server),
|
||||
lists:foreach(
|
||||
fun({U, S, R}) when S == ServerHost ->
|
||||
case user_resources(U, S) of
|
||||
[] -> %% offline
|
||||
PeerJID = jid:make(U, S, R),
|
||||
self() ! {presence, User, Server, [Resource], PeerJID};
|
||||
_ -> %% online
|
||||
%% this is already handled by presence probe
|
||||
ok
|
||||
end;
|
||||
(_) ->
|
||||
%% we can not do anything in any cases
|
||||
ok
|
||||
end, Contacts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
send_loop(State);
|
||||
{presence, User, Server, Resources, JID} ->
|
||||
spawn(fun() ->
|
||||
Host = State#state.host,
|
||||
Owner = jid:remove_resource(jid:tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
|
||||
case match_option(Options, send_last_published_item, on_sub_and_presence) of
|
||||
true ->
|
||||
lists:foreach(fun(Resource) ->
|
||||
LJID = {User, Server, Resource},
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, LJID, last);
|
||||
true -> ok
|
||||
end
|
||||
end,
|
||||
Resources);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
tree_action(Host, get_nodes, [Owner, JID]))
|
||||
end),
|
||||
send_loop(State);
|
||||
stop ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% -------
|
||||
%% disco hooks handling functions
|
||||
%%
|
||||
@@ -546,13 +445,11 @@ disco_identity(Host, Node, From) ->
|
||||
case get_allowed_items_call(Host, Nidx, From, Type,
|
||||
Options, Owners) of
|
||||
{result, _} ->
|
||||
{result, [#identity{category = <<"pubsub">>,
|
||||
type = <<"pep">>},
|
||||
#identity{category = <<"pubsub">>,
|
||||
type = <<"leaf">>,
|
||||
{result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
|
||||
#identity{category = <<"pubsub">>, type = <<"leaf">>,
|
||||
name = case get_option(Options, title) of
|
||||
false -> <<>>;
|
||||
[Title] -> Title
|
||||
Title -> Title
|
||||
end}]};
|
||||
_ ->
|
||||
{result, []}
|
||||
@@ -586,8 +483,7 @@ disco_features(Host, Node, From) ->
|
||||
Type, Options, Owners) of
|
||||
{result, _} ->
|
||||
{result,
|
||||
[?NS_PUBSUB |
|
||||
[feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
|
||||
[?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
|
||||
_ ->
|
||||
{result, []}
|
||||
end
|
||||
@@ -620,7 +516,7 @@ disco_items(Host, <<>>, From) ->
|
||||
jid = jid:make(Host),
|
||||
name = case get_option(Options, title) of
|
||||
false -> <<>>;
|
||||
[Title] -> Title
|
||||
Title -> Title
|
||||
end} | Acc];
|
||||
_ ->
|
||||
Acc
|
||||
@@ -655,12 +551,12 @@ disco_items(Host, Node, From) ->
|
||||
end.
|
||||
|
||||
%% -------
|
||||
%% presence hooks handling functions
|
||||
%% presence and session hooks handling functions
|
||||
%%
|
||||
|
||||
-spec caps_add(jid(), jid(), [binary()]) -> ok.
|
||||
caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
|
||||
when Host =/= S ->
|
||||
caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features)
|
||||
when S1 =/= S2 ->
|
||||
%% When a remote contact goes online while the local user is offline, the
|
||||
%% remote contact won't receive last items from the local user even if
|
||||
%% ignore_pep_from_offline is set to false. To work around this issue a bit,
|
||||
@@ -670,36 +566,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID
|
||||
%% contact becomes available; the former is also executed when the local
|
||||
%% user goes online (because that triggers the contact to send a presence
|
||||
%% packet with CAPS).
|
||||
presence(Host, {presence, U, S, [R], JID});
|
||||
send_last_pep(To, From);
|
||||
caps_add(_From, _To, _Feature) ->
|
||||
ok.
|
||||
|
||||
-spec caps_update(jid(), jid(), [binary()]) -> ok.
|
||||
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
|
||||
presence(Host, {presence, U, S, [R], JID}).
|
||||
caps_update(From, To, _Features) ->
|
||||
send_last_pep(To, From).
|
||||
|
||||
-spec presence_probe(jid(), jid(), pid()) -> ok.
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
|
||||
presence(S, {presence, JID, Pid}),
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
|
||||
%% ignore presence_probe from my other ressources
|
||||
%% to not get duplicated last items
|
||||
ok;
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(_Host, _JID, _Pid) ->
|
||||
%% ignore presence_probe from remote contacts,
|
||||
%% those are handled via caps_add
|
||||
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
|
||||
send_last_pep(To, From);
|
||||
presence_probe(_From, _To, _Pid) ->
|
||||
%% ignore presence_probe from remote contacts, those are handled via caps_add
|
||||
ok.
|
||||
|
||||
presence(ServerHost, Presence) ->
|
||||
{SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
|
||||
undefined -> init_send_loop(ServerHost);
|
||||
Pid -> {Pid, undefined}
|
||||
end,
|
||||
SendLoop ! Presence,
|
||||
ok.
|
||||
-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state().
|
||||
on_user_online(C2SState) ->
|
||||
JID = maps:get(jid, C2SState),
|
||||
send_last_items(JID),
|
||||
C2SState.
|
||||
|
||||
-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state().
|
||||
on_user_offline(#{jid := JID} = C2SState, _Reason) ->
|
||||
purge_offline(jid:tolower(JID)),
|
||||
C2SState;
|
||||
on_user_offline(C2SState, _Reason) ->
|
||||
C2SState.
|
||||
|
||||
%% -------
|
||||
%% subscription hooks handling functions
|
||||
@@ -708,14 +604,8 @@ presence(ServerHost, Presence) ->
|
||||
-spec out_subscription(
|
||||
binary(), binary(), jid(),
|
||||
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
|
||||
out_subscription(User, Server, JID, subscribed) ->
|
||||
Owner = jid:make(User, Server),
|
||||
{PUser, PServer, PResource} = jid:tolower(JID),
|
||||
PResources = case PResource of
|
||||
<<>> -> user_resources(PUser, PServer);
|
||||
_ -> [PResource]
|
||||
end,
|
||||
presence(Server, {presence, PUser, PServer, PResources, Owner}),
|
||||
out_subscription(User, Server, To, subscribed) ->
|
||||
send_last_pep(jid:make(User, Server), To),
|
||||
true;
|
||||
out_subscription(_, _, _, _) ->
|
||||
true.
|
||||
@@ -864,7 +754,7 @@ handle_info(_Info, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
terminate(_Reason,
|
||||
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
|
||||
#state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:delete(caps_add, ServerHost,
|
||||
@@ -884,7 +774,9 @@ terminate(_Reason,
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost,
|
||||
ejabberd_hooks:delete(c2s_session_opened, ServerHost,
|
||||
?MODULE, on_user_online, 75),
|
||||
ejabberd_hooks:delete(c2s_terminated, ServerHost,
|
||||
?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:delete(disco_local_identity, ServerHost,
|
||||
?MODULE, disco_local_identity, 75),
|
||||
@@ -902,20 +794,17 @@ terminate(_Reason,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
|
||||
?MODULE, c2s_handle_info, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
|
||||
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
|
||||
undefined ->
|
||||
?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]);
|
||||
Pid ->
|
||||
Pid ! stop
|
||||
end,
|
||||
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
|
||||
ejabberd_router:unregister_route(Host).
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
|
||||
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
|
||||
ejabberd_router:unregister_route(Host)
|
||||
end, Hosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
@@ -1723,7 +1612,7 @@ delete_node(Host, Node, Owner) ->
|
||||
%%<li>The node does not support subscriptions.</li>
|
||||
%%<li>The node does not exist.</li>
|
||||
%%</ul>
|
||||
-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
|
||||
-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
|
||||
{result, pubsub()} | {error, stanza_error()}.
|
||||
subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
SubModule = subscription_plugin(Host),
|
||||
@@ -1795,7 +1684,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
Nidx = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
|
||||
send_items(Host, Node, Nidx, Type, Options, Subscriber, 1),
|
||||
ServerHost = serverhost(Host),
|
||||
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
|
||||
[ServerHost, Host, Node, Subscriber, SubId]),
|
||||
@@ -1906,8 +1795,6 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
|
||||
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true ->
|
||||
@@ -1916,6 +1803,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
|
||||
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
@@ -2067,20 +1956,14 @@ purge_node(Host, Node, Owner) ->
|
||||
%% @doc <p>Return the items of a given node.</p>
|
||||
%% <p>The number of items to return is limited by MaxItems.</p>
|
||||
%% <p>The permission are not checked in this function.</p>
|
||||
%% @todo We probably need to check that the user doing the query has the right
|
||||
%% to read the items.
|
||||
-spec get_items(host(), binary(), jid(), binary(),
|
||||
binary(), [binary()], undefined | rsm_set()) ->
|
||||
{result, pubsub()} | {error, stanza_error()}.
|
||||
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
|
||||
MaxItems = if SMaxItems == undefined ->
|
||||
case get_max_items_node(Host) of
|
||||
undefined -> ?MAXITEMS;
|
||||
Max -> Max
|
||||
end;
|
||||
true ->
|
||||
SMaxItems
|
||||
end,
|
||||
get_items(Host, Node, From, SubId, MaxItems, ItemIds, undefined)
|
||||
when MaxItems =/= undefined ->
|
||||
get_items(Host, Node, From, SubId, MaxItems, ItemIds,
|
||||
#rsm_set{max = MaxItems, before = <<>>});
|
||||
get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
|
||||
Action =
|
||||
fun(#pubsub_node{options = Options, type = Type,
|
||||
id = Nidx, owners = O}) ->
|
||||
@@ -2099,8 +1982,14 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
|
||||
Owners = node_owners_call(Host, Type, Nidx, O),
|
||||
{PS, RG} = get_presence_and_roster_permissions(
|
||||
Host, From, Owners, AccessModel, AllowedGroups),
|
||||
node_call(Host, Type, get_items,
|
||||
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
|
||||
case ItemIds of
|
||||
[ItemId] ->
|
||||
node_call(Host, Type, get_item,
|
||||
[Nidx, ItemId, From, AccessModel, PS, RG, undefined]);
|
||||
_ ->
|
||||
node_call(Host, Type, get_items,
|
||||
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
|
||||
end
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
@@ -2116,8 +2005,12 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
|
||||
end,
|
||||
{result,
|
||||
#pubsub{items = #ps_items{node = Node,
|
||||
items = itemsEls(lists:sublist(SendItems, MaxItems))},
|
||||
items = itemsEls(SendItems)},
|
||||
rsm = RsmOut}};
|
||||
{result, {_, Item}} ->
|
||||
{result,
|
||||
#pubsub{items = #ps_items{node = Node,
|
||||
items = itemsEls([Item])}}};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@@ -2151,38 +2044,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
|
||||
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
|
||||
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
|
||||
|
||||
get_last_items(Host, Type, Nidx, LJID, Count) ->
|
||||
get_last_items(Host, Type, Nidx, LJID, 1) ->
|
||||
case get_cached_item(Host, Nidx) of
|
||||
undefined ->
|
||||
case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
|
||||
{result, Items} -> Items;
|
||||
_ -> []
|
||||
end;
|
||||
LastItem ->
|
||||
[LastItem]
|
||||
end;
|
||||
get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 ->
|
||||
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
|
||||
{result, Items} -> Items;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
|
||||
case get_last_items(Host, Type, Nidx, LJID, 1) of
|
||||
[LastItem] ->
|
||||
Stanza = items_event_stanza(Node, Options, [LastItem]),
|
||||
dispatch_items(Host, LJID, Node, Stanza);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
|
||||
Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)),
|
||||
dispatch_items(Host, LJID, Node, Stanza);
|
||||
send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
|
||||
Stanza = items_event_stanza(Node, Options, []),
|
||||
dispatch_items(Host, LJID, Node, Stanza).
|
||||
|
||||
dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) ->
|
||||
SenderResource = user_resource(FromU, FromS, FromR),
|
||||
ejabberd_sm:route(jid:make(FromU, FromS, SenderResource),
|
||||
{send_filtered, {pep_message, <<((Node))/binary, "+notify">>},
|
||||
jid:make(FromU, FromS), jid:make(To),
|
||||
Stanza});
|
||||
dispatch_items(From, To, _Node, Stanza) ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))).
|
||||
get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
|
||||
[].
|
||||
|
||||
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
|
||||
-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
|
||||
@@ -2529,14 +2407,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
|
||||
{result, Subs} = node_action(Host, PType,
|
||||
get_entity_subscriptions_for_send_last,
|
||||
[Host, JID]),
|
||||
[{Node, Sub, SubId, SubJID}
|
||||
[{Node, SubId, SubJID}
|
||||
|| {Node, Sub, SubId, SubJID} <- Subs,
|
||||
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)];
|
||||
% sql version already filter result by on_sub_and_presence
|
||||
get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
|
||||
{result, Subs} = node_action(Host, PType,
|
||||
get_entity_subscriptions,
|
||||
[Host, JID]),
|
||||
[{Node, Sub, SubId, SubJID}
|
||||
[{Node, SubId, SubJID}
|
||||
|| {Node, Sub, SubId, SubJID} <- Subs,
|
||||
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
|
||||
match_option(Node, send_last_published_item, on_sub_and_presence)].
|
||||
@@ -2925,8 +2804,9 @@ get_options_for_subs(Host, Nidx, Subs, true) ->
|
||||
broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
NotificationType = get_option(NodeOptions, notification_type, headline),
|
||||
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
|
||||
From = service_jid(Host),
|
||||
Stanza = add_message_type(BaseStanza, NotificationType),
|
||||
Stanza = add_message_type(
|
||||
xmpp:set_from(BaseStanza, service_jid(Host)),
|
||||
NotificationType),
|
||||
%% Handles explicit subscriptions
|
||||
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
|
||||
lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
|
||||
@@ -2949,7 +2829,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
|
||||
end,
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(StanzaToSend, From, jid:make(To)))
|
||||
xmpp:set_to(StanzaToSend, jid:make(To)))
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID).
|
||||
|
||||
@@ -2958,55 +2838,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
|
||||
%% Handles implicit presence subscriptions
|
||||
SenderResource = user_resource(LUser, LServer, LResource),
|
||||
NotificationType = get_option(NodeOptions, notification_type, headline),
|
||||
Stanza = add_message_type(BaseStanza, NotificationType),
|
||||
Stanza = add_message_type(
|
||||
xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
|
||||
NotificationType),
|
||||
%% set the from address on the notification to the bare JID of the account owner
|
||||
%% Also, add "replyto" if entity has presence subscription to the account owner
|
||||
%% See XEP-0163 1.1 section 4.3.1
|
||||
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
|
||||
{pep_message, <<((Node))/binary, "+notify">>,
|
||||
jid:make(LUser, LServer),
|
||||
add_extended_headers(
|
||||
Stanza, extended_headers([Publisher]))});
|
||||
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
|
||||
|
||||
-spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
|
||||
c2s_handle_info(#{server := Server} = C2SState,
|
||||
{pep_message, Feature, From, Packet}) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
lists:foreach(
|
||||
fun({USR, Caps}) ->
|
||||
Features = mod_caps:get_features(LServer, Caps),
|
||||
case lists:member(Feature, Features) of
|
||||
true ->
|
||||
To = jid:make(USR),
|
||||
NewPacket = xmpp:set_from_to(Packet, From, To),
|
||||
ejabberd_router:route(NewPacket);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, mod_caps:list_features(C2SState)),
|
||||
c2s_handle_info(#{lserver := LServer} = C2SState,
|
||||
{pep_message, Feature, Packet}) ->
|
||||
[maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
|
||||
|| {USR, Caps} <- mod_caps:list_features(C2SState)],
|
||||
{stop, C2SState};
|
||||
c2s_handle_info(#{server := Server} = C2SState,
|
||||
{send_filtered, {pep_message, Feature}, From, To, Packet}) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case mod_caps:get_user_caps(To, C2SState) of
|
||||
{ok, Caps} ->
|
||||
Features = mod_caps:get_features(LServer, Caps),
|
||||
case lists:member(Feature, Features) of
|
||||
true ->
|
||||
NewPacket = xmpp:set_from_to(Packet, From, To),
|
||||
ejabberd_router:route(NewPacket);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
error ->
|
||||
ok
|
||||
c2s_handle_info(#{lserver := LServer} = C2SState,
|
||||
{pep_message, Feature, Packet, USR}) ->
|
||||
case mod_caps:get_user_caps(USR, C2SState) of
|
||||
{ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
|
||||
error -> ok
|
||||
end,
|
||||
{stop, C2SState};
|
||||
c2s_handle_info(C2SState, _) ->
|
||||
C2SState.
|
||||
|
||||
send_items(Host, Node, Nidx, Type, Options, LJID, Number) ->
|
||||
send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number).
|
||||
send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ->
|
||||
case get_last_items(Host, Type, Nidx, SubLJID, Number) of
|
||||
[] ->
|
||||
ok;
|
||||
Items ->
|
||||
Stanza = items_event_stanza(Node, Options, Items),
|
||||
send_stanza(Publisher, ToLJID, Node, Stanza)
|
||||
end.
|
||||
|
||||
send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
|
||||
Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
|
||||
USRs = case USR of
|
||||
{PUser, PServer, <<>>} ->
|
||||
[{PUser, PServer, PRessource}
|
||||
|| PRessource <- user_resources(PUser, PServer)];
|
||||
_ ->
|
||||
[USR]
|
||||
end,
|
||||
[ejabberd_sm:route(jid:make(Publisher),
|
||||
{pep_message, <<((Node))/binary, "+notify">>,
|
||||
add_extended_headers(
|
||||
Stanza, extended_headers([Publisher])),
|
||||
To}) || To <- USRs];
|
||||
send_stanza(Host, USR, _Node, Stanza) ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))).
|
||||
|
||||
maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) ->
|
||||
Features = mod_caps:get_features(LServer, Caps),
|
||||
case lists:member(Feature, Features) of
|
||||
true ->
|
||||
ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR)));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
send_last_items(JID) ->
|
||||
ServerHost = JID#jid.lserver,
|
||||
Host = host(ServerHost),
|
||||
DBType = config(ServerHost, db_type),
|
||||
LJID = jid:tolower(JID),
|
||||
BJID = jid:remove_resource(LJID),
|
||||
lists:foreach(
|
||||
fun(PType) ->
|
||||
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
|
||||
lists:foreach(
|
||||
fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx,
|
||||
options = Options}, _, SubJID})
|
||||
when Type == PType->
|
||||
send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1);
|
||||
(_) ->
|
||||
ok
|
||||
end,
|
||||
lists:usort(Subs))
|
||||
end, config(ServerHost, plugins)).
|
||||
% pep_from_offline hack can not work anymore, as sender c2s does not
|
||||
% exists when sender is offline, so we can't get match receiver caps
|
||||
% does it make sens to send PEP from an offline contact anyway ?
|
||||
% case config(ServerHost, ignore_pep_from_offline) of
|
||||
% false ->
|
||||
% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [],
|
||||
% [{JID#jid.luser, ServerHost}]),
|
||||
% lists:foreach(
|
||||
% fun(#roster{jid = {U, S, R}, subscription = Sub})
|
||||
% when Sub == both orelse Sub == from,
|
||||
% S == ServerHost ->
|
||||
% case user_resources(U, S) of
|
||||
% [] -> send_last_pep(jid:make(U, S, R), JID);
|
||||
% _ -> ok %% this is already handled by presence probe
|
||||
% end;
|
||||
% (_) ->
|
||||
% ok %% we can not do anything in any cases
|
||||
% end, Roster);
|
||||
% true ->
|
||||
% ok
|
||||
% end.
|
||||
send_last_pep(From, To) ->
|
||||
ServerHost = From#jid.lserver,
|
||||
Host = host(ServerHost),
|
||||
Publisher = jid:tolower(From),
|
||||
Owner = jid:remove_resource(Publisher),
|
||||
lists:foreach(
|
||||
fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
|
||||
case match_option(Options, send_last_published_item, on_sub_and_presence) of
|
||||
true ->
|
||||
LJID = jid:tolower(To),
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1);
|
||||
true -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
tree_action(Host, get_nodes, [Owner, From])).
|
||||
|
||||
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
|
||||
NodeName = case Node#pubsub_node.nodeid of
|
||||
@@ -3129,10 +3096,10 @@ get_option(Options, Var, Def) ->
|
||||
|
||||
-spec node_options(host(), binary()) -> [{atom(), any()}].
|
||||
node_options(Host, Type) ->
|
||||
case config(Host, default_node_config) of
|
||||
undefined -> node_plugin_options(Host, Type);
|
||||
[] -> node_plugin_options(Host, Type);
|
||||
Config -> Config
|
||||
DefaultOpts = node_plugin_options(Host, Type),
|
||||
case config(Host, plugins) of
|
||||
[Type|_] -> config(Host, default_node_config, DefaultOpts);
|
||||
_ -> DefaultOpts
|
||||
end.
|
||||
|
||||
-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
|
||||
@@ -3146,13 +3113,6 @@ node_plugin_options(Host, Type) ->
|
||||
Result
|
||||
end.
|
||||
|
||||
-spec filter_node_options([{atom(), any()}], [{atom(), any()}]) -> [{atom(), any()}].
|
||||
filter_node_options(Options, BaseOptions) ->
|
||||
lists:foldl(fun({Key, Val}, Acc) ->
|
||||
DefaultValue = proplists:get_value(Key, Options, Val),
|
||||
[{Key, DefaultValue}|Acc]
|
||||
end, [], BaseOptions).
|
||||
|
||||
-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
|
||||
node_owners_action(Host, Type, Nidx, []) ->
|
||||
case node_action(Host, Type, get_node_affiliations, [Nidx]) of
|
||||
@@ -3179,9 +3139,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) ->
|
||||
%% @doc <p>Return the maximum number of items for a given node.</p>
|
||||
%% <p>Unlimited means that there is no limit in the number of items that can
|
||||
%% be stored.</p>
|
||||
%% @todo In practice, the current data structure means that we cannot manage
|
||||
%% millions of items on a given node. This should be addressed in a new
|
||||
%% version.
|
||||
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
|
||||
max_items(Host, Options) ->
|
||||
case get_option(Options, persist_items) of
|
||||
@@ -3240,8 +3197,8 @@ set_configure(Host, Node, From, Config, Lang) ->
|
||||
case tree_call(Host,
|
||||
set_node,
|
||||
[N#pubsub_node{options = NewOpts}]) of
|
||||
{result, Nidx} -> {result, ok};
|
||||
ok -> {result, ok};
|
||||
{result, Nidx} -> {result, NewOpts};
|
||||
ok -> {result, NewOpts};
|
||||
Err -> Err
|
||||
end;
|
||||
_ ->
|
||||
@@ -3250,10 +3207,9 @@ set_configure(Host, Node, From, Config, Lang) ->
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
{result, {TNode, ok}} ->
|
||||
{result, {TNode, Options}} ->
|
||||
Nidx = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
|
||||
{result, undefined};
|
||||
Other ->
|
||||
@@ -3261,11 +3217,11 @@ set_configure(Host, Node, From, Config, Lang) ->
|
||||
end.
|
||||
|
||||
-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()].
|
||||
merge_config(Config1, Config2) ->
|
||||
merge_config(CustomConfig, DefaultConfig) ->
|
||||
lists:foldl(
|
||||
fun({Opt, Val}, Acc) ->
|
||||
lists:keystore(Opt, 1, Acc, {Opt, Val})
|
||||
end, Config2, Config1).
|
||||
end, DefaultConfig, CustomConfig).
|
||||
|
||||
-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
|
||||
pubsub_node_config:result() |
|
||||
@@ -3785,14 +3741,6 @@ subid_shim(SubIds) ->
|
||||
extended_headers(Jids) ->
|
||||
[#address{type = replyto, jid = Jid} || Jid <- Jids].
|
||||
|
||||
-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
|
||||
on_user_offline(_, JID, _) ->
|
||||
{User, Server, Resource} = jid:tolower(JID),
|
||||
case user_resources(User, Server) of
|
||||
[] -> purge_offline({User, Server, Resource});
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
-spec purge_offline(ljid()) -> ok.
|
||||
purge_offline(LJID) ->
|
||||
Host = host(element(2, LJID)),
|
||||
@@ -3833,7 +3781,7 @@ purge_offline(LJID) ->
|
||||
end
|
||||
end, lists:usort(lists:flatten(Affs)));
|
||||
{Error, _} ->
|
||||
?DEBUG("on_user_offline ~p", [Error])
|
||||
?ERROR_MSG("can not purge offline: ~p", [Error])
|
||||
end.
|
||||
|
||||
-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
|
||||
@@ -3845,13 +3793,13 @@ purge_offline(Host, LJID, Node) ->
|
||||
{result, {[], _}} ->
|
||||
ok;
|
||||
{result, {Items, _}} ->
|
||||
{User, Server, _} = LJID,
|
||||
{User, Server, Resource} = LJID,
|
||||
PublishModel = get_option(Options, publish_model),
|
||||
ForceNotify = get_option(Options, notify_retract),
|
||||
{_, NodeId} = Node#pubsub_node.nodeid,
|
||||
lists:foreach(fun
|
||||
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}})
|
||||
when (U == User) and (S == Server) ->
|
||||
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
|
||||
when (U == User) and (S == Server) and (R == Resource) ->
|
||||
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
|
||||
{result, {_, broadcast}} ->
|
||||
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
|
||||
@@ -3871,9 +3819,14 @@ purge_offline(Host, LJID, Node) ->
|
||||
Error
|
||||
end.
|
||||
|
||||
export(Server) ->
|
||||
pubsub_db_sql:export(Server).
|
||||
|
||||
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(ignore_pep_from_offline) ->
|
||||
fun (A) when is_boolean(A) -> A end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
@@ -3892,7 +3845,7 @@ mod_opt_type(pep_mapping) ->
|
||||
mod_opt_type(plugins) ->
|
||||
fun (A) when is_list(A) -> A end;
|
||||
mod_opt_type(_) ->
|
||||
[access_createnode, db_type, host,
|
||||
[access_createnode, db_type, host, hosts,
|
||||
ignore_pep_from_offline, iqdisc, last_item_cache,
|
||||
max_items_node, nodetree, pep_mapping, plugins,
|
||||
max_subscriptions_node, default_node_config].
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Push Notifications (XEP-0357)
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2017 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(mod_push).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
-protocol({xep, 357, '0.2'}).
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
|
||||
c2s_handle_cast/2, c2s_stanza/3, mam_message/6, offline_message/1,
|
||||
remove_user/2]).
|
||||
|
||||
%% gen_iq_handler callback.
|
||||
-export([process_iq/1]).
|
||||
|
||||
%% ejabberd command.
|
||||
-export([get_commands_spec/0, delete_old_sessions/1]).
|
||||
|
||||
%% API (used by mod_push_keepalive).
|
||||
-export([notify/1, notify/3, notify/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(PUSH_CACHE, push_cache).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
-type timestamp() :: erlang:timestamp().
|
||||
-type push_session() :: {timestamp(), ljid(), binary(), xdata()}.
|
||||
|
||||
-callback init(binary(), gen_mod:opts())
|
||||
-> any().
|
||||
-callback store_session(binary(), binary(), timestamp(), jid(), binary(),
|
||||
xdata())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_session(binary(), binary(), jid(), binary())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_session(binary(), binary(), timestamp())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_sessions(binary(), binary(), jid())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback lookup_sessions(binary(), binary())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback lookup_sessions(binary())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback delete_session(binary(), binary(), timestamp())
|
||||
-> ok | error.
|
||||
-callback delete_old_sessions(binary() | global, erlang:timestamp())
|
||||
-> any().
|
||||
-callback use_cache(binary())
|
||||
-> boolean().
|
||||
-callback cache_nodes(binary())
|
||||
-> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
register_iq_handlers(Host, IQDisc),
|
||||
register_hooks(Host),
|
||||
ejabberd_commands:register_commands(get_commands_spec()).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
unregister_hooks(Host),
|
||||
unregister_iq_handlers(Host),
|
||||
ejabberd_commands:unregister_commands(get_commands_spec()).
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(Host, NewOpts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts,
|
||||
gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
register_iq_handlers(Host, IQDisc);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(db_type) ->
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(iqdisc) ->
|
||||
fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) ->
|
||||
[db_type, cache_life_time, cache_size, use_cache, cache_missed, iqdisc].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd command callback.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_commands_spec() -> [ejabberd_commands()].
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = delete_old_push_sessions, tags = [purge],
|
||||
desc = "Remove push sessions older than DAYS",
|
||||
module = ?MODULE, function = delete_old_sessions,
|
||||
args = [{days, integer}],
|
||||
result = {res, rescode}}].
|
||||
|
||||
-spec delete_old_sessions(non_neg_integer()) -> ok | any().
|
||||
delete_old_sessions(Days) ->
|
||||
CurrentTime = p1_time_compat:system_time(micro_seconds),
|
||||
Diff = Days * 24 * 60 * 60 * 1000000,
|
||||
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
||||
DBTypes = lists:usort(
|
||||
lists:map(
|
||||
fun(Host) ->
|
||||
case gen_mod:db_type(Host, ?MODULE) of
|
||||
sql -> {sql, Host};
|
||||
Other -> {Other, global}
|
||||
end
|
||||
end, ?MYHOSTS)),
|
||||
Results = lists:map(
|
||||
fun({DBType, Host}) ->
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
Mod:delete_old_sessions(Host, TimeStamp)
|
||||
end, DBTypes),
|
||||
ets_cache:clear(?PUSH_CACHE, ejabberd_cluster:get_nodes()),
|
||||
case lists:filter(fun(Res) -> Res /= ok end, Results) of
|
||||
[] ->
|
||||
?INFO_MSG("Deleted push sessions older than ~B days", [Days]),
|
||||
ok;
|
||||
[NotOk | _] ->
|
||||
?ERROR_MSG("Error while deleting old push sessions: ~p", [NotOk]),
|
||||
NotOk
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Register/unregister hooks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_hooks(binary()) -> ok.
|
||||
register_hooks(Host) ->
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 50),
|
||||
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50),
|
||||
ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
|
||||
mam_message, 50),
|
||||
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
|
||||
offline_message, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50).
|
||||
|
||||
-spec unregister_hooks(binary()) -> ok.
|
||||
unregister_hooks(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50),
|
||||
ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
|
||||
mam_message, 50),
|
||||
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
|
||||
offline_message, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Service discovery.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec disco_sm_features(empty | {result, [binary()]} | {error, stanza_error()},
|
||||
jid(), jid(), binary(), binary())
|
||||
-> {result, [binary()]} | {error, stanza_error()}.
|
||||
disco_sm_features(empty, From, To, Node, Lang) ->
|
||||
disco_sm_features({result, []}, From, To, Node, Lang);
|
||||
disco_sm_features({result, OtherFeatures},
|
||||
#jid{luser = U, lserver = S},
|
||||
#jid{luser = U, lserver = S}, <<"">>, _Lang) ->
|
||||
{result, [?NS_PUSH_0 | OtherFeatures]};
|
||||
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% IQ handlers.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_iq_handlers(binary(), gen_iq_handler:type()) -> ok.
|
||||
register_iq_handlers(Host, IQDisc) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0,
|
||||
?MODULE, process_iq, IQDisc).
|
||||
|
||||
-spec unregister_iq_handlers(binary()) -> ok.
|
||||
unregister_iq_handlers(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0).
|
||||
|
||||
-spec process_iq(iq()) -> iq().
|
||||
process_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) ->
|
||||
Txt = <<"Enabling push without 'node' attribute is not supported">>,
|
||||
xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
|
||||
process_iq(#iq{from = #jid{lserver = LServer} = JID,
|
||||
to = #jid{lserver = LServer},
|
||||
sub_els = [#push_enable{jid = PushJID,
|
||||
node = Node,
|
||||
xdata = XData}]} = IQ) ->
|
||||
case enable(JID, PushJID, Node, XData) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
error ->
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error())
|
||||
end;
|
||||
process_iq(#iq{from = #jid{lserver = LServer} = JID,
|
||||
to = #jid{lserver = LServer},
|
||||
sub_els = [#push_disable{jid = PushJID,
|
||||
node = Node}]} = IQ) ->
|
||||
case disable(JID, PushJID, Node) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
error ->
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found())
|
||||
end;
|
||||
process_iq(IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed()).
|
||||
|
||||
-spec enable(jid(), jid(), binary(), xdata()) -> ok | error.
|
||||
enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
|
||||
PushJID, Node, XData) ->
|
||||
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
|
||||
{TS, PID} ->
|
||||
case store_session(LUser, LServer, TS, PushJID, Node, XData) of
|
||||
{ok, _} ->
|
||||
?INFO_MSG("Enabling push notifications for ~s",
|
||||
[jid:encode(JID)]),
|
||||
ejabberd_c2s:cast(PID, push_enable);
|
||||
error ->
|
||||
?ERROR_MSG("Cannot enable push for ~s: database error",
|
||||
[jid:encode(JID)]),
|
||||
error
|
||||
end;
|
||||
none ->
|
||||
?WARNING_MSG("Cannot enable push for ~s: session not found",
|
||||
[jid:encode(JID)]),
|
||||
error
|
||||
end.
|
||||
|
||||
-spec disable(jid(), jid(), binary() | undefined) -> ok | error.
|
||||
disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
|
||||
PushJID, Node) ->
|
||||
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
|
||||
{_TS, PID} ->
|
||||
?INFO_MSG("Disabling push notifications for ~s",
|
||||
[jid:encode(JID)]),
|
||||
ejabberd_c2s:cast(PID, push_disable);
|
||||
none ->
|
||||
?WARNING_MSG("Session not found while disabling push for ~s",
|
||||
[jid:encode(JID)])
|
||||
end,
|
||||
if Node /= undefined ->
|
||||
delete_session(LUser, LServer, PushJID, Node);
|
||||
true ->
|
||||
delete_sessions(LUser, LServer, PushJID)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Hook callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
notify(State),
|
||||
State;
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
|
||||
-spec mam_message(message() | drop, binary(), binary(), jid(),
|
||||
chat | groupchat, recv | send) -> message().
|
||||
mam_message(#message{meta = #{push_notified := true}} = Pkt,
|
||||
_LUser, _LServer, _Peer, _Type, _Dir) ->
|
||||
Pkt;
|
||||
mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) ->
|
||||
case lookup_sessions(LUser, LServer) of
|
||||
{ok, [_|_] = Clients} ->
|
||||
case drop_online_sessions(LUser, LServer, Clients) of
|
||||
[_|_] = Clients1 ->
|
||||
?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients1);
|
||||
[] ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
xmpp:put_meta(Pkt, push_notified, true);
|
||||
mam_message(Pkt, _LUser, _LServer, _Peer, _Type, _Dir) ->
|
||||
Pkt.
|
||||
|
||||
-spec offline_message({any(), message()}) -> {any(), message()}.
|
||||
offline_message({_Action, #message{meta = #{push_notified := true}}} = Acc) ->
|
||||
Acc;
|
||||
offline_message({Action, #message{to = #jid{luser = LUser,
|
||||
lserver = LServer}} = Pkt}) ->
|
||||
case lookup_sessions(LUser, LServer) of
|
||||
{ok, [_|_] = Clients} ->
|
||||
?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{Action, xmpp:put_meta(Pkt, push_notified, true)}.
|
||||
|
||||
-spec c2s_session_pending(c2s_state()) -> c2s_state().
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
Len when Len > 0 ->
|
||||
?DEBUG("Notifying client of unacknowledged messages", []),
|
||||
notify(State),
|
||||
State;
|
||||
0 ->
|
||||
State
|
||||
end;
|
||||
c2s_session_pending(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{push_enabled := true}) ->
|
||||
State#{push_enabled => true};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
|
||||
c2s_handle_cast(State, push_enable) ->
|
||||
{stop, State#{push_enabled => true}};
|
||||
c2s_handle_cast(State, push_disable) ->
|
||||
{stop, maps:remove(push_enabled, State)};
|
||||
c2s_handle_cast(State, _Msg) ->
|
||||
State.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok | error.
|
||||
remove_user(LUser, LServer) ->
|
||||
?INFO_MSG("Removing any push sessions of ~s@~s", [LUser, LServer]),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer) end,
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Generate push notifications.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec notify(c2s_state()) -> ok.
|
||||
notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
|
||||
case lookup_session(LUser, LServer, TS) of
|
||||
{ok, Client} ->
|
||||
notify(LUser, LServer, [Client]);
|
||||
error ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec notify(binary(), binary(), [push_session()]) -> ok.
|
||||
notify(LUser, LServer, Clients) ->
|
||||
lists:foreach(
|
||||
fun({TS, PushLJID, Node, XData}) ->
|
||||
HandleResponse = fun(#iq{type = result}) ->
|
||||
ok;
|
||||
(#iq{type = error}) ->
|
||||
delete_session(LUser, LServer, TS);
|
||||
(timeout) ->
|
||||
ok % Hmm.
|
||||
end,
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse)
|
||||
end, Clients).
|
||||
|
||||
-spec notify(binary(), ljid(), binary(), xdata(),
|
||||
fun((iq() | timeout) -> any())) -> ok.
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse) ->
|
||||
From = jid:make(LServer),
|
||||
Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
|
||||
PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]},
|
||||
publish_options = XData},
|
||||
IQ = #iq{type = set,
|
||||
from = From,
|
||||
to = jid:make(PushLJID),
|
||||
id = randoms:get_string(),
|
||||
sub_els = [PubSub]},
|
||||
ejabberd_local:route_iq(IQ, HandleResponse),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec store_session(binary(), binary(), timestamp(), jid(), binary(), xdata())
|
||||
-> {ok, push_session()} | error.
|
||||
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
delete_session(LUser, LServer, PushJID, Node),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ets_cache:update(
|
||||
?PUSH_CACHE,
|
||||
{LUser, LServer, TS}, {ok, {TS, PushJID, Node, XData}},
|
||||
fun() ->
|
||||
Mod:store_session(LUser, LServer, TS, PushJID, Node,
|
||||
XData)
|
||||
end, cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
Mod:store_session(LUser, LServer, TS, PushJID, Node, XData)
|
||||
end.
|
||||
|
||||
-spec lookup_session(binary(), binary(), timestamp())
|
||||
-> {ok, push_session()} | error.
|
||||
lookup_session(LUser, LServer, TS) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PUSH_CACHE, {LUser, LServer, TS},
|
||||
fun() -> Mod:lookup_session(LUser, LServer, TS) end);
|
||||
false ->
|
||||
Mod:lookup_session(LUser, LServer, TS)
|
||||
end.
|
||||
|
||||
-spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | error.
|
||||
lookup_sessions(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PUSH_CACHE, {LUser, LServer},
|
||||
fun() -> Mod:lookup_sessions(LUser, LServer) end);
|
||||
false ->
|
||||
Mod:lookup_sessions(LUser, LServer)
|
||||
end.
|
||||
|
||||
-spec delete_session(binary(), binary(), timestamp()) -> ok | error.
|
||||
delete_session(LUser, LServer, TS) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
ok = Mod:delete_session(LUser, LServer, TS),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec delete_session(binary(), binary(), jid(), binary()) -> ok | error.
|
||||
delete_session(LUser, LServer, PushJID, Node) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:lookup_session(LUser, LServer, PushJID, Node) of
|
||||
{ok, {TS, _, _, _}} ->
|
||||
delete_session(LUser, LServer, TS);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec delete_sessions(binary(), binary(), jid()) -> ok | error.
|
||||
delete_sessions(LUser, LServer, PushJID) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer, PushJID) end,
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod).
|
||||
|
||||
-spec delete_sessions(binary(), binary(), fun(() -> ok | error), module())
|
||||
-> ok | error.
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod) ->
|
||||
case LookupFun() of
|
||||
{ok, Clients} ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({TS, _, _, _}) ->
|
||||
ok = Mod:delete_session(LUser, LServer, TS),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE,
|
||||
{LUser, LServer, TS},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, Clients);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec drop_online_sessions(binary(), binary(), [push_session()])
|
||||
-> [push_session()].
|
||||
drop_online_sessions(LUser, LServer, Clients) ->
|
||||
SessIDs = ejabberd_sm:get_session_sids(LUser, LServer),
|
||||
[Client || {TS, _, _, _} = Client <- Clients,
|
||||
lists:keyfind(TS, 1, SessIDs) == false].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Caching.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
init_cache(Mod, Host, Opts) ->
|
||||
case use_cache(Mod, Host) of
|
||||
true ->
|
||||
CacheOpts = cache_opts(Host, Opts),
|
||||
ets_cache:new(?PUSH_CACHE, CacheOpts);
|
||||
false ->
|
||||
ets_cache:delete(?PUSH_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
||||
cache_opts(Host, Opts) ->
|
||||
MaxSize = gen_mod:get_opt(
|
||||
cache_size, Opts,
|
||||
ejabberd_config:cache_size(Host)),
|
||||
CacheMissed = gen_mod:get_opt(
|
||||
cache_missed, Opts,
|
||||
ejabberd_config:cache_missed(Host)),
|
||||
LifeTime = case gen_mod:get_opt(
|
||||
cache_life_time, Opts,
|
||||
ejabberd_config:cache_life_time(Host)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(Host);
|
||||
false ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host))
|
||||
end.
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(Host);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
@@ -0,0 +1,236 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push_keepalive.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2017 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(mod_push_keepalive).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2,
|
||||
c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(PUSH_BEFORE_TIMEOUT_SECS, 120).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:get_opt(wake_on_start, Opts, false) of
|
||||
true ->
|
||||
wake_all(Host);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
register_hooks(Host).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
unregister_hooks(Host).
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts, false) of
|
||||
{false, true, _} ->
|
||||
wake_all(Host);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_push, hard},
|
||||
{mod_client_state, soft},
|
||||
{mod_stream_mgmt, soft}].
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(resume_timeout) ->
|
||||
fun(I) when is_integer(I), I >= 0 -> I;
|
||||
(undefined) -> undefined
|
||||
end;
|
||||
mod_opt_type(wake_on_start) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(wake_on_timeout) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[resume_timeout, wake_on_start, wake_on_timeout, cache_life_time,
|
||||
cache_size, use_cache, cache_missed, iqdisc].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Register/unregister hooks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_hooks(binary()) -> ok.
|
||||
register_hooks(Host) ->
|
||||
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE,
|
||||
c2s_session_resumed, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 40),
|
||||
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
|
||||
c2s_handle_info, 50),
|
||||
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50).
|
||||
|
||||
-spec unregister_hooks(binary()) -> ok.
|
||||
unregister_hooks(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE,
|
||||
c2s_session_resumed, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 40),
|
||||
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
|
||||
c2s_handle_info, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Hook callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
|
||||
-spec c2s_session_pending(c2s_state()) -> c2s_state().
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
0 ->
|
||||
State1 = maybe_adjust_resume_timeout(State),
|
||||
maybe_start_wakeup_timer(State1);
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
c2s_session_pending(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_session_resumed(c2s_state()) -> c2s_state().
|
||||
c2s_session_resumed(#{push_enabled := true} = State) ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
c2s_session_resumed(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{push_enabled := true,
|
||||
push_resume_timeout := ResumeTimeout,
|
||||
push_wake_on_timeout := WakeOnTimeout}) ->
|
||||
State#{push_resume_timeout => ResumeTimeout,
|
||||
push_wake_on_timeout => WakeOnTimeout};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state().
|
||||
c2s_handle_cast(#{lserver := LServer} = State, push_enable) ->
|
||||
ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
resume_timeout, 86400),
|
||||
WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
wake_on_timeout, true),
|
||||
State#{push_resume_timeout => ResumeTimeout,
|
||||
push_wake_on_timeout => WakeOnTimeout};
|
||||
c2s_handle_cast(State, push_disable) ->
|
||||
State1 = maps:remove(push_resume_timeout, State),
|
||||
maps:remove(push_wake_on_timeout, State1);
|
||||
c2s_handle_cast(State, _Msg) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
|
||||
c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
|
||||
jid := JID} = State, {timeout, _, push_keepalive}) ->
|
||||
?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]),
|
||||
mod_push:notify(State),
|
||||
{stop, State};
|
||||
c2s_handle_info(State, _) ->
|
||||
State.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state().
|
||||
maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) ->
|
||||
State;
|
||||
maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) ->
|
||||
OrigTimeout = mod_stream_mgmt:get_resume_timeout(State),
|
||||
?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout]),
|
||||
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
|
||||
State1#{push_resume_timeout_orig => OrigTimeout}.
|
||||
|
||||
-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state().
|
||||
maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) ->
|
||||
?DEBUG("Restoring resume timeout to ~B seconds", [Timeout]),
|
||||
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
|
||||
maps:remove(push_resume_timeout_orig, State1);
|
||||
maybe_restore_resume_timeout(State) ->
|
||||
State.
|
||||
|
||||
-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state().
|
||||
maybe_start_wakeup_timer(#{push_wake_on_timeout := true,
|
||||
push_resume_timeout := ResumeTimeout} = State)
|
||||
when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_SECS ->
|
||||
WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_SECS,
|
||||
?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout]),
|
||||
erlang:start_timer(timer:seconds(WakeTimeout), self(), push_keepalive),
|
||||
State;
|
||||
maybe_start_wakeup_timer(State) ->
|
||||
State.
|
||||
|
||||
-spec wake_all(binary()) -> ok | error.
|
||||
wake_all(LServer) ->
|
||||
?INFO_MSG("Waking all push clients on ~s", [LServer]),
|
||||
Mod = gen_mod:db_mod(LServer, mod_push),
|
||||
case Mod:lookup_sessions(LServer) of
|
||||
{ok, Sessions} ->
|
||||
IgnoreResponse = fun(_) -> ok end,
|
||||
lists:foreach(fun({_, PushLJID, Node, XData}) ->
|
||||
mod_push:notify(LServer, PushLJID, Node,
|
||||
XData, IgnoreResponse)
|
||||
end, Sessions);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
@@ -0,0 +1,204 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push_mnesia.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2017 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(mod_push_mnesia).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(mod_push).
|
||||
|
||||
%% API
|
||||
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
|
||||
lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
|
||||
delete_session/3, delete_old_sessions/2]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(push_session,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
|
||||
service = {<<"">>, <<"">>, <<"">>} :: ljid(),
|
||||
node = <<"">> :: binary(),
|
||||
xdata = #xdata{} :: xdata()}).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%-------------------------------------------------------------------
|
||||
init(_Host, _Opts) ->
|
||||
ejabberd_mnesia:create(?MODULE, push_session,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, push_session)}]).
|
||||
|
||||
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
|
||||
US = {LUser, LServer},
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
|
||||
F = fun() ->
|
||||
if is_integer(MaxSessions) ->
|
||||
enforce_max_sessions(US, MaxSessions - 1);
|
||||
MaxSessions == infinity ->
|
||||
ok
|
||||
end,
|
||||
mnesia:write(#push_session{us = US,
|
||||
timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData})
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot store push session for ~s@~s: ~p",
|
||||
[LUser, LServer, E]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_session(LUser, LServer, PushJID, Node) ->
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
P == PushLJID,
|
||||
N == Node ->
|
||||
Rec
|
||||
end),
|
||||
case mnesia:dirty_select(push_session, MatchSpec) of
|
||||
[#push_session{timestamp = TS, xdata = XData}] ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
_ ->
|
||||
?DEBUG("No push session found for ~s@~s (~p, ~s)",
|
||||
[LUser, LServer, PushJID, Node]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_session(LUser, LServer, TS) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
T == TS ->
|
||||
Rec
|
||||
end),
|
||||
case mnesia:dirty_select(push_session, MatchSpec) of
|
||||
[#push_session{service = PushLJID, node = Node, xdata = XData}] ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
_ ->
|
||||
?DEBUG("No push session found for ~s@~s (~p)",
|
||||
[LUser, LServer, TS]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_sessions(LUser, LServer, PushJID) ->
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
P == PushLJID ->
|
||||
Rec
|
||||
end),
|
||||
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
|
||||
|
||||
lookup_sessions(LUser, LServer) ->
|
||||
Records = mnesia:dirty_read(push_session, {LUser, LServer}),
|
||||
Clients = [{TS, PushLJID, Node, XData}
|
||||
|| #push_session{timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData} <- Records],
|
||||
{ok, Clients}.
|
||||
|
||||
lookup_sessions(LServer) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {_U, S},
|
||||
timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData})
|
||||
when S == LServer ->
|
||||
{TS, PushLJID, Node, XData}
|
||||
end),
|
||||
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
|
||||
|
||||
delete_session(LUser, LServer, TS) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
T == TS ->
|
||||
Rec
|
||||
end),
|
||||
F = fun() ->
|
||||
Recs = mnesia:select(push_session, MatchSpec),
|
||||
lists:foreach(fun mnesia:delete_object/1, Recs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot delete push session of ~s@~s: ~p",
|
||||
[LUser, LServer, E]),
|
||||
error
|
||||
end.
|
||||
|
||||
delete_old_sessions(_LServer, Time) ->
|
||||
DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
|
||||
mnesia:delete_object(Rec);
|
||||
(_Rec, ok) ->
|
||||
ok
|
||||
end,
|
||||
F = fun() ->
|
||||
mnesia:foldl(DelIfOld, ok, push_session)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
|
||||
error
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec enforce_max_sessions({binary(), binary()}, non_neg_integer()) -> ok.
|
||||
enforce_max_sessions({U, S} = US, Max) ->
|
||||
Recs = mnesia:wread({push_session, US}),
|
||||
NumRecs = length(Recs),
|
||||
if NumRecs > Max ->
|
||||
NumOldRecs = NumRecs - Max,
|
||||
Recs1 = lists:keysort(#push_session.timestamp, Recs),
|
||||
Recs2 = lists:reverse(Recs1),
|
||||
OldRecs = lists:sublist(Recs2, Max + 1, NumOldRecs),
|
||||
?INFO_MSG("Disabling ~B old push session(s) of ~s@~s",
|
||||
[NumOldRecs, U, S]),
|
||||
lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
+15
-6
@@ -127,7 +127,7 @@ process_iq(#iq{from = From, to = To} = IQ, Source) ->
|
||||
process_iq(#iq{type = set, lang = Lang,
|
||||
sub_els = [#register{remove = true}]} = IQ,
|
||||
_Source, _IsCaptchaEnabled, _AllowRemove = false) ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
|
||||
process_iq(#iq{type = set, lang = Lang, to = To, from = From,
|
||||
sub_els = [#register{remove = true,
|
||||
@@ -210,7 +210,14 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
|
||||
Instr = translate:translate(
|
||||
Lang, <<"Choose a username and password to register "
|
||||
"with this server">>),
|
||||
if IsCaptchaEnabled and not IsRegistered ->
|
||||
URL = gen_mod:get_module_opt(Server, ?MODULE, redirect_url, <<"">>),
|
||||
if (URL /= <<"">>) and not IsRegistered ->
|
||||
Txt = translate:translate(Lang, <<"To register, visit ~s">>),
|
||||
Desc = str:format(Txt, [URL]),
|
||||
xmpp:make_iq_result(
|
||||
IQ, #register{instructions = Desc,
|
||||
sub_els = [#oob_x{url = URL}]});
|
||||
IsCaptchaEnabled and not IsRegistered ->
|
||||
TopInstr = translate:translate(
|
||||
Lang, <<"You need a client that supports x:data "
|
||||
"and CAPTCHA to register">>),
|
||||
@@ -263,7 +270,7 @@ try_register_or_set_password(User, Server, Password,
|
||||
xmpp:make_error(IQ, Error)
|
||||
end;
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
|
||||
end;
|
||||
_ ->
|
||||
@@ -315,8 +322,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
|
||||
case {acl:match_rule(Server, Access, JID),
|
||||
check_ip_access(SourceRaw, IPAccess)}
|
||||
of
|
||||
{deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
|
||||
{deny, _} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
{_, deny} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
|
||||
{allow, allow} ->
|
||||
Source = may_remove_resource(SourceRaw),
|
||||
case check_timeout(Source) of
|
||||
@@ -614,9 +621,11 @@ mod_opt_type({welcome_message, subject}) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type({welcome_message, body}) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(redirect_url) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(_) ->
|
||||
[access, access_from, access_remove, captcha_protected, ip_access,
|
||||
iqdisc, password_strength, registration_watchers,
|
||||
iqdisc, password_strength, registration_watchers, redirect_url,
|
||||
{welcome_message, subject}, {welcome_message, body}].
|
||||
|
||||
-spec opt_type(registration_timeout) -> fun((timeout()) -> timeout());
|
||||
|
||||
+4
-8
@@ -35,7 +35,6 @@
|
||||
-module(mod_roster).
|
||||
|
||||
-protocol({xep, 237, '1.3'}).
|
||||
-protocol({xep, 321, '0.1'}).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
@@ -185,7 +184,7 @@ process_local_iq(#iq{type = set, from = From, lang = Lang,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
allow ->
|
||||
process_iq_set(IQ)
|
||||
@@ -441,14 +440,13 @@ decode_item(Item, R, Managed) ->
|
||||
end,
|
||||
groups = Item#roster_item.groups}.
|
||||
|
||||
process_iq_set(#iq{from = From, to = To,
|
||||
process_iq_set(#iq{from = _From, to = To,
|
||||
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
|
||||
#jid{user = User, luser = LUser, lserver = LServer} = To,
|
||||
Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
|
||||
LJID = jid:tolower(QueryItem#roster_item.jid),
|
||||
F = fun () ->
|
||||
Item = get_roster_item(LUser, LServer, LJID),
|
||||
Item2 = decode_item(QueryItem, Item, Managed),
|
||||
Item2 = decode_item(QueryItem, Item, false),
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2,
|
||||
[LServer]),
|
||||
@@ -1200,8 +1198,6 @@ mod_opt_type(access) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(managers) ->
|
||||
fun (B) when is_list(B) -> B end;
|
||||
mod_opt_type(store_current_id) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(versioning) ->
|
||||
@@ -1213,5 +1209,5 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[access, db_type, iqdisc, managers, store_current_id,
|
||||
[access, db_type, iqdisc, store_current_id,
|
||||
versioning, cache_life_time, cache_size, use_cache, cache_missed].
|
||||
|
||||
@@ -320,7 +320,7 @@ check_from_to(From, To) ->
|
||||
|
||||
-spec mk_error(term()) -> stanza_error().
|
||||
mk_error(forbidden) ->
|
||||
xmpp:err_forbidden(<<"Denied by ACL">>, ?MYLANG);
|
||||
xmpp:err_forbidden(<<"Access denied by service policy">>, ?MYLANG);
|
||||
mk_error(host_unknown) ->
|
||||
xmpp:err_not_allowed(<<"Host unknown">>, ?MYLANG);
|
||||
mk_error({codec_error, Why}) ->
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
-ifndef(SIP).
|
||||
-export([]).
|
||||
-else.
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% API
|
||||
-export([start/2, start_link/2, route/3, route/4]).
|
||||
@@ -58,10 +57,10 @@ start(LServer, Opts) ->
|
||||
supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
|
||||
|
||||
start_link(LServer, Opts) ->
|
||||
?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
|
||||
p1_fsm:start_link(?MODULE, [LServer, Opts], []).
|
||||
|
||||
route(SIPMsg, _SIPSock, TrID, Pid) ->
|
||||
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
|
||||
p1_fsm:send_event(Pid, {SIPMsg, TrID}).
|
||||
|
||||
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
|
||||
case proplists:get_bool(authenticated, Opts) of
|
||||
|
||||
+48
-15
@@ -33,6 +33,8 @@
|
||||
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
|
||||
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
|
||||
c2s_handle_recv/3]).
|
||||
%% adjust pending session timeout
|
||||
-export([get_resume_timeout/1, set_resume_timeout/2]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -69,7 +71,12 @@ start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
|
||||
|
||||
stop(Host) ->
|
||||
%% TODO: do something with global 'c2s_init' hook
|
||||
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50)
|
||||
end,
|
||||
ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE,
|
||||
c2s_stream_started, 50),
|
||||
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
|
||||
@@ -235,8 +242,9 @@ c2s_handle_info(#{mgmt_ack_timer := TRef, jid := JID, mod := Mod} = State,
|
||||
[jid:encode(JID)]),
|
||||
State1 = Mod:close(State),
|
||||
{stop, transition_to_pending(State1)};
|
||||
c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State,
|
||||
{timeout, _, pending_timeout}) ->
|
||||
c2s_handle_info(#{mgmt_state := pending,
|
||||
mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State,
|
||||
{timeout, TRef, pending_timeout}) ->
|
||||
?DEBUG("Timed out waiting for resumption of stream for ~s",
|
||||
[jid:encode(JID)]),
|
||||
Mod:stop(State#{mgmt_state => timeout});
|
||||
@@ -282,6 +290,20 @@ c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID,
|
||||
c2s_terminated(State, _Reason) ->
|
||||
State.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Adjust pending session timeout
|
||||
%%%===================================================================
|
||||
-spec get_resume_timeout(state()) -> non_neg_integer().
|
||||
get_resume_timeout(#{mgmt_timeout := Timeout}) ->
|
||||
Timeout.
|
||||
|
||||
-spec set_resume_timeout(state(), non_neg_integer()) -> state().
|
||||
set_resume_timeout(#{mgmt_timeout := Timeout} = State, Timeout) ->
|
||||
State;
|
||||
set_resume_timeout(State, Timeout) ->
|
||||
State1 = restart_pending_timer(State, Timeout),
|
||||
State1#{mgmt_timeout => Timeout}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -388,8 +410,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod,
|
||||
previd = AttrId}),
|
||||
State3 = resend_unacked_stanzas(State2),
|
||||
State4 = send(State3, #sm_r{xmlns = AttrXmlns}),
|
||||
%% TODO: move this to mod_client_state
|
||||
%% csi_flush_queue(State4),
|
||||
State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []),
|
||||
?INFO_MSG("(~s) Resumed session for ~s",
|
||||
[SockMod:pp(Socket), jid:encode(JID)]),
|
||||
@@ -408,8 +428,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID,
|
||||
lserver := LServer, mgmt_timeout := Timeout} = State) ->
|
||||
State1 = cancel_ack_timer(State),
|
||||
?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]),
|
||||
erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
|
||||
State2 = State1#{mgmt_state => pending},
|
||||
TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
|
||||
State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef},
|
||||
ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []);
|
||||
transition_to_pending(State) ->
|
||||
State.
|
||||
@@ -648,20 +668,33 @@ add_resent_delay_info(_State, El, _Time) ->
|
||||
send(#{mod := Mod} = State, Pkt) ->
|
||||
Mod:send(State, Pkt).
|
||||
|
||||
-spec restart_pending_timer(state(), non_neg_integer()) -> state().
|
||||
restart_pending_timer(#{mgmt_pending_timer := TRef} = State, NewTimeout) ->
|
||||
cancel_timer(TRef),
|
||||
NewTRef = erlang:start_timer(timer:seconds(NewTimeout), self(),
|
||||
pending_timeout),
|
||||
State#{mgmt_pending_timer => NewTRef};
|
||||
restart_pending_timer(State, _NewTimeout) ->
|
||||
State.
|
||||
|
||||
-spec cancel_ack_timer(state()) -> state().
|
||||
cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
cancel_timer(TRef),
|
||||
maps:remove(mgmt_ack_timer, State);
|
||||
cancel_ack_timer(State) ->
|
||||
State.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec bounce_message_queue() -> ok.
|
||||
bounce_message_queue() ->
|
||||
receive {route, Pkt} ->
|
||||
|
||||
+74
-56
@@ -38,7 +38,7 @@
|
||||
remove_user/2, export/1, import_info/0, import/5, import_start/2,
|
||||
depends/2, process_search/1, process_vcard/1, get_vcard/2,
|
||||
disco_items/5, disco_features/5, disco_identity/5,
|
||||
decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
||||
vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
-record(state, {host :: binary(), server_host :: binary()}).
|
||||
-record(state, {hosts :: [binary()], server_host :: binary()}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
@@ -95,37 +95,41 @@ init([Host, Opts]) ->
|
||||
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts, false),
|
||||
if Search ->
|
||||
ejabberd_hooks:add(
|
||||
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
case Mod:is_search_supported(Host) of
|
||||
false ->
|
||||
?WARNING_MSG("vcard search functionality is "
|
||||
"not implemented for ~s backend",
|
||||
[gen_mod:db_type(Host, Opts, ?MODULE)]);
|
||||
true ->
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end;
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_hooks:add(
|
||||
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
case Mod:is_search_supported(Host) of
|
||||
false ->
|
||||
?WARNING_MSG("vcard search functionality is "
|
||||
"not implemented for ~s backend",
|
||||
[gen_mod:db_type(Host, Opts, ?MODULE)]);
|
||||
true ->
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end
|
||||
end, MyHosts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ok, #state{host = MyHost, server_host = Host}}.
|
||||
{ok, #state{hosts = MyHosts, server_host = Host}}.
|
||||
|
||||
handle_call(_Call, _From, State) ->
|
||||
{noreply, State}.
|
||||
@@ -144,21 +148,25 @@ handle_info(Info, State) ->
|
||||
?WARNING_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = MyHost, server_host = Host}) ->
|
||||
terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:stop(Host),
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO).
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO)
|
||||
end, MyHosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
@@ -185,14 +193,6 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel().
|
||||
%% Tell gen_iq_handler not to decode vcard elements
|
||||
decode_iq_subel(El) ->
|
||||
case xmpp:get_ns(El) of
|
||||
?NS_VCARD -> xmpp:encode(El);
|
||||
_ -> xmpp:decode(El)
|
||||
end.
|
||||
|
||||
-spec process_local_iq(iq()) -> iq().
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
@@ -206,13 +206,15 @@ process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
bday = <<"2002-11-16">>}).
|
||||
|
||||
-spec process_sm_iq(iq()) -> iq().
|
||||
process_sm_iq(#iq{type = set, lang = Lang, from = From,
|
||||
sub_els = [SubEl]} = IQ) ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) ->
|
||||
#jid{lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
set_vcard(User, LServer, SubEl),
|
||||
xmpp:make_iq_result(IQ);
|
||||
case ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []) of
|
||||
drop -> ignore;
|
||||
#stanza_error{} = Err -> xmpp:make_error(IQ, Err);
|
||||
_ -> xmpp:make_iq_result(IQ)
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
|
||||
@@ -374,19 +376,33 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
|
||||
orgunit = OrgUnit,
|
||||
lorgunit = LOrgUnit}.
|
||||
|
||||
-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok.
|
||||
-spec vcard_iq_set(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_set(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
case set_vcard(User, LServer, VCard) of
|
||||
{error, badarg} ->
|
||||
%% Should not be here?
|
||||
Txt = <<"Nodeprep has failed">>,
|
||||
{stop, xmpp:err_internal_server_error(Txt, Lang)};
|
||||
ok ->
|
||||
IQ
|
||||
end;
|
||||
vcard_iq_set(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec set_vcard(binary(), binary(), xmlel() | vcard_temp()) -> {error, badarg} | ok.
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
case jid:nodeprep(User) of
|
||||
error ->
|
||||
{error, badarg};
|
||||
LUser ->
|
||||
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
|
||||
VCardEl = xmpp:encode(VCARD),
|
||||
VCardSearch = make_vcard_search(User, LUser, LServer, VCardEl),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
|
||||
Mod:set_vcard(LUser, LServer, VCardEl, VCardSearch),
|
||||
ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ejabberd_hooks:run(vcard_set, LServer,
|
||||
[LUser, LServer, VCARD])
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec string2lower(binary()) -> binary().
|
||||
@@ -527,6 +543,8 @@ mod_opt_type(allow_return_all) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(matches) ->
|
||||
fun (infinity) -> infinity;
|
||||
@@ -543,6 +561,6 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[allow_return_all, db_type, host, iqdisc, matches,
|
||||
[allow_return_all, db_type, host, hosts, iqdisc, matches,
|
||||
search, search_all_hosts, cache_life_time, cache_size,
|
||||
use_cache, cache_missed].
|
||||
|
||||
+27
-27
@@ -42,12 +42,13 @@
|
||||
-include("logger.hrl").
|
||||
-include("eldap.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_vcard_ldap).
|
||||
|
||||
-record(state,
|
||||
{serverhost = <<"">> :: binary(),
|
||||
myhost = <<"">> :: binary(),
|
||||
myhosts = [] :: [binary()],
|
||||
eldap_id = <<"">> :: binary(),
|
||||
search = false :: boolean(),
|
||||
servers = [] :: [binary()],
|
||||
@@ -324,35 +325,34 @@ default_vcard_map() ->
|
||||
{<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
|
||||
|
||||
default_search_fields() ->
|
||||
[{<<"User">>, <<"%u">>},
|
||||
{<<"Full Name">>, <<"displayName">>},
|
||||
{<<"Given Name">>, <<"givenName">>},
|
||||
{<<"Middle Name">>, <<"initials">>},
|
||||
{<<"Family Name">>, <<"sn">>},
|
||||
{<<"Nickname">>, <<"%u">>},
|
||||
{<<"Birthday">>, <<"birthDay">>},
|
||||
{<<"Country">>, <<"c">>},
|
||||
{<<"City">>, <<"l">>},
|
||||
{<<"Email">>, <<"mail">>},
|
||||
{<<"Organization Name">>, <<"o">>},
|
||||
{<<"Organization Unit">>, <<"ou">>}].
|
||||
[{?T("User"), <<"%u">>},
|
||||
{?T("Full Name"), <<"displayName">>},
|
||||
{?T("Given Name"), <<"givenName">>},
|
||||
{?T("Middle Name"), <<"initials">>},
|
||||
{?T("Family Name"), <<"sn">>},
|
||||
{?T("Nickname"), <<"%u">>},
|
||||
{?T("Birthday"), <<"birthDay">>},
|
||||
{?T("Country"), <<"c">>},
|
||||
{?T("City"), <<"l">>},
|
||||
{?T("Email"), <<"mail">>},
|
||||
{?T("Organization Name"), <<"o">>},
|
||||
{?T("Organization Unit"), <<"ou">>}].
|
||||
|
||||
default_search_reported() ->
|
||||
[{<<"Full Name">>, <<"FN">>},
|
||||
{<<"Given Name">>, <<"FIRST">>},
|
||||
{<<"Middle Name">>, <<"MIDDLE">>},
|
||||
{<<"Family Name">>, <<"LAST">>},
|
||||
{<<"Nickname">>, <<"NICK">>},
|
||||
{<<"Birthday">>, <<"BDAY">>},
|
||||
{<<"Country">>, <<"CTRY">>},
|
||||
{<<"City">>, <<"LOCALITY">>},
|
||||
{<<"Email">>, <<"EMAIL">>},
|
||||
{<<"Organization Name">>, <<"ORGNAME">>},
|
||||
{<<"Organization Unit">>, <<"ORGUNIT">>}].
|
||||
[{?T("Full Name"), <<"FN">>},
|
||||
{?T("Given Name"), <<"FIRST">>},
|
||||
{?T("Middle Name"), <<"MIDDLE">>},
|
||||
{?T("Family Name"), <<"LAST">>},
|
||||
{?T("Nickname"), <<"NICK">>},
|
||||
{?T("Birthday"), <<"BDAY">>},
|
||||
{?T("Country"), <<"CTRY">>},
|
||||
{?T("City"), <<"LOCALITY">>},
|
||||
{?T("Email"), <<"EMAIL">>},
|
||||
{?T("Organization Name"), <<"ORGNAME">>},
|
||||
{?T("Organization Unit"), <<"ORGUNIT">>}].
|
||||
|
||||
parse_options(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"vjud.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts, false),
|
||||
Matches = gen_mod:get_opt(matches, Opts, 30),
|
||||
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
|
||||
@@ -394,7 +394,7 @@ parse_options(Host, Opts) ->
|
||||
end,
|
||||
SearchReported)
|
||||
++ UIDAttrs),
|
||||
#state{serverhost = Host, myhost = MyHost,
|
||||
#state{serverhost = Host, myhosts = MyHosts,
|
||||
eldap_id = Eldap_ID, search = Search,
|
||||
servers = Cfg#eldap_config.servers,
|
||||
backups = Cfg#eldap_config.backups,
|
||||
|
||||
+25
-24
@@ -36,6 +36,7 @@
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_vcard.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -95,32 +96,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
|
||||
end.
|
||||
|
||||
search_fields(_LServer) ->
|
||||
[{<<"User">>, <<"user">>},
|
||||
{<<"Full Name">>, <<"fn">>},
|
||||
{<<"Name">>, <<"first">>},
|
||||
{<<"Middle Name">>, <<"middle">>},
|
||||
{<<"Family Name">>, <<"last">>},
|
||||
{<<"Nickname">>, <<"nick">>},
|
||||
{<<"Birthday">>, <<"bday">>},
|
||||
{<<"Country">>, <<"ctry">>},
|
||||
{<<"City">>, <<"locality">>},
|
||||
{<<"Email">>, <<"email">>},
|
||||
{<<"Organization Name">>, <<"orgname">>},
|
||||
{<<"Organization Unit">>, <<"orgunit">>}].
|
||||
[{?T("User"), <<"user">>},
|
||||
{?T("Full Name"), <<"fn">>},
|
||||
{?T("Name"), <<"first">>},
|
||||
{?T("Middle Name"), <<"middle">>},
|
||||
{?T("Family Name"), <<"last">>},
|
||||
{?T("Nickname"), <<"nick">>},
|
||||
{?T("Birthday"), <<"bday">>},
|
||||
{?T("Country"), <<"ctry">>},
|
||||
{?T("City"), <<"locality">>},
|
||||
{?T("Email"), <<"email">>},
|
||||
{?T("Organization Name"), <<"orgname">>},
|
||||
{?T("Organization Unit"), <<"orgunit">>}].
|
||||
|
||||
search_reported(_LServer) ->
|
||||
[{<<"Jabber ID">>, <<"jid">>},
|
||||
{<<"Full Name">>, <<"fn">>},
|
||||
{<<"Name">>, <<"first">>},
|
||||
{<<"Middle Name">>, <<"middle">>},
|
||||
{<<"Family Name">>, <<"last">>},
|
||||
{<<"Nickname">>, <<"nick">>},
|
||||
{<<"Birthday">>, <<"bday">>},
|
||||
{<<"Country">>, <<"ctry">>},
|
||||
{<<"City">>, <<"locality">>},
|
||||
{<<"Email">>, <<"email">>},
|
||||
{<<"Organization Name">>, <<"orgname">>},
|
||||
{<<"Organization Unit">>, <<"orgunit">>}].
|
||||
[{?T("Jabber ID"), <<"jid">>},
|
||||
{?T("Full Name"), <<"fn">>},
|
||||
{?T("Name"), <<"first">>},
|
||||
{?T("Middle Name"), <<"middle">>},
|
||||
{?T("Family Name"), <<"last">>},
|
||||
{?T("Nickname"), <<"nick">>},
|
||||
{?T("Birthday"), <<"bday">>},
|
||||
{?T("Country"), <<"ctry">>},
|
||||
{?T("City"), <<"locality">>},
|
||||
{?T("Email"), <<"email">>},
|
||||
{?T("Organization Name"), <<"orgname">>},
|
||||
{?T("Organization Unit"), <<"orgunit">>}].
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
|
||||
+25
-24
@@ -37,6 +37,7 @@
|
||||
-include("mod_vcard.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -150,32 +151,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
|
||||
end.
|
||||
|
||||
search_fields(_LServer) ->
|
||||
[{<<"User">>, <<"user">>},
|
||||
{<<"Full Name">>, <<"fn">>},
|
||||
{<<"Name">>, <<"first">>},
|
||||
{<<"Middle Name">>, <<"middle">>},
|
||||
{<<"Family Name">>, <<"last">>},
|
||||
{<<"Nickname">>, <<"nick">>},
|
||||
{<<"Birthday">>, <<"bday">>},
|
||||
{<<"Country">>, <<"ctry">>},
|
||||
{<<"City">>, <<"locality">>},
|
||||
{<<"Email">>, <<"email">>},
|
||||
{<<"Organization Name">>, <<"orgname">>},
|
||||
{<<"Organization Unit">>, <<"orgunit">>}].
|
||||
[{?T("User"), <<"user">>},
|
||||
{?T("Full Name"), <<"fn">>},
|
||||
{?T("Name"), <<"first">>},
|
||||
{?T("Middle Name"), <<"middle">>},
|
||||
{?T("Family Name"), <<"last">>},
|
||||
{?T("Nickname"), <<"nick">>},
|
||||
{?T("Birthday"), <<"bday">>},
|
||||
{?T("Country"), <<"ctry">>},
|
||||
{?T("City"), <<"locality">>},
|
||||
{?T("Email"), <<"email">>},
|
||||
{?T("Organization Name"), <<"orgname">>},
|
||||
{?T("Organization Unit"), <<"orgunit">>}].
|
||||
|
||||
search_reported(_LServer) ->
|
||||
[{<<"Jabber ID">>, <<"jid">>},
|
||||
{<<"Full Name">>, <<"fn">>},
|
||||
{<<"Name">>, <<"first">>},
|
||||
{<<"Middle Name">>, <<"middle">>},
|
||||
{<<"Family Name">>, <<"last">>},
|
||||
{<<"Nickname">>, <<"nick">>},
|
||||
{<<"Birthday">>, <<"bday">>},
|
||||
{<<"Country">>, <<"ctry">>},
|
||||
{<<"City">>, <<"locality">>},
|
||||
{<<"Email">>, <<"email">>},
|
||||
{<<"Organization Name">>, <<"orgname">>},
|
||||
{<<"Organization Unit">>, <<"orgunit">>}].
|
||||
[{?T("Jabber ID"), <<"jid">>},
|
||||
{?T("Full Name"), <<"fn">>},
|
||||
{?T("Name"), <<"first">>},
|
||||
{?T("Middle Name"), <<"middle">>},
|
||||
{?T("Family Name"), <<"last">>},
|
||||
{?T("Nickname"), <<"nick">>},
|
||||
{?T("Birthday"), <<"bday">>},
|
||||
{?T("Country"), <<"ctry">>},
|
||||
{?T("City"), <<"locality">>},
|
||||
{?T("Email"), <<"email">>},
|
||||
{?T("Organization Name"), <<"orgname">>},
|
||||
{?T("Organization Unit"), <<"orgunit">>}].
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
ejabberd_sql:sql_transaction(
|
||||
|
||||
+50
-24
@@ -30,8 +30,8 @@
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, stop/1, reload/3]).
|
||||
|
||||
-export([update_presence/1, vcard_set/3, remove_user/2,
|
||||
mod_opt_type/1, depends/2]).
|
||||
-export([update_presence/1, vcard_set/1, remove_user/2,
|
||||
user_send_packet/1, mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -47,15 +47,19 @@ start(Host, Opts) ->
|
||||
init_cache(Host, Opts),
|
||||
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
|
||||
update_presence, 100),
|
||||
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
|
||||
100),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 50),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_set,
|
||||
90),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(c2s_self_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
|
||||
vcard_set, 100),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 50),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_set, 90),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50).
|
||||
|
||||
reload(Host, NewOpts, _OldOpts) ->
|
||||
@@ -71,16 +75,33 @@ depends(_Host, _Opts) ->
|
||||
-> {presence(), ejabberd_c2s:state()}.
|
||||
update_presence({#presence{type = available} = Pres,
|
||||
#{jid := #jid{luser = LUser, lserver = LServer}} = State}) ->
|
||||
Hash = get_xupdate(LUser, LServer),
|
||||
Pres1 = xmpp:set_subtag(Pres, #vcard_xupdate{hash = Hash}),
|
||||
Pres1 = case get_xupdate(LUser, LServer) of
|
||||
undefined -> xmpp:remove_subtag(Pres, #vcard_xupdate{});
|
||||
XUpdate -> xmpp:set_subtag(Pres, XUpdate)
|
||||
end,
|
||||
{Pres1, State};
|
||||
update_presence(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec vcard_set(binary(), binary(), xmlel()) -> ok.
|
||||
vcard_set(LUser, LServer, _VCARD) ->
|
||||
-spec user_send_packet({presence(), ejabberd_c2s:state()})
|
||||
-> {presence(), ejabberd_c2s:state()}.
|
||||
user_send_packet({#presence{type = available,
|
||||
to = #jid{luser = U, lserver = S,
|
||||
lresource = <<"">>}},
|
||||
#{jid := #jid{luser = U, lserver = S}}} = Acc) ->
|
||||
%% This is processed by update_presence/2 explicitly, we don't
|
||||
%% want to call this multiple times for performance reasons
|
||||
Acc;
|
||||
user_send_packet(Acc) ->
|
||||
update_presence(Acc).
|
||||
|
||||
-spec vcard_set(iq()) -> iq().
|
||||
vcard_set(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
|
||||
ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}),
|
||||
ejabberd_sm:force_update_presence({LUser, LServer}).
|
||||
ejabberd_sm:force_update_presence({LUser, LServer}),
|
||||
IQ;
|
||||
vcard_set(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
remove_user(User, Server) ->
|
||||
@@ -91,7 +112,7 @@ remove_user(User, Server) ->
|
||||
%%====================================================================
|
||||
%% Storage
|
||||
%%====================================================================
|
||||
-spec get_xupdate(binary(), binary()) -> binary() | undefined.
|
||||
-spec get_xupdate(binary(), binary()) -> vcard_xupdate() | undefined.
|
||||
get_xupdate(LUser, LServer) ->
|
||||
Result = case use_cache(LServer) of
|
||||
true ->
|
||||
@@ -102,11 +123,12 @@ get_xupdate(LUser, LServer) ->
|
||||
db_get_xupdate(LUser, LServer)
|
||||
end,
|
||||
case Result of
|
||||
{ok, Hash} -> Hash;
|
||||
error -> undefined
|
||||
{ok, external} -> undefined;
|
||||
{ok, Hash} -> #vcard_xupdate{hash = Hash};
|
||||
error -> #vcard_xupdate{}
|
||||
end.
|
||||
|
||||
-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error.
|
||||
-spec db_get_xupdate(binary(), binary()) -> {ok, binary() | external} | error.
|
||||
db_get_xupdate(LUser, LServer) ->
|
||||
case mod_vcard:get_vcard(LUser, LServer) of
|
||||
[VCard] ->
|
||||
@@ -147,17 +169,21 @@ use_cache(Host) ->
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host)).
|
||||
|
||||
-spec compute_hash(xmlel()) -> binary().
|
||||
-spec compute_hash(xmlel()) -> binary() | external.
|
||||
compute_hash(VCard) ->
|
||||
case fxml:get_path_s(VCard,
|
||||
[{elem, <<"PHOTO">>},
|
||||
{elem, <<"BINVAL">>},
|
||||
cdata]) of
|
||||
<<>> ->
|
||||
case fxml:get_subtag(VCard, <<"PHOTO">>) of
|
||||
false ->
|
||||
<<>>;
|
||||
BinVal ->
|
||||
try str:sha(base64:decode(BinVal))
|
||||
catch _:badarg -> <<>>
|
||||
Photo ->
|
||||
try xmpp:decode(Photo, ?NS_VCARD, []) of
|
||||
#vcard_photo{binval = <<_, _/binary>> = BinVal} ->
|
||||
str:sha(BinVal);
|
||||
#vcard_photo{extval = <<_, _/binary>>} ->
|
||||
external;
|
||||
_ ->
|
||||
<<>>
|
||||
catch _:{xmpp_codec, _} ->
|
||||
<<>>
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user