aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in5
-rw-r--r--mix.exs62
-rw-r--r--mix.lock13
-rw-r--r--priv/msgs/cs.msg5
-rw-r--r--priv/msgs/cs.po8
-rw-r--r--rebar.config1
-rw-r--r--src/acl.erl40
-rw-r--r--src/adhoc.erl12
-rw-r--r--src/cyrsasl_scram.erl4
-rw-r--r--src/ejabberd.erl4
-rw-r--r--src/ejabberd_access_permissions.erl543
-rw-r--r--src/ejabberd_admin.erl3
-rw-r--r--src/ejabberd_app.erl1
-rw-r--r--src/ejabberd_auth_mnesia.erl2
-rw-r--r--src/ejabberd_auth_riak.erl2
-rw-r--r--src/ejabberd_auth_sql.erl2
-rw-r--r--src/ejabberd_c2s.erl9
-rw-r--r--src/ejabberd_commands.erl53
-rw-r--r--src/ejabberd_config.erl36
-rw-r--r--src/ejabberd_ctl.erl13
-rw-r--r--src/ejabberd_oauth.erl25
-rw-r--r--src/ejabberd_s2s_out.erl3
-rw-r--r--src/ejabberd_service.erl4
-rw-r--r--src/ejabberd_sm.erl14
-rw-r--r--src/ejabberd_sql.erl6
-rw-r--r--src/ejabberd_web_admin.erl4
-rw-r--r--src/ejabberd_xmlrpc.erl80
-rw-r--r--src/extauth.erl3
-rw-r--r--src/gen_mod.erl39
-rw-r--r--src/mod_admin_extra.erl44
-rw-r--r--src/mod_echo.erl2
-rw-r--r--src/mod_http_api.erl195
-rw-r--r--src/mod_mam.erl5
-rw-r--r--src/mod_muc.erl44
-rw-r--r--src/mod_muc_admin.erl10
-rw-r--r--src/mod_muc_room.erl7
-rw-r--r--src/mod_offline_sql.erl2
-rw-r--r--src/mod_privacy_sql.erl2
-rw-r--r--src/mod_stats.erl9
-rw-r--r--src/node_flat_sql.erl6
-rw-r--r--src/randoms.erl19
-rw-r--r--test/ejabberd_admin_test.exs1
-rw-r--r--test/ejabberd_commands_mock_test.exs1
-rw-r--r--test/ejabberd_commands_test.exs1
-rw-r--r--test/mod_admin_extra_test.exs1
-rw-r--r--test/mod_http_api_mock_test.exs13
-rw-r--r--test/mod_http_api_test.exs13
47 files changed, 1049 insertions, 322 deletions
diff --git a/Makefile.in b/Makefile.in
index eb1474926..18c611e96 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -207,6 +207,11 @@ install: all copy-files
> ejabberd.init
chmod 755 ejabberd.init
#
+ # Service script
+ $(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*" ejabberd.service.template \
+ > ejabberd.service
+ chmod 755 ejabberd.service
+ #
# Spool directory
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
diff --git a/mix.exs b/mix.exs
index ee4b60fb2..c77f2abb4 100644
--- a/mix.exs
+++ b/mix.exs
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
- version: "16.08.0",
+ version: "16.11.0",
description: description,
elixir: "~> 1.2",
elixirc_paths: ["lib"],
@@ -17,7 +17,7 @@ defmodule Ejabberd.Mixfile do
deps: deps]
end
- defp description do
+ def description do
"""
Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform.
"""
@@ -28,9 +28,8 @@ defmodule Ejabberd.Mixfile do
applications: [:ssl],
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml,
- :stun, :fast_yaml, :ezlib, :iconv,
- :esip, :jiffy, :p1_oauth2, :eredis,
- :p1_mysql, :p1_pgsql, :sqlite3]]
+ :stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
+ ++ cond_apps]
end
defp erlc_options do
@@ -51,22 +50,40 @@ defmodule Ejabberd.Mixfile do
{:esip, "~> 1.0"},
{:jiffy, "~> 0.14.7"},
{:p1_oauth2, "~> 0.6.1"},
- {:p1_mysql, "~> 1.0"},
- {:p1_pgsql, "~> 1.1"},
- {:sqlite3, "~> 1.1"},
- {:ezlib, "~> 1.0"},
- {:iconv, "~> 1.0"},
- {:eredis, "~> 1.0"},
{:exrm, "~> 1.0.0", only: :dev},
# relx is used by exrm. Lock version as for now, ejabberd doesn not compile fine with
# version 3.20:
{:relx, "~> 3.21", only: :dev},
- {:ex_doc, ">= 0.0.0", only: :dev},
- {:meck, "~> 0.8.4", only: :test},
- {:moka, github: "processone/moka", tag: "1.0.5c", only: :test}]
+ {:ex_doc, ">= 0.0.0", only: :dev}]
+ ++ cond_deps
end
- defp package do
+ defp cond_deps do
+ for {:true, dep} <- [{config(:mysql), {:p1_mysql, "~> 1.0"}},
+ {config(:pgsql), {:p1_pgsql, "~> 1.1"}},
+ {config(:sqlite), {:sqlite3, "~> 1.1"}},
+ {config(:riak), {:riakc, "~> 2.4"}},
+ {config(:redis), {:eredis, "~> 1.0"}},
+ {config(:zlib), {:ezlib, "~> 1.0"}},
+ {config(:iconv), {:iconv, "~> 1.0"}},
+ {config(:pam), {:p1_pam, "~> 1.0"}},
+ {config(:tools), {:luerl, github: "rvirding/luerl", tag: "v0.2"}},
+ {config(:tools), {:meck, "~> 0.8.4"}},
+ {config(:tools), {:moka, github: "processone/moka", tag: "1.0.5c"}}], do:
+ dep
+ end
+
+ defp cond_apps do
+ for {:true, app} <- [{config(:redis), :eredis},
+ {config(:mysql), :p1_mysql},
+ {config(:pgsql), :p1_pgsql},
+ {config(:sqlite), :sqlite3},
+ {config(:zlib), :ezlib},
+ {config(:iconv), :iconv}], do:
+ app
+ end
+
+ def package do
[# These are the default files included in the package
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
maintainers: ["ProcessOne"],
@@ -76,6 +93,21 @@ defmodule Ejabberd.Mixfile do
"Source" => "https://github.com/processone/ejabberd",
"ProcessOne" => "http://www.process-one.net/"}]
end
+
+ def vars do
+ case :file.consult("vars.config") do
+ {:ok,config} -> config
+ _ -> [zlib: true, iconv: true]
+ end
+ end
+
+ defp config(key) do
+ case vars[key] do
+ nil -> false
+ value -> value
+ end
+ end
+
end
defmodule Mix.Tasks.Compile.Asn1 do
diff --git a/mix.lock b/mix.lock
index fc2cdc924..e515fd346 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,11 +1,10 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
"cache_tab": {:hex, :cache_tab, "1.0.4", "3fd2b1ab40c36e7830a4e09e836c6b0fa89191cd4e5fd471873e4eb42f5cd37c", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
- "earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
- "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
+ "earmark": {:hex, :earmark, "1.0.2", "a0b0904d74ecc14da8bd2e6e0248e1a409a2bc91aade75fcf428125603de3853", [:mix], []},
"erlware_commons": {:hex, :erlware_commons, "0.21.0", "a04433071ad7d112edefc75ac77719dd3e6753e697ac09428fc83d7564b80b15", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
"esip": {:hex, :esip, "1.0.8", "69885a6c07964aabc6c077fe1372aa810a848bd3d9a415b160dabdce9c7a79b5", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}, {:stun, "1.0.7", [hex: :stun, optional: false]}]},
- "ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
+ "ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"exrm": {:hex, :exrm, "1.0.8", "5aa8990cdfe300282828b02cefdc339e235f7916388ce99f9a1f926a9271a45d", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
"fast_tls": {:hex, :fast_tls, "1.0.7", "9b72ecfcdcad195ab072c196fab8334f49d8fea76bf1a51f536d69e7527d902a", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
@@ -16,15 +15,9 @@
"iconv": {:hex, :iconv, "1.0.2", "a0792f06ab4b5ea1b5bb49789405739f1281a91c44cf3879cb70e4d777666217", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:rebar, :make], []},
"lager": {:hex, :lager, "3.2.1", "eef4e18b39e4195d37606d9088ea05bf1b745986cf8ec84f01d332456fe88d17", [:rebar3], [{:goldrush, "0.1.8", [hex: :goldrush, optional: false]}]},
- "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []},
- "moka": {:git, "https://github.com/processone/moka.git", "3eed3a6dd7dedb70a6cd18f86c7561a18626eb3b", [tag: "1.0.5c"]},
- "p1_mysql": {:hex, :p1_mysql, "1.0.1", "d2be1cfc71bb4f1391090b62b74c3f5cb8e7a45b0076b8cb290cd6b2856c581b", [:rebar3], []},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
- "p1_pgsql": {:hex, :p1_pgsql, "1.1.0", "ca525c42878eac095e5feb19563acc9915c845648f48fdec7ba6266c625d4ac7", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [:rebar3], []},
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]},
- "relx": {:hex, :relx, "3.21.0", "91e1ea9f09b4edfda8461901f4b5c5e0226e43ec161e147eeab29f7761df6eb5", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.21.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]},
- "samerlib": {:git, "https://github.com/processone/samerlib", "fbbba035b1548ac4e681df00d61bf609645333a0", [tag: "0.8.0c"]},
- "sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
+ "relx": {:hex, :relx, "3.21.1", "f989dc520730efd9075e9f4debcb8ba1d7d1e86b018b0bcf45a2eb80270b4ad6", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.21.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]},
"stringprep": {:hex, :stringprep, "1.0.6", "1cf1c439eb038aa590da5456e019f86afbfbfeb5a2d37b6e5f873041624c6701", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.7", "904dc6f26a3c30c54881c4c3003699f2a4968067ee6b3aecdf9895aad02df75e", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]}}
diff --git a/priv/msgs/cs.msg b/priv/msgs/cs.msg
index f0c749887..01897aadf 100644
--- a/priv/msgs/cs.msg
+++ b/priv/msgs/cs.msg
@@ -144,7 +144,7 @@
{"Import Users from Dir at ","Importovat uživatele z adresáře na "}.
{"Import Users From jabberd14 Spool Files","Importovat uživatele z jabberd14 spool souborů"}.
{"Improper message type","Nesprávný typ zprávy"}.
-{"Incoming s2s Connections:",""}.
+{"Incoming s2s Connections:","Příchozí s2s spojení:"}.
{"Incorrect password","Nesprávné heslo"}.
{"Invalid affiliation: ~s","Neplatné přiřazení: ~s"}.
{"Invalid role: ~s","Neplatná role: ~s"}.
@@ -333,7 +333,6 @@
{"September",". září"}.
{"Server ~b","Server ~b"}.
{"Server:","Server:"}.
-{"Server","Server"}.
{"Set message of the day and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Set message of the day on all hosts and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Shared Roster Groups","Skupiny pro sdílený seznam kontaktů"}.
@@ -409,10 +408,10 @@
{"User JID","Jabber ID uživatele"}.
{"User Management","Správa uživatelů"}.
{"Username:","Uživatelské jméno:"}.
-{"User ~s",""}.
{"Users are not allowed to register accounts so quickly","Je zakázáno registrovat účty v tak rychlém sledu"}.
{"Users Last Activity","Poslední aktivita uživatele"}.
{"Users","Uživatelé"}.
+{"User ~s","Uživatel ~s"}.
{"User","Uživatel"}.
{"Validate","Ověřit"}.
{"vCard User Search","Hledání uživatelů podle vizitek"}.
diff --git a/priv/msgs/cs.po b/priv/msgs/cs.po
index 81c60756e..4f06621ac 100644
--- a/priv/msgs/cs.po
+++ b/priv/msgs/cs.po
@@ -242,9 +242,7 @@ msgstr "Odchozí s2s spojení:"
#: ejabberd_web_admin.erl:1559
msgid "Incoming s2s Connections:"
-msgstr ""
-"Příchozí\n"
-" s2s spojení:"
+msgstr "Příchozí s2s spojení:"
#: ejabberd_web_admin.erl:1595 ejabberd_web_admin.erl:1794
#: ejabberd_web_admin.erl:1804 ejabberd_web_admin.erl:2214 mod_roster.erl:1429
@@ -258,9 +256,7 @@ msgstr "Změnit heslo"
#: ejabberd_web_admin.erl:1673
msgid "User ~s"
-msgstr ""
-"Uživatel\n"
-" ~s"
+msgstr "Uživatel ~s"
#: ejabberd_web_admin.erl:1684
msgid "Connected Resources:"
diff --git a/rebar.config b/rebar.config
index 434c16af3..27439109b 100644
--- a/rebar.config
+++ b/rebar.config
@@ -71,6 +71,7 @@
{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_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
{if_var_true, hipe, native},
{src_dirs, [asn1, src,
{if_var_true, tools, tools},
diff --git a/src/acl.erl b/src/acl.erl
index 349198182..1476081dd 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -36,7 +36,8 @@
acl_rule_verify/1, access_matches/3,
transform_access_rules_config/1,
parse_ip_netmask/1,
- access_rules_validator/1, shaper_rules_validator/1]).
+ access_rules_validator/1, shaper_rules_validator/1,
+ normalize_spec/1, resolve_access/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -437,30 +438,35 @@ acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
acl_rule_matches(_ACL, _Data, _Host) ->
false.
--spec access_matches(atom()|list(), any(), global|binary()) -> any().
-access_matches(all, _Data, _Host) ->
- allow;
-access_matches(none, _Data, _Host) ->
- deny;
-access_matches(Name, Data, Host) when is_atom(Name) ->
+resolve_access(all, _Host) ->
+ all;
+resolve_access(none, _Host) ->
+ none;
+resolve_access(Name, Host) when is_atom(Name) ->
GAccess = mnesia:dirty_read(access, {Name, global}),
LAccess =
- if Host /= global -> mnesia:dirty_read(access, {Name, Host});
- true -> []
- end,
+ if Host /= global -> mnesia:dirty_read(access, {Name, Host});
+ true -> []
+ end,
case GAccess ++ LAccess of
[] ->
- deny;
+ [];
AccessList ->
- Rules = lists:flatmap(
+ lists:flatmap(
fun(#access{rules = Rs}) ->
Rs
- end, AccessList),
- access_rules_matches(Rules, Data, Host)
+ end, AccessList)
end;
-access_matches(Rules, Data, Host) when is_list(Rules) ->
- access_rules_matches(Rules, Data, Host).
-
+resolve_access(Rules, _Host) when is_list(Rules) ->
+ Rules.
+
+-spec access_matches(atom()|list(), any(), global|binary()) -> allow|deny.
+access_matches(Rules, Data, Host) ->
+ case resolve_access(Rules, Host) of
+ all -> allow;
+ none -> deny;
+ RRules -> access_rules_matches(RRules, Data, Host)
+ end.
-spec access_rules_matches(list(), any(), global|binary()) -> any().
diff --git a/src/adhoc.erl b/src/adhoc.erl
index 6970584f9..23ffd8dd8 100644
--- a/src/adhoc.erl
+++ b/src/adhoc.erl
@@ -112,9 +112,17 @@ produce_response(
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
true -> jlib:now_to_utc_string(p1_time_compat:timestamp())
end,
- case Actions of
- [] ->
+ case {Actions, Status} of
+ {[], completed} ->
ActionsEls = [];
+ {[], _} ->
+ ActionsEls = [
+ #xmlel{
+ name = <<"actions">>,
+ attrs = [{<<"execute">>, <<"complete">>}],
+ children = [#xmlel{name = <<"complete">>}]
+ }
+ ];
_ ->
case DefaultAction of
<<"">> -> ActionsElAttrs = [];
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index 18f52b48f..1c464e121 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -87,7 +87,7 @@ mech_step(#state{step = 2} = State, ClientIn) ->
if is_tuple(Ret) -> Ret;
true ->
TempSalt =
- crypto:rand_bytes(?SALT_LENGTH),
+ randoms:bytes(?SALT_LENGTH),
SaltedPassword =
scram:salted_password(Ret,
TempSalt,
@@ -101,7 +101,7 @@ mech_step(#state{step = 2} = State, ClientIn) ->
str:substr(ClientIn,
str:str(ClientIn, <<"n=">>)),
ServerNonce =
- jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
+ jlib:encode_base64(randoms:bytes(?NONCE_LENGTH)),
ServerFirstMessage =
iolist_to_binary(
["r=",
diff --git a/src/ejabberd.erl b/src/ejabberd.erl
index 6bd2422ae..5a6fc64d7 100644
--- a/src/ejabberd.erl
+++ b/src/ejabberd.erl
@@ -105,8 +105,6 @@ start_app([], _Type, _StartFlag) ->
ok.
check_app_modules(App, StartFlag) ->
- {A, B, C} = p1_time_compat:timestamp(),
- random:seed(A, B, C),
sleep(5000),
case application:get_key(App, modules) of
{ok, Mods} ->
@@ -140,7 +138,7 @@ exit_or_halt(Reason, StartFlag) ->
end.
sleep(N) ->
- timer:sleep(random:uniform(N)).
+ timer:sleep(randoms:uniform(N)).
get_module_file(App, Mod) ->
BaseName = atom_to_list(Mod),
diff --git a/src/ejabberd_access_permissions.erl b/src/ejabberd_access_permissions.erl
new file mode 100644
index 000000000..7ce75aa9c
--- /dev/null
+++ b/src/ejabberd_access_permissions.erl
@@ -0,0 +1,543 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_access_permissions.erl
+%%% Author : Paweł Chmielowski <pawel@process-one.net>
+%%% Purpose : Administrative functions and commands
+%%% Created : 7 Sep 2016 by Paweł Chmielowski <pawel@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 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_access_permissions).
+-author("pawel@process-one.net").
+
+-include("ejabberd_commands.hrl").
+-include("logger.hrl").
+
+-behaviour(gen_server).
+-behavior(ejabberd_config).
+
+%% API
+-export([start_link/0,
+ parse_api_permissions/1,
+ can_access/2,
+ invalidate/0,
+ opt_type/1,
+ show_current_definitions/0,
+ register_permission_addon/2,
+ unregister_permission_addon/1]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {
+ definitions = none,
+ fragments_generators = []
+}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec can_access(atom(), map()) -> allow | deny.
+can_access(Cmd, CallerInfo) ->
+ gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}).
+
+-spec invalidate() -> ok.
+invalidate() ->
+ gen_server:cast(?MODULE, invalidate).
+
+-spec register_permission_addon(atom(), fun()) -> ok.
+register_permission_addon(Name, Fun) ->
+ gen_server:call(?MODULE, {register_config_fragment_generator, Name, Fun}).
+
+-spec unregister_permission_addon(atom()) -> ok.
+unregister_permission_addon(Name) ->
+ gen_server:call(?MODULE, {unregister_config_fragment_generator, Name}).
+
+-spec show_current_definitions() -> any().
+show_current_definitions() ->
+ gen_server:call(?MODULE, show_current_definitions).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}.
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+-spec init(Args :: term()) ->
+ {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
+ {stop, Reason :: term()} | ignore.
+init([]) ->
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()},
+ State :: #state{}) ->
+ {reply, Reply :: term(), NewState :: #state{}} |
+ {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
+ {noreply, NewState :: #state{}} |
+ {noreply, NewState :: #state{}, timeout() | hibernate} |
+ {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
+ {stop, Reason :: term(), NewState :: #state{}}.
+handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
+ CallerModule = maps:get(caller_module, CallerInfo, none),
+ Host = maps:get(caller_host, CallerInfo, global),
+ {State2, Defs0} = get_definitions(State),
+ Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
+ Res = lists:foldl(
+ fun({Name, _} = Def, none) ->
+ case matches_definition(Def, Cmd, CallerModule, Host, CallerInfo) of
+ true ->
+ ?DEBUG("Command '~p' execution allowed by rule '~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
+ allow;
+ _ ->
+ none
+ end;
+ (_, Val) ->
+ Val
+ end, none, Defs),
+ Res2 = case Res of
+ allow -> allow;
+ _ ->
+ ?DEBUG("Command '~p' execution denied (CallerInfo=~p)", [Cmd, CallerInfo]),
+ deny
+ end,
+ {reply, Res2, State2};
+handle_call(show_current_definitions, _From, State) ->
+ {State2, Defs} = get_definitions(State),
+ {reply, Defs, State2};
+handle_call({register_config_fragment_generator, Name, Fun}, _From, #state{fragments_generators = Gens} = State) ->
+ NGens = lists:keystore(Name, 1, Gens, {Name, Fun}),
+ {reply, ok, State#state{fragments_generators = NGens}};
+handle_call({unregister_config_fragment_generator, Name}, _From, #state{fragments_generators = Gens} = State) ->
+ NGens = lists:keydelete(Name, 1, Gens),
+ {reply, ok, State#state{fragments_generators = NGens}};
+handle_call(_Request, _From, State) ->
+ {reply, ok, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_cast(Request :: term(), State :: #state{}) ->
+ {noreply, NewState :: #state{}} |
+ {noreply, NewState :: #state{}, timeout() | hibernate} |
+ {stop, Reason :: term(), NewState :: #state{}}.
+handle_cast(invalidate, State) ->
+ {noreply, State#state{definitions = none}};
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_info(Info :: timeout() | term(), State :: #state{}) ->
+ {noreply, NewState :: #state{}} |
+ {noreply, NewState :: #state{}, timeout() | hibernate} |
+ {stop, Reason :: term(), NewState :: #state{}}.
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+-spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
+ State :: #state{}) -> term().
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+-spec code_change(OldVsn :: term() | {down, term()}, State :: #state{},
+ Extra :: term()) ->
+ {ok, NewState :: #state{}} | {error, Reason :: term()}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+-spec get_definitions(#state{}) -> {#state{}, any()}.
+get_definitions(#state{definitions = Defs, fragments_generators = Gens} = State) ->
+ DefaultOptions = [{<<"console commands">>,
+ {[ejabberd_ctl],
+ [{acl, all}],
+ {all, none}}},
+ {<<"admin access">>,
+ {[],
+ [{acl, admin}],
+ {all, [start, stop]}}}],
+ NDefs = case Defs of
+ none ->
+ ApiPerms = ejabberd_config:get_option(api_permissions, fun(A) -> A end, DefaultOptions),
+ AllCommands = ejabberd_commands:get_commands_definition(),
+ Frags = lists:foldl(
+ fun({_Name, Generator}, Acc) ->
+ Acc ++ Generator()
+ end, [], Gens),
+ lists:map(
+ fun({Name, {From, Who, {Add, Del}}}) ->
+ Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
+ {Name, {From, Who, Cmds}}
+ end, ApiPerms ++ Frags);
+ V ->
+ V
+ end,
+ {State#state{definitions = NDefs}, NDefs}.
+
+matches_definition({_Name, {From, Who, What}}, Cmd, Module, Host, CallerInfo) ->
+ case What == all orelse lists:member(Cmd, What) of
+ true ->
+ case From == [] orelse lists:member(Module, From) of
+ true ->
+ Scope = maps:get(oauth_scope, CallerInfo, none),
+ lists:any(
+ fun({access, Access}) when Scope == none ->
+ acl:access_matches(Access, CallerInfo, Host) == allow;
+ ({acl, _} = Acl) when Scope == none ->
+ acl:acl_rule_matches(Acl, CallerInfo, Host);
+ ({oauth, Scopes, List}) when Scope /= none ->
+ case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
+ true ->
+ lists:any(
+ fun({access, Access}) ->
+ acl:access_matches(Access, CallerInfo, Host) == allow;
+ ({acl, _} = Acl) ->
+ acl:acl_rule_matches(Acl, CallerInfo, Host)
+ end, List);
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end, Who);
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+filter_commands_with_permissions(AllCommands, Add, Del) ->
+ CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
+ CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
+ lists:map(fun(#ejabberd_commands{name = N}) -> N end,
+ CommandsAdd -- CommandsDel).
+
+filter_commands_with_patterns([], _Patterns, Acc) ->
+ Acc;
+filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
+ case command_matches_patterns(C, Patterns) of
+ true ->
+ filter_commands_with_patterns(CRest, Patterns, [C | Acc]);
+ _ ->
+ filter_commands_with_patterns(CRest, Patterns, Acc)
+ end.
+
+command_matches_patterns(_, all) ->
+ true;
+command_matches_patterns(_, none) ->
+ false;
+command_matches_patterns(_, []) ->
+ false;
+command_matches_patterns(#ejabberd_commands{tags = Tags} = C, [{tag, Tag} | Tail]) ->
+ case lists:member(Tag, Tags) of
+ true ->
+ true;
+ _ ->
+ command_matches_patterns(C, Tail)
+ end;
+command_matches_patterns(#ejabberd_commands{name = Name}, [Name | _Tail]) ->
+ true;
+command_matches_patterns(C, [_ | Tail]) ->
+ command_matches_patterns(C, Tail).
+
+%%%===================================================================
+%%% Options parsing code
+%%%===================================================================
+
+parse_api_permissions(Data) when is_list(Data) ->
+ throw({replace_with, [parse_api_permission(Name, Args) || {Name, Args} <- Data]}).
+
+parse_api_permission(Name, Args) ->
+ {From, Who, What} = case key_split(Args, [{from, []}, {who, none}, {what, []}]) of
+ {error, Msg} ->
+ report_error(<<"~s inside api_permission '~s' section">>, [Msg, Name]);
+ Val -> Val
+ end,
+ {Name, {parse_from(Name, From), parse_who(Name, Who, oauth), parse_what(Name, What)}}.
+
+parse_from(_Name, Module) when is_atom(Module) ->
+ [Module];
+parse_from(Name, Modules) when is_list(Modules) ->
+ lists:foreach(fun(Module) when is_atom(Module) ->
+ ok;
+ (Val) ->
+ report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
+ [Val, Name])
+ end, Modules),
+ Modules;
+parse_from(Name, Val) ->
+ report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
+ [Val, Name]).
+
+parse_who(Name, Atom, ParseOauth) when is_atom(Atom) ->
+ parse_who(Name, [Atom], ParseOauth);
+parse_who(Name, Defs, ParseOauth) when is_list(Defs) ->
+ lists:map(
+ fun([{access, Val}]) ->
+ try acl:access_rules_validator(Val) of
+ Rule ->
+ {access, Rule}
+ catch
+ throw:{invalid_syntax, Msg} ->
+ report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>,
+ [Msg, Name]);
+ throw:{replace_with, NVal} ->
+ {access, NVal};
+ error:_ ->
+ report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>,
+ [Val, Name])
+ end;
+ ([{oauth, OauthList}]) when is_list(OauthList) ->
+ case ParseOauth of
+ oauth ->
+ Nested = parse_who(Name, lists:flatten(OauthList), scope),
+ {Scopes, Rest} = lists:partition(
+ fun({scope, _}) -> true;
+ (_) -> false
+ end, Nested),
+ case Scopes of
+ [] ->
+ report_error(<<"Oauth rule must contain at least one scope rule in 'who' section for api_permission '~s'">>,
+ [Name]);
+ _ ->
+ {oauth, lists:foldl(fun({scope, S}, A) -> S ++ A end, [], Scopes), Rest}
+ end;
+ scope ->
+ report_error(<<"Oauth rule can't be embeded inside other oauth rule in 'who' section for api_permission '~s'">>,
+ [Name])
+ end;
+ ({scope, ScopeList}) ->
+ case ParseOauth of
+ oauth ->
+ report_error(<<"Scope can be included only inside oauth rule in 'who' section for api_permission '~s'">>,
+ [Name]);
+ scope ->
+ ScopeList2 = case ScopeList of
+ V when is_binary(V) -> [V];
+ V2 when is_list(V2) -> V2;
+ V3 ->
+ report_error(<<"Invalid value for scope '~p' in 'who' section for api_permission '~s'">>,
+ [V3, Name])
+ end,
+ {scope, ScopeList2}
+ end;
+ (Atom) when is_atom(Atom) ->
+ {acl, Atom};
+ ([Other]) ->
+ try acl:normalize_spec(Other) of
+ Rule2 ->
+ {acl, Rule2}
+ catch
+ _:_ ->
+ report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
+ [Other, Name])
+ end;
+ (Invalid) ->
+ report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
+ [Invalid, Name])
+ end, Defs);
+parse_who(Name, Val, _ParseOauth) ->
+ report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
+ [Val, Name]).
+
+parse_what(Name, Binary) when is_binary(Binary) ->
+ parse_what(Name, [Binary]);
+parse_what(Name, Defs) when is_list(Defs) ->
+ {A, D} = lists:foldl(
+ fun(Def, {Add, Del}) ->
+ case parse_single_what(Def) of
+ {error, Err} ->
+ report_error(<<"~s used in value '~p' in 'what' section for api_permission '~s'">>,
+ [Err, Def, Name]);
+ all ->
+ {case Add of none -> none; _ -> all end, Del};
+ {neg, all} ->
+ {none, all};
+ {neg, Value} ->
+ {Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
+ Value ->
+ {case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
+ end
+ end, {[], []}, Defs),
+ case {A, D} of
+ {[], _} ->
+ {none, all};
+ {A2, []} ->
+ {A2, none};
+ V ->
+ V
+ end;
+parse_what(Name, Val) ->
+ report_error(<<"Invalid value '~p' used inside 'what' section for api_permission '~s'">>,
+ [Val, Name]).
+
+parse_single_what(<<"*">>) ->
+ all;
+parse_single_what(<<"!*">>) ->
+ {neg, all};
+parse_single_what(<<"!", Rest/binary>>) ->
+ case parse_single_what(Rest) of
+ {neg, _} ->
+ {error, <<"Double negation">>};
+ {error, _} = Err ->
+ Err;
+ V ->
+ {neg, V}
+ end;
+parse_single_what(<<"[tag:", Rest/binary>>) ->
+ case binary:split(Rest, <<"]">>) of
+ [TagName, <<"">>] ->
+ case parse_single_what(TagName) of
+ {error, _} = Err ->
+ Err;
+ V when is_atom(V) ->
+ {tag, V};
+ _ ->
+ {error, <<"Invalid tag">>}
+ end;
+ _ ->
+ {error, <<"Invalid tag">>}
+ end;
+parse_single_what(Binary) when is_binary(Binary) ->
+ case is_valid_command_name(Binary) of
+ true ->
+ binary_to_atom(Binary, latin1);
+ _ ->
+ {error, <<"Invalid value">>}
+ end;
+parse_single_what(_) ->
+ {error, <<"Invalid value">>}.
+
+is_valid_command_name(<<>>) ->
+ false;
+is_valid_command_name(Val) ->
+ is_valid_command_name2(Val).
+
+is_valid_command_name2(<<>>) ->
+ true;
+is_valid_command_name2(<<K:8, Rest/binary>>) when K >= $a andalso K =< $z orelse K == $_ ->
+ is_valid_command_name2(Rest);
+is_valid_command_name2(_) ->
+ false.
+
+key_split(Args, Fields) ->
+ {_, Order1, Results1, Required1} = lists:foldl(
+ fun({Field, Default}, {Idx, Order, Results, Required}) ->
+ {Idx + 1, maps:put(Field, Idx, Order), [Default | Results], Required};
+ (Field, {Idx, Order, Results, Required}) ->
+ {Idx + 1, maps:put(Field, Idx, Order), [none | Results], maps:put(Field, 1, Required)}
+ end, {1, #{}, [], #{}}, Fields),
+ key_split(Args, list_to_tuple(Results1), Order1, Required1, #{}).
+
+key_split([], _Results, _Order, Required, _Duplicates) when map_size(Required) > 0 ->
+ parse_error(<<"Missing fields '~s">>, [str:join(maps:keys(Required), <<", ">>)]);
+key_split([], Results, _Order, _Required, _Duplicates) ->
+ Results;
+key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) ->
+ case maps:find(Arg, Order) of
+ {ok, Idx} ->
+ case maps:is_key(Arg, Duplicates) of
+ false ->
+ Results2 = setelement(Idx, Results, Value),
+ key_split(Rest, Results2, Order, maps:remove(Arg, Required), maps:put(Arg, 1, Duplicates));
+ true ->
+ parse_error(<<"Duplicate field '~s'">>, [Arg])
+ end;
+ _ ->
+ parse_error(<<"Unknown field '~s'">>, [Arg])
+ end.
+
+report_error(Format, Args) ->
+ throw({invalid_syntax, iolist_to_binary(io_lib:format(Format, Args))}).
+
+parse_error(Format, Args) ->
+ {error, iolist_to_binary(io_lib:format(Format, Args))}.
+
+opt_type(api_permissions) ->
+ fun parse_api_permissions/1;
+opt_type(_) ->
+ [api_permissions].
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index d2145f211..412e2bbd0 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -404,7 +404,8 @@ registered_vhosts() ->
reload_config() ->
ejabberd_config:reload_file(),
acl:start(),
- shaper:start().
+ shaper:start(),
+ ejabberd_access_permissions:invalidate().
%%%
%%% Cluster management
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 33da45013..e4087142b 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -51,6 +51,7 @@ start(normal, _Args) ->
db_init(),
start(),
translate:start(),
+ ejabberd_access_permissions:start_link(),
ejabberd_ctl:init(),
ejabberd_commands:init(),
ejabberd_admin:start(),
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl
index 2a4554d15..f36c9fbc7 100644
--- a/src/ejabberd_auth_mnesia.erl
+++ b/src/ejabberd_auth_mnesia.erl
@@ -450,7 +450,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
- Salt = crypto:rand_bytes(?SALT_LENGTH),
+ Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index c74f1b28e..05add262e 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -270,7 +270,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
- Salt = crypto:rand_bytes(?SALT_LENGTH),
+ Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl
index d6d945e02..93dac4f4f 100644
--- a/src/ejabberd_auth_sql.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -406,7 +406,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
- Salt = crypto:rand_bytes(?SALT_LENGTH),
+ Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 226c5e0da..6068c85ef 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -2998,10 +2998,13 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
lists:foreach(
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
From_s = fxml:get_attr_s(<<"from">>, Attrs),
- From = jid:from_string(From_s),
To_s = fxml:get_attr_s(<<"to">>, Attrs),
- To = jid:from_string(To_s),
- F(From, To, El, Time)
+ case {jid:from_string(From_s), jid:from_string(To_s)} of
+ {#jid{} = From, #jid{} = To} ->
+ F(From, To, El, Time);
+ {_, _} ->
+ ?DEBUG("Dropping stanza due to invalid JID(s)", [])
+ end
end, queue:to_list(Queue))
end;
handle_unacked_stanzas(_StateData, _F) ->
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index d5649b2d7..8d74ad5a2 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -218,23 +218,26 @@
get_command_format/1,
get_command_format/2,
get_command_format/3,
- get_command_policy_and_scope/1,
+ get_command_policy_and_scope/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
get_tags_commands/1,
- get_exposed_commands/0,
+ get_exposed_commands/0,
register_commands/1,
- unregister_commands/1,
- expose_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
- ]).
+ opt_type/1,
+ get_commands_spec/0,
+ get_commands_definition/0,
+ get_commands_definition/1,
+ execute_command2/3,
+ execute_command2/4]).
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
@@ -280,7 +283,8 @@ init() ->
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
- register_commands(get_commands_spec()).
+ register_commands(get_commands_spec()),
+ ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0).
-spec register_commands([ejabberd_commands()]) -> ok.
@@ -296,7 +300,9 @@ register_commands(Commands) ->
mnesia:dirty_write(Command)
%% ?DEBUG("This command is already defined:~n~p", [Command])
end,
- Commands).
+ Commands),
+ ejabberd_access_permissions:invalidate(),
+ ok.
-spec unregister_commands([ejabberd_commands()]) -> ok.
@@ -306,7 +312,9 @@ unregister_commands(Commands) ->
fun(Command) ->
mnesia:dirty_delete_object(Command)
end,
- Commands).
+ Commands),
+ ejabberd_access_permissions:invalidate(),
+ ok.
%% @doc Expose command through ejabberd ReST API.
%% Pass a list of command names or policy to expose.
@@ -427,6 +435,9 @@ get_command_definition(Name, Version) ->
_E -> throw({error, unknown_command})
end.
+get_commands_definition() ->
+ get_commands_definition(?DEFAULT_VERSION).
+
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
% @doc Returns all commands for a given API version
@@ -448,6 +459,18 @@ get_commands_definition(Version) ->
end,
lists:foldl(F, [], L).
+execute_command2(Name, Arguments, CallerInfo) ->
+ execute_command2(Name, Arguments, CallerInfo, ?DEFAULT_VERSION).
+
+execute_command2(Name, Arguments, CallerInfo, Version) ->
+ Command = get_command_definition(Name, Version),
+ case ejabberd_access_permissions:can_access(Name, CallerInfo) of
+ allow ->
+ do_execute_command(Command, Arguments);
+ _ ->
+ throw({error, access_rules_unauthorized})
+ end.
+
%% @spec (Name::atom(), Arguments) -> ResultTerm
%% where
%% Arguments = [any()]
@@ -811,6 +834,8 @@ is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false;
+is_admin(_Name, Map, _extra) when is_map(Map) ->
+ true;
is_admin(Name, Auth, Extra) ->
{ACLInfo, Server} = case Auth of
{U, S, _, _} ->
@@ -832,6 +857,14 @@ is_admin(Name, Auth, Extra) ->
deny -> false
end.
+permission_addon() ->
+ [{<<"'commands' option compatibility shim">>,
+ {[],
+ [{access, ejabberd_config:get_option(commands_admin_access,
+ fun(V) -> V end,
+ none)}],
+ {get_exposed_commands(), []}}}].
+
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 6ca6a40a8..af26767f8 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -29,16 +29,16 @@
-export([start/0, load_file/1, reload_file/0, read_file/1,
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/2, get_option/3, add_option/2, has_option/1,
- get_vh_by_auth_method/1, is_file_readable/1,
- get_version/0, get_myhosts/0, get_mylang/0,
- get_ejabberd_config_path/0, is_using_elixir_config/0,
- prepare_opt_val/4, convert_table_to_binary/5,
- transform_options/1, collect_options/1, default_db/2,
- convert_to_yaml/1, convert_to_yaml/2, v_db/2,
- env_binary_to_list/2, opt_type/1, may_hide_data/1,
- is_elixir_enabled/0]).
+ get_global_option/3, get_local_option/3,
+ get_option/2, get_option/3, add_option/2, has_option/1,
+ get_vh_by_auth_method/1, is_file_readable/1,
+ get_version/0, get_myhosts/0, get_mylang/0,
+ get_ejabberd_config_path/0, is_using_elixir_config/0,
+ prepare_opt_val/4, convert_table_to_binary/5,
+ transform_options/1, collect_options/1, default_db/2,
+ convert_to_yaml/1, convert_to_yaml/2, v_db/2,
+ env_binary_to_list/2, opt_type/1, may_hide_data/1,
+ is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1]).
-export([start/2]).
@@ -178,7 +178,8 @@ read_file(File, Opts) ->
-spec load_file(string()) -> ok.
load_file(File) ->
- State = read_file(File),
+ State0 = read_file(File),
+ State = validate_opts(State0),
set_opts(State).
-spec reload_file() -> ok.
@@ -892,6 +893,19 @@ v_db(Mod, Type) ->
[] -> erlang:error(badarg)
end.
+-spec v_dbs(module()) -> [atom()].
+
+v_dbs(Mod) ->
+ lists:flatten(ets:match(module_db, {Mod, '$1'})).
+
+-spec v_dbs_mods(module()) -> [module()].
+
+v_dbs_mods(Mod) ->
+ lists:map(fun([M]) ->
+ binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_",
+ (atom_to_binary(M, utf8))/binary>>, utf8)
+ end, ets:match(module_db, {Mod, '$1'})).
+
-spec default_db(binary(), module()) -> atom().
default_db(Host, Module) ->
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index d52b55cf9..a96a28016 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -321,10 +321,15 @@ call_command([CmdString | Args], Auth, AccessCommands, Version) ->
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
- Result = ejabberd_commands:execute_command(AccessCommands,
- Auth, Command,
- ArgsFormatted,
- Version),
+ CI = case Auth of
+ {U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S};
+ _ -> #{}
+ end,
+ CI2 = CI#{caller_module => ?MODULE},
+ Result = ejabberd_commands:execute_command2(Command,
+ ArgsFormatted,
+ CI2,
+ Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index 4541190ad..d11548c22 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -42,8 +42,10 @@
associate_access_code/3,
associate_access_token/3,
associate_refresh_token/3,
+ check_token/1,
check_token/4,
check_token/2,
+ scope_in_scope_list/2,
process/2,
opt_type/1]).
@@ -305,6 +307,29 @@ associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}.
+scope_in_scope_list(Scope, ScopeList) ->
+ TokenScopeSet = oauth2_priv_set:new(Scope),
+ lists:any(fun(Scope2) ->
+ oauth2_priv_set:is_member(Scope2, TokenScopeSet) end,
+ ScopeList).
+
+check_token(Token) ->
+ case lookup(Token) of
+ {ok, #oauth_token{us = US,
+ scope = TokenScope,
+ expire = Expire}} ->
+ {MegaSecs, Secs, _} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ if
+ Expire > TS ->
+ {ok, US, TokenScope};
+ true ->
+ {false, expired}
+ end;
+ _ ->
+ {false, not_found}
+ end.
+
check_token(User, Server, ScopeList, Token) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index a30f2f438..ae3433a6a 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -1099,13 +1099,12 @@ get_addr_port(Server) ->
?DEBUG("srv lookup of '~s': ~p~n",
[Server, HEnt#hostent.h_addr_list]),
AddrList = HEnt#hostent.h_addr_list,
- random:seed(p1_time_compat:timestamp()),
case catch lists:map(fun ({Priority, Weight, Port,
Host}) ->
N = case Weight of
0 -> 0;
_ ->
- (Weight + 1) * random:uniform()
+ (Weight + 1) * randoms:uniform()
end,
{Priority * 65536 - N, Host, Port}
end,
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index 9dd7c831e..26374c1f1 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -135,13 +135,13 @@ init([{SockMod, Socket}, Opts]) ->
fun({H, Os}, D) ->
P = proplists:get_value(
password, Os,
- p1_sha:sha(crypto:rand_bytes(20))),
+ p1_sha:sha(randoms:bytes(20))),
dict:store(H, P, D)
end, dict:new(), HOpts);
false ->
Pass = proplists:get_value(
password, Opts,
- p1_sha:sha(crypto:rand_bytes(20))),
+ p1_sha:sha(randoms:bytes(20))),
dict:from_list([{global, Pass}])
end,
%% privilege access to entities data
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 16e0f9114..3369b7ca0 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -713,10 +713,18 @@ get_resource_sessions(User, Server, Resource) ->
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
- SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
+ Ss = Mod:get_sessions(LUser, LServer),
+ {OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
MaxSessions = get_max_user_sessions(LUser, LServer),
- if length(SIDs) =< MaxSessions -> ok;
- true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
+ if length(OnlineSs) =< MaxSessions -> ok;
+ true ->
+ #session{sid = {_, Pid}} = lists:min(OnlineSs),
+ Pid ! replaced
+ end,
+ if length(OfflineSs) =< MaxSessions -> ok;
+ true ->
+ #session{sid = SID, usr = {_, _, R}} = lists:min(OfflineSs),
+ Mod:delete_session(LUser, LServer, R, SID)
end.
%% Get the user_max_session setting
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index b19f16414..27c2815ba 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -629,7 +629,7 @@ generic_sql_query_format(SQLQuery) ->
generic_escape() ->
#sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
- integer = fun(X) -> integer_to_binary(X) end,
+ integer = fun(X) -> jlib:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
@@ -646,7 +646,7 @@ sqlite_sql_query_format(SQLQuery) ->
sqlite_escape() ->
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
- integer = fun(X) -> integer_to_binary(X) end,
+ integer = fun(X) -> jlib:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
@@ -670,7 +670,7 @@ pgsql_prepare(SQLQuery, State) ->
pgsql_execute_escape() ->
#sql_escape{string = fun(X) -> X end,
- integer = fun(X) -> [integer_to_binary(X)] end,
+ integer = fun(X) -> [jlib:i2l(X)] end,
boolean = fun(true) -> "1";
(false) -> "0"
end
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index 184f9775b..fb57fa560 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -1552,7 +1552,7 @@ su_to_list({Server, User}) ->
%%%% get_stats
get_stats(global, Lang) ->
- OnlineUsers = mnesia:table_info(session, size),
+ OnlineUsers = ejabberd_sm:connected_users_number(),
RegisteredUsers = lists:foldl(fun (Host, Total) ->
ejabberd_auth:get_vh_registered_users_number(Host)
+ Total
@@ -2178,7 +2178,7 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) ->
CPUTime = ejabberd_cluster:call(Node, erlang, statistics, [runtime]),
CPUTimeS = list_to_binary(io_lib:format("~.3f",
[element(1, CPUTime) / 1000])),
- OnlineUsers = mnesia:table_info(session, size),
+ OnlineUsers = ejabberd_sm:connected_users_number(),
TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_commits]),
TransactionsAborted = ejabberd_cluster:call(Node, mnesia,
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 6680451e4..1dd88f837 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -47,7 +47,8 @@
-record(state,
{access_commands = [] :: list(),
auth = noauth :: noauth | {binary(), binary(), binary()},
- get_auth = true :: boolean()}).
+ get_auth = true :: boolean(),
+ ip :: inet:ip_address()}).
%% Test:
@@ -195,7 +196,7 @@ socket_type() -> raw.
%% -----------------------------
%% HTTP interface
%% -----------------------------
-process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
+process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
AccessCommandsOpts = gen_mod:get_opt(access_commands, Opts,
fun(L) when is_list(L) -> L end,
undefined),
@@ -206,7 +207,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
lists:flatmap(
fun({Ac, AcOpts}) ->
Commands = gen_mod:get_opt(
- commands, AcOpts,
+ commands, lists:flatten(AcOpts),
fun(A) when is_atom(A) ->
A;
(L) when is_list(L) ->
@@ -219,15 +220,15 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
options, AcOpts,
fun(L) when is_list(L) -> L end,
[]),
- [{Ac, Commands, CommOpts}];
+ [{<<"ejabberd_xmlrpc compatibility shim">>, {[?MODULE], [{access, Ac}], Commands}}];
(Wrong) ->
?WARNING_MSG("wrong options format for ~p: ~p",
[?MODULE, Wrong]),
[]
- end, AccessCommandsOpts)
+ end, lists:flatten(AccessCommandsOpts))
end,
GetAuth = true,
- State = #state{access_commands = AccessCommands, get_auth = GetAuth},
+ State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP},
case fxml_stream:parse_element(Data) of
{error, _} ->
{400, [],
@@ -258,21 +259,35 @@ process(_, _) ->
%% Access verification
%% -----------------------------
-get_auth(AuthList) ->
- Admin =
- case lists:keysearch(admin, 1, AuthList) of
- {value, {admin, true}} -> true;
- _ -> false
- end,
+extract_auth(AuthList) ->
+ ?DEBUG("AUTHLIST ~p", [AuthList]),
try get_attrs([user, server, token], AuthList) of
- [U, S, T] -> {U, S, {oauth, T}, Admin}
+ [U0, S0, T] ->
+ U = jid:nodeprep(U0),
+ S = jid:nameprep(S0),
+ case ejabberd_oauth:check_token(T) of
+ {ok, {U, S}, Scope} ->
+ #{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S};
+ {false, Reason} ->
+ {error, Reason};
+ _ ->
+ {error, not_found}
+ end
catch
exit:{attribute_not_found, _Attr, _} ->
try get_attrs([user, server, password], AuthList) of
- [U, S, P] -> {U, S, P, Admin}
+ [U0, S0, P] ->
+ U = jid:nodeprep(U0),
+ S = jid:nameprep(S0),
+ case ejabberd_auth:check_password(U, <<"">>, S, P) of
+ true ->
+ #{usr => {U, S, <<"">>}, caller_server => S};
+ false ->
+ {error, invalid_auth}
+ end
catch
- exit:{attribute_not_found, Attr, _} ->
- throw({error, missing_auth_arguments, Attr})
+ exit:{attribute_not_found, _Attr, _} ->
+ #{}
end
end.
@@ -300,12 +315,28 @@ get_auth(AuthList) ->
%% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, [{struct, [{user, "badlop"}, {server, "localhost"}, {password, "79C1574A43BC995F2B145A299EF97277"}]}, 152]}).
%% {ok,{response,[152]}}
-handler(#state{get_auth = true, auth = noauth} = State,
+handler(#state{get_auth = true, auth = noauth, ip = IP} = State,
{call, Method,
[{struct, AuthList} | Arguments] = AllArgs}) ->
- try get_auth(AuthList) of
+ try extract_auth(AuthList) of
+ {error, invalid_auth} ->
+ build_fault_response(-118,
+ "Invalid authentication data",
+ []);
+ {error, not_found} ->
+ build_fault_response(-118,
+ "Invalid oauth token",
+ []);
+ {error, expired} ->
+ build_fault_response(-118,
+ "Invalid oauth token",
+ []);
+ {error, Value} ->
+ build_fault_response(-118,
+ "Invalid authentication data: ~p",
+ [Value]);
Auth ->
- handler(State#state{get_auth = false, auth = Auth},
+ handler(State#state{get_auth = false, auth = Auth#{ip => IP, caller_module => ?MODULE}},
{call, Method, Arguments})
catch
{error, missing_auth_arguments, _Attr} ->
@@ -393,9 +424,14 @@ build_fault_response(Code, ParseString, ParseArgs) ->
do_command(AccessCommands, Auth, Command, AttrL, ArgsF,
ResultF) ->
ArgsFormatted = format_args(AttrL, ArgsF),
+ Auth2 = case AccessCommands of
+ V when is_list(V) ->
+ Auth#{extra_permissions => AccessCommands};
+ _ ->
+ Auth
+ end,
Result =
- ejabberd_commands:execute_command(AccessCommands, Auth,
- Command, ArgsFormatted),
+ ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2),
ResultFormatted = format_result(Result, ResultF),
{command_result, ResultFormatted}.
@@ -489,6 +525,8 @@ process_unicode_codepoints(Str) ->
format_result({error, Error}, _) ->
throw({error, Error});
+format_result({error, _Type, _Code, Error}, _) ->
+ throw({error, Error});
format_result(String, string) -> lists:flatten(String);
format_result(Atom, {Name, atom}) ->
{struct,
diff --git a/src/extauth.erl b/src/extauth.erl
index 50330b47b..6063d3670 100644
--- a/src/extauth.erl
+++ b/src/extauth.erl
@@ -102,8 +102,7 @@ call_port(Server, Msg) ->
receive {eauth, Result} -> Result end.
random_instance(MaxNum) ->
- random:seed(p1_time_compat:timestamp()),
- random:uniform(MaxNum) - 1.
+ randoms:uniform(MaxNum) - 1.
get_instances(Server) ->
ejabberd_config:get_option(
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index c4306577c..aaf452aeb 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -308,10 +308,47 @@ get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
+
+get_module_mod_opt_type_fun(Module) ->
+ DBSubMods = ejabberd_config:v_dbs_mods(Module),
+ fun(Opt) ->
+ Res = lists:foldl(fun(Mod, {Funs, ArgsList, _} = Acc) ->
+ case catch Mod:mod_opt_type(Opt) of
+ Fun when is_function(Fun) ->
+ {[Fun | Funs], ArgsList, true};
+ L when is_list(L) ->
+ {Funs, L ++ ArgsList, true};
+ _ ->
+ Acc
+ end
+ end, {[], [], false}, [Module | DBSubMods]),
+ case Res of
+ {[], [], false} ->
+ throw({'EXIT', {undef, mod_opt_type}});
+ {[], Args, _} -> Args;
+ {Funs, _, _} ->
+ fun(Val) ->
+ lists:any(fun(F) ->
+ try F(Val) of
+ _ ->
+ true
+ catch {replace_with, _NewVal} = E ->
+ throw(E);
+ {invalid_syntax, _Error} = E2 ->
+ throw(E2);
+ _:_ ->
+ false
+ end
+ end, Funs)
+ end
+ end
+ end.
+
validate_opts(Module, Opts) ->
+ ModOptFun = get_module_mod_opt_type_fun(Module),
lists:filtermap(
fun({Opt, Val}) ->
- case catch Module:mod_opt_type(Opt) of
+ case catch ModOptFun(Opt) of
VFun when is_function(VFun) ->
try VFun(Val) of
_ ->
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index 2bb436f31..48732ea35 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -377,6 +377,7 @@ get_commands_spec() ->
#ejabberd_commands{name = add_rosteritem, tags = [roster],
desc = "Add an item to a user's roster (supports ODBC)",
+ longdesc = "Group can be several groups separated by ; for example: \"g1;g2;g3\"",
module = ?MODULE, function = add_rosteritem,
args = [{localuser, binary}, {localserver, binary},
{user, binary}, {server, binary},
@@ -1204,11 +1205,13 @@ push_roster_item(LU, LS, R, U, S, Action) ->
ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ).
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
+ GNames = binary:split(Group,<<";">>, [global]),
+ GroupEls = [{xmlel, <<"group">>, [], [{xmlcdata, GName}]} || GName <- GNames],
{xmlel, <<"item">>,
[{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))},
{<<"name">>, Nick},
{<<"subscription">>, Subs}],
- [{xmlel, <<"group">>, [], [{xmlcdata, Group}]}]
+ GroupEls
};
build_roster_item(U, S, remove) ->
{xmlel, <<"item">>,
@@ -1357,44 +1360,9 @@ srg_user_del(User, Host, Group, GroupHost) ->
%% @doc Send a message to a Jabber account.
%% @spec (Type::binary(), From::binary(), To::binary(), Subject::binary(), Body::binary()) -> ok
send_message(Type, From, To, Subject, Body) ->
+ FromJID = jid:from_string(From),
+ ToJID = jid:from_string(To),
Packet = build_packet(Type, Subject, Body),
- send_packet_all_resources(From, To, Packet).
-
-%% @doc Send a packet to a Jabber account.
-%% If a resource was specified in the JID,
-%% the packet is sent only to that specific resource.
-%% If no resource was specified in the JID,
-%% and the user is remote or local but offline,
-%% the packet is sent to the bare JID.
-%% If the user is local and is online in several resources,
-%% the packet is sent to all its resources.
-send_packet_all_resources(FromJIDString, ToJIDString, Packet) ->
- FromJID = jid:from_string(FromJIDString),
- ToJID = jid:from_string(ToJIDString),
- ToUser = ToJID#jid.user,
- ToServer = ToJID#jid.server,
- case ToJID#jid.resource of
- <<>> ->
- send_packet_all_resources(FromJID, ToUser, ToServer, Packet);
- Res ->
- send_packet_all_resources(FromJID, ToUser, ToServer, Res, Packet)
- end.
-
-send_packet_all_resources(FromJID, ToUser, ToServer, Packet) ->
- case ejabberd_sm:get_user_resources(ToUser, ToServer) of
- [] ->
- send_packet_all_resources(FromJID, ToUser, ToServer, <<>>, Packet);
- ToResources ->
- lists:foreach(
- fun(ToResource) ->
- send_packet_all_resources(FromJID, ToUser, ToServer,
- ToResource, Packet)
- end,
- ToResources)
- end.
-
-send_packet_all_resources(FromJID, ToU, ToS, ToR, Packet) ->
- ToJID = jid:make(ToU, ToS, ToR),
ejabberd_router:route(FromJID, ToJID, Packet).
build_packet(Type, Subject, Body) ->
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 96651aebf..da3f5cf0f 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -172,7 +172,7 @@ do_client_version(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) ->
ToS = jid:to_string(To),
Random_resource =
- iolist_to_binary(integer_to_list(random:uniform(100000))),
+ iolist_to_binary(integer_to_list(randoms:uniform(100000))),
From2 = From#jid{resource = Random_resource,
lresource = Random_resource},
Packet = #xmlel{name = <<"iq">>,
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 7a95f8c6f..491383769 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -118,9 +118,11 @@
%% -------------------
start(_Host, _Opts) ->
+ ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0),
ok.
stop(_Host) ->
+ ejabberd_access_permissions:unregister_permission_addon(?MODULE),
ok.
depends(_Host, _Opts) ->
@@ -130,76 +132,39 @@ depends(_Host, _Opts) ->
%% basic auth
%% ----------
-check_permissions(Request, Command) ->
- case catch binary_to_existing_atom(Command, utf8) of
- Call when is_atom(Call) ->
- {ok, CommandPolicy, Scope} = ejabberd_commands:get_command_policy_and_scope(Call),
- check_permissions2(Request, Call, CommandPolicy, Scope);
- _ ->
- json_error(404, 40, <<"Endpoint not found.">>)
- end.
-
-check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _, ScopeList)
- when HTTPAuth /= undefined ->
- Admin =
- case lists:keysearch(<<"X-Admin">>, 1, Headers) of
- {value, {_, <<"true">>}} -> true;
- _ -> false
- end,
- Auth =
- case HTTPAuth of
- {SJID, Pass} ->
- case jid:from_string(SJID) of
- #jid{user = User, server = Server} ->
- case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
- true -> {ok, {User, Server, Pass, Admin}};
- false -> false
- end;
- _ ->
- false
- end;
- {oauth, Token, _} ->
- case oauth_check_token(ScopeList, Token) of
- {ok, user, {User, Server}} ->
- {ok, {User, Server, {oauth, Token}, Admin}};
- {false, Reason} ->
- {false, Reason}
- end;
- _ ->
- false
- end,
- case Auth of
- {ok, A} -> {allowed, Call, A};
- {false, no_matching_scope} -> outofscope_response();
- _ -> unauthorized_response()
+extract_auth(#request{auth = HTTPAuth, ip = {IP, _}}) ->
+ Info = case HTTPAuth of
+ {SJID, Pass} ->
+ case jid:from_string(SJID) of
+ #jid{luser = User, lserver = Server} ->
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ true ->
+ #{usr => {User, Server, <<"">>}, caller_server => Server};
+ false ->
+ {error, invalid_auth}
+ end;
+ _ ->
+ {error, invalid_auth}
+ end;
+ {oauth, Token, _} ->
+ case ejabberd_oauth:check_token(Token) of
+ {ok, {U, S}, Scope} ->
+ #{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S};
+ {false, Reason} ->
+ {error, Reason}
+ end;
+ _ ->
+ #{}
+ end,
+ case Info of
+ Map when is_map(Map) ->
+ Map#{caller_module => ?MODULE, ip => IP};
+ _ ->
+ ?DEBUG("Invalid auth data: ~p", [Info]),
+ Info
end;
-check_permissions2(_Request, Call, open, _Scope) ->
- {allowed, Call, noauth};
-check_permissions2(#request{ip={IP, _Port}}, Call, _Policy, _Scope) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
- fun(V) -> V end,
- none),
- Res = acl:match_rule(global, Access, IP),
- case Res of
- all ->
- {allowed, Call, admin};
- [all] ->
- {allowed, Call, admin};
- allow ->
- {allowed, Call, admin};
- Commands when is_list(Commands) ->
- case lists:member(Call, Commands) of
- true -> {allowed, Call, admin};
- _ -> outofscope_response()
- end;
- _E ->
- {allowed, Call, noauth}
- end;
-check_permissions2(_Request, _Call, _Policy, _Scope) ->
- unauthorized_response().
-
-oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
- ejabberd_oauth:check_token(ScopeList, Token).
+extract_auth(#request{ip = IP}) ->
+ #{ip => IP, caller_module => ?MODULE}.
%% ------------------
%% command processing
@@ -210,19 +175,12 @@ oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
badrequest_response(<<"Missing POST data">>);
-process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
+process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
Version = get_api_version(Req),
try
Args = extract_args(Data),
log(Call, Args, IPPort),
- case check_permissions(Req, Call) of
- {allowed, Cmd, Auth} ->
- Result = handle(Cmd, Auth, Args, Version, IP),
- json_format(Result);
- %% Warning: check_permission direcly formats 401 reply if not authorized
- ErrorResponse ->
- ErrorResponse
- end
+ perform_call(Call, Args, Req, Version)
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
@@ -234,7 +192,7 @@ process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} =
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
-process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
Version = get_api_version(Req),
try
Args = case Data of
@@ -242,14 +200,7 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
_ -> Data
end,
log(Call, Args, IP),
- case check_permissions(Req, Call) of
- {allowed, Cmd, Auth} ->
- Result = handle(Cmd, Auth, Args, Version, IP),
- json_format(Result);
- %% Warning: check_permission direcly formats 401 reply if not authorized
- ErrorResponse ->
- ErrorResponse
- end
+ perform_call(Call, Args, Req, Version)
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
@@ -267,6 +218,22 @@ process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
json_error(400, 40, <<"Missing command name.">>).
+perform_call(Command, Args, Req, Version) ->
+ case catch binary_to_existing_atom(Command, utf8) of
+ Call when is_atom(Call) ->
+ case extract_auth(Req) of
+ {error, expired} -> invalid_token_response();
+ {error, not_found} -> invalid_token_response();
+ {error, invalid_auth} -> unauthorized_response();
+ {error, _} -> unauthorized_response();
+ Auth when is_map(Auth) ->
+ Result = handle(Call, Auth, Args, Version),
+ json_format(Result)
+ end;
+ _ ->
+ json_error(404, 40, <<"Endpoint not found.">>)
+ end.
+
%% Be tolerant to make API more easily usable from command-line pipe.
extract_args(<<"\n">>) -> [];
extract_args(Data) ->
@@ -298,7 +265,7 @@ get_api_version([]) ->
%% TODO Check accept types of request before decided format of reply.
% generic ejabberd command handler
-handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
+handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
case ejabberd_commands:get_command_format(Call, Auth, Version) of
{ArgsSpec, _} when is_list(ArgsSpec) ->
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
@@ -315,7 +282,7 @@ handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
try
- handle2(Call, Auth, match(Args2, Spec), Version, IP)
+ handle2(Call, Auth, match(Args2, Spec), Version)
catch throw:not_found ->
{404, <<"not_found">>};
throw:{not_found, Why} when is_atom(Why) ->
@@ -354,10 +321,15 @@ handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
{400, <<"Error">>}
end.
-handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
+handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
ArgsFormatted = format_args(Args, ArgsF),
- ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
+ case ejabberd_commands:execute_command2(Call, ArgsFormatted, Auth, Version) of
+ {error, Error} ->
+ throw(Error);
+ Res ->
+ format_command_result(Call, Auth, Res, Version)
+ end.
get_elem_delete(A, L) ->
case proplists:get_all_values(A, L) of
@@ -456,18 +428,6 @@ process_unicode_codepoints(Str) ->
match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
-ejabberd_command(Auth, Cmd, Args, Version, IP) ->
- Access = case Auth of
- admin -> [];
- _ -> undefined
- end,
- case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
- {error, Error} ->
- throw(Error);
- Res ->
- format_command_result(Cmd, Auth, Res, Version)
- end.
-
format_command_result(Cmd, Auth, Result, Version) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
@@ -538,6 +498,9 @@ format_error_result(_ErrorAtom, Code, Msg) ->
{500, Code, iolist_to_binary(Msg)}.
unauthorized_response() ->
+ json_error(401, 10, <<"You are not authorized to call this command.">>).
+
+invalid_token_response() ->
json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
outofscope_response() ->
@@ -571,5 +534,31 @@ log(Call, Args, {Addr, Port}) ->
log(Call, Args, IP) ->
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
+permission_addon() ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
+ fun(V) -> V end,
+ none),
+ Rules = acl:resolve_access(Access, global),
+ R = lists:filtermap(
+ fun({V, AclRules}) when V == all; V == [all]; V == [allow]; V == allow ->
+ {true, {[{allow, AclRules}], {[<<"*">>], []}}};
+ ({List, AclRules}) when is_list(List) ->
+ {true, {[{allow, AclRules}], {List, []}}};
+ (_) ->
+ false
+ end, Rules),
+ case R of
+ [] ->
+ none;
+ _ ->
+ {_, Res} = lists:foldl(
+ fun({R2, L2}, {Idx, Acc}) ->
+ {Idx+1, [{<<"'mod_http_api admin_ip_access' option compatibility shim ",
+ (integer_to_binary(Idx))/binary>>,
+ {[?MODULE], [{access, R2}], L2}} | Acc]}
+ end, {1, []}, R),
+ Res
+ end.
+
mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1;
mod_opt_type(_) -> [admin_ip_access].
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index f6d3c8f1f..8f6492047 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -966,13 +966,14 @@ send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
[{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
end,
+ Hint = [#xmlel{name = <<"no-store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
Els = lists:map(
fun({ID, _IDInt, El}) ->
#xmlel{name = <<"message">>,
children = [#xmlel{name = <<"result">>,
attrs = [{<<"xmlns">>, NS},
{<<"id">>, ID}|QIDAttr],
- children = [El]}]}
+ children = [El]} | Hint]}
end, Msgs),
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
@@ -990,7 +991,7 @@ send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
end, Els),
ejabberd_router:route(
To, From, #xmlel{name = <<"message">>,
- children = RSMOut}),
+ children = RSMOut ++ Hint}),
ignore
end.
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index ad2be4cce..6b878b05b 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -43,7 +43,7 @@
forget_room/3,
create_room/5,
shutdown_rooms/1,
- process_iq_disco_items/4,
+ process_iq_disco_items/5,
broadcast_service_message/2,
export/1,
import/1,
@@ -66,13 +66,12 @@
server_host = <<"">> :: binary(),
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
history_size = 20 :: non_neg_integer(),
+ max_rooms_discoitems = 100 :: non_neg_integer(),
default_room_opts = [] :: list(),
room_shaper = none :: shaper:shaper()}).
-define(PROCNAME, ejabberd_mod_muc).
--define(MAX_ROOMS_DISCOITEMS, 100).
-
-type muc_room_opts() :: [{atom(), any()}].
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass.
@@ -154,7 +153,7 @@ forget_room(ServerHost, Host, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
-process_iq_disco_items(Host, From, To,
+process_iq_disco_items(Host, From, To, MaxRoomsDiscoItems,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
@@ -162,7 +161,7 @@ process_iq_disco_items(Host, From, To,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
- children = iq_disco_items(Host, From, Lang, DiscoNode, Rsm)}]},
+ children = iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, DiscoNode, Rsm)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
@@ -200,6 +199,9 @@ init([Host, Opts]) ->
HistorySize = gen_mod:get_opt(history_size, Opts,
fun(I) when is_integer(I), I>=0 -> I end,
20),
+ MaxRoomsDiscoItems = gen_mod:get_opt(max_rooms_discoitems, Opts,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 100),
DefRoomOpts1 = gen_mod:get_opt(default_room_options, Opts,
fun(L) when is_list(L) -> L end,
[]),
@@ -265,6 +267,7 @@ init([Host, Opts]) ->
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
default_room_opts = DefRoomOpts,
history_size = HistorySize,
+ max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper}}.
handle_call(stop, _From, State) ->
@@ -293,9 +296,10 @@ handle_info({route, From, To, Packet},
#state{host = Host, server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize,
+ max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper} = State) ->
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts) of
+ From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
@@ -326,12 +330,12 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts) ->
+ From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) ->
{AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
case acl:match_rule(ServerHost, AccessRoute, From) of
allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts);
+ From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems);
_ ->
#xmlel{attrs = Attrs} = Packet,
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
@@ -343,7 +347,7 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts) ->
+ From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To),
#xmlel{name = Name, attrs = Attrs} = Packet,
@@ -374,7 +378,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
jlib:iq_to_xml(Res));
#iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
spawn(?MODULE, process_iq_disco_items,
- [Host, From, To, IQ]);
+ [Host, From, To, MaxRoomsDiscoItems, IQ]);
#iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
lang = Lang, sub_el = _SubEl} =
IQ ->
@@ -636,15 +640,15 @@ iq_disco_info(ServerHost, Lang) ->
[]
end.
-iq_disco_items(Host, From, Lang, <<>>, none) ->
+iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<>>, none) ->
Rooms = get_vh_rooms(Host),
- case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
+ case erlang:length(Rooms) < MaxRoomsDiscoItems of
true ->
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
false ->
- iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
+ iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<"nonemptyrooms">>, none)
end;
-iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
+iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"nonemptyrooms">>, none) ->
XmlEmpty = #xmlel{name = <<"item">>,
attrs =
[{<<"jid">>, <<"conference.localhost">>},
@@ -653,9 +657,9 @@ iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
children = []},
Query = {get_disco_item, only_non_empty, From, Lang},
[XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
-iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) ->
+iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"emptyrooms">>, none) ->
iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
-iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
+iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, _DiscoNode, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
RsmOut = jlib:rsm_encode(RsmO),
iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
@@ -984,6 +988,8 @@ mod_opt_type(max_room_id) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
end;
+mod_opt_type(max_rooms_discoitems) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
mod_opt_type(regexp_room_id) ->
fun iolist_to_binary/1;
mod_opt_type(max_room_name) ->
@@ -1011,8 +1017,8 @@ mod_opt_type(user_presence_shaper) ->
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
db_type, default_room_options, history_size, host,
- max_room_desc, max_room_id, max_room_name, regexp_room_id,
- max_user_conferences, max_users,
+ max_room_desc, max_room_id, max_room_name,
+ max_rooms_discoitems, max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,
min_message_interval, min_presence_interval,
- room_shaper, user_message_shaper, user_presence_shaper].
+ regexp_room_id, room_shaper, user_message_shaper, user_presence_shaper].
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index e334dca2b..bd1c55f66 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -432,8 +432,8 @@ create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
- Name = jid:nodeprep(Name1),
- Host = jid:nodeprep(Host1),
+ true = (error /= (Name = jid:nodeprep(Name1))),
+ true = (error /= (Host = jid:nodeprep(Host1))),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
@@ -514,7 +514,7 @@ destroy_room({N, H, SH}) ->
%% The file encoding must be UTF-8
destroy_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read, binary]),
+ {ok, F} = file:open(Filename, [read]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -533,7 +533,7 @@ read_room(F) ->
eof -> eof;
String ->
case io_lib:fread("~s", String) of
- {ok, [RoomJID], _} -> split_roomjid(RoomJID);
+ {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
{error, What} ->
io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
end
@@ -551,7 +551,7 @@ split_roomjid(RoomJID) ->
%%----------------------------
create_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read, binary]),
+ {ok, F} = file:open(Filename, [read]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index fc2aeebb6..6010e0bbf 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -959,6 +959,7 @@ process_groupchat_message(From,
_ ->
case
can_change_subject(Role,
+ IsSubscriber,
StateData)
of
true ->
@@ -2821,10 +2822,10 @@ check_subject(Packet) ->
SubjEl -> fxml:get_tag_cdata(SubjEl)
end.
-can_change_subject(Role, StateData) ->
+can_change_subject(Role, IsSubscriber, StateData) ->
case (StateData#state.config)#config.allow_change_subj
of
- true -> Role == moderator orelse Role == participant;
+ true -> Role == moderator orelse Role == participant orelse IsSubscriber == true;
_ -> Role == moderator
end.
@@ -5056,6 +5057,8 @@ process_invitations(From, InviteEls, Lang, StateData) ->
throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
JID1 -> JID1
end,
+ ejabberd_hooks:run(muc_invite, StateData#state.server_host,
+ [StateData#state.jid, StateData#state.config, From, JID, Reason]),
ejabberd_router:route(StateData#state.jid, JID, Msg),
JID
end,
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index feefd3dd0..d9de50e04 100644
--- a/src/mod_offline_sql.erl
+++ b/src/mod_offline_sql.erl
@@ -81,7 +81,7 @@ remove_old_messages(Days, LServer) ->
[<<"DELETE FROM spool"
" WHERE created_at < "
"NOW() - INTERVAL '">>,
- integer_to_list(Days), <<"';">>]) of
+ integer_to_list(Days), <<"' DAY;">>]) of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl
index 6da917e9d..a700db391 100644
--- a/src/mod_privacy_sql.erl
+++ b/src/mod_privacy_sql.erl
@@ -238,7 +238,7 @@ export(Server) ->
"values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
" %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
" %(MatchMessage)b, %(MatchPresenceIn)b,"
- " %(MatchPresenceOut)b)")
+ " %(MatchPresenceOut)b);")
|| {SType, SValue, SAction, Order,
MatchAll, MatchIQ,
MatchMessage, MatchPresenceIn,
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index 99059839a..66bbb5b5b 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -161,13 +161,8 @@ get_local_stat(Server, [], Name)
end;
get_local_stat(_Server, [], Name)
when Name == <<"users/all-hosts/online">> ->
- case catch mnesia:table_info(session, size) of
- {'EXIT', _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
- Users ->
- ?STATVAL((iolist_to_binary(integer_to_list(Users))),
- <<"users">>)
- end;
+ Users = ejabberd_sm:connected_users_number(),
+ ?STATVAL((iolist_to_binary(integer_to_list(Users))), <<"users">>);
get_local_stat(_Server, [], Name)
when Name == <<"users/all-hosts/total">> ->
NumUsers = lists:foldl(fun (Host, Total) ->
diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl
index 15cf9b37a..61156ee06 100644
--- a/src/node_flat_sql.erl
+++ b/src/node_flat_sql.erl
@@ -688,7 +688,7 @@ get_items(Nidx, _From,
before -> {<<">">>, <<"asc">>};
_ -> {<<"is not">>, <<"desc">>}
end,
- SNidx = integer_to_binary(Nidx),
+ SNidx = jlib:i2l(Nidx),
[AttrName, Id] = case I of
undefined when IncIndex =/= undefined ->
case catch
@@ -790,7 +790,7 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
get_last_items(Nidx, _From, Count) ->
Limit = jlib:i2l(Count),
- SNidx = integer_to_binary(Nidx),
+ SNidx = jlib:i2l(Nidx),
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
[<<"select top ">>, Limit,
@@ -890,7 +890,7 @@ del_items(Nidx, [ItemId]) ->
del_item(Nidx, ItemId);
del_items(Nidx, ItemIds) ->
I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
- SNidx = integer_to_binary(Nidx),
+ SNidx = jlib:i2l(Nidx),
catch
ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
I, <<") and nodeid='">>, SNidx, <<"';">>]).
diff --git a/src/randoms.erl b/src/randoms.erl
index 52fceef4e..1353f48af 100644
--- a/src/randoms.erl
+++ b/src/randoms.erl
@@ -27,14 +27,29 @@
-author('alexey@process-one.net').
--export([get_string/0]).
+-export([get_string/0, uniform/0, uniform/1, bytes/1]).
-export([start/0]).
+-define(THRESHOLD, 16#10000000000000000).
+
start() ->
ok.
get_string() ->
- R = crypto:rand_uniform(0, 16#10000000000000000),
+ R = crypto:rand_uniform(0, ?THRESHOLD),
jlib:integer_to_binary(R).
+uniform() ->
+ crypto:rand_uniform(0, ?THRESHOLD)/?THRESHOLD.
+
+uniform(N) ->
+ crypto:rand_uniform(1, N+1).
+
+-ifdef(STRONG_RAND_BYTES).
+bytes(N) ->
+ crypto:strong_rand_bytes(N).
+-else.
+bytes(N) ->
+ crypto:rand_bytes(N).
+-endif.
diff --git a/test/ejabberd_admin_test.exs b/test/ejabberd_admin_test.exs
index 1c999314c..31b8ab2e2 100644
--- a/test/ejabberd_admin_test.exs
+++ b/test/ejabberd_admin_test.exs
@@ -28,6 +28,7 @@ defmodule EjabberdAdminTest do
# For some myterious reason, :ejabberd_commands.init mays
# sometimes fails if module is not loaded before
{:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ejabberd_commands.init
:ejabberd_admin.start
:ok
diff --git a/test/ejabberd_commands_mock_test.exs b/test/ejabberd_commands_mock_test.exs
index 785e74cd7..419a989d6 100644
--- a/test/ejabberd_commands_mock_test.exs
+++ b/test/ejabberd_commands_mock_test.exs
@@ -50,6 +50,7 @@ defmodule EjabberdCommandsMockTest do
:mnesia.start
:ok = :jid.start
:ok = :ejabberd_config.start(["domain1", "domain2"], [])
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ok = :acl.start
EjabberdOauthMock.init
on_exit fn -> :meck.unload end
diff --git a/test/ejabberd_commands_test.exs b/test/ejabberd_commands_test.exs
index 10b656140..c8219d0cf 100644
--- a/test/ejabberd_commands_test.exs
+++ b/test/ejabberd_commands_test.exs
@@ -30,6 +30,7 @@ defmodule EjabberdCommandsTest do
:mnesia.start
:stringprep.start
:ok = :ejabberd_config.start(["localhost"], [])
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ejabberd_commands.init
:ok
diff --git a/test/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
index 03422264f..fde66f03f 100644
--- a/test/mod_admin_extra_test.exs
+++ b/test/mod_admin_extra_test.exs
@@ -45,6 +45,7 @@ defmodule EjabberdModAdminExtraTest do
rescue
_ -> :ok
end
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ejabberd_commands.init
:ok = :ejabberd_config.start([@domain], [])
:mod_admin_extra.start(@domain, [])
diff --git a/test/mod_http_api_mock_test.exs b/test/mod_http_api_mock_test.exs
index 9cba35365..4809ecd59 100644
--- a/test/mod_http_api_mock_test.exs
+++ b/test/mod_http_api_mock_test.exs
@@ -46,6 +46,7 @@ defmodule ModHttpApiMockTest do
:mnesia.start
:stringprep.start
:ejabberd_config.start([@domain], [])
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ejabberd_commands.init
rescue
_ -> :ok
@@ -240,10 +241,10 @@ defmodule ModHttpApiMockTest do
result = :ejabberd_oauth.process([], req)
assert 200 = elem(result, 0) #http code
{kv} = :jiffy.decode(elem(result,2))
- assert {_, "bearer"} = List.keyfind(kv, "token_type", 0)
- assert {_, @command} = List.keyfind(kv, "scope", 0)
- assert {_, 4000} = List.keyfind(kv, "expires_in", 0)
- {"access_token", _token} = List.keyfind(kv, "access_token", 0)
+ assert {_, "bearer"} = List.keyfind(kv, "token_type", 0)
+ assert {_, @command} = List.keyfind(kv, "scope", 0)
+ assert {_, 4000} = List.keyfind(kv, "expires_in", 0)
+ {"access_token", _token} = List.keyfind(kv, "access_token", 0)
#missing grant_type
req = request(method: :POST,
@@ -254,7 +255,7 @@ defmodule ModHttpApiMockTest do
result = :ejabberd_oauth.process([], req)
assert 400 = elem(result, 0) #http code
{kv} = :jiffy.decode(elem(result,2))
- assert {_, "unsupported_grant_type"} = List.keyfind(kv, "error", 0)
+ assert {_, "unsupported_grant_type"} = List.keyfind(kv, "error", 0)
# incorrect user/pass
@@ -266,7 +267,7 @@ defmodule ModHttpApiMockTest do
result = :ejabberd_oauth.process([], req)
assert 400 = elem(result, 0) #http code
{kv} = :jiffy.decode(elem(result,2))
- assert {_, "invalid_grant"} = List.keyfind(kv, "error", 0)
+ assert {_, "invalid_grant"} = List.keyfind(kv, "error", 0)
assert :meck.validate :ejabberd_auth
assert :meck.validate :ejabberd_commands
diff --git a/test/mod_http_api_test.exs b/test/mod_http_api_test.exs
index e2ae3d784..c68270f1f 100644
--- a/test/mod_http_api_test.exs
+++ b/test/mod_http_api_test.exs
@@ -31,6 +31,7 @@ defmodule ModHttpApiTest do
:ok = :mnesia.start
:stringprep.start
:ok = :ejabberd_config.start(["localhost"], [])
+ {:ok, _} = :ejabberd_access_permissions.start_link()
:ok = :ejabberd_commands.init
:ok = :ejabberd_commands.register_commands(cmds)
on_exit fn ->
@@ -46,12 +47,12 @@ defmodule ModHttpApiTest do
assert Enum.member?(commands, :user_cmd)
end
- test "We can call open commands without authentication" do
- setup_mocks()
- :ejabberd_commands.expose_commands([:open_cmd])
- request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
- {200, _, _} = :mod_http_api.process(["open_cmd"], request)
- end
+# test "We can call open commands without authentication" do
+# setup_mocks()
+# :ejabberd_commands.expose_commands([:open_cmd])
+# request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
+# {200, _, _} = :mod_http_api.process(["open_cmd"], request)
+# end
# This related to the commands config file option
test "Attempting to access a command that is not exposed as HTTP API returns 403" do