diff options
-rw-r--r-- | Dockerfile | 2 | ||||
-rw-r--r-- | README | 5 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | mix.exs | 2 | ||||
-rw-r--r-- | mix.lock | 20 | ||||
-rw-r--r-- | rebar.config | 3 | ||||
-rw-r--r-- | src/ejabberd_auth.erl | 4 | ||||
-rw-r--r-- | src/ejabberd_auth_mnesia.erl | 2 | ||||
-rw-r--r-- | src/ejabberd_http_ws.erl | 2 | ||||
-rw-r--r-- | src/ejabberd_logger.erl | 3 | ||||
-rw-r--r-- | src/ejabberd_s2s.erl | 30 | ||||
-rw-r--r-- | src/ejabberd_sm.erl | 26 | ||||
-rw-r--r-- | src/ejd2sql.erl | 5 | ||||
-rw-r--r-- | src/ext_mod.erl | 68 | ||||
-rw-r--r-- | src/mod_http_api.erl | 12 | ||||
-rw-r--r-- | src/mod_http_fileserver.erl | 16 | ||||
-rw-r--r-- | src/mod_mam.erl | 3 | ||||
-rw-r--r-- | src/mod_muc.erl | 3 | ||||
-rw-r--r-- | src/mod_muc_riak.erl | 2 | ||||
-rw-r--r-- | src/mod_muc_room.erl | 2 | ||||
-rw-r--r-- | src/mod_pubsub.erl | 41 | ||||
-rw-r--r-- | src/mod_roster.erl | 8 | ||||
-rw-r--r-- | src/node_pep.erl | 2 | ||||
-rw-r--r-- | src/node_pep_sql.erl | 2 | ||||
-rw-r--r-- | src/pubsub_db_sql.erl | 94 | ||||
-rw-r--r-- | test/ejabberd_SUITE_data/ejabberd.yml | 8 | ||||
-rwxr-xr-x | test/ejabberd_SUITE_data/extauth.py | 26 | ||||
-rw-r--r-- | test/muc_tests.erl | 29 |
28 files changed, 314 insertions, 110 deletions
diff --git a/Dockerfile b/Dockerfile index 6de6f5783..e5f3d78f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ @@ -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 diff --git a/configure.ac b/configure.ac index 884db5d4e..edf54722c 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) @@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do def project do [app: :ejabberd, - version: "17.6.0", + version: "17.8.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]}]}} diff --git a/rebar.config b/rebar.config index 58a8e80f4..160185511 100644 --- a/rebar.config +++ b/rebar.config @@ -28,7 +28,7 @@ {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}}, {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.14"}}}}, {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.15"}}}}, @@ -71,6 +71,7 @@ p1_utils, p1_mysql, p1_pgsql, + p1_oauth2, epam, ezlib, iconv]}}. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 251e36ff7..b34925ff0 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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; diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 9c2152578..690152674 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -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 diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index f4a73cc39..2c44d6552 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -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) -> diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl index 1e48732c8..eee9d3b83 100644 --- a/src/ejabberd_logger.erl +++ b/src/ejabberd_logger.erl @@ -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 diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 4b74b8c4a..a0e9411cf 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -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_all_connections, tags = [s2s], + desc = "Stop all outgoing and incoming connections", + policy = admin, + module = ?MODULE, function = stop_all_connections, + args = [], result = {res, rescode}}]. %% TODO Move those stats commands to ejabberd stats command ? incoming_s2s_number() -> diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 302cfded4..f7f7447bf 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -975,30 +975,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()]. diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index c4f00a551..c801eb973 100644 --- a/src/ejd2sql.erl +++ b/src/ejd2sql.erl @@ -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). diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 86bd21983..ecdfa04c3 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -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()). diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index 7be05dbf1..ef881d14b 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -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), diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index c2144042e..4e3cfd08b 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -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, diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 30dd7dd57..674cefc05 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -999,6 +999,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}}]. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 69fc2d0dc..8b6d7b8b9 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -166,7 +166,7 @@ restore_room(ServerHost, Host, Name) -> forget_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), - ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]), + ejabberd_hooks:run(destroy_room, LServer, [LServer, Name, Host]), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). @@ -256,6 +256,7 @@ 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}) -> diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl index 8dd1eddf8..42e644fdd 100644 --- a/src/mod_muc_riak.erl +++ b/src/mod_muc_riak.erl @@ -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, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index ec1cffd6a..31dbbbfa7 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -4004,6 +4004,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 +4012,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(). diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 1a620cb6b..d682eb5f3 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -89,7 +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]). + terminate/2, code_change/3, depends/2, export/1]). -export([send_loop/1, mod_opt_type/1]). @@ -546,13 +546,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 +584,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 +617,7 @@ disco_items(Host, <<>>, From) -> jid = jid:make(Host), name = case get_option(Options, title) of false -> <<>>; - [Title] -> Title + Title -> Title end} | Acc]; _ -> Acc @@ -679,16 +676,14 @@ caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = presence(Host, {presence, U, S, [R], JID}). -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) -> +presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) -> + presence(S, {presence, From, Pid}), + presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To}); +presence_probe(_From, _To, _Pid) -> %% ignore presence_probe from remote contacts, %% those are handled via caps_add ok. @@ -1723,7 +1718,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), @@ -2152,9 +2147,14 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]). get_last_items(Host, Type, Nidx, LJID, Count) -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of - {result, Items} -> Items; - _ -> [] + case get_cached_item(Host, Nidx) of + undefined -> + case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + {result, Items} -> Items; + _ -> [] + end; + LastItem -> + [LastItem] end. %% @doc <p>Resend the items of a node to the user.</p> @@ -3871,6 +3871,9 @@ 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; diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 28bc06171..cb9b0f7bf 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -35,7 +35,6 @@ -module(mod_roster). -protocol({xep, 237, '1.3'}). --protocol({xep, 321, '0.1'}). -author('alexey@process-one.net'). @@ -444,11 +443,10 @@ decode_item(Item, R, Managed) -> 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]. diff --git a/src/node_pep.erl b/src/node_pep.erl index 6dd7a68c4..cc0dd41fb 100644 --- a/src/node_pep.erl +++ b/src/node_pep.erl @@ -119,7 +119,7 @@ create_node(Nidx, Owner) -> delete_node(Nodes) -> {result, {_, _, Result}} = node_flat:delete_node(Nodes), - {result, {[], Result}}. + {result, {default, Result}}. subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index 68a2ce946..b84c945bd 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -73,7 +73,7 @@ create_node(Nidx, Owner) -> delete_node(Nodes) -> {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes), - {result, {[], Result}}. + {result, {default, Result}}. subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl index ae7a9fde6..ae28184db 100644 --- a/src/pubsub_db_sql.erl +++ b/src/pubsub_db_sql.erl @@ -25,12 +25,16 @@ -module(pubsub_db_sql). +-compile([{parse_transform, ejabberd_sql_pt}]). + -author("pablo.polvorin@process-one.net"). -include("pubsub.hrl"). +-include("ejabberd_sql_pt.hrl"). -export([add_subscription/1, read_subscription/1, delete_subscription/1, update_subscription/1]). +-export([export/1]). %% TODO: Those -spec lines produce errors in old Erlang versions. %% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. @@ -139,3 +143,93 @@ sql_to_integer(N) -> binary_to_integer(N). sql_to_boolean(B) -> B == <<"1">>. sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T). + +%% REVIEW: +%% * this code takes NODEID from Itemid2, and forgets about Nodeidx +%% * this code assumes Payload only contains one xmlelement() +%% * PUBLISHER is taken from Creation +export(_Server) -> + [{pubsub_item, + fun(_Host, #pubsub_item{itemid = {Itemid1, NODEID}, + %nodeidx = _Nodeidx, + creation = {{C1, C2, C3}, Cusr}, + modification = {{M1, M2, M3}, _Musr}, + payload = Payload}) -> + ITEMID = ejabberd_sql:escape(Itemid1), + CREATION = ejabberd_sql:escape(list_to_binary( + string:join([string:right(integer_to_list(I),6,$0)||I<-[C1,C2,C3]],":"))), + MODIFICATION = ejabberd_sql:escape(list_to_binary( + string:join([string:right(integer_to_list(I),6,$0)||I<-[M1,M2,M3]],":"))), + PUBLISHER = ejabberd_sql:escape(jid:encode(Cusr)), + [PayloadEl] = [El || {xmlel,_,_,_} = El <- Payload], + PAYLOAD = ejabberd_sql:escape(fxml:element_to_binary(PayloadEl)), + [?SQL("delete from pubsub_item where itemid=%(ITEMID)s;"), + ?SQL("insert into pubsub_item(itemid,nodeid,creation,modification,publisher,payload) \n" + " values (%(ITEMID)s, %(NODEID)d, %(CREATION)s, + %(MODIFICATION)s, %(PUBLISHER)s, %(PAYLOAD)s);")]; + (_Host, _R) -> + [] + end}, +%% REVIEW: +%% * From the mnesia table, the #pubsub_state.items is not used in ODBC +%% * Right now AFFILIATION is the first letter of Affiliation +%% * Right now SUBSCRIPTIONS expects only one Subscription +%% * Right now SUBSCRIPTIONS letter is the first letter of Subscription + {pubsub_state, + fun(_Host, #pubsub_state{stateid = {Jid, Stateid}, + %nodeidx = Nodeidx, + items = _Items, + affiliation = Affiliation, + subscriptions = Subscriptions}) -> + STATEID = list_to_binary(integer_to_list(Stateid)), + JID = ejabberd_sql:escape(jid:encode(Jid)), + NODEID = <<"unknown">>, %% TODO: integer_to_list(Nodeidx), + AFFILIATION = list_to_binary(string:substr(atom_to_list(Affiliation),1,1)), + SUBSCRIPTIONS = list_to_binary(parse_subscriptions(Subscriptions)), + [?SQL("delete from pubsub_state where stateid=%(STATEID)s;"), + ?SQL("insert into pubsub_state(stateid,jid,nodeid,affiliation,subscriptions)\n" + " values (%(STATEID)s, %(JID)s, %(NODEID)s, %(AFFILIATION)s, %(SUBSCRIPTIONS)s);")]; + (_Host, _R) -> + [] + end}, + +%% REVIEW: +%% * Parents is not migrated to PARENTs +%% * Probably some option VALs are not correctly represented in mysql + {pubsub_node, + fun(_Host, #pubsub_node{nodeid = {Hostid, Nodeid}, + id = Id, + parents = _Parents, + type = Type, + owners = Owners, + options = Options}) -> + HOST = case Hostid of + {U,S,R} -> ejabberd_sql:escape(jid:encode({U,S,R})); + _ -> ejabberd_sql:escape(Hostid) + end, + NODE = ejabberd_sql:escape(Nodeid), + PARENT = <<"">>, + IdB = integer_to_binary(Id), + TYPE = ejabberd_sql:escape(<<Type/binary, "_odbc">>), + [?SQL("delete from pubsub_node where nodeid=%(Id)d;"), + ?SQL("insert into pubsub_node(host,node,nodeid,parent,type) \n" + " values (%(HOST)s, %(NODE)s, %(Id)d, %(PARENT)s, %(TYPE)s);"), + ?SQL("delete from pubsub_node_option where nodeid=%(Id)d;"), + [["insert into pubsub_node_option(nodeid,name,val)\n" + " values (", IdB, ", '", atom_to_list(Name), "', '", + io_lib:format("~p", [Val]), "');\n"] || {Name,Val} <- Options], + ?SQL("delete from pubsub_node_owner where nodeid=%(Id)d;"), + [["insert into pubsub_node_owner(nodeid,owner)\n" + " values (", IdB, ", '", jid:encode(Usr), "');\n"] || Usr <- Owners],"\n"]; + (_Host, _R) -> + [] + end}]. + +parse_subscriptions([]) -> + ""; +parse_subscriptions([{State, Item}]) -> + STATE = case State of + subscribed -> "s" + end, + string:join([STATE, Item],":"). + diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 74cabf584..a648cb422 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -458,6 +458,8 @@ listen: port: @@web_port@@ module: ejabberd_http captcha: true + request_handlers: + "/api": mod_http_api - port: @@component_port@@ module: ejabberd_service @@ -474,6 +476,7 @@ modules: mod_proxy65: [] mod_legacy: [] mod_muc: [] + mod_muc_admin: [] mod_register: welcome_message: subject: "Welcome!" @@ -496,3 +499,8 @@ outgoing_s2s_port: @@s2s_port@@ shaper: fast: 50000 normal: 10000 + +api_permissions: + "public commands": + who: all + what: "*" diff --git a/test/ejabberd_SUITE_data/extauth.py b/test/ejabberd_SUITE_data/extauth.py index fa2c9efd0..263d6464e 100755 --- a/test/ejabberd_SUITE_data/extauth.py +++ b/test/ejabberd_SUITE_data/extauth.py @@ -3,23 +3,27 @@ import struct def read(): (pkt_size,) = struct.unpack('>H', sys.stdin.read(2)) - pkt = sys.stdin.read(pkt_size).split(':') - cmd = pkt[0] - args_num = len(pkt) - 1 - if cmd == 'auth' and args_num >= 3: - if pkt[1] == "wrong": + pkt = sys.stdin.read(pkt_size) + cmd = pkt.split(':')[0] + if cmd == 'auth': + u, s, p = pkt.split(':', 3)[1:] + if u == "wrong": write(False) else: write(True) - elif cmd == 'isuser' and args_num == 2: + elif cmd == 'isuser': + u, s = pkt.split(':', 2)[1:] + elif cmd == 'setpass': + u, s, p = pkt.split(':', 3)[1:] write(True) - elif cmd == 'setpass' and args_num >= 3: + elif cmd == 'tryregister': + u, s, p = pkt.split(':', 3)[1:] write(True) - elif cmd == 'tryregister' and args_num >= 3: + elif cmd == 'removeuser': + u, s = pkt.split(':', 2)[1:] write(True) - elif cmd == 'removeuser' and args_num == 2: - write(True) - elif cmd == 'removeuser3' and args_num >= 3: + elif cmd == 'removeuser3': + u, s, p = pkt.split(':', 3)[1:] write(True) else: write(False) diff --git a/test/muc_tests.erl b/test/muc_tests.erl index 9ded2e0fe..bcceb6938 100644 --- a/test/muc_tests.erl +++ b/test/muc_tests.erl @@ -53,7 +53,8 @@ single_cases() -> single_test(service_vcard), single_test(configure_non_existent), single_test(cancel_configure_non_existent), - single_test(service_subscriptions)]}. + single_test(service_subscriptions), + single_test(set_room_affiliation)]}. service_presence_error(Config) -> Service = muc_jid(Config), @@ -242,6 +243,32 @@ service_subscriptions(Config) -> end, Rooms), disconnect(Config). +set_room_affiliation(Config) -> + #jid{server = RoomService} = muc_jid(Config), + RoomName = <<"set_room_affiliation">>, + RoomJID = jid:make(RoomName, RoomService), + MyJID = my_jid(Config), + PeerJID = jid:remove_resource(?config(slave, Config)), + + ct:pal("joining room ~p", [RoomJID]), + ok = join_new(Config, RoomJID), + + ct:pal("setting affiliation in room ~p to 'member' for ~p", [RoomJID, PeerJID]), + ServerHost = ?config(server_host, Config), + WebPort = ct:get_config(web_port, 5280), + RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation", + Headers = [{"X-Admin", "true"}], + ContentType = "application/json", + Body = jiffy:encode(#{name => RoomName, service => RoomService, jid => jid:encode(PeerJID), affiliation => member}), + {ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []), + + #message{id = _, from = RoomJID, to = MyJID, sub_els = [ + #muc_user{items = [ + #muc_item{affiliation = member, role = none, jid = PeerJID}]}]} = recv_message(Config), + + ok = leave(Config, RoomJID), + disconnect(Config). + %%%=================================================================== %%% Master-slave tests %%%=================================================================== |