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 +%%% Purpose : Administrative functions and commands +%%% Created : 7 Sep 2016 by Paweł Chmielowski +%%% +%%% +%%% 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(<>) 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