Merge branch 'master' of github.com:processone/ejabberd

This commit is contained in:
Mickael Remond 2016-11-09 09:04:58 +01:00
commit 42bede77a1
No known key found for this signature in database
GPG Key ID: E6F6045D79965AA3
47 changed files with 1048 additions and 321 deletions

View File

@ -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)

62
mix.exs
View File

@ -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

View File

@ -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]}]}}

View File

@ -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"}.

View File

@ -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:"

View File

@ -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},

View File

@ -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().

View File

@ -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 = [];

View File

@ -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=",

View File

@ -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),

View File

@ -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].

View File

@ -404,7 +404,8 @@ registered_vhosts() ->
reload_config() ->
ejabberd_config:reload_file(),
acl:start(),
shaper:start().
shaper:start(),
ejabberd_access_permissions:invalidate().
%%%
%%% Cluster management

View File

@ -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(),

View File

@ -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 =

View File

@ -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 =

View File

@ -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 =

View File

@ -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) ->

View File

@ -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;

View File

@ -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) ->

View File

@ -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} =

View File

@ -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),

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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(

View File

@ -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
_ ->

View File

@ -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) ->

View File

@ -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">>,

View File

@ -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].

View File

@ -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.

View File

@ -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].

View File

@ -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),

View File

@ -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,

View File

@ -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 ->

View File

@ -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,

View File

@ -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) ->

View File

@ -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, <<"';">>]).

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, [])

View File

@ -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

View File

@ -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