summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/acl.erl54
-rw-r--r--src/adhoc.erl12
-rw-r--r--src/cyrsasl.erl25
-rw-r--r--src/cyrsasl_anonymous.erl14
-rw-r--r--src/cyrsasl_digest.erl10
-rw-r--r--src/cyrsasl_oauth.erl91
-rw-r--r--src/cyrsasl_plain.erl4
-rw-r--r--src/cyrsasl_scram.erl6
-rw-r--r--src/ejabberd.erl14
-rw-r--r--src/ejabberd_admin.erl107
-rw-r--r--src/ejabberd_app.erl50
-rw-r--r--src/ejabberd_auth.erl26
-rw-r--r--src/ejabberd_auth_anonymous.erl31
-rw-r--r--src/ejabberd_auth_external.erl33
-rw-r--r--src/ejabberd_auth_internal.erl112
-rw-r--r--src/ejabberd_auth_ldap.erl63
-rw-r--r--src/ejabberd_auth_odbc.erl334
-rw-r--r--src/ejabberd_auth_pam.erl46
-rw-r--r--src/ejabberd_auth_riak.erl96
-rw-r--r--src/ejabberd_c2s.erl1404
-rw-r--r--src/ejabberd_c2s_config.erl9
-rw-r--r--src/ejabberd_captcha.erl72
-rw-r--r--src/ejabberd_cluster.erl104
-rw-r--r--src/ejabberd_commands.erl254
-rw-r--r--src/ejabberd_commands_doc.erl565
-rw-r--r--src/ejabberd_config.erl266
-rw-r--r--src/ejabberd_ctl.erl114
-rw-r--r--src/ejabberd_frontend_socket.erl69
-rw-r--r--src/ejabberd_hooks.erl9
-rw-r--r--src/ejabberd_http.erl246
-rw-r--r--src/ejabberd_http_bind.erl252
-rw-r--r--src/ejabberd_http_ws.erl58
-rw-r--r--src/ejabberd_idna.erl (renamed from src/idna.erl)6
-rw-r--r--src/ejabberd_listener.erl114
-rw-r--r--src/ejabberd_local.erl61
-rw-r--r--src/ejabberd_logger.erl132
-rw-r--r--src/ejabberd_node_groups.erl16
-rw-r--r--src/ejabberd_oauth.erl490
-rw-r--r--src/ejabberd_odbc.erl417
-rw-r--r--src/ejabberd_odbc_sup.erl27
-rw-r--r--src/ejabberd_piefxis.erl177
-rw-r--r--src/ejabberd_rdbms.erl22
-rw-r--r--src/ejabberd_receiver.erl156
-rw-r--r--src/ejabberd_regexp.erl2
-rw-r--r--src/ejabberd_riak.erl29
-rw-r--r--src/ejabberd_riak_sup.erl70
-rw-r--r--src/ejabberd_router.erl149
-rw-r--r--src/ejabberd_router_multicast.erl44
-rw-r--r--src/ejabberd_s2s.erl215
-rw-r--r--src/ejabberd_s2s_in.erl176
-rw-r--r--src/ejabberd_s2s_out.erl257
-rw-r--r--src/ejabberd_service.erl144
-rw-r--r--src/ejabberd_sm.erl252
-rw-r--r--src/ejabberd_sm_mnesia.erl2
-rw-r--r--src/ejabberd_sm_odbc.erl6
-rw-r--r--src/ejabberd_sm_redis.erl36
-rw-r--r--src/ejabberd_socket.erl29
-rw-r--r--src/ejabberd_sql_pt.erl544
-rw-r--r--src/ejabberd_stun.erl13
-rw-r--r--src/ejabberd_sup.erl28
-rw-r--r--src/ejabberd_system_monitor.erl55
-rw-r--r--src/ejabberd_tmp_sup.erl2
-rw-r--r--src/ejabberd_update.erl27
-rw-r--r--src/ejabberd_web.erl2
-rw-r--r--src/ejabberd_web_admin.erl706
-rw-r--r--src/ejabberd_websocket.erl16
-rw-r--r--src/ejabberd_xmlrpc.erl135
-rw-r--r--src/ejd2odbc.erl50
-rw-r--r--src/eldap.erl84
-rw-r--r--src/eldap_filter.erl2
-rw-r--r--src/eldap_pool.erl2
-rw-r--r--src/eldap_utils.erl100
-rw-r--r--src/elixir_logger_backend.erl122
-rw-r--r--src/ext_mod.erl149
-rw-r--r--src/extauth.erl15
-rw-r--r--src/gen_iq_handler.erl115
-rw-r--r--src/gen_mod.erl99
-rw-r--r--src/gen_pubsub_node.erl45
-rw-r--r--src/gen_pubsub_nodetree.erl45
-rw-r--r--src/jd2ejd.erl32
-rw-r--r--src/jid.erl232
-rw-r--r--src/jlib.erl305
-rw-r--r--src/mod_adhoc.erl13
-rw-r--r--src/mod_admin_extra.erl378
-rw-r--r--src/mod_announce.erl66
-rw-r--r--src/mod_blocking.erl70
-rw-r--r--src/mod_caps.erl132
-rw-r--r--src/mod_carboncopy.erl104
-rw-r--r--src/mod_client_state.erl47
-rw-r--r--src/mod_configure.erl71
-rw-r--r--src/mod_configure2.erl32
-rw-r--r--src/mod_disco.erl34
-rw-r--r--src/mod_echo.erl15
-rw-r--r--src/mod_fail2ban.erl35
-rw-r--r--src/mod_http_api.erl392
-rw-r--r--src/mod_http_bind.erl191
-rw-r--r--src/mod_http_fileserver.erl37
-rw-r--r--src/mod_http_upload.erl1043
-rw-r--r--src/mod_http_upload_quota.erl372
-rw-r--r--src/mod_ip_blacklist.erl7
-rw-r--r--src/mod_irc.erl109
-rw-r--r--src/mod_irc_connection.erl122
-rw-r--r--src/mod_last.erl63
-rw-r--r--src/mod_mam.erl1421
-rw-r--r--src/mod_metrics.erl128
-rw-r--r--src/mod_mix.erl347
-rw-r--r--src/mod_muc.erl542
-rw-r--r--src/mod_muc_admin.erl207
-rw-r--r--src/mod_muc_log.erl180
-rw-r--r--src/mod_muc_room.erl1250
-rw-r--r--src/mod_multicast.erl267
-rw-r--r--src/mod_offline.erl678
-rw-r--r--src/mod_ping.erl41
-rw-r--r--src/mod_pres_counter.erl20
-rw-r--r--src/mod_privacy.erl295
-rw-r--r--src/mod_private.erl83
-rw-r--r--src/mod_proxy65.erl39
-rw-r--r--src/mod_proxy65_lib.erl2
-rw-r--r--src/mod_proxy65_service.erl21
-rw-r--r--src/mod_proxy65_sm.erl8
-rw-r--r--src/mod_proxy65_stream.erl7
-rw-r--r--src/mod_pubsub.erl615
-rw-r--r--src/mod_register.erl116
-rw-r--r--src/mod_register_web.erl46
-rw-r--r--src/mod_roster.erl374
-rw-r--r--src/mod_service_log.erl35
-rw-r--r--src/mod_shared_roster.erl134
-rw-r--r--src/mod_shared_roster_ldap.erl1156
-rw-r--r--src/mod_sic.erl9
-rw-r--r--src/mod_sip.erl74
-rw-r--r--src/mod_sip_proxy.erl36
-rw-r--r--src/mod_sip_registrar.erl33
-rw-r--r--src/mod_stats.erl28
-rw-r--r--src/mod_time.erl40
-rw-r--r--src/mod_vcard.erl188
-rw-r--r--src/mod_vcard_ldap.erl126
-rw-r--r--src/mod_vcard_xupdate.erl11
-rw-r--r--src/mod_version.erl12
-rw-r--r--src/node_buddy.erl97
-rw-r--r--src/node_club.erl97
-rw-r--r--src/node_dag.erl34
-rw-r--r--src/node_dispatch.erl55
-rw-r--r--src/node_flat.erl810
-rw-r--r--src/node_flat_odbc.erl1035
-rw-r--r--src/node_hometree.erl850
-rw-r--r--src/node_hometree_odbc.erl1160
-rw-r--r--src/node_mb.erl57
-rw-r--r--src/node_mix.erl167
-rw-r--r--src/node_mix_odbc.erl170
-rw-r--r--src/node_online.erl173
-rw-r--r--src/node_pep.erl114
-rw-r--r--src/node_pep_odbc.erl136
-rw-r--r--src/node_private.erl97
-rw-r--r--src/node_public.erl97
-rw-r--r--src/nodetree_dag.erl40
-rw-r--r--src/nodetree_tree.erl44
-rw-r--r--src/nodetree_tree_odbc.erl60
-rw-r--r--src/nodetree_virtual.erl84
-rw-r--r--src/odbc_queries.erl1157
-rw-r--r--src/prosody2ejabberd.erl341
-rw-r--r--src/pubsub_db_odbc.erl40
-rw-r--r--src/pubsub_index.erl42
-rw-r--r--src/pubsub_migrate.erl35
-rw-r--r--src/pubsub_subscription.erl44
-rw-r--r--src/pubsub_subscription_odbc.erl46
-rw-r--r--src/randoms.erl23
-rw-r--r--src/scram.erl8
-rw-r--r--src/shaper.erl19
-rw-r--r--src/str.erl2
-rw-r--r--src/translate.erl2
-rw-r--r--src/win32_dns.erl2
171 files changed, 19239 insertions, 10305 deletions
diff --git a/src/acl.erl b/src/acl.erl
index 8d9692ff..fdf397d8 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -5,7 +5,7 @@
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,14 @@
-module(acl).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-export([start/0, to_record/3, add/3, add_list/3,
- add_local/3, add_list_local/3, load_from_config/0,
- match_rule/3, match_acl/3, transform_options/1]).
+ add_local/3, add_list_local/3, load_from_config/0,
+ match_rule/3, match_acl/3, transform_options/1,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -95,7 +98,7 @@ to_record(Host, ACLName, ACLSpec) ->
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
- {ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
+ {ResL, BadNodes} = ejabberd_cluster:multicall(
?MODULE, add_local,
[Host, ACLName, ACLSpec]),
case lists:keyfind(aborted, 1, ResL) of
@@ -122,7 +125,7 @@ add_local(Host, ACLName, ACLSpec) ->
-spec add_list(binary(), [acl()], boolean()) -> ok | {error, any()}.
add_list(Host, ACLs, Clear) ->
- {ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
+ {ResL, BadNodes} = ejabberd_cluster:multicall(
?MODULE, add_list_local,
[Host, ACLs, Clear]),
case lists:keyfind(aborted, 1, ResL) of
@@ -164,16 +167,12 @@ add_list_local(Host, ACLs, Clear) ->
access_name(), [access_rule()]) -> ok | {error, any()}.
add_access(Host, Access, Rules) ->
- case mnesia:transaction(
- fun() ->
- mnesia:write(
- #access{name = {Access, Host},
- rules = Rules})
- end) of
- {atomic, ok} ->
- ok;
- Err ->
- {error, Err}
+ Obj = #access{name = {Access, Host}, rules = Rules},
+ case mnesia:transaction(fun() -> mnesia:write(Obj) end) of
+ {atomic, ok} ->
+ ok;
+ Err ->
+ {error, Err}
end.
-spec load_from_config() -> ok.
@@ -209,13 +208,13 @@ b(S) ->
iolist_to_binary(S).
nodeprep(S) ->
- jlib:nodeprep(b(S)).
+ jid:nodeprep(b(S)).
nameprep(S) ->
- jlib:nameprep(b(S)).
+ jid:nameprep(b(S)).
resourceprep(S) ->
- jlib:resourceprep(b(S)).
+ jid:resourceprep(b(S)).
normalize_spec(Spec) ->
case Spec of
@@ -236,8 +235,7 @@ normalize_spec(Spec) ->
{server_regexp, SR} -> {server_regexp, b(SR)};
{server_glob, S} -> {server_glob, b(S)};
{resource_glob, R} -> {resource_glob, b(R)};
- {ip, {Net, Mask}} ->
- {ip, {Net, Mask}};
+ {ip, {Net, Mask}} -> {ip, {Net, Mask}};
{ip, S} ->
case parse_ip_netmask(b(S)) of
{ok, Net, Mask} ->
@@ -295,11 +293,9 @@ match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
is_ip_match(IP, Net, Mask);
(_) ->
false
- end,
- ets:lookup(acl, {ACL, Host}) ++
- ets:lookup(acl, {ACL, global}));
+ end, get_aclspecs(ACL, Host));
match_acl(ACL, JID, Host) ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
+ {User, Server, Resource} = jid:tolower(JID),
lists:any(
fun(#acl{aclspec = Spec}) ->
case Spec of
@@ -347,8 +343,10 @@ match_acl(ACL, JID, Host) ->
false
end
end,
- ets:lookup(acl, {ACL, Host}) ++
- ets:lookup(acl, {ACL, global})).
+ get_aclspecs(ACL, Host)).
+
+get_aclspecs(ACL, Host) ->
+ ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -476,3 +474,7 @@ transform_options({access, Name, Rules}, Opts) ->
[{access, [{Name, NewRules}]}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
+
+opt_type(access) -> fun (V) -> V end;
+opt_type(acl) -> fun (V) -> V end;
+opt_type(_) -> [access, acl].
diff --git a/src/adhoc.erl b/src/adhoc.erl
index a68b54d8..788bf65a 100644
--- a/src/adhoc.erl
+++ b/src/adhoc.erl
@@ -5,7 +5,7 @@
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -51,9 +51,9 @@
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
- SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
- Action = xml:get_tag_attr_s(<<"action">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
+ SessionID = fxml:get_tag_attr_s(<<"sessionid">>, SubEl),
+ Action = fxml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
#xmlel{children = AllEls} = SubEl,
Others = case XData of
@@ -76,7 +76,7 @@ find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1([]) -> false;
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_XDATA -> El;
_ -> find_xdata_el1(Els)
end;
@@ -121,7 +121,7 @@ produce_response(
}) ->
SessionID = if is_binary(ProvidedSessionID),
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
- true -> jlib:now_to_utc_string(now())
+ true -> jlib:now_to_utc_string(p1_time_compat:timestamp())
end,
case Actions of
[] ->
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index 09b1a1a6..21fbc966 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,10 +25,13 @@
-module(cyrsasl).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-export([start/0, register_mechanism/3, listmech/1,
- server_new/7, server_start/3, server_step/2]).
+ server_new/7, server_start/3, server_step/2,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -81,6 +84,7 @@ start() ->
cyrsasl_digest:start([]),
cyrsasl_scram:start([]),
cyrsasl_anonymous:start([]),
+ cyrsasl_oauth:start([]),
ok.
%%
@@ -107,13 +111,13 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%%-include("ejabberd.hrl").
%%-include("jlib.hrl").
%%check_authzid(_State, Props) ->
-%% AuthzId = xml:get_attr_s(authzid, Props),
-%% case jlib:string_to_jid(AuthzId) of
+%% AuthzId = fxml:get_attr_s(authzid, Props),
+%% case jid:from_string(AuthzId) of
%% error ->
%% {error, "invalid-authzid"};
%% JID ->
-%% LUser = jlib:nodeprep(xml:get_attr_s(username, Props)),
-%% {U, S, R} = jlib:jid_tolower(JID),
+%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
+%% {U, S, R} = jid:tolower(JID),
%% case R of
%% "" ->
%% {error, "invalid-authzid"};
@@ -129,7 +133,7 @@ register_mechanism(Mechanism, Module, PasswordType) ->
check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>),
- case jlib:nodeprep(User) of
+ case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
_LUser -> ok
@@ -237,3 +241,10 @@ is_disabled(Mechanism) ->
[str:to_upper(V)]
end, []),
lists:member(Mechanism, Disabled).
+
+opt_type(disable_sasl_mechanisms) ->
+ fun (V) when is_list(V) ->
+ lists:map(fun (M) -> str:to_upper(M) end, V);
+ (V) -> [str:to_upper(V)]
+ end;
+opt_type(_) -> [disable_sasl_mechanisms].
diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl
index 51d5db9d..2b2a9f63 100644
--- a/src/cyrsasl_anonymous.erl
+++ b/src/cyrsasl_anonymous.erl
@@ -6,7 +6,7 @@
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -26,6 +26,8 @@
-module(cyrsasl_anonymous).
+-protocol({xep, 175, '1.2'}).
+
-export([start/1, stop/0, mech_new/4, mech_step/2]).
-behaviour(cyrsasl).
@@ -41,11 +43,11 @@ stop() -> ok.
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{server = Host}}.
-mech_step(#state{server = Server}, _ClientIn) ->
- User = iolist_to_binary([randoms:get_string()
- | [jlib:integer_to_binary(X)
- || X <- tuple_to_list(now())]]),
+mech_step(#state{server = Server} = S, ClientIn) ->
+ User = iolist_to_binary([randoms:get_string(),
+ randoms:get_string(),
+ randoms:get_string()]),
case ejabberd_auth:is_user_exists(User, Server) of
- true -> {error, <<"not-authorized">>};
+ true -> mech_step(S, ClientIn);
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
end.
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 12e5555a..e58cb303 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,9 +25,12 @@
-module(cyrsasl_digest).
+-behaviour(ejabberd_config).
+
-author('alexey@sevcom.net').
--export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
+-export([start/1, stop/0, mech_new/4, mech_step/2,
+ parse/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -256,3 +259,6 @@ response(KeyVals, User, Passwd, Nonce, AuthzId,
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
":", (hex((erlang:md5(A2))))/binary>>,
hex((erlang:md5(T))).
+
+opt_type(fqdn) -> fun iolist_to_binary/1;
+opt_type(_) -> [fqdn].
diff --git a/src/cyrsasl_oauth.erl b/src/cyrsasl_oauth.erl
new file mode 100644
index 00000000..16f1e3df
--- /dev/null
+++ b/src/cyrsasl_oauth.erl
@@ -0,0 +1,91 @@
+%%%----------------------------------------------------------------------
+%%% File : cyrsasl_oauth.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : X-OAUTH2 SASL mechanism
+%%% Created : 17 Sep 2015 by Alexey Shchepin <alexey@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(cyrsasl_oauth).
+
+-author('alexey@process-one.net').
+
+-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
+
+-behaviour(cyrsasl).
+
+-record(state, {host}).
+
+start(_Opts) ->
+ cyrsasl:register_mechanism(<<"X-OAUTH2">>, ?MODULE, plain),
+ ok.
+
+stop() -> ok.
+
+mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
+ {ok, #state{host = Host}}.
+
+mech_step(State, ClientIn) ->
+ case prepare(ClientIn) of
+ [AuthzId, User, Token] ->
+ case ejabberd_oauth:check_token(
+ User, State#state.host, <<"sasl_auth">>, Token) of
+ true ->
+ {ok,
+ [{username, User}, {authzid, AuthzId},
+ {auth_module, ejabberd_oauth}]};
+ false ->
+ {error, <<"not-authorized">>, User}
+ end;
+ _ -> {error, <<"bad-protocol">>}
+ end.
+
+prepare(ClientIn) ->
+ case parse(ClientIn) of
+ [<<"">>, UserMaybeDomain, Token] ->
+ case parse_domain(UserMaybeDomain) of
+ %% <NUL>login@domain<NUL>pwd
+ [User, _Domain] -> [UserMaybeDomain, User, Token];
+ %% <NUL>login<NUL>pwd
+ [User] -> [<<"">>, User, Token]
+ end;
+ %% login@domain<NUL>login<NUL>pwd
+ [AuthzId, User, Token] -> [AuthzId, User, Token];
+ _ -> error
+ end.
+
+parse(S) -> parse1(binary_to_list(S), "", []).
+
+parse1([0 | Cs], S, T) ->
+ parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
+parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
+%parse1([], [], T) ->
+% lists:reverse(T);
+parse1([], S, T) ->
+ lists:reverse([list_to_binary(lists:reverse(S)) | T]).
+
+parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
+
+parse_domain1([$@ | Cs], S, T) ->
+ parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
+parse_domain1([C | Cs], S, T) ->
+ parse_domain1(Cs, [C | S], T);
+parse_domain1([], S, T) ->
+ lists:reverse([list_to_binary(lists:reverse(S)) | T]).
diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl
index ceceacca..82d68f87 100644
--- a/src/cyrsasl_plain.erl
+++ b/src/cyrsasl_plain.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -66,7 +66,7 @@ prepare(ClientIn) ->
end;
[AuthzId, User, Password] ->
case parse_domain(AuthzId) of
- %% login@domain<NUL>login<NUL>pwd
+ %% login@domain<NUL>login<NUL>pwd
[AuthzUser, _Domain] -> [AuthzUser, User, Password];
%% login<NUL>login<NUL>pwd
[AuthzUser] -> [AuthzUser, User, Password]
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index deef51c5..059938f5 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -5,7 +5,7 @@
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,8 @@
-author('stephen.roettger@googlemail.com').
+-protocol({rfc, 5802}).
+
-export([start/1, stop/0, mech_new/4, mech_step/2]).
-include("ejabberd.hrl").
@@ -77,7 +79,7 @@ mech_step(#state{step = 2} = State, ClientIn) ->
case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} ->
{Ret, _AuthModule} = (State#state.get_password)(UserName),
- case {Ret, jlib:resourceprep(Ret)} of
+ case {Ret, jid:resourceprep(Ret)} of
{false, _} -> {error, <<"not-authorized">>, UserName};
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, <<"not-authorized">>, UserName};
{Ret, _} ->
diff --git a/src/ejabberd.erl b/src/ejabberd.erl
index 64ac5a0d..e1b92c26 100644
--- a/src/ejabberd.erl
+++ b/src/ejabberd.erl
@@ -5,7 +5,7 @@
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -26,6 +26,16 @@
-module(ejabberd).
-author('alexey@process-one.net').
+-protocol({xep, 4, '2.9'}).
+-protocol({xep, 86, '1.0'}).
+-protocol({xep, 106, '1.1'}).
+-protocol({xep, 170, '1.0'}).
+-protocol({xep, 205, '1.0'}).
+-protocol({xep, 212, '1.0'}).
+-protocol({xep, 216, '1.0'}).
+-protocol({xep, 243, '1.0'}).
+-protocol({xep, 270, '1.0'}).
+
-export([start/0, stop/0, start_app/1, start_app/2,
get_pid_file/0, check_app/1]).
@@ -95,7 +105,7 @@ start_app([], _Type, _StartFlag) ->
ok.
check_app_modules(App, StartFlag) ->
- {A, B, C} = now(),
+ {A, B, C} = p1_time_compat:timestamp(),
random:seed(A, B, C),
sleep(5000),
case application:get_key(App, modules) of
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 8b6e27b8..b22a7038 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -5,7 +5,7 @@
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -28,10 +28,13 @@
-export([start/0, stop/0,
%% Server
- status/0, reopen_log/0,
+ status/0, reopen_log/0, rotate_log/0,
+ set_loglevel/1,
stop_kindly/2, send_service_message_all_mucs/2,
registered_vhosts/0,
reload_config/0,
+ %% Cluster
+ join_cluster/1, leave_cluster/1, list_cluster/0,
%% Erlang
update_list/0, update/1,
%% Accounts
@@ -42,14 +45,14 @@
%% Purge DB
delete_expired_messages/0, delete_old_messages/1,
%% Mnesia
- export2odbc/2,
set_master/1,
backup_mnesia/1, restore_mnesia/1,
dump_mnesia/1, dump_table/2, load_mnesia/1,
install_fallback_mnesia/1,
dump_to_textfile/1, dump_to_textfile/2,
mnesia_change_nodename/4,
- restore/1 % Still used by some modules
+ restore/1, % Still used by some modules
+ get_commands_spec/0
]).
-include("ejabberd.hrl").
@@ -57,16 +60,16 @@
-include("ejabberd_commands.hrl").
start() ->
- ejabberd_commands:register_commands(commands()).
+ ejabberd_commands:register_commands(get_commands_spec()).
stop() ->
- ejabberd_commands:unregister_commands(commands()).
+ ejabberd_commands:unregister_commands(get_commands_spec()).
%%%
%%% ejabberd commands
%%%
-commands() ->
+get_commands_spec() ->
[
%% The commands status, stop and restart are implemented also in ejabberd_ctl
%% They are defined here so that other interfaces can use them too
@@ -86,6 +89,10 @@ commands() ->
desc = "Reopen the log files",
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
+ #ejabberd_commands{name = rotate_log, tags = [logs, server],
+ desc = "Rotate the log files",
+ module = ?MODULE, function = rotate_log,
+ args = [], result = {res, rescode}},
#ejabberd_commands{name = stop_kindly, tags = [server],
desc = "Inform users and rooms, wait, and stop the server",
longdesc = "Provide the delay in seconds, and the "
@@ -103,6 +110,11 @@ commands() ->
{levelatom, atom},
{leveldesc, string}
]}}},
+ #ejabberd_commands{name = set_loglevel, tags = [logs, server],
+ desc = "Set the loglevel (0 to 5)",
+ module = ?MODULE, function = set_loglevel,
+ args = [{loglevel, integer}],
+ result = {logger, atom}},
#ejabberd_commands{name = update_list, tags = [server],
desc = "List modified modules that can be updated",
@@ -136,11 +148,27 @@ commands() ->
args = [],
result = {vhosts, {list, {vhost, string}}}},
#ejabberd_commands{name = reload_config, tags = [server],
- desc = "Reload ejabberd configuration file into memory",
+ desc = "Reload config file in memory (only affects ACL and Access)",
module = ?MODULE, function = reload_config,
args = [],
result = {res, rescode}},
+ #ejabberd_commands{name = join_cluster, tags = [cluster],
+ desc = "Join this node into the cluster handled by Node",
+ module = ?MODULE, function = join_cluster,
+ args = [{node, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = leave_cluster, tags = [cluster],
+ desc = "Remove node handled by Node from the cluster",
+ module = ?MODULE, function = leave_cluster,
+ args = [{node, binary}],
+ result = {res, rescode}},
+ #ejabberd_commands{name = list_cluster, tags = [cluster],
+ desc = "List nodes that are part of the cluster handled by Node",
+ module = ?MODULE, function = list_cluster,
+ args = [],
+ result = {nodes, {list, {node, atom}}}},
+
#ejabberd_commands{name = import_file, tags = [mnesia],
desc = "Import user data from jabberd14 spool file",
module = ?MODULE, function = import_file,
@@ -168,6 +196,19 @@ commands() ->
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
+ #ejabberd_commands{name = delete_mnesia, tags = [mnesia, odbc],
+ desc = "Export all tables as SQL queries to a file",
+ module = ejd2odbc, function = delete,
+ args = [{host, string}], result = {res, rescode}},
+ #ejabberd_commands{name = convert_to_scram, tags = [odbc],
+ desc = "Convert the passwords in 'users' ODBC table to SCRAM",
+ module = ejabberd_auth_odbc, function = convert_to_scram,
+ args = [{host, binary}], result = {res, rescode}},
+
+ #ejabberd_commands{name = import_prosody, tags = [mnesia, odbc, riak],
+ desc = "Import data from Prosody",
+ module = prosody2ejabberd, function = from_dir,
+ args = [{dir, string}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
@@ -184,14 +225,9 @@ commands() ->
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
- #ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
- desc = "Update PubSub table from old ejabberd trunk SVN to 2.1.0",
- module = mod_pubsub, function = rename_default_nodeplugin,
- args = [], result = {res, rescode}},
-
#ejabberd_commands{name = export2odbc, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
- module = ?MODULE, function = export2odbc,
+ module = ejd2odbc, function = export,
args = [{host, string}, {directory, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
@@ -254,6 +290,15 @@ reopen_log() ->
ejabberd_hooks:run(reopen_log_hook, []),
ejabberd_logger:reopen_log().
+rotate_log() ->
+ ejabberd_hooks:run(rotate_log_hook, []),
+ ejabberd_logger:rotate_log().
+
+set_loglevel(LogLevel) ->
+ {module, Module} = ejabberd_logger:set(LogLevel),
+ Module.
+
+
%%%
%%% Stop Kindly
%%%
@@ -364,6 +409,19 @@ reload_config() ->
shaper:start().
%%%
+%%% Cluster management
+%%%
+
+join_cluster(NodeBin) ->
+ ejabberd_cluster:join(list_to_atom(binary_to_list(NodeBin))).
+
+leave_cluster(NodeBin) ->
+ ejabberd_cluster:leave(list_to_atom(binary_to_list(NodeBin))).
+
+list_cluster() ->
+ ejabberd_cluster:get_nodes().
+
+%%%
%%% Migration management
%%%
@@ -408,23 +466,6 @@ delete_old_messages(Days) ->
%%% Mnesia management
%%%
-export2odbc(Host, Directory) ->
- Tables = [{export_last, last},
- {export_offline, offline},
- {export_private_storage, private_storage},
- {export_roster, roster},
- {export_vcard, vcard},
- {export_vcard_search, vcard_search},
- {export_passwd, passwd}],
- Export = fun({TableFun, Table}) ->
- Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
- io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
- Res = (catch ejd2odbc:TableFun(Host, Filename)),
- io:format(" Result: ~p~n", [Res])
- end,
- lists:foreach(Export, Tables),
- ok.
-
set_master("self") ->
set_master(node());
set_master(NodeString) when is_list(NodeString) ->
@@ -469,7 +510,7 @@ restore_mnesia(Path) ->
%% Mnesia database restore
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
-%% mod_configure/adhoc
+%% mod_configure/adhoc
restore(Path) ->
mnesia:restore(Path, [{keep_tables,keep_tables()},
{default_op, skip_tables}]).
@@ -484,7 +525,7 @@ keep_tables() ->
%% Returns the list of modules tables in use, according to the list of actually
%% loaded modules
-keep_modules_tables() ->
+keep_modules_tables() ->
lists:map(fun(Module) -> module_tables(Module) end,
gen_mod:loaded_modules(?MYNAME)).
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index fabe2d3e..e493eac0 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,11 +24,14 @@
%%%----------------------------------------------------------------------
-module(ejabberd_app).
+
+-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(application).
--export([start_modules/0,start/2, prep_stop/1, stop/1, init/0]).
+-export([start_modules/0, start/2, prep_stop/1, stop/1,
+ init/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -40,6 +43,7 @@
start(normal, _Args) ->
ejabberd_logger:start(),
write_pid_file(),
+ jid:start(),
start_apps(),
ejabberd:check_app(ejabberd),
randoms:start(),
@@ -50,8 +54,9 @@ start(normal, _Args) ->
ejabberd_commands:init(),
ejabberd_admin:start(),
gen_mod:start(),
+ ext_mod:start(),
ejabberd_config:start(),
- set_loglevel_from_config(),
+ set_settings_from_config(),
acl:start(),
shaper:start(),
connect_nodes(),
@@ -59,13 +64,13 @@ start(normal, _Args) ->
ejabberd_rdbms:start(),
ejabberd_riak_sup:start(),
ejabberd_sm:start(),
- ejabberd_auth:start(),
cyrsasl:start(),
% Profiling
%ejabberd_debug:eprof_start(),
%ejabberd_debug:fprof_start(),
maybe_add_nameservers(),
- ext_mod:start(),
+ ejabberd_auth:start(),
+ ejabberd_oauth:start(),
start_modules(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
@@ -231,20 +236,39 @@ delete_pid_file() ->
file:delete(PidFilename)
end.
-set_loglevel_from_config() ->
+set_settings_from_config() ->
Level = ejabberd_config:get_option(
loglevel,
fun(P) when P>=0, P=<5 -> P end,
4),
- ejabberd_logger:set(Level).
+ ejabberd_logger:set(Level),
+ Ticktime = ejabberd_config:get_option(
+ net_ticktime,
+ opt_type(net_ticktime),
+ 60),
+ net_kernel:set_net_ticktime(Ticktime).
start_apps() ->
crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
- ejabberd:start_app(p1_yaml),
- ejabberd:start_app(p1_tls),
- ejabberd:start_app(p1_xml),
- ejabberd:start_app(p1_stringprep),
- ejabberd:start_app(p1_zlib),
- ejabberd:start_app(p1_cache_tab).
+ ejabberd:start_app(fast_yaml),
+ ejabberd:start_app(fast_tls),
+ ejabberd:start_app(fast_xml),
+ ejabberd:start_app(stringprep),
+ ejabberd:start_app(cache_tab).
+
+opt_type(net_ticktime) ->
+ fun (P) when is_integer(P), P > 0 -> P end;
+opt_type(cluster_nodes) ->
+ fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
+opt_type(loglevel) ->
+ fun (P) when P >= 0, P =< 5 -> P end;
+opt_type(modules) ->
+ fun (Mods) ->
+ lists:map(fun ({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end,
+ Mods)
+ end;
+opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index bf47af85..343ad943 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,8 @@
-module(ejabberd_auth).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
%% External exports
@@ -42,7 +44,7 @@
remove_user/2, remove_user/3, plain_password_required/1,
store_type/1, entropy/1]).
--export([auth_modules/1]).
+-export([auth_modules/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -75,7 +77,7 @@
-callback get_password_s(binary(), binary()) -> binary() | {binary(), binary(), binary(), integer()}.
start() ->
-%% This is only executed by ejabberd_c2s for non-SASL auth client
+ %% This is only executed by ejabberd_c2s for non-SASL auth client
lists:foreach(fun (Host) ->
lists:foreach(fun (M) -> M:start(Host) end,
auth_modules(Host))
@@ -116,7 +118,7 @@ check_password(User, AuthzId, Server, Password) ->
%% true | false
-spec check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-
+
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
case check_password_with_authmodule(User, AuthzId, Server,
@@ -185,7 +187,8 @@ try_register(User, Server, Password) ->
case is_user_exists(User, Server) of
true -> {atomic, exists};
false ->
- case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
+ LServer = jid:nameprep(Server),
+ case lists:member(LServer, ?MYHOSTS) of
true ->
Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
(M, _) ->
@@ -355,7 +358,7 @@ remove_user(User, Server) ->
lists:foreach(fun (M) -> M:remove_user(User, Server)
end,
auth_modules(Server)),
- ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
+ ejabberd_hooks:run(remove_user, jid:nameprep(Server),
[User, Server]),
ok.
@@ -373,7 +376,7 @@ remove_user(User, Server, Password) ->
error, auth_modules(Server)),
case R of
ok ->
- ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
+ ejabberd_hooks:run(remove_user, jid:nameprep(Server),
[User, Server]);
_ -> none
end,
@@ -424,7 +427,7 @@ auth_modules() ->
%% Return the list of authenticated modules for a given host
auth_modules(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
Default = case gen_mod:default_db(LServer) of
mnesia -> internal;
DBType -> DBType
@@ -453,3 +456,10 @@ import(Server, riak, Passwd) ->
ejabberd_auth_riak:import(Server, riak, Passwd);
import(_, _, _) ->
pass.
+
+opt_type(auth_method) ->
+ fun (V) when is_list(V) ->
+ true = lists:all(fun is_atom/1, V), V;
+ (V) when is_atom(V) -> [V]
+ end;
+opt_type(_) -> [auth_method].
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index 05f790db..9c4b719c 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -5,7 +5,7 @@
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,6 +24,8 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_anonymous).
+
+-behaviour(ejabberd_config).
-author('mickael.remond@process-one.net').
-export([start/1,
@@ -36,16 +38,15 @@
unregister_connection/3
]).
-
-%% Function used by ejabberd_auth:
-export([login/2, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2, get_vh_registered_users_number/1,
- get_vh_registered_users_number/2, get_password_s/2,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password_s/2,
get_password/2, get_password/3, is_user_exists/2,
remove_user/2, remove_user/3, store_type/0,
- plain_password_required/0]).
+ plain_password_required/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -55,7 +56,7 @@
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- sid = {now(), self()} :: ejabberd_sm:sid()}).
+ sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode
@@ -121,8 +122,8 @@ allow_multiple_connections(Host) ->
%% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({anonymous, US}) of
[] ->
@@ -141,7 +142,7 @@ remove_connection(SID, LUser, LServer) ->
%% Register connection
register_connection(SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
- AuthModule = list_to_atom(binary_to_list(xml:get_attr_s(<<"auth_module">>, Info))),
+ AuthModule = proplists:get_value(auth_module, Info, undefined),
case AuthModule == (?MODULE) of
true ->
ejabberd_hooks:run(register_user, LServer,
@@ -269,3 +270,13 @@ plain_password_required() -> false.
store_type() ->
plain.
+
+opt_type(allow_multiple_connections) ->
+ fun (V) when is_boolean(V) -> V end;
+opt_type(anonymous_protocol) ->
+ fun (sasl_anon) -> sasl_anon;
+ (login_anon) -> login_anon;
+ (both) -> both
+ end;
+opt_type(_) ->
+ [allow_multiple_connections, anonymous_protocol].
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index 44c931cb..5897fba5 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,12 @@
-module(ejabberd_auth_external).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-%% External exports
-export([start/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
@@ -37,8 +38,8 @@
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
- remove_user/3, store_type/0,
- plain_password_required/0]).
+ remove_user/3, store_type/0, plain_password_required/0,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -79,9 +80,9 @@ check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- case get_cache_option(Server) of
+ case get_cache_option(Server) of
false -> check_password_extauth(User, AuthzId, Server, Password);
- {true, CacheTime} ->
+ {true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password, CacheTime)
end
end.
@@ -225,7 +226,7 @@ check_password_cache(User, AuthzId, Server, Password,
get_password_internal(User, Server) ->
ejabberd_auth_internal:get_password(User, Server).
-%% @spec (User, Server, CacheTime) -> false | Password::string()
+-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_internal(User, Server);
@@ -273,18 +274,16 @@ set_password_internal(User, Server, Password) ->
Password).
is_fresh_enough(TimeStampLast, CacheTime) ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- Now = MegaSecs * 1000000 + Secs,
+ Now = p1_time_compat:system_time(seconds),
TimeStampLast + CacheTime > Now.
-%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
+-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
- _US = {User, Server},
case get_last_info(User, Server) of
mod_last_required -> mod_last_required;
not_found -> never;
@@ -314,4 +313,14 @@ get_mod_last_configured(Server) ->
end.
is_configured(Host, Module) ->
- gen_mod:is_loaded(Host, Module).
+ Os = ejabberd_config:get_local_option({modules, Host},
+ fun(M) when is_list(M) -> M end),
+ lists:keymember(Module, 1, Os).
+
+opt_type(extauth_cache) ->
+ fun (false) -> undefined;
+ (I) when is_integer(I), I >= 0 -> I
+ end;
+opt_type(extauth_program) ->
+ fun (V) -> binary_to_list(iolist_to_binary(V)) end;
+opt_type(_) -> [extauth_cache, extauth_program].
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index bb4ceab5..3b30b360 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,12 @@
-module(ejabberd_auth_internal).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-%% External exports
-export([start/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
@@ -38,7 +39,7 @@
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/1,
- import/3, plain_password_required/0]).
+ import/3, plain_password_required/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -69,7 +70,7 @@ start(Host) ->
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
F = fun () ->
mnesia:write(#reg_users_counter{vhost = LServer,
count = Size})
@@ -89,17 +90,17 @@ check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Password}]
- when is_binary(Password) ->
- Password /= <<"">>;
- [#passwd{password = Scram}]
- when is_record(Scram, scram) ->
- is_password_scram_valid(Password, Scram);
- _ -> false
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ US = {LUser, LServer},
+ case catch mnesia:dirty_read({passwd, US}) of
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ Password /= <<"">>;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
+ _ -> false
end
end.
@@ -108,37 +109,37 @@ check_password(User, AuthzId, Server, Password, Digest,
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Passwd}] when is_binary(Passwd) ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- [#passwd{password = Scram}]
- when is_record(Scram, scram) ->
- Passwd = jlib:decode_base64(Scram#scram.storedkey),
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- _ -> false
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ US = {LUser, LServer},
+ case catch mnesia:dirty_read({passwd, US}) of
+ [#passwd{password = Passwd}] when is_binary(Passwd) ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ Passwd = jlib:decode_base64(Scram#scram.storedkey),
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ _ -> false
end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
@@ -157,8 +158,8 @@ set_password(User, Server, Password) ->
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason}
try_register(User, Server, PasswordList) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
@@ -192,7 +193,7 @@ dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
get_vh_registered_users(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
@@ -251,7 +252,7 @@ get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
Query = mnesia:dirty_select(reg_users_counter,
[{#reg_users_counter{vhost = LServer,
count = '$1'},
@@ -272,8 +273,8 @@ get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
@@ -289,8 +290,8 @@ get_password(User, Server) ->
end.
get_password_s(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
@@ -304,8 +305,8 @@ get_password_s(User, Server) ->
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] -> false;
@@ -317,8 +318,8 @@ is_user_exists(User, Server) ->
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
F = fun () ->
mnesia:delete({passwd, US}),
@@ -331,8 +332,8 @@ remove_user(User, Server) ->
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
F = fun () ->
case mnesia:read({passwd, US}) of
@@ -492,3 +493,6 @@ import(_LServer, mnesia, #passwd{} = P) ->
mnesia:dirty_write(P);
import(_, _, _) ->
pass.
+
+opt_type(auth_password_format) -> fun (V) -> V end;
+opt_type(_) -> [auth_password_format].
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index 45964d66..51b466ef 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_auth_ldap).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -34,16 +36,15 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-%% External exports
-export([start/1, stop/1, start_link/1, set_password/3,
check_password/4, check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2,
- get_vh_registered_users_number/1,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
- remove_user/3, store_type/0,
- plain_password_required/0]).
+ remove_user/3, store_type/0, plain_password_required/0,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -119,13 +120,13 @@ check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- if Password == <<"">> -> false;
- true ->
- case catch check_password_ldap(User, Server, Password)
- of
- {'EXIT', _} -> false;
- Result -> Result
- end
+ if Password == <<"">> -> false;
+ true ->
+ case catch check_password_ldap(User, Server, Password)
+ of
+ {'EXIT', _} -> false;
+ Result -> Result
+ end
end
end.
@@ -231,11 +232,11 @@ get_vh_registered_users_ldap(Server) ->
UIDFormat)
of
{ok, U} ->
- case jlib:nodeprep(U) of
+ case jid:nodeprep(U) of
error -> [];
LU ->
[{LU,
- jlib:nameprep(Server)}]
+ jid:nameprep(Server)}]
end;
_ -> []
end
@@ -364,7 +365,7 @@ parse_options(Host) ->
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
Bind_Eldap_ID = jlib:atom_to_binary(
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
- UIDsTemp = eldap_utils:get_opt(
+ UIDsTemp = gen_mod:get_opt(
{ldap_uids, Host}, [],
fun(Us) ->
lists:map(
@@ -379,7 +380,7 @@ parse_options(Host) ->
end, [{<<"uid">>, <<"%u">>}]),
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
SubFilter = eldap_utils:generate_subfilter(UIDs),
- UserFilter = case eldap_utils:get_opt(
+ UserFilter = case gen_mod:get_opt(
{ldap_filter, Host}, [],
fun check_filter/1, <<"">>) of
<<"">> ->
@@ -390,7 +391,7 @@ parse_options(Host) ->
SearchFilter = eldap_filter:do_sub(UserFilter,
[{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} =
- eldap_utils:get_opt({ldap_dn_filter, Host}, [],
+ gen_mod:get_opt({ldap_dn_filter, Host}, [],
fun([{DNF, DNFA}]) ->
NewDNFA = case DNFA of
undefined ->
@@ -402,7 +403,7 @@ parse_options(Host) ->
NewDNF = check_filter(DNF),
{NewDNF, NewDNFA}
end, {undefined, []}),
- LocalFilter = eldap_utils:get_opt(
+ LocalFilter = gen_mod:get_opt(
{ldap_local_filter, Host}, [], fun(V) -> V end),
#state{host = Host, eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID,
@@ -422,3 +423,27 @@ check_filter(F) ->
NewF = iolist_to_binary(F),
{ok, _} = eldap_filter:parse(NewF),
NewF.
+
+opt_type(ldap_dn_filter) ->
+ fun ([{DNF, DNFA}]) ->
+ NewDNFA = case DNFA of
+ undefined -> [];
+ _ -> [iolist_to_binary(A) || A <- DNFA]
+ end,
+ NewDNF = check_filter(DNF),
+ {NewDNF, NewDNFA}
+ end;
+opt_type(ldap_filter) -> fun check_filter/1;
+opt_type(ldap_local_filter) -> fun (V) -> V end;
+opt_type(ldap_uids) ->
+ fun (Us) ->
+ lists:map(fun ({U, P}) ->
+ {iolist_to_binary(U), iolist_to_binary(P)};
+ ({U}) -> {iolist_to_binary(U)};
+ (U) -> {iolist_to_binary(U)}
+ end,
+ lists:flatten(Us))
+ end;
+opt_type(_) ->
+ [ldap_dn_filter, ldap_filter, ldap_local_filter,
+ ldap_uids].
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
index b3bcd369..dc3248fe 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_odbc.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,12 @@
-module(ejabberd_auth_odbc).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-%% External exports
-export([start/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
@@ -37,9 +38,8 @@
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
- remove_user/3, store_type/0,
- plain_password_required/0,
- convert_to_scram/1]).
+ remove_user/3, store_type/0, plain_password_required/0,
+ convert_to_scram/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -68,51 +68,47 @@ check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
- if (LUser == error) or (LServer == error) ->
- false;
- (LUser == <<>>) or (LServer == <<>>) ->
- false;
- true ->
- Username = ejabberd_odbc:escape(LUser),
- case is_scrammed() of
- true ->
- try odbc_queries:get_password_scram(LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
- Scram =
- #scram{storedkey = StoredKey,
- serverkey = ServerKey,
- salt = Salt,
- iterationcount = jlib:binary_to_integer(
- IterationCount)},
- is_password_scram_valid(Password, Scram);
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end;
- false ->
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} ->
- Password /= <<"">>;
- {selected, [<<"password">>], [[_Password2]]} ->
- false; %% Password is not correct
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
- end
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ case is_scrammed() of
+ true ->
+ try odbc_queries:get_password_scram(LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
+ Scram =
+ #scram{storedkey = StoredKey,
+ serverkey = ServerKey,
+ salt = Salt,
+ iterationcount = IterationCount},
+ is_password_scram_valid(Password, Scram);
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ false ->
+ try odbc_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} ->
+ Password /= <<"">>;
+ {selected, [{_Password2}]} ->
+ false; %% Password is not correct
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end
+ end
end
end.
@@ -122,70 +118,67 @@ check_password(User, AuthzId, Server, Password, Digest,
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
- if (LUser == error) or (LServer == error) ->
- false;
- (LUser == <<>>) or (LServer == <<>>) ->
- false;
- true ->
- case is_scrammed() of
- false ->
- Username = ejabberd_odbc:escape(LUser),
- try odbc_queries:get_password(LServer, Username) of
- %% Account exists, check if password is valid
- {selected, [<<"password">>], [[Passwd]]} ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end;
- true ->
- false
- end
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ case is_scrammed() of
+ false ->
+ try odbc_queries:get_password(LServer, LUser) of
+ %% Account exists, check if password is valid
+ {selected, [{Passwd}]} ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ true ->
+ false
+ end
end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch odbc_queries:set_password_scram_t(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- jlib:integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
case catch odbc_queries:set_password_t(LServer,
- Username, Pass)
+ LUser, Password)
of
{atomic, ok} -> ok;
Other -> {error, Other}
@@ -195,33 +188,30 @@ set_password(User, Server, Password) ->
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch odbc_queries:add_user_scram(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- jlib:integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
- case catch odbc_queries:add_user(LServer, Username,
- Pass)
- of
+ case catch odbc_queries:add_user(LServer, LUser,
+ Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end
@@ -236,71 +226,85 @@ dirty_get_registered_users() ->
Servers).
get_vh_registered_users(Server) ->
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:list_users(LServer) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch odbc_queries:list_users(LServer) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users(Server, Opts) ->
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:list_users(LServer, Opts) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch odbc_queries:list_users(LServer, Opts) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users_number(Server) ->
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:users_number(LServer) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch odbc_queries:users_number(LServer) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
+ end
end.
get_vh_registered_users_number(Server, Opts) ->
- LServer = jlib:nameprep(Server),
- case catch odbc_queries:users_number(LServer, Opts) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _Other -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch odbc_queries:users_number(LServer, Opts) of
+ {selected, [{Res}]} ->
+ Res;
+ _Other -> 0
+ end
end.
get_password(User, Server) ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
case catch odbc_queries:get_password_scram(
- LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{jlib:decode_base64(StoredKey),
jlib:decode_base64(ServerKey),
jlib:decode_base64(Salt),
- jlib:binary_to_integer(IterationCount)};
+ IterationCount};
_ -> false
end;
false ->
- case catch odbc_queries:get_password(LServer, Username)
+ case catch odbc_queries:get_password(LServer, LUser)
of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ {selected, [{Password}]} -> Password;
_ -> false
end
end
end.
get_password_s(User, Server) ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
<<"">>;
(LUser == <<>>) or (LServer == <<>>) ->
@@ -308,9 +312,8 @@ get_password_s(User, Server) ->
true ->
case is_scrammed() of
false ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ case catch odbc_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} -> Password;
_ -> <<"">>
end;
true -> <<"">>
@@ -319,15 +322,17 @@ get_password_s(User, Server) ->
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
- case jlib:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[_Password]]} ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ try odbc_queries:get_password(LServer, LUser) of
+ {selected, [{_Password}]} ->
true; %% Account exists
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, Error} -> {error, Error}
catch
@@ -339,20 +344,22 @@ is_user_exists(User, Server) ->
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
- case jlib:nodeprep(User) of
- error -> error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jlib:nameprep(Server),
- catch odbc_queries:del_user(LServer, Username),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ error;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ error;
+ true ->
+ catch odbc_queries:del_user(LServer, LUser),
ok
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
error;
(LUser == <<>>) or (LServer == <<>>) ->
@@ -367,16 +374,12 @@ remove_user(User, Server, Password) ->
false -> not_allowed
end;
false ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
F = fun () ->
Result = odbc_queries:del_user_return_password(
- LServer, Username, Pass),
+ LServer, LUser, Password),
case Result of
- {selected, [<<"password">>],
- [[Password]]} -> ok;
- {selected, [<<"password">>],
- []} -> not_exists;
+ {selected, [{Password}]} -> ok;
+ {selected, []} -> not_exists;
_ -> not_allowed
end
end,
@@ -437,7 +440,7 @@ set_password_scram_t(Username,
<<"'">>]).
convert_to_scram(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
if
LServer == error;
LServer == <<>> ->
@@ -447,7 +450,7 @@ convert_to_scram(Server) ->
case ejabberd_odbc:sql_query_t(
[<<"select username, password from users where "
"iterationcount=0 limit ">>,
- jlib:integer_to_binary(?BATCH_SIZE),
+ integer_to_binary(?BATCH_SIZE),
<<";">>]) of
{selected, [<<"username">>, <<"password">>], []} ->
ok;
@@ -461,7 +464,7 @@ convert_to_scram(Server) ->
ejabberd_odbc:escape(Scram#scram.storedkey),
ejabberd_odbc:escape(Scram#scram.serverkey),
ejabberd_odbc:escape(Scram#scram.salt),
- jlib:integer_to_binary(Scram#scram.iterationcount)
+ integer_to_binary(Scram#scram.iterationcount)
)
end, Rs),
continue;
@@ -475,3 +478,6 @@ convert_to_scram(Server) ->
Error -> Error
end
end.
+
+opt_type(auth_password_format) -> fun (V) -> V end;
+opt_type(_) -> [auth_password_format].
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
index da893eb4..fa4b9f07 100644
--- a/src/ejabberd_auth_pam.erl
+++ b/src/ejabberd_auth_pam.erl
@@ -5,7 +5,7 @@
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,22 +24,21 @@
%%%-------------------------------------------------------------------
-module(ejabberd_auth_pam).
+-behaviour(ejabberd_config).
+
-author('xram@jabber.ru').
-behaviour(ejabberd_auth).
-%% External exports
-%%====================================================================
-%% API
-%%====================================================================
-export([start/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2, get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2, get_password_s/2, is_user_exists/2,
- remove_user/2, remove_user/3, store_type/0,
- plain_password_required/0]).
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0, plain_password_required/0,
+ opt_type/1]).
start(_Host) ->
ejabberd:start_app(p1_pam).
@@ -55,16 +54,16 @@ check_password(User, AuthzId, Host, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- Service = get_pam_service(Host),
- UserInfo = case get_pam_userinfotype(Host) of
- username -> User;
- jid -> <<User/binary, "@", Host/binary>>
- end,
- case catch epam:authenticate(Service, UserInfo,
- Password)
- of
- true -> true;
- _ -> false
+ Service = get_pam_service(Host),
+ UserInfo = case get_pam_userinfotype(Host) of
+ username -> User;
+ jid -> <<User/binary, "@", Host/binary>>
+ end,
+ case catch epam:authenticate(Service, UserInfo,
+ Password)
+ of
+ true -> true;
+ _ -> false
end
end.
@@ -122,3 +121,10 @@ get_pam_userinfotype(Host) ->
(jid) -> jid
end,
username).
+
+opt_type(pam_service) -> fun iolist_to_binary/1;
+opt_type(pam_userinfotype) ->
+ fun (username) -> username;
+ (jid) -> jid
+ end;
+opt_type(_) -> [pam_service, pam_userinfotype].
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index 8c926f4b..bc745fea 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Nov 2012 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -70,15 +70,15 @@ check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
- {ok, #passwd{password = Password}} when is_binary(Password) ->
- Password /= <<"">>;
- {ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
- is_password_scram_valid(Password, Scram);
- _ ->
- false
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
+ {ok, #passwd{password = Password}} when is_binary(Password) ->
+ Password /= <<"">>;
+ {ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
+ _ ->
+ false
end
end.
@@ -87,34 +87,34 @@ check_password(User, AuthzId, Server, Password, Digest,
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
- {ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- {ok, #passwd{password = Scram}}
- when is_record(Scram, scram) ->
- Passwd = jlib:decode_base64(Scram#scram.storedkey),
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- _ -> false
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
+ {ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {ok, #passwd{password = Scram}}
+ when is_record(Scram, scram) ->
+ Passwd = jlib:decode_base64(Scram#scram.storedkey),
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ _ -> false
end
end.
set_password(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
@@ -130,8 +130,8 @@ set_password(User, Server, Password) ->
end.
try_register(User, Server, PasswordList) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
@@ -167,7 +167,7 @@ dirty_get_registered_users() ->
end, ejabberd_config:get_vh_by_auth_method(riak)).
get_vh_registered_users(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
{ok, Users} ->
Users;
@@ -179,7 +179,7 @@ get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
{ok, N} ->
N;
@@ -191,8 +191,8 @@ get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
@@ -207,8 +207,8 @@ get_password(User, Server) ->
end.
get_password_s(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
@@ -220,8 +220,8 @@ get_password_s(User, Server) ->
end.
is_user_exists(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{error, notfound} -> false;
{ok, _} -> true;
@@ -229,14 +229,14 @@ is_user_exists(User, Server) ->
end.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok.
remove_user(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 22e17bee..936abc7a 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -5,7 +5,7 @@
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,8 +25,14 @@
-module(ejabberd_c2s).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
+-protocol({xep, 78, '2.5'}).
+-protocol({xep, 138, '2.0'}).
+-protocol({xep, 198, '1.3'}).
+
-update_info({update, 0}).
-define(GEN_FSM, p1_fsm).
@@ -37,6 +43,7 @@
-export([start/2,
stop/1,
start_link/2,
+ close/1,
send_text/2,
send_element/2,
socket_type/0,
@@ -50,23 +57,12 @@
get_subscribed/1,
transform_listen_option/2]).
-%% gen_fsm callbacks
--export([init/1,
- wait_for_stream/2,
- wait_for_auth/2,
- wait_for_feature_request/2,
- wait_for_bind/2,
- wait_for_session/2,
+-export([init/1, wait_for_stream/2, wait_for_auth/2,
+ wait_for_feature_request/2, wait_for_bind/2,
wait_for_sasl_response/2,
- wait_for_resume/2,
- session_established/2,
- handle_event/3,
- handle_sync_event/4,
- code_change/4,
- handle_info/3,
- terminate/3,
- print_state/1
- ]).
+ wait_for_resume/2, session_established/2,
+ handle_event/3, handle_sync_event/4, code_change/4,
+ handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -115,9 +111,11 @@
mgmt_max_queue,
mgmt_pending_since,
mgmt_timeout,
+ mgmt_max_timeout,
mgmt_resend,
mgmt_stanzas_in = 0,
mgmt_stanzas_out = 0,
+ ask_offline = true,
lang = <<"">>}).
%-define(DBGFSM, true).
@@ -132,26 +130,17 @@
-endif.
-%% Module start with or without supervisor:
--ifdef(NO_TRANSIENT_SUPERVISORS).
--define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts],
- fsm_limit_opts(Opts) ++ ?FSMOPTS)).
--else.
--define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
- [SockData, Opts])).
--endif.
-
%% This is the timeout to apply between event when starting a new
%% session:
-define(C2S_OPEN_TIMEOUT, 60000).
--define(C2S_HIBERNATE_TIMEOUT, 90000).
+-define(C2S_HIBERNATE_TIMEOUT, ejabberd_config:get_option(c2s_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
-define(STREAM_HEADER,
<<"<?xml version='1.0'?><stream:stream "
"xmlns='jabber:client' xmlns:stream='http://et"
- "herx.jabber.org/streams' id='~s' from='~s'~s~"
- "s>">>).
+ "herx.jabber.org/streams' id='~s' from='~s'~s"
+ "~s>">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
@@ -169,14 +158,14 @@
%% XEP-0198:
-define(IS_STREAM_MGMT_TAG(Name),
- Name == <<"enable">>;
- Name == <<"resume">>;
- Name == <<"a">>;
- Name == <<"r">>).
+ (Name == <<"enable">>) or
+ (Name == <<"resume">>) or
+ (Name == <<"a">>) or
+ (Name == <<"r">>)).
-define(IS_SUPPORTED_MGMT_XMLNS(Xmlns),
- Xmlns == ?NS_STREAM_MGMT_2;
- Xmlns == ?NS_STREAM_MGMT_3).
+ (Xmlns == ?NS_STREAM_MGMT_2) or
+ (Xmlns == ?NS_STREAM_MGMT_3)).
-define(MGMT_FAILED(Condition, Xmlns),
#xmlel{name = <<"failed">>,
@@ -204,11 +193,14 @@
%%% API
%%%----------------------------------------------------------------------
start(SockData, Opts) ->
- ?SUPERVISOR_START.
+ ?GEN_FSM:start(ejabberd_c2s,
+ [SockData, Opts],
+ fsm_limit_opts(Opts) ++ ?FSMOPTS).
start_link(SockData, Opts) ->
- ?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
- fsm_limit_opts(Opts) ++ ?FSMOPTS).
+ (?GEN_FSM):start_link(ejabberd_c2s,
+ [SockData, Opts],
+ fsm_limit_opts(Opts) ++ ?FSMOPTS).
socket_type() -> xml_stream.
@@ -233,7 +225,7 @@ del_aux_field(Key, #state{aux_fields = Opts} = State) ->
State#state{aux_fields = Opts1}.
get_subscription(From = #jid{}, StateData) ->
- get_subscription(jlib:jid_tolower(From), StateData);
+ get_subscription(jid:tolower(From), StateData);
get_subscription(LFrom, StateData) ->
LBFrom = setelement(3, LFrom, <<"">>),
F = (?SETS):is_element(LFrom, StateData#state.pres_f)
@@ -254,19 +246,14 @@ send_filtered(FsmRef, Feature, From, To, Packet) ->
broadcast(FsmRef, Type, From, Packet) ->
FsmRef ! {broadcast, Type, From, Packet}.
-stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
+stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, stop).
+
+close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
Access = case lists:keysearch(access, 1, Opts) of
{value, {_, A}} -> A;
@@ -288,6 +275,7 @@ init([{SockMod, Socket}, Opts]) ->
StartTLSRequired orelse TLSEnabled,
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
({ciphers, _}) -> true;
+ ({dhfile, _}) -> true;
(_) -> false
end,
Opts),
@@ -312,12 +300,16 @@ init([{SockMod, Socket}, Opts]) ->
MaxAckQueue = case proplists:get_value(max_ack_queue, Opts) of
Limit when is_integer(Limit), Limit > 0 -> Limit;
infinity -> infinity;
- _ -> 500
+ _ -> 1000
end,
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
_ -> 300
end,
+ MaxResumeTimeout = case proplists:get_value(max_resume_timeout, Opts) of
+ Max when is_integer(Max), Max >= ResumeTimeout -> Max;
+ _ -> ResumeTimeout
+ end,
ResendOnTimeout = case proplists:get_value(resend_on_timeout, Opts) of
Resend when is_boolean(Resend) -> Resend;
if_offline -> if_offline;
@@ -335,11 +327,12 @@ init([{SockMod, Socket}, Opts]) ->
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled, tls_options = TLSOpts,
- sid = {now(), self()}, streamid = new_id(),
+ sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
access = Access, shaper = Shaper, ip = IP,
mgmt_state = StreamMgmtState,
mgmt_max_queue = MaxAckQueue,
mgmt_timeout = ResumeTimeout,
+ mgmt_max_timeout = MaxResumeTimeout,
mgmt_resend = ResendOnTimeout},
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}.
@@ -348,41 +341,34 @@ get_subscribed(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
get_subscribed, 1000).
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
DefaultLang = ?MYLANG,
- case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of
?NS_STREAM ->
- Server =
- case StateData#state.server of
- <<"">> ->
- jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs));
- S -> S
- end,
- Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
- Lang1 when byte_size(Lang1) =< 35 ->
- %% As stated in BCP47, 4.4.1:
- %% Protocols or specifications that
- %% specify limited buffer sizes for
- %% language tags MUST allow for
- %% language tags of at least 35 characters.
- Lang1;
- _ ->
- %% Do not store long language tag to
- %% avoid possible DoS/flood attacks
- <<"">>
- end,
+ Server =
+ case StateData#state.server of
+ <<"">> ->
+ jid:nameprep(fxml:get_attr_s(<<"to">>, Attrs));
+ S -> S
+ end,
+ Lang = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
+ Lang1 when byte_size(Lang1) =< 35 ->
+ %% As stated in BCP47, 4.4.1:
+ %% Protocols or specifications that
+ %% specify limited buffer sizes for
+ %% language tags MUST allow for
+ %% language tags of at least 35 characters.
+ Lang1;
+ _ ->
+ %% Do not store long language tag to
+ %% avoid possible DoS/flood attacks
+ <<"">>
+ end,
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
case lists:member(Server, ?MYHOSTS) of
true when IsBlacklistedIP == false ->
- change_shaper(StateData, jlib:make_jid(<<"">>, Server, <<"">>)),
- case xml:get_attr_s(<<"version">>, Attrs) of
+ change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
+ case fxml:get_attr_s(<<"version">>, Attrs) of
<<"1.0">> ->
send_header(StateData, Server, <<"1.0">>, DefaultLang),
case StateData#state.authenticated of
@@ -390,168 +376,180 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
- SASLState =
- cyrsasl:server_new(
- <<"jabber">>, Server, <<"">>, [],
- fun(U) ->
- ejabberd_auth:get_password_with_authmodule(
- U, Server)
- end,
+ SASLState = cyrsasl:server_new(
+ <<"jabber">>, Server, <<"">>, [],
+ fun (U) ->
+ ejabberd_auth:get_password_with_authmodule(
+ U, Server)
+ end,
fun(U, AuthzId, P) ->
- ejabberd_auth:check_password_with_authmodule(
+ ejabberd_auth:check_password_with_authmodule(
U, AuthzId, Server, P)
- end,
+ end,
fun(U, AuthzId, P, D, DG) ->
- ejabberd_auth:check_password_with_authmodule(
+ ejabberd_auth:check_password_with_authmodule(
U, AuthzId, Server, P, D, DG)
- end),
+ end),
Mechs =
case TLSEnabled or not TLSRequired of
- true ->
- Ms = lists:map(fun (S) ->
- #xmlel{name = <<"mechanism">>,
- attrs = [],
- children = [{xmlcdata, S}]}
- end,
- cyrsasl:listmech(Server)),
- [#xmlel{name = <<"mechanisms">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = Ms}];
- false ->
- []
- end,
+ true ->
+ Ms = lists:map(fun (S) ->
+ #xmlel{name = <<"mechanism">>,
+ attrs = [],
+ children = [{xmlcdata, S}]}
+ end,
+ cyrsasl:listmech(Server)),
+ [#xmlel{name = <<"mechanisms">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = Ms}];
+ false ->
+ []
+ end,
SockMod =
- (StateData#state.sockmod):get_sockmod(
- StateData#state.socket),
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
Zlib = StateData#state.zlib,
- CompressFeature =
- case Zlib andalso
- ((SockMod == gen_tcp) orelse
- (SockMod == p1_tls)) of
- true ->
- [#xmlel{name = <<"compression">>,
- attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
- children = [#xmlel{name = <<"method">>,
- attrs = [],
- children = [{xmlcdata, <<"zlib">>}]}]}];
- _ ->
- []
- end,
+ CompressFeature = case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
TLSFeature =
case (TLS == true) andalso
- (TLSEnabled == false) andalso
- (SockMod == gen_tcp) of
- true ->
- case TLSRequired of
- true ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = [#xmlel{name = <<"required">>,
- attrs = [],
- children = []}]}];
- _ ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = []}]
- end;
- false ->
- []
- end,
+ (TLSEnabled == false) andalso
+ (SockMod == gen_tcp) of
+ true ->
+ case TLSRequired of
+ true ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = [#xmlel{name = <<"required">>,
+ attrs = [],
+ children = []}]}];
+ _ ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = []}]
+ end;
+ false ->
+ []
+ end,
+ StreamFeatures1 = TLSFeature ++ CompressFeature ++ Mechs,
+ StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, StreamFeatures1, [Server]),
send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children =
- TLSFeature ++ CompressFeature ++ Mechs
- ++
- ejabberd_hooks:run_fold(c2s_stream_features,
- Server, [], [Server])}),
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = StreamFeatures}),
fsm_next_state(wait_for_feature_request,
- StateData#state{
- server = Server,
- sasl_state = SASLState,
- lang = Lang});
+ StateData#state{server = Server,
+ sasl_state = SASLState,
+ lang = Lang});
_ ->
case StateData#state.resource of
- <<"">> ->
- RosterVersioningFeature =
- ejabberd_hooks:run_fold(roster_get_versioning_feature,
- Server, [],
- [Server]),
- StreamManagementFeature =
- case stream_mgmt_enabled(StateData) of
- true ->
- [#xmlel{name = <<"sm">>,
- attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_2}],
- children = []},
- #xmlel{name = <<"sm">>,
- attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_3}],
- children = []}];
- false ->
- []
+ <<"">> ->
+ RosterVersioningFeature =
+ ejabberd_hooks:run_fold(roster_get_versioning_feature,
+ Server, [],
+ [Server]),
+ StreamManagementFeature =
+ case stream_mgmt_enabled(StateData) of
+ true ->
+ [#xmlel{name = <<"sm">>,
+ attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_2}],
+ children = []},
+ #xmlel{name = <<"sm">>,
+ attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_3}],
+ children = []}];
+ false ->
+ []
end,
- StreamFeatures = [#xmlel{name = <<"bind">>,
- attrs = [{<<"xmlns">>, ?NS_BIND}],
- children = []},
- #xmlel{name = <<"session">>,
- attrs = [{<<"xmlns">>, ?NS_SESSION}],
- children = []}]
- ++
- RosterVersioningFeature ++
- StreamManagementFeature ++
- ejabberd_hooks:run_fold(c2s_post_auth_features,
- Server, [], [Server]) ++
- ejabberd_hooks:run_fold(c2s_stream_features,
- Server, [], [Server]),
- send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = StreamFeatures}),
- fsm_next_state(wait_for_bind,
- StateData#state{server = Server, lang = Lang});
- _ ->
- send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = []}),
- fsm_next_state(wait_for_session,
- StateData#state{server = Server, lang = Lang})
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(
+ StateData#state.socket),
+ Zlib = StateData#state.zlib,
+ CompressFeature =
+ case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
+ StreamFeatures1 = [#xmlel{name = <<"bind">>,
+ attrs = [{<<"xmlns">>, ?NS_BIND}],
+ children = []},
+ #xmlel{name = <<"session">>,
+ attrs = [{<<"xmlns">>, ?NS_SESSION}],
+ children =
+ [#xmlel{name = <<"optional">>}]}]
+ ++
+ RosterVersioningFeature ++
+ StreamManagementFeature ++
+ CompressFeature ++
+ ejabberd_hooks:run_fold(c2s_post_auth_features,
+ Server, [], [Server]),
+ StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, StreamFeatures1, [Server]),
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = StreamFeatures}),
+ fsm_next_state(wait_for_bind,
+ StateData#state{server = Server, lang = Lang});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = []}),
+ fsm_next_state(session_established,
+ StateData#state{server = Server, lang = Lang})
end
end;
- _ ->
- send_header(StateData, Server, <<"">>, DefaultLang),
- if not StateData#state.tls_enabled and
- StateData#state.tls_required ->
- send_element(StateData,
+ _ ->
+ send_header(StateData, Server, <<"">>, DefaultLang),
+ if not StateData#state.tls_enabled and
+ StateData#state.tls_required ->
+ send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
- <<"Use of STARTTLS required">>)),
- send_trailer(StateData),
- {stop, normal, StateData};
- true ->
- fsm_next_state(wait_for_auth,
+ <<"Use of STARTTLS required">>)),
+ send_trailer(StateData),
+ {stop, normal, StateData};
+ true ->
+ fsm_next_state(wait_for_auth,
StateData#state{server = Server,
- lang = Lang})
- end
+ lang = Lang})
+ end
+ end;
+ true ->
+ IP = StateData#state.ip,
+ {true, LogReason, ReasonT} = IsBlacklistedIP,
+ ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
+ [jlib:ip_to_list(IP), LogReason]),
+ send_header(StateData, Server, <<"">>, DefaultLang),
+ send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
+ send_trailer(StateData),
+ {stop, normal, StateData};
+ _ ->
+ send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
+ send_element(StateData, ?HOST_UNKNOWN_ERR),
+ send_trailer(StateData),
+ {stop, normal, StateData}
end;
- true ->
- IP = StateData#state.ip,
- {true, LogReason, ReasonT} = IsBlacklistedIP,
- ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
- [jlib:ip_to_list(IP), LogReason]),
- send_header(StateData, Server, <<"">>, DefaultLang),
- send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
- send_trailer(StateData),
- {stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
- send_element(StateData, ?HOST_UNKNOWN_ERR),
+ send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
- end;
- _ ->
- send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
- send_element(StateData, ?INVALID_NS_ERR),
- send_trailer(StateData),
- {stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
@@ -569,154 +567,153 @@ wait_for_stream({xmlstreamerror, _}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
+ {stop, normal, StateData};
+wait_for_stream(stop, StateData) ->
{stop, normal, StateData}.
wait_for_auth({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
- when ?IS_STREAM_MGMT_TAG(Name) ->
+ when ?IS_STREAM_MGMT_TAG(Name) ->
fsm_next_state(wait_for_auth, dispatch_stream_mgmt(El, StateData));
wait_for_auth({xmlstreamelement, El}, StateData) ->
case is_auth_packet(El) of
- {auth, _ID, get, {U, _, _, _}} ->
- #xmlel{name = Name, attrs = Attrs} =
- jlib:make_result_iq_reply(El),
- case U of
- <<"">> -> UCdata = [];
- _ -> UCdata = [{xmlcdata, U}]
- end,
- Res = case
- ejabberd_auth:plain_password_required(StateData#state.server)
- of
- false ->
- #xmlel{name = Name, attrs = Attrs,
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_AUTH}],
- children =
- [#xmlel{name = <<"username">>,
- attrs = [],
- children = UCdata},
- #xmlel{name = <<"password">>,
- attrs = [], children = []},
- #xmlel{name = <<"digest">>,
- attrs = [], children = []},
- #xmlel{name = <<"resource">>,
- attrs = [],
- children = []}]}]};
- true ->
- #xmlel{name = Name, attrs = Attrs,
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_AUTH}],
- children =
- [#xmlel{name = <<"username">>,
- attrs = [],
- children = UCdata},
- #xmlel{name = <<"password">>,
- attrs = [], children = []},
- #xmlel{name = <<"resource">>,
- attrs = [],
- children = []}]}]}
- end,
- send_element(StateData, Res),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
- Err = jlib:make_error_reply(El,
- ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {U, P, D, R}} ->
- JID = jlib:make_jid(U, StateData#state.server, R),
- case JID /= error andalso
- acl:match_rule(StateData#state.server,
- StateData#state.access, JID)
- == allow
- of
- true ->
- DGen = fun (PW) ->
- p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
- end,
+ {auth, _ID, get, {U, _, _, _}} ->
+ #xmlel{name = Name, attrs = Attrs} = jlib:make_result_iq_reply(El),
+ case U of
+ <<"">> -> UCdata = [];
+ _ -> UCdata = [{xmlcdata, U}]
+ end,
+ Res = case
+ ejabberd_auth:plain_password_required(StateData#state.server)
+ of
+ false ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"digest">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]};
+ true ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]}
+ end,
+ send_element(StateData, Res),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
+ Err = jlib:make_error_reply(El,
+ ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {U, P, D, R}} ->
+ JID = jid:make(U, StateData#state.server, R),
+ case JID /= error andalso
+ acl:match_rule(StateData#state.server,
+ StateData#state.access, JID)
+ == allow
+ of
+ true ->
+ DGen = fun (PW) ->
+ p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
+ end,
case ejabberd_auth:check_password_with_authmodule(U, U,
- StateData#state.server,
- P, D, DGen)
+ StateData#state.server,
+ P, D, DGen)
of
- {true, AuthModule} ->
- ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID), AuthModule,
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [true, U, StateData#state.server,
- StateData#state.ip]),
- Conn = get_conn_type(StateData),
- Info = [{ip, StateData#state.ip}, {conn, Conn},
+ {true, AuthModule} ->
+ ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID), AuthModule,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [true, U, StateData#state.server,
+ StateData#state.ip]),
+ Conn = get_conn_type(StateData),
+ Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, AuthModule}],
- Res = jlib:make_result_iq_reply(
- El#xmlel{children = []}),
- send_element(StateData, Res),
- ejabberd_sm:open_session(StateData#state.sid, U,
- StateData#state.server, R,
- Info),
- change_shaper(StateData, JID),
- {Fs, Ts} =
- ejabberd_hooks:run_fold(roster_get_subscription_lists,
- StateData#state.server,
- {[], []},
- [U,
- StateData#state.server]),
- LJID =
- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- Fs1 = [LJID | Fs],
- Ts1 = [LJID | Ts],
- PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
- StateData#state.server,
- #userlist{},
- [U, StateData#state.server]),
- NewStateData = StateData#state{user = U,
- resource = R,
- jid = JID,
- conn = Conn,
- auth_module = AuthModule,
- pres_f = (?SETS):from_list(Fs1),
- pres_t = (?SETS):from_list(Ts1),
- privacy_list = PrivList},
- fsm_next_state(session_established, NewStateData);
- _ ->
- ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID),
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [false, U, StateData#state.server,
- StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end;
- _ ->
- if JID == error ->
- ?INFO_MSG("(~w) Forbidden legacy authentication "
- "for username '~s' with resource '~s'",
- [StateData#state.socket, U, R]),
- Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- true ->
- ?INFO_MSG("(~w) Forbidden legacy authentication "
- "for ~s from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID),
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [false, U, StateData#state.server,
- StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end
- end;
- _ ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_auth, StateData)
+ Res = jlib:make_result_iq_reply(
+ El#xmlel{children = []}),
+ send_element(StateData, Res),
+ ejabberd_sm:open_session(StateData#state.sid, U,
+ StateData#state.server, R,
+ Info),
+ change_shaper(StateData, JID),
+ {Fs, Ts} =
+ ejabberd_hooks:run_fold(roster_get_subscription_lists,
+ StateData#state.server,
+ {[], []},
+ [U, StateData#state.server]),
+ LJID = jid:tolower(jid:remove_resource(JID)),
+ Fs1 = [LJID | Fs],
+ Ts1 = [LJID | Ts],
+ PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
+ StateData#state.server,
+ #userlist{},
+ [U, StateData#state.server]),
+ NewStateData = StateData#state{user = U,
+ resource = R,
+ jid = JID,
+ conn = Conn,
+ auth_module = AuthModule,
+ pres_f = (?SETS):from_list(Fs1),
+ pres_t = (?SETS):from_list(Ts1),
+ privacy_list = PrivList},
+ fsm_next_state(session_established, NewStateData);
+ _ ->
+ ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [false, U, StateData#state.server,
+ StateData#state.ip]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end;
+ _ ->
+ if JID == error ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for username '~s' with resource '~s'",
+ [StateData#state.socket, U, R]),
+ Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ true ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for ~s from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [false, U, StateData#state.server,
+ StateData#state.ip]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end
+ end;
+ _ ->
+ process_unauthenticated_stanza(StateData, El),
+ fsm_next_state(wait_for_auth, StateData)
end;
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
@@ -727,6 +724,8 @@ wait_for_auth({xmlstreamerror, _}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
+ {stop, normal, StateData};
+wait_for_auth(stop, StateData) ->
{stop, normal, StateData}.
wait_for_feature_request({xmlstreamelement, #xmlel{name = Name} = El},
@@ -743,11 +742,11 @@ wait_for_feature_request({xmlstreamelement, El},
TLSRequired = StateData#state.tls_required,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"auth">>}
when TLSEnabled or not TLSRequired ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_start(StateData#state.sasl_state,
Mech, ClientIn)
of
@@ -758,7 +757,7 @@ wait_for_feature_request({xmlstreamelement, El},
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
[StateData#state.socket, U, AuthModule,
- jlib:ip_to_list(StateData#state.ip)]),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[true, U, StateData#state.server,
StateData#state.ip]),
@@ -782,10 +781,10 @@ wait_for_feature_request({xmlstreamelement, El},
fsm_next_state(wait_for_sasl_response,
StateData#state{sasl_state = NewSASLState});
{error, Error, Username} ->
- ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s",
- [StateData#state.socket,
- Username, StateData#state.server,
- jlib:ip_to_list(StateData#state.ip)]),
+ ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s",
+ [StateData#state.socket,
+ Username, StateData#state.server,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, Username, StateData#state.server,
StateData#state.ip]),
@@ -819,7 +818,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state.tls_options)]
end,
Socket = StateData#state.socket,
- BProceed = xml:element_to_binary(#xmlel{name = <<"proceed">>,
+ BProceed = fxml:element_to_binary(#xmlel{name = <<"proceed">>,
attrs = [{<<"xmlns">>, ?NS_TLS}]}),
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
@@ -830,39 +829,8 @@ wait_for_feature_request({xmlstreamelement, El},
tls_enabled = true});
{?NS_COMPRESS, <<"compress">>}
when Zlib == true,
- (SockMod == gen_tcp) or (SockMod == p1_tls) ->
- case xml:get_subtag(El, <<"method">>) of
- false ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name = <<"setup-failed">>,
- attrs = [], children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- Method ->
- case xml:get_tag_cdata(Method) of
- <<"zlib">> ->
- Socket = StateData#state.socket,
- BCompressed = xml:element_to_binary(#xmlel{name = <<"compressed">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}),
- ZlibSocket = (StateData#state.sockmod):compress(Socket,
- BCompressed),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = ZlibSocket,
- streamid = new_id()});
- _ ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name =
- <<"unsupported-method">>,
- attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end
- end;
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_feature_request, StateData);
_ ->
if TLSRequired and not TLSEnabled ->
Lang = StateData#state.lang,
@@ -887,6 +855,8 @@ wait_for_feature_request({xmlstreamerror, _},
send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
+ {stop, normal, StateData};
+wait_for_feature_request(stop, StateData) ->
{stop, normal, StateData}.
wait_for_sasl_response({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
@@ -895,9 +865,9 @@ wait_for_sasl_response({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
wait_for_sasl_response({xmlstreamelement, El},
StateData) ->
#xmlel{name = Name, attrs = Attrs, children = Els} = El,
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"response">>} ->
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_step(StateData#state.sasl_state,
ClientIn)
of
@@ -909,7 +879,7 @@ wait_for_sasl_response({xmlstreamelement, El},
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
[StateData#state.socket, U, AuthModule,
- jlib:ip_to_list(StateData#state.ip)]),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[true, U, StateData#state.server,
StateData#state.ip]),
@@ -930,7 +900,7 @@ wait_for_sasl_response({xmlstreamelement, El},
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
[StateData#state.socket, U, AuthModule,
- jlib:ip_to_list(StateData#state.ip)]),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[true, U, StateData#state.server,
StateData#state.ip]),
@@ -959,7 +929,7 @@ wait_for_sasl_response({xmlstreamelement, El},
?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s",
[StateData#state.socket,
Username, StateData#state.server,
- jlib:ip_to_list(StateData#state.ip)]),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, Username, StateData#state.server,
StateData#state.ip]),
@@ -994,6 +964,8 @@ wait_for_sasl_response({xmlstreamerror, _},
send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
+ {stop, normal, StateData};
+wait_for_sasl_response(stop, StateData) ->
{stop, normal, StateData}.
resource_conflict_action(U, S, R) ->
@@ -1021,9 +993,7 @@ resource_conflict_action(U, S, R) ->
acceptnew -> {accept_resource, R};
closenew -> closenew;
setresource ->
- Rnew = iolist_to_binary([randoms:get_string()
- | [jlib:integer_to_binary(X)
- || X <- tuple_to_list(now())]]),
+ Rnew = new_uniq_id(),
{accept_resource, Rnew}
end.
@@ -1046,14 +1016,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
#iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
IQ ->
U = StateData#state.user,
- R1 = xml:get_path_s(SubEl,
+ R1 = fxml:get_path_s(SubEl,
[{elem, <<"resource">>}, cdata]),
- R = case jlib:resourceprep(R1) of
+ R = case jid:resourceprep(R1) of
error -> error;
- <<"">> ->
- iolist_to_binary([randoms:get_string()
- | [jlib:integer_to_binary(X)
- || X <- tuple_to_list(now())]]);
+ <<"">> -> new_uniq_id();
Resource -> Resource
end,
case R of
@@ -1073,23 +1040,47 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
send_element(StateData, Err),
fsm_next_state(wait_for_bind, StateData);
{accept_resource, R2} ->
- JID = jlib:make_jid(U, StateData#state.server, R2),
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"bind">>,
- attrs = [{<<"xmlns">>, ?NS_BIND}],
- children =
- [#xmlel{name = <<"jid">>,
- attrs = [],
- children =
- [{xmlcdata,
- jlib:jid_to_string(JID)}]}]}]},
- send_element(StateData, jlib:iq_to_xml(Res)),
- fsm_next_state(wait_for_session,
- StateData#state{resource = R2, jid = JID})
+ JID = jid:make(U, StateData#state.server, R2),
+ StateData2 =
+ StateData#state{resource = R2, jid = JID},
+ case open_session(StateData2) of
+ {ok, StateData3} ->
+ Res =
+ IQ#iq{
+ type = result,
+ sub_el =
+ [#xmlel{name = <<"bind">>,
+ attrs = [{<<"xmlns">>, ?NS_BIND}],
+ children =
+ [#xmlel{name = <<"jid">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ jid:to_string(JID)}]}]}]},
+ send_element(StateData3, jlib:iq_to_xml(Res)),
+ fsm_next_state_pack(
+ session_established,
+ StateData3);
+ {error, Error} ->
+ Err = jlib:make_error_reply(El, Error),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_bind, StateData)
+ end
end
end;
- _ -> fsm_next_state(wait_for_bind, StateData)
+ _ ->
+ #xmlel{name = Name, attrs = Attrs, children = _Els} = El,
+ Zlib = StateData#state.zlib,
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_COMPRESS, <<"compress">>}
+ when Zlib == true,
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_bind, StateData);
+ _ ->
+ fsm_next_state(wait_for_bind, StateData)
+ end
end;
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
@@ -1100,78 +1091,54 @@ wait_for_bind({xmlstreamerror, _}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
- {stop, normal, StateData}.
-
-wait_for_session({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
- when ?IS_STREAM_MGMT_TAG(Name) ->
- fsm_next_state(wait_for_session, dispatch_stream_mgmt(El, StateData));
-wait_for_session({xmlstreamelement, El}, StateData) ->
- NewStateData = update_num_stanzas_in(StateData, El),
- case jlib:iq_query_info(El) of
- #iq{type = set, xmlns = ?NS_SESSION} ->
- U = NewStateData#state.user,
- R = NewStateData#state.resource,
- JID = NewStateData#state.jid,
- case acl:match_rule(NewStateData#state.server,
- NewStateData#state.access, JID) of
- allow ->
- ?INFO_MSG("(~w) Opened session for ~s",
- [NewStateData#state.socket,
- jlib:jid_to_string(JID)]),
- Res = jlib:make_result_iq_reply(El#xmlel{children = []}),
- NewState = send_stanza(NewStateData, Res),
- change_shaper(NewState, JID),
- {Fs, Ts} = ejabberd_hooks:run_fold(
- roster_get_subscription_lists,
- NewState#state.server,
- {[], []},
- [U, NewState#state.server]),
- LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- Fs1 = [LJID | Fs],
- Ts1 = [LJID | Ts],
- PrivList =
- ejabberd_hooks:run_fold(
- privacy_get_user_list, NewState#state.server,
- #userlist{},
- [U, NewState#state.server]),
- Conn = get_conn_type(NewState),
- Info = [{ip, NewState#state.ip}, {conn, Conn},
- {auth_module, NewState#state.auth_module}],
- ejabberd_sm:open_session(
- NewState#state.sid, U, NewState#state.server, R, Info),
- UpdatedStateData =
- NewState#state{
- conn = Conn,
- pres_f = ?SETS:from_list(Fs1),
- pres_t = ?SETS:from_list(Ts1),
- privacy_list = PrivList},
- fsm_next_state_pack(session_established,
- UpdatedStateData);
- _ ->
- ejabberd_hooks:run(forbidden_session_hook,
- NewStateData#state.server, [JID]),
- ?INFO_MSG("(~w) Forbidden session for ~s",
- [NewStateData#state.socket,
- jlib:jid_to_string(JID)]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
- send_element(NewStateData, Err),
- fsm_next_state(wait_for_session, NewStateData)
- end;
- _ ->
- fsm_next_state(wait_for_session, NewStateData)
- end;
-
-wait_for_session(timeout, StateData) ->
{stop, normal, StateData};
-wait_for_session({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
-wait_for_session({xmlstreamerror, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
- {stop, normal, StateData};
-wait_for_session(closed, StateData) ->
+wait_for_bind(stop, StateData) ->
{stop, normal, StateData}.
+open_session(StateData) ->
+ U = StateData#state.user,
+ R = StateData#state.resource,
+ JID = StateData#state.jid,
+ case acl:match_rule(StateData#state.server,
+ StateData#state.access, JID) of
+ allow ->
+ ?INFO_MSG("(~w) Opened session for ~s",
+ [StateData#state.socket, jid:to_string(JID)]),
+ change_shaper(StateData, JID),
+ {Fs, Ts} = ejabberd_hooks:run_fold(
+ roster_get_subscription_lists,
+ StateData#state.server,
+ {[], []},
+ [U, StateData#state.server]),
+ LJID = jid:tolower(jid:remove_resource(JID)),
+ Fs1 = [LJID | Fs],
+ Ts1 = [LJID | Ts],
+ PrivList =
+ ejabberd_hooks:run_fold(
+ privacy_get_user_list,
+ StateData#state.server,
+ #userlist{},
+ [U, StateData#state.server]),
+ Conn = get_conn_type(StateData),
+ Info = [{ip, StateData#state.ip}, {conn, Conn},
+ {auth_module, StateData#state.auth_module}],
+ ejabberd_sm:open_session(
+ StateData#state.sid, U, StateData#state.server, R, Info),
+ UpdatedStateData =
+ StateData#state{
+ conn = Conn,
+ pres_f = ?SETS:from_list(Fs1),
+ pres_t = ?SETS:from_list(Ts1),
+ privacy_list = PrivList},
+ {ok, UpdatedStateData};
+ _ ->
+ ejabberd_hooks:run(forbidden_session_hook,
+ StateData#state.server, [JID]),
+ ?INFO_MSG("(~w) Forbidden session for ~s",
+ [StateData#state.socket, jid:to_string(JID)]),
+ {error, ?ERR_NOT_ALLOWED}
+ end.
+
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
when ?IS_STREAM_MGMT_TAG(Name) ->
fsm_next_state(session_established, dispatch_stream_mgmt(El, StateData));
@@ -1218,36 +1185,38 @@ session_established({xmlstreamerror, _}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
+ catch (StateData#state.sockmod):close(StateData#state.socket),
fsm_next_state(wait_for_resume, StateData);
session_established(closed, StateData) ->
+ {stop, normal, StateData};
+session_established(stop, StateData) ->
{stop, normal, StateData}.
-%% Process packets sent by user (coming from user on c2s XMPP
-%% connection)
+%% Process packets sent by user (coming from user on c2s XMPP connection)
session_established2(El, StateData) ->
#xmlel{name = Name, attrs = Attrs} = El,
NewStateData = update_num_stanzas_in(StateData, El),
User = NewStateData#state.user,
Server = NewStateData#state.server,
FromJID = NewStateData#state.jid,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
- <<"">> -> jlib:make_jid(User, Server, <<"">>);
- _ -> jlib:string_to_jid(To)
+ <<"">> -> jid:make(User, Server, <<"">>);
+ _ -> jid:from_string(To)
end,
NewEl1 = jlib:remove_attr(<<"xmlns">>, El),
- NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ NewEl = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
<<"">> ->
case NewStateData#state.lang of
<<"">> -> NewEl1;
Lang ->
- xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
+ fxml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
end;
_ -> NewEl1
end,
NewState = case ToJID of
error ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> NewStateData;
<<"result">> -> NewStateData;
_ ->
@@ -1258,12 +1227,14 @@ session_established2(El, StateData) ->
_ ->
case Name of
<<"presence">> ->
- PresenceEl =
+ PresenceEl0 =
ejabberd_hooks:run_fold(c2s_update_presence,
Server, NewEl,
[User, Server]),
- ejabberd_hooks:run(user_send_packet, Server,
- [FromJID, ToJID, PresenceEl]),
+ PresenceEl =
+ ejabberd_hooks:run_fold(
+ user_send_packet, Server, PresenceEl0,
+ [NewStateData, FromJID, ToJID]),
case ToJID of
#jid{user = User, server = Server,
resource = <<"">>} ->
@@ -1282,17 +1253,23 @@ session_established2(El, StateData) ->
Xmlns == (?NS_BLOCKING) ->
process_privacy_iq(FromJID, ToJID, IQ,
NewStateData);
+ #iq{xmlns = ?NS_SESSION} ->
+ Res = jlib:make_result_iq_reply(
+ NewEl#xmlel{children = []}),
+ send_stanza(NewStateData, Res);
_ ->
- ejabberd_hooks:run(user_send_packet, Server,
- [FromJID, ToJID, NewEl]),
+ NewEl0 = ejabberd_hooks:run_fold(
+ user_send_packet, Server, NewEl,
+ [NewStateData, FromJID, ToJID]),
check_privacy_route(FromJID, NewStateData,
- FromJID, ToJID, NewEl)
+ FromJID, ToJID, NewEl0)
end;
<<"message">> ->
- ejabberd_hooks:run(user_send_packet, Server,
- [FromJID, ToJID, NewEl]),
+ NewEl0 = ejabberd_hooks:run_fold(
+ user_send_packet, Server, NewEl,
+ [NewStateData, FromJID, ToJID]),
check_privacy_route(FromJID, NewStateData, FromJID,
- ToJID, NewEl);
+ ToJID, NewEl0);
_ -> NewStateData
end
end,
@@ -1301,47 +1278,19 @@ session_established2(El, StateData) ->
fsm_next_state(session_established, NewState).
wait_for_resume({xmlstreamelement, _El} = Event, StateData) ->
- session_established(Event, StateData),
- fsm_next_state(wait_for_resume, StateData);
+ Result = session_established(Event, StateData),
+ fsm_next_state(wait_for_resume, element(3, Result));
wait_for_resume(timeout, StateData) ->
?DEBUG("Timed out waiting for resumption of stream for ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
{stop, normal, StateData};
wait_for_resume(Event, StateData) ->
?DEBUG("Ignoring event while waiting for resumption: ~p", [Event]),
fsm_next_state(wait_for_resume, StateData).
-%%----------------------------------------------------------------------
-%% Func: StateName/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
-%state_name(Event, From, StateData) ->
-% Reply = ok,
-% {reply, Reply, state_name, StateData}.
-
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
fsm_next_state(StateName, StateData).
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
handle_sync_event({get_presence}, _From, StateName,
StateData) ->
User = StateData#state.user,
@@ -1374,12 +1323,6 @@ handle_sync_event(_Event, _From, StateName,
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
ejabberd_hooks:run(c2s_loop_debug, [Text]),
@@ -1431,10 +1374,11 @@ handle_info({route, _From, _To, {broadcast, Data}},
PrivListName}],
children = []}]}]},
PrivPushEl = jlib:replace_from_to(
- jlib:jid_remove_resource(StateData#state.jid),
+ jid:remove_resource(StateData#state.jid),
StateData#state.jid,
jlib:iq_to_xml(PrivPushIQ)),
- NewState = send_stanza(StateData, PrivPushEl),
+ NewState = send_stanza(
+ StateData, PrivPushEl),
fsm_next_state(StateName,
NewState#state{privacy_list = NewPL})
end;
@@ -1456,11 +1400,11 @@ handle_info({route, From, To,
StateData,
[{From, To,
Packet}]),
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"probe">> ->
- LFrom = jlib:jid_tolower(From),
+ LFrom = jid:tolower(From),
LBFrom =
- jlib:jid_remove_resource(LFrom),
+ jid:remove_resource(LFrom),
NewStateData = case
(?SETS):is_element(LFrom,
State#state.pres_a)
@@ -1503,7 +1447,7 @@ handle_info({route, From, To,
{false, Attrs, NewStateData};
<<"error">> ->
NewA =
- remove_element(jlib:jid_tolower(From),
+ remove_element(jid:tolower(From),
State#state.pres_a),
{true, Attrs,
State#state{pres_a = NewA}};
@@ -1539,9 +1483,9 @@ handle_info({route, From, To,
of
allow ->
LFrom =
- jlib:jid_tolower(From),
+ jid:tolower(From),
LBFrom =
- jlib:jid_remove_resource(LFrom),
+ jid:remove_resource(LFrom),
case
(?SETS):is_element(LFrom,
State#state.pres_a)
@@ -1592,9 +1536,9 @@ handle_info({route, From, To,
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = ?NS_LAST} ->
- LFrom = jlib:jid_tolower(From),
+ LFrom = jid:tolower(From),
LBFrom =
- jlib:jid_remove_resource(LFrom),
+ jid:remove_resource(LFrom),
HasFromSub =
((?SETS):is_element(LFrom,
StateData#state.pres_f)
@@ -1670,10 +1614,13 @@ handle_info({route, From, To,
From, To,
Packet, in)
of
- allow -> {true, Attrs, StateData};
+ allow ->
+ {true, Attrs, StateData};
deny ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
+ <<"groupchat">> -> ok;
+ <<"headline">> -> ok;
<<"result">> -> ok;
_ ->
Err =
@@ -1688,13 +1635,15 @@ handle_info({route, From, To,
end,
if Pass ->
Attrs2 =
- jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To), NewAttrs),
- FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els},
+ jlib:replace_from_to_attrs(jid:to_string(From),
+ jid:to_string(To), NewAttrs),
+ FixedPacket0 = #xmlel{name = Name, attrs = Attrs2, children = Els},
+ FixedPacket = ejabberd_hooks:run_fold(
+ user_receive_packet,
+ NewState#state.server,
+ FixedPacket0,
+ [NewState, NewState#state.jid, From, To]),
SentStateData = send_packet(NewState, FixedPacket),
- ejabberd_hooks:run(user_receive_packet,
- SentStateData#state.server,
- [SentStateData#state.jid, From, To, FixedPacket]),
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, SentStateData);
true ->
@@ -1727,8 +1676,8 @@ handle_info({route_xmlstreamelement, El}, _StateName, StateData) ->
{next_state, NStateName, NStateData, _Timeout} =
session_established({xmlstreamelement, El}, StateData),
fsm_next_state(NStateName, NStateData);
-handle_info({force_update_presence, LUser}, StateName,
- #state{user = LUser, server = LServer} = StateData) ->
+handle_info({force_update_presence, LUser, LServer}, StateName,
+ #state{jid = #jid{luser = LUser, lserver = LServer}} = StateData) ->
NewStateData = case StateData#state.pres_last of
#xmlel{name = <<"presence">>} ->
PresenceEl =
@@ -1749,8 +1698,8 @@ handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) ->
Feature, To, Packet]),
NewStateData = if Drop ->
?DEBUG("Dropping packet from ~p to ~p",
- [jlib:jid_to_string(From),
- jlib:jid_to_string(To)]),
+ [jid:to_string(From),
+ jid:to_string(To)]),
StateData;
true ->
FinalPacket = jlib:replace_from_to(From, To, Packet),
@@ -1777,35 +1726,25 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
lists:foreach(
fun(USR) ->
ejabberd_router:route(
- From, jlib:make_jid(USR), Packet)
+ From, jid:make(USR), Packet)
end, lists:usort(Recipients)),
fsm_next_state(StateName, StateData);
+handle_info(dont_ask_offline, StateName, StateData) ->
+ fsm_next_state(StateName, StateData#state{ask_offline = false});
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
-
-%%----------------------------------------------------------------------
-%% Func: print_state/1
-%% Purpose: Prepare the state to be printed on error log
-%% Returns: State to print
-%%----------------------------------------------------------------------
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A}) ->
- State#state{pres_t = {pres_t, ?SETS:size(T)},
- pres_f = {pres_f, ?SETS:size(F)},
- pres_a = {pres_a, ?SETS:size(A)}
- }.
-
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
+ State#state{pres_t = {pres_t, (?SETS):size(T)},
+ pres_f = {pres_f, (?SETS):size(F)},
+ pres_a = {pres_a, (?SETS):size(A)}}.
+
terminate(_Reason, StateName, StateData) ->
case StateData#state.mgmt_state of
resumed ->
?INFO_MSG("Closing former stream of resumed session for ~s",
- [jlib:jid_to_string(StateData#state.jid)]);
+ [jid:to_string(StateData#state.jid)]);
_ ->
if StateName == session_established;
StateName == wait_for_resume ->
@@ -1813,7 +1752,7 @@ terminate(_Reason, StateName, StateData) ->
replaced ->
?INFO_MSG("(~w) Replaced session for ~s",
[StateData#state.socket,
- jlib:jid_to_string(StateData#state.jid)]),
+ jid:to_string(StateData#state.jid)]),
From = StateData#state.jid,
Packet = #xmlel{name = <<"presence">>,
attrs = [{<<"type">>, <<"unavailable">>}],
@@ -1833,7 +1772,7 @@ terminate(_Reason, StateName, StateData) ->
_ ->
?INFO_MSG("(~w) Close session for ~s",
[StateData#state.socket,
- jlib:jid_to_string(StateData#state.jid)]),
+ jid:to_string(StateData#state.jid)]),
EmptySet = (?SETS):new(),
case StateData of
#state{pres_last = undefined, pres_a = EmptySet} ->
@@ -1885,7 +1824,7 @@ send_text(StateData, Text) when StateData#state.mgmt_state == active ->
case catch (StateData#state.sockmod):send(StateData#state.socket, Text) of
{'EXIT', _} ->
(StateData#state.sockmod):close(StateData#state.socket),
- error;
+ {error, closed};
_ ->
ok
end;
@@ -1899,19 +1838,14 @@ send_element(StateData, El) when StateData#state.xml_socket ->
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
csi_filter_stanza(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
mgmt_queue_add(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
- NewStateData = case send_stanza_and_ack_req(StateData, Stanza) of
- ok ->
- StateData;
- error ->
- StateData#state{mgmt_state = pending}
- end,
+ NewStateData = send_stanza_and_ack_req(StateData, Stanza),
mgmt_queue_add(NewStateData, Stanza);
send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
@@ -1972,19 +1906,23 @@ send_trailer(StateData) ->
new_id() -> randoms:get_string().
+new_uniq_id() ->
+ iolist_to_binary([randoms:get_string(),
+ jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]).
+
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
#iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} ->
#xmlel{children = Els} = SubEl,
{auth, ID, Type,
- get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)};
+ get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)};
_ -> false
end.
is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>;
Name == <<"presence">>;
Name == <<"iq">> ->
- case xml:get_attr(<<"xmlns">>, Attrs) of
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
{value, NS} when NS /= <<"jabber:client">>,
NS /= <<"jabber:server">> ->
false;
@@ -1996,7 +1934,7 @@ is_stanza(_El) ->
get_auth_tags([#xmlel{name = Name, children = Els} | L],
U, P, D, R) ->
- CData = xml:get_cdata(Els),
+ CData = fxml:get_cdata(Els),
case Name of
<<"username">> -> get_auth_tags(L, CData, P, D, R);
<<"password">> -> get_auth_tags(L, U, CData, D, R);
@@ -2015,29 +1953,29 @@ get_auth_tags([], U, P, D, R) ->
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
- p1_tls -> c2s_tls;
+ fast_tls -> c2s_tls;
ezlib ->
case ezlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
- p1_tls -> c2s_compressed_tls
+ fast_tls -> c2s_compressed_tls
end;
ejabberd_http_bind -> http_bind;
+ ejabberd_http_ws -> websocket;
_ -> unknown
end.
process_presence_probe(From, To, StateData) ->
- LFrom = jlib:jid_tolower(From),
+ LFrom = jid:tolower(From),
LBFrom = setelement(3, LFrom, <<"">>),
case StateData#state.pres_last of
undefined ->
ok;
_ ->
- Cond = ?SETS:is_element(LFrom, StateData#state.pres_f)
- orelse
- ((LFrom /= LBFrom) andalso
- ?SETS:is_element(LBFrom, StateData#state.pres_f)),
- if
- Cond ->
+ Cond = ((?SETS):is_element(LFrom, StateData#state.pres_f)
+ orelse
+ ((LFrom /= LBFrom) andalso
+ (?SETS):is_element(LBFrom, StateData#state.pres_f))),
+ if Cond ->
%% To is the one sending the presence (the probe target)
Packet = jlib:add_delay_info(StateData#state.pres_last, To,
StateData#state.pres_timestamp),
@@ -2063,11 +2001,11 @@ process_presence_probe(From, To, StateData) ->
%% User updates his presence (non-directed presence packet)
presence_update(From, Packet, StateData) ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
- Status = case xml:get_subtag(Packet, <<"status">>) of
+ Status = case fxml:get_subtag(Packet, <<"status">>) of
false -> <<"">>;
- StatusTag -> xml:get_tag_cdata(StatusTag)
+ StatusTag -> fxml:get_tag_cdata(StatusTag)
end,
Info = [{ip, StateData#state.ip},
{conn, StateData#state.conn},
@@ -2096,7 +2034,7 @@ presence_update(From, Packet, StateData) ->
FromUnavail = (StateData#state.pres_last == undefined),
?DEBUG("from unavail = ~p~n", [FromUnavail]),
NewStateData = StateData#state{pres_last = Packet,
- pres_timestamp = now()},
+ pres_timestamp = p1_time_compat:timestamp()},
NewState = if FromUnavail ->
ejabberd_hooks:run(user_available_hook,
NewStateData#state.server,
@@ -2125,10 +2063,10 @@ presence_update(From, Packet, StateData) ->
%% User sends a directed presence packet
presence_track(From, To, Packet, StateData) ->
#xmlel{attrs = Attrs} = Packet,
- LTo = jlib:jid_tolower(To),
+ LTo = jid:tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
A = remove_element(LTo, StateData#state.pres_a),
check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet);
@@ -2138,14 +2076,14 @@ presence_track(From, To, Packet, StateData) ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, subscribed]),
check_privacy_route(From, StateData,
- jlib:jid_remove_resource(From), To, Packet);
+ jid:remove_resource(From), To, Packet);
<<"unsubscribe">> ->
try_roster_subscribe(unsubscribe, User, Server, From, To, Packet, StateData);
<<"unsubscribed">> ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, unsubscribed]),
check_privacy_route(From, StateData,
- jlib:jid_remove_resource(From), To, Packet);
+ jid:remove_resource(From), To, Packet);
<<"error">> ->
check_privacy_route(From, StateData, From, To, Packet);
<<"probe">> ->
@@ -2188,7 +2126,7 @@ is_privacy_allow(StateData, From, To, Packet, Dir) ->
%%% Check ACL before allowing to send a subscription stanza
try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) ->
- JID1 = jlib:make_jid(User, Server, <<"">>),
+ JID1 = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, mod_roster, access, fun(A) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, JID1) of
deny ->
@@ -2198,7 +2136,7 @@ try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) ->
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, Type]),
- check_privacy_route(From, StateData, jlib:jid_remove_resource(From),
+ check_privacy_route(From, StateData, jid:remove_resource(From),
To, Packet)
end.
@@ -2240,7 +2178,7 @@ presence_broadcast_first(From, StateData, Packet) ->
StateData#state{pres_a = As}.
format_and_check_privacy(From, StateData, Packet, JIDs, Dir) ->
- FJIDs = [jlib:make_jid(JID) || JID <- JIDs],
+ FJIDs = [jid:make(JID) || JID <- JIDs],
lists:filter(
fun(FJID) ->
case ejabberd_hooks:run_fold(
@@ -2267,17 +2205,17 @@ remove_element(E, Set) ->
end.
roster_change(IJID, ISubscription, StateData) ->
- LIJID = jlib:jid_tolower(IJID),
+ LIJID = jid:tolower(IJID),
IsFrom = (ISubscription == both) or (ISubscription == from),
IsTo = (ISubscription == both) or (ISubscription == to),
OldIsFrom = (?SETS):is_element(LIJID, StateData#state.pres_f),
FSet = if
IsFrom -> (?SETS):add_element(LIJID, StateData#state.pres_f);
- not IsFrom -> remove_element(LIJID, StateData#state.pres_f)
+ true -> remove_element(LIJID, StateData#state.pres_f)
end,
TSet = if
IsTo -> (?SETS):add_element(LIJID, StateData#state.pres_t);
- not IsTo -> remove_element(LIJID, StateData#state.pres_t)
+ true -> remove_element(LIJID, StateData#state.pres_t)
end,
case StateData#state.pres_last of
undefined ->
@@ -2286,7 +2224,7 @@ roster_change(IJID, ISubscription, StateData) ->
?DEBUG("roster changed for ~p~n",
[StateData#state.user]),
From = StateData#state.jid,
- To = jlib:make_jid(IJID),
+ To = jid:make(IJID),
Cond1 = IsFrom andalso not OldIsFrom,
Cond2 = not IsFrom andalso OldIsFrom andalso
((?SETS):is_element(LIJID, StateData#state.pres_a)),
@@ -2325,11 +2263,11 @@ update_priority(Priority, Packet, StateData) ->
StateData#state.resource, Priority, Packet, Info).
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -2370,7 +2308,7 @@ process_privacy_iq(From, To,
ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)),
NewStateData.
-resend_offline_messages(StateData) ->
+resend_offline_messages(#state{ask_offline = true} = StateData) ->
case ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server, [],
[StateData#state.user, StateData#state.server])
@@ -2391,7 +2329,9 @@ resend_offline_messages(StateData) ->
end
end,
Rs)
- end.
+ end;
+resend_offline_messages(_StateData) ->
+ ok.
resend_subscription_requests(#state{user = User,
server = Server} = StateData) ->
@@ -2406,21 +2346,21 @@ resend_subscription_requests(#state{user = User,
get_showtag(undefined) -> <<"unavailable">>;
get_showtag(Presence) ->
- case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
+ case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
<<"">> -> <<"available">>;
ShowTag -> ShowTag
end.
get_statustag(undefined) -> <<"">>;
get_statustag(Presence) ->
- xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
+ fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
process_unauthenticated_stanza(StateData, El) ->
- NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of
+ NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of
<<"">> ->
case StateData#state.lang of
<<"">> -> El;
- Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El)
+ Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El)
end;
_ -> El
end,
@@ -2434,10 +2374,10 @@ process_unauthenticated_stanza(StateData, El) ->
empty ->
ResIQ = IQ#iq{type = error,
sub_el = [?ERR_SERVICE_UNAVAILABLE]},
- Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>,
+ Res1 = jlib:replace_from_to(jid:make(<<"">>,
StateData#state.server,
<<"">>),
- jlib:make_jid(<<"">>, <<"">>,
+ jid:make(<<"">>, <<"">>,
<<"">>),
jlib:iq_to_xml(ResIQ)),
send_element(StateData,
@@ -2475,7 +2415,7 @@ fsm_next_state_gc(StateName, PackedStateData) ->
fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
StateData) ->
?WARNING_MSG("ACK queue too long, terminating session for ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
<<"Too many unacked stanzas">>),
send_element(StateData, Err),
@@ -2491,7 +2431,7 @@ fsm_next_state(wait_for_resume, #state{mgmt_timeout = 0} = StateData) ->
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined} =
StateData) ->
?INFO_MSG("Waiting for resumption of stream for ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
{next_state, wait_for_resume,
StateData#state{mgmt_state = pending, mgmt_pending_since = os:timestamp()},
StateData#state.mgmt_timeout};
@@ -2522,23 +2462,23 @@ is_ip_blacklisted({IP, _Port}, Lang) ->
%% Check from attributes
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
- case xml:get_tag_attr(<<"from">>, El) of
+ case fxml:get_tag_attr(<<"from">>, El) of
false ->
El;
{value, SJID} ->
- JID = jlib:string_to_jid(SJID),
+ JID = jid:from_string(SJID),
case JID of
error ->
'invalid-from';
#jid{} ->
if
(JID#jid.luser == FromJID#jid.luser) and
- (JID#jid.lserver == FromJID#jid.lserver) and
- (JID#jid.lresource == FromJID#jid.lresource) ->
+ (JID#jid.lserver == FromJID#jid.lserver) and
+ (JID#jid.lresource == FromJID#jid.lresource) ->
El;
(JID#jid.luser == FromJID#jid.luser) and
- (JID#jid.lserver == FromJID#jid.lserver) and
- (JID#jid.lresource == <<"">>) ->
+ (JID#jid.lserver == FromJID#jid.lserver) and
+ (JID#jid.lresource == <<"">>) ->
El;
true ->
'invalid-from'
@@ -2565,6 +2505,41 @@ bounce_messages() ->
after 0 -> ok
end.
+process_compression_request(El, StateName, StateData) ->
+ case fxml:get_subtag(El, <<"method">>) of
+ false ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"setup-failed">>,
+ attrs = [], children = []}]}),
+ fsm_next_state(StateName, StateData);
+ Method ->
+ case fxml:get_tag_cdata(Method) of
+ <<"zlib">> ->
+ Socket = StateData#state.socket,
+ BCompressed = fxml:element_to_binary(
+ #xmlel{name = <<"compressed">>,
+ attrs = [{<<"xmlns">>,
+ ?NS_COMPRESS}]}),
+ ZlibSocket = (StateData#state.sockmod):compress(
+ Socket, BCompressed),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = ZlibSocket,
+ streamid = new_id()});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"unsupported-method">>,
+ attrs = [],
+ children = []}]}),
+ fsm_next_state(StateName, StateData)
+ end
+ end.
+
%%%----------------------------------------------------------------------
%%% XEP-0191
%%%----------------------------------------------------------------------
@@ -2579,7 +2554,7 @@ route_blocking(What, StateData) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
- jlib:jid_to_string(JID)}],
+ jid:to_string(JID)}],
children = []}
end,
JIDs)};
@@ -2591,7 +2566,7 @@ route_blocking(What, StateData) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
- jlib:jid_to_string(JID)}],
+ jid:to_string(JID)}],
children = []}
end,
JIDs)};
@@ -2601,7 +2576,7 @@ route_blocking(What, StateData) ->
end,
PrivPushIQ = #iq{type = set, id = <<"push">>, sub_el = [SubEl]},
PrivPushEl =
- jlib:replace_from_to(jlib:jid_remove_resource(StateData#state.jid),
+ jlib:replace_from_to(jid:remove_resource(StateData#state.jid),
StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)),
%% No need to replace active privacy list here,
%% blocking pushes are always accompanied by
@@ -2633,7 +2608,7 @@ negotiate_stream_mgmt(_El, #state{resource = <<"">>} = StateData) ->
send_element(StateData, ?MGMT_UNEXPECTED_REQUEST(?NS_STREAM_MGMT_3)),
StateData;
negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
@@ -2661,7 +2636,7 @@ negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
end.
perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when Xmlns == StateData#state.mgmt_xmlns ->
case Name of
<<"r">> ->
@@ -2684,16 +2659,17 @@ perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
StateData
end.
-handle_enable(#state{mgmt_timeout = ConfigTimeout} = StateData, Attrs) ->
- Timeout = case xml:get_attr_s(<<"resume">>, Attrs) of
+handle_enable(#state{mgmt_timeout = DefaultTimeout,
+ mgmt_max_timeout = MaxTimeout} = StateData, Attrs) ->
+ Timeout = case fxml:get_attr_s(<<"resume">>, Attrs) of
ResumeAttr when ResumeAttr == <<"true">>;
ResumeAttr == <<"1">> ->
- MaxAttr = xml:get_attr_s(<<"max">>, Attrs),
+ MaxAttr = fxml:get_attr_s(<<"max">>, Attrs),
case catch jlib:binary_to_integer(MaxAttr) of
- Max when is_integer(Max), Max > 0, Max =< ConfigTimeout ->
+ Max when is_integer(Max), Max > 0, Max =< MaxTimeout ->
Max;
_ ->
- ConfigTimeout
+ DefaultTimeout
end;
_ ->
0
@@ -2701,13 +2677,13 @@ handle_enable(#state{mgmt_timeout = ConfigTimeout} = StateData, Attrs) ->
ResAttrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}] ++
if Timeout > 0 ->
?INFO_MSG("Stream management with resumption enabled for ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
[{<<"id">>, make_resume_id(StateData)},
{<<"resume">>, <<"true">>},
{<<"max">>, jlib:integer_to_binary(Timeout)}];
true ->
?INFO_MSG("Stream management without resumption enabled for ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
[]
end,
Res = #xmlel{name = <<"enabled">>,
@@ -2728,22 +2704,22 @@ handle_r(StateData) ->
StateData.
handle_a(StateData, Attrs) ->
- case catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs)) of
+ case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
H when is_integer(H), H >= 0 ->
check_h_attribute(StateData, H);
_ ->
?DEBUG("Ignoring invalid ACK element from ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
StateData
end.
handle_resume(StateData, Attrs) ->
- R = case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ R = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
- case {xml:get_attr(<<"previd">>, Attrs),
- catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs))}
+ case {fxml:get_attr(<<"previd">>, Attrs),
+ catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs))}
of
{{value, PrevID}, H} when is_integer(H), H >= 0 ->
case inherit_session_state(StateData, PrevID) of
@@ -2788,7 +2764,7 @@ handle_resume(StateData, Attrs) ->
FlushedState = csi_queue_flush(NewState),
NewStateData = FlushedState#state{csi_state = active},
?INFO_MSG("Resumed session for ~s",
- [jlib:jid_to_string(NewStateData#state.jid)]),
+ [jid:to_string(NewStateData#state.jid)]),
{ok, NewStateData};
{error, El, Msg} ->
send_element(StateData, El),
@@ -2800,11 +2776,11 @@ handle_resume(StateData, Attrs) ->
check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H)
when H > NumStanzasOut ->
?DEBUG("~s acknowledged ~B stanzas, but only ~B were sent",
- [jlib:jid_to_string(StateData#state.jid), H, NumStanzasOut]),
+ [jid:to_string(StateData#state.jid), H, NumStanzasOut]),
mgmt_queue_drop(StateData#state{mgmt_stanzas_out = H}, NumStanzasOut);
check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) ->
?DEBUG("~s acknowledged ~B of ~B stanzas",
- [jlib:jid_to_string(StateData#state.jid), H, NumStanzasOut]),
+ [jid:to_string(StateData#state.jid), H, NumStanzasOut]),
mgmt_queue_drop(StateData, H).
update_num_stanzas_in(#state{mgmt_state = active} = StateData, El) ->
@@ -2824,11 +2800,12 @@ send_stanza_and_ack_req(StateData, Stanza) ->
AckReq = #xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}],
children = []},
- case send_element(StateData, Stanza) of
- ok ->
- send_element(StateData, AckReq);
- error ->
- error
+ case send_element(StateData, Stanza) == ok andalso
+ send_element(StateData, AckReq) == ok of
+ true ->
+ StateData;
+ false ->
+ StateData#state{mgmt_state = pending}
end.
mgmt_queue_add(StateData, El) ->
@@ -2838,7 +2815,7 @@ mgmt_queue_add(StateData, El) ->
Num ->
Num + 1
end,
- NewQueue = queue:in({NewNum, now(), El}, StateData#state.mgmt_queue),
+ NewQueue = queue:in({NewNum, p1_time_compat:timestamp(), El}, StateData#state.mgmt_queue),
NewState = StateData#state{mgmt_queue = NewQueue,
mgmt_stanzas_out = NewNum},
check_queue_length(NewState).
@@ -2870,13 +2847,13 @@ handle_unacked_stanzas(StateData, F)
ok;
N ->
?INFO_MSG("~B stanzas were not acknowledged by ~s",
- [N, jlib:jid_to_string(StateData#state.jid)]),
+ [N, jid:to_string(StateData#state.jid)]),
lists:foreach(
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
- From_s = xml:get_attr_s(<<"from">>, Attrs),
- From = jlib:string_to_jid(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
- To = jlib:string_to_jid(To_s),
+ 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)
end, queue:to_list(Queue))
end;
@@ -2891,8 +2868,16 @@ handle_unacked_stanzas(StateData)
Resend when is_boolean(Resend) ->
Resend;
if_offline ->
- ejabberd_sm:get_user_resources(StateData#state.user,
- StateData#state.server) == []
+ Resource = StateData#state.resource,
+ case ejabberd_sm:get_user_resources(StateData#state.user,
+ StateData#state.server) of
+ [Resource] -> % Same resource opened new session
+ true;
+ [] ->
+ true;
+ _ ->
+ false
+ end
end,
ReRoute = case ResendOnTimeout of
true ->
@@ -2908,7 +2893,13 @@ handle_unacked_stanzas(StateData)
ejabberd_router:route(To, From, Err)
end
end,
- F = fun(From, To, El, Time) ->
+ F = fun(From, _To, #xmlel{name = <<"presence">>}, _Time) ->
+ ?DEBUG("Dropping presence stanza from ~s",
+ [jid:to_string(From)]);
+ (From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
+ Err = jlib:make_error_reply(El, ?ERR_SERVICE_UNAVAILABLE),
+ ejabberd_router:route(To, From, Err);
+ (From, To, El, Time) ->
%% We'll drop the stanza if it was <forwarded/> by some
%% encapsulating protocol as per XEP-0297. One such protocol is
%% XEP-0280, which says: "When a receiving server attempts to
@@ -2918,10 +2909,19 @@ handle_unacked_stanzas(StateData)
%% stanza could easily lead to unexpected results as well.
case is_encapsulated_forward(El) of
true ->
- ?DEBUG("Dropping forwarded stanza from ~s",
- [xml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
+ ?DEBUG("Dropping forwarded message stanza from ~s",
+ [fxml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
false ->
- ReRoute(From, To, El, Time)
+ case ejabberd_hooks:run_fold(message_is_archived,
+ StateData#state.server,
+ false,
+ [StateData, From,
+ StateData#state.jid, El]) of
+ true ->
+ ok;
+ false ->
+ ReRoute(From, To, El, Time)
+ end
end
end,
handle_unacked_stanzas(StateData, F);
@@ -2929,9 +2929,9 @@ handle_unacked_stanzas(_StateData) ->
ok.
is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
- SubTag = case {xml:get_subtag(El, <<"sent">>),
- xml:get_subtag(El, <<"received">>),
- xml:get_subtag(El, <<"result">>)} of
+ SubTag = case {fxml:get_subtag(El, <<"sent">>),
+ fxml:get_subtag(El, <<"received">>),
+ fxml:get_subtag(El, <<"result">>)} of
{false, false, false} ->
false;
{Tag, false, false} ->
@@ -2944,7 +2944,7 @@ is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
if SubTag == false ->
false;
true ->
- case xml:get_subtag(SubTag, <<"forwarded">>) of
+ case fxml:get_subtag(SubTag, <<"forwarded">>) of
false ->
false;
_ ->
@@ -3006,12 +3006,14 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
end.
resume_session({Time, PID}) ->
- (?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 3000).
+ (?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 5000).
make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
jlib:term_to_base64({StateData#state.resource, Time}).
+add_resent_delay_info(_State, #xmlel{name = <<"iq">>} = El, _Time) ->
+ El;
add_resent_delay_info(#state{server = From}, El, Time) ->
jlib:add_delay_info(El, From, Time, <<"Resent">>).
@@ -3025,12 +3027,12 @@ csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
StateData#state.server,
send, [Stanza]),
?DEBUG("Going to ~p stanza for inactive client ~p",
- [Action, jlib:jid_to_string(JID)]),
+ [Action, jid:to_string(JID)]),
case Action of
queue -> csi_queue_add(StateData, Stanza);
drop -> StateData;
send ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
StateData1 = csi_queue_send(StateData, From),
StateData2 = send_stanza(StateData1#state{csi_state = active},
Stanza),
@@ -3041,8 +3043,8 @@ csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
false ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
- NewQueue = lists:keystore(From, 1, Queue, {From, now(), Stanza}),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
+ NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}),
StateData#state{csi_queue = NewQueue}
end.
@@ -3060,7 +3062,7 @@ csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState, server = Host} =
csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID,
server = Host} = StateData) ->
- ?DEBUG("Flushing CSI queue for ~s", [jlib:jid_to_string(JID)]),
+ ?DEBUG("Flushing CSI queue for ~s", [jid:to_string(JID)]),
NewStateData =
lists:foldl(fun({_From, Time, Stanza}, AccState) ->
NewStanza =
@@ -3122,7 +3124,19 @@ transform_listen_option(Opt, Opts) ->
[Opt|Opts].
identity(Props) ->
- case proplists:get_value(authzid, Props, <<>>) of
- <<>> -> proplists:get_value(username, Props, <<>>);
- AuthzId -> AuthzId
- end.
+ case proplists:get_value(authzid, Props, <<>>) of
+ <<>> -> proplists:get_value(username, Props, <<>>);
+ AuthzId -> AuthzId
+ end.
+
+opt_type(domain_certfile) -> fun iolist_to_binary/1;
+opt_type(max_fsm_queue) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(resource_conflict) ->
+ fun (setresource) -> setresource;
+ (closeold) -> closeold;
+ (closenew) -> closenew;
+ (acceptnew) -> acceptnew
+ end;
+opt_type(_) ->
+ [domain_certfile, max_fsm_queue, resource_conflict].
diff --git a/src/ejabberd_c2s_config.erl b/src/ejabberd_c2s_config.erl
index a971f0af..3384e338 100644
--- a/src/ejabberd_c2s_config.erl
+++ b/src/ejabberd_c2s_config.erl
@@ -6,7 +6,7 @@
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -26,9 +26,11 @@
-module(ejabberd_c2s_config).
+-behaviour(ejabberd_config).
+
-author('mremond@process-one.net').
--export([get_c2s_limits/0]).
+-export([get_c2s_limits/0, opt_type/1]).
%% Get first c2s configuration limitations to apply it to other c2s
%% connectors.
@@ -63,3 +65,6 @@ select_opts_values([{max_stanza_size, Value} | Opts],
[{max_stanza_size, Value} | SelectedValues]);
select_opts_values([_Opt | Opts], SelectedValues) ->
select_opts_values(Opts, SelectedValues).
+
+opt_type(listen) -> fun (V) -> V end;
+opt_type(_) -> [listen].
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 110da1f6..157700c4 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -5,7 +5,7 @@
%%% Created : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.com>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,10 @@
-module(ejabberd_captcha).
+-behaviour(ejabberd_config).
+
+-protocol({xep, 158, '1.0'}).
+
-behaviour(gen_server).
%% API
@@ -37,7 +41,7 @@
-export([create_captcha/6, build_captcha_html/2,
check_captcha/2, process_reply/1, process/2,
is_feature_available/0, create_captcha_x/5,
- create_captcha_x/6]).
+ create_captcha_x/6, opt_type/1]).
-include("jlib.hrl").
@@ -71,13 +75,6 @@
tref :: reference(),
args :: any()}).
-%%====================================================================
-%% API
-%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
@@ -91,7 +88,7 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
B64Image = jlib:encode_base64((Image)),
- JID = jlib:jid_to_string(From),
+ JID = jid:to_string(From),
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
"@bob.xmpp.org">>,
Data = #xmlel{name = <<"data">>,
@@ -112,7 +109,7 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
{xmlcdata, ?NS_CAPTCHA}),
?VFIELD(<<"hidden">>, <<"from">>,
{xmlcdata,
- jlib:jid_to_string(To)}),
+ jid:to_string(To)}),
?VFIELD(<<"hidden">>,
<<"challenge">>,
{xmlcdata, Id}),
@@ -236,7 +233,7 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls,
[{xmlcdata,
Imageurl}]}]},
?VFIELD(<<"hidden">>, <<"from">>,
- {xmlcdata, jlib:jid_to_string(To)}),
+ {xmlcdata, jid:to_string(To)}),
?VFIELD(<<"hidden">>, <<"challenge">>,
{xmlcdata, Id}),
?VFIELD(<<"hidden">>, <<"sid">>,
@@ -272,12 +269,6 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls,
Err -> Err
end.
-%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
-%% where FormEl = xmlelement()
-%% ImgEl = xmlelement()
-%% TextEl = xmlelement()
-%% IdEl = xmlelement()
-%% KeyEl = xmlelement()
-spec build_captcha_html(binary(), binary()) -> captcha_not_found |
{xmlel(),
{xmlel(), xmlel(),
@@ -326,16 +317,10 @@ build_captcha_html(Id, Lang) ->
_ -> captcha_not_found
end.
-%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
--spec check_captcha(binary(), binary()) -> captcha_not_found |
- captcha_valid |
- captcha_non_valid.
-
-
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
process_reply(#xmlel{} = El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> {error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
@@ -401,9 +386,6 @@ process(_Handlers,
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
-%%====================================================================
-%% gen_server callbacks
-%%====================================================================
init([]) ->
mnesia:delete_table(captcha),
ets:new(captcha,
@@ -450,16 +432,6 @@ terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%% Function: create_image() -> {ok, Type, Key, Image} | {error, Reason}
-%% Type = "image/png" | "image/jpeg" | "image/gif"
-%% Key = string()
-%% Image = binary()
-%% Reason = atom()
-%%--------------------------------------------------------------------
create_image() -> create_image(undefined).
create_image(Limiter) ->
@@ -592,12 +564,6 @@ is_limited(Limiter) ->
end
end.
-%%--------------------------------------------------------------------
-%% Function: cmd(Cmd) -> Data | {error, Reason}
-%% Cmd = string()
-%% Data = binary()
-%% Description: os:cmd/1 replacement
-%%--------------------------------------------------------------------
-define(CMD_TIMEOUT, 5000).
-define(MAX_FILE_SIZE, 64 * 1024).
@@ -659,6 +625,10 @@ lookup_captcha(Id) ->
_ -> {error, enoent}
end.
+-spec check_captcha(binary(), binary()) -> captcha_not_found |
+ captcha_valid |
+ captcha_non_valid.
+
check_captcha(Id, ProvidedKey) ->
case ets:lookup(captcha, Id) of
[#captcha{pid = Pid, args = Args, key = ValidKey,
@@ -691,5 +661,15 @@ clean_treap(Treap, CleanPriority) ->
end.
now_priority() ->
- {MSec, Sec, USec} = now(),
- -((MSec * 1000000 + Sec) * 1000000 + USec).
+ -p1_time_compat:system_time(micro_seconds).
+
+opt_type(captcha_cmd) ->
+ fun (FileName) ->
+ F = iolist_to_binary(FileName), if F /= <<"">> -> F end
+ end;
+opt_type(captcha_host) -> fun iolist_to_binary/1;
+opt_type(captcha_limit) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(listen) -> fun (V) -> V end;
+opt_type(_) ->
+ [captcha_cmd, captcha_host, captcha_limit, listen].
diff --git a/src/ejabberd_cluster.erl b/src/ejabberd_cluster.erl
new file mode 100644
index 00000000..1e3f02a9
--- /dev/null
+++ b/src/ejabberd_cluster.erl
@@ -0,0 +1,104 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_cluster.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Ejabberd clustering management
+%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
+%%%
+%%%
+%%% 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_cluster).
+
+%% API
+-export([get_nodes/0, call/4, multicall/3, multicall/4]).
+-export([join/1, leave/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-spec get_nodes() -> [node()].
+
+get_nodes() ->
+ mnesia:system_info(running_db_nodes).
+
+-spec call(node(), module(), atom(), [any()]) -> any().
+
+call(Node, Module, Function, Args) ->
+ rpc:call(Node, Module, Function, Args, 5000).
+
+-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
+
+multicall(Module, Function, Args) ->
+ multicall(get_nodes(), Module, Function, Args).
+
+-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
+
+multicall(Nodes, Module, Function, Args) ->
+ rpc:multicall(Nodes, Module, Function, Args, 5000).
+
+-spec join(node()) -> ok | {error, any()}.
+
+join(Node) ->
+ case {node(), net_adm:ping(Node)} of
+ {Node, _} ->
+ {error, {not_master, Node}};
+ {_, pong} ->
+ application:stop(ejabberd),
+ application:stop(mnesia),
+ mnesia:delete_schema([node()]),
+ application:start(mnesia),
+ mnesia:change_config(extra_db_nodes, [Node]),
+ mnesia:change_table_copy_type(schema, node(), disc_copies),
+ spawn(fun() ->
+ lists:foreach(fun(Table) ->
+ Type = call(Node, mnesia, table_info, [Table, storage_type]),
+ mnesia:add_table_copy(Table, node(), Type)
+ end, mnesia:system_info(tables)--[schema])
+ end),
+ application:start(ejabberd);
+ _ ->
+ {error, {no_ping, Node}}
+ end.
+
+-spec leave(node()) -> ok | {error, any()}.
+
+leave(Node) ->
+ case {node(), net_adm:ping(Node)} of
+ {Node, _} ->
+ Cluster = get_nodes()--[Node],
+ leave(Cluster, Node);
+ {_, pong} ->
+ rpc:call(Node, ?MODULE, leave, [Node], 10000);
+ {_, pang} ->
+ case mnesia:del_table_copy(schema, Node) of
+ {atomic, ok} -> ok;
+ {aborted, Reason} -> {error, Reason}
+ end
+ end.
+leave([], Node) ->
+ {error, {no_cluster, Node}};
+leave([Master|_], Node) ->
+ application:stop(ejabberd),
+ application:stop(mnesia),
+ call(Master, mnesia, del_table_copy, [schema, Node]),
+ spawn(fun() ->
+ mnesia:delete_schema([node()]),
+ erlang:halt(0)
+ end),
+ ok.
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index ca40d5dc..3c98316d 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -5,7 +5,7 @@
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -211,22 +211,58 @@
-export([init/0,
list_commands/0,
get_command_format/1,
+ get_command_format/2,
get_command_definition/1,
get_tags_commands/0,
+ get_commands/0,
register_commands/1,
unregister_commands/1,
execute_command/2,
- execute_command/4
+ execute_command/4,
+ opt_type/1,
+ get_commands_spec/0
]).
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-
+-define(POLICY_ACCESS, '$policy').
+
+get_commands_spec() ->
+ [
+ #ejabberd_commands{name = gen_html_doc_for_commands, tags = [documentation],
+ desc = "Generates html documentation for ejabberd_commands",
+ module = ejabberd_commands_doc, function = generate_html_output,
+ args = [{file, binary}, {regexp, binary}, {examples, binary}],
+ result = {res, rescode},
+ args_desc = ["Path to file where generated "
+ "documentation should be stored",
+ "Regexp matching names of commands or modules "
+ "that will be included inside generated document",
+ "Comma separated list of languages (choosen from java, perl, xmlrpc, json)"
+ "that will have example invocation include in markdown document"],
+ result_desc = "0 if command failed, 1 when succedded",
+ args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
+ result_example = ok},
+ #ejabberd_commands{name = gen_markdown_doc_for_commands, tags = [documentation],
+ desc = "Generates markdown documentation for ejabberd_commands",
+ module = ejabberd_commands_doc, function = generate_md_output,
+ args = [{file, binary}, {regexp, binary}, {examples, binary}],
+ result = {res, rescode},
+ args_desc = ["Path to file where generated "
+ "documentation should be stored",
+ "Regexp matching names of commands or modules "
+ "that will be included inside generated document",
+ "Comma separated list of languages (choosen from java, perl, xmlrpc, json)"
+ "that will have example invocation include in markdown document"],
+ result_desc = "0 if command failed, 1 when succedded",
+ args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
+ result_example = ok}].
init() ->
ets:new(ejabberd_commands, [named_table, set, public,
- {keypos, #ejabberd_commands.name}]).
+ {keypos, #ejabberd_commands.name}]),
+ register_commands(get_commands_spec()).
-spec register_commands([ejabberd_commands()]) -> ok.
@@ -265,19 +301,40 @@ list_commands() ->
_ = '_'}),
[{A, B, C} || [A, B, C] <- Commands].
+-spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
+
+%% @doc Get a list of all the available commands, arguments, description, and
+%% policy.
+list_commands_policy() ->
+ Commands = ets:match(ejabberd_commands,
+ #ejabberd_commands{name = '$1',
+ args = '$2',
+ desc = '$3',
+ policy = '$4',
+ _ = '_'}),
+ [{A, B, C, D} || [A, B, C, D] <- Commands].
+
-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
+ get_command_format(Name, noauth).
+
+get_command_format(Name, Auth) ->
+ Admin = is_admin(Name, Auth),
Matched = ets:match(ejabberd_commands,
#ejabberd_commands{name = Name,
args = '$1',
result = '$2',
+ policy = '$3',
_ = '_'}),
case Matched of
[] ->
{error, command_unknown};
- [[Args, Result]] ->
+ [[Args, Result, user]] when Admin;
+ Auth == noauth ->
+ {[{user, binary}, {server, binary} | Args], Result};
+ [[Args, Result, _]] ->
{Args, Result}
end.
@@ -295,24 +352,57 @@ get_command_definition(Name) ->
execute_command(Name, Arguments) ->
execute_command([], noauth, Name, Arguments).
+-spec execute_command([{atom(), [atom()], [any()]}],
+ {binary(), binary(), binary(), boolean()} |
+ noauth | admin,
+ atom(),
+ [any()]
+ ) -> any().
+
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
-%% where
+%% where
%% AccessCommands = [{Access, CommandNames, Arguments}]
-%% Auth = {User::string(), Server::string(), Password::string()} | noauth
+%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
+%% | noauth
+%% | admin
%% Method = atom()
%% Arguments = [any()]
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
-execute_command(AccessCommands, Auth, Name, Arguments) ->
+execute_command(AccessCommands1, Auth1, Name, Arguments) ->
+ Auth = case is_admin(Name, Auth1) of
+ true -> admin;
+ false -> Auth1
+ end,
case ets:lookup(ejabberd_commands, Name) of
[Command] ->
+ AccessCommands = get_access_commands(AccessCommands1),
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
- ok -> execute_command2(Command, Arguments)
+ ok -> execute_command2(Auth, Command, Arguments)
catch
{error, Error} -> {error, Error}
end;
[] -> {error, command_unknown}
end.
+execute_command2(
+ _Auth, #ejabberd_commands{policy = open} = Command, Arguments) ->
+ execute_command2(Command, Arguments);
+execute_command2(
+ _Auth, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
+ execute_command2(Command, Arguments);
+execute_command2(
+ _Auth, #ejabberd_commands{policy = admin} = Command, Arguments) ->
+ execute_command2(Command, Arguments);
+execute_command2(
+ admin, #ejabberd_commands{policy = user} = Command, Arguments) ->
+ execute_command2(Command, Arguments);
+execute_command2(
+ noauth, #ejabberd_commands{policy = user} = Command, Arguments) ->
+ execute_command2(Command, Arguments);
+execute_command2(
+ {User, Server, _, _}, #ejabberd_commands{policy = user} = Command, Arguments) ->
+ execute_command2(Command, [User, Server | Arguments]).
+
execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
@@ -361,7 +451,7 @@ get_tags_commands() ->
%% -----------------------------
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
-%% where
+%% where
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
%% Method = atom()
@@ -372,11 +462,31 @@ get_tags_commands() ->
%% Error = account_unprivileged | invalid_account_data
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
ok;
-check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
+check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
+ Command =
+ case {Command1#ejabberd_commands.policy, Auth} of
+ {user, {_, _, _}} ->
+ Command1;
+ {user, _} ->
+ Command1#ejabberd_commands{
+ args = [{user, binary}, {server, binary} |
+ Command1#ejabberd_commands.args]};
+ _ ->
+ Command1
+ end,
AccessCommandsAllowed =
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
- case check_access(Access, Auth) of
+ case check_access(Command, Access, Auth) of
+ true ->
+ check_access_command(Commands, Command, ArgumentRestrictions,
+ Method, Arguments);
+ false ->
+ false
+ end;
+ ({Access, Commands}) ->
+ ArgumentRestrictions = [],
+ case check_access(Command, Access, Auth) of
true ->
check_access_command(Commands, Command, ArgumentRestrictions,
Method, Arguments);
@@ -390,31 +500,53 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
L when is_list(L) -> ok
end.
--spec check_auth(noauth) -> noauth_provided;
- ({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
+-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
+ (ejabberd_commands(),
+ {binary(), binary(), binary(), boolean()}) ->
+ {ok, binary(), binary()}.
-check_auth(noauth) ->
+check_auth(_Command, noauth) ->
no_auth_provided;
-check_auth({User, Server, Password}) ->
+check_auth(Command, {User, Server, {oauth, Token}, _}) ->
+ Scope = erlang:atom_to_binary(Command#ejabberd_commands.name, utf8),
+ case ejabberd_oauth:check_token(User, Server, Scope, Token) of
+ true ->
+ {ok, User, Server};
+ false ->
+ throw({error, invalid_account_data})
+ end;
+check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
%% Check the account exists and password is valid
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
- true -> {ok, User, Server};
- _ -> throw({error, invalid_account_data})
+ true -> {ok, User, Server};
+ _ -> throw({error, invalid_account_data})
end.
-check_access(all, _) ->
+check_access(Command, ?POLICY_ACCESS, _)
+ when Command#ejabberd_commands.policy == open ->
true;
-check_access(Access, Auth) ->
- case check_auth(Auth) of
+check_access(_Command, _Access, admin) ->
+ true;
+check_access(_Command, _Access, {_User, _Server, _, true}) ->
+ false;
+check_access(Command, Access, Auth)
+ when Access =/= ?POLICY_ACCESS;
+ Command#ejabberd_commands.policy == open;
+ Command#ejabberd_commands.policy == user ->
+ case check_auth(Command, Auth) of
{ok, User, Server} ->
- check_access(Access, User, Server);
+ check_access2(Access, User, Server);
_ ->
false
- end.
+ end;
+check_access(_Command, _Access, _Auth) ->
+ false.
-check_access(Access, User, Server) ->
+check_access2(?POLICY_ACCESS, _User, _Server) ->
+ true;
+check_access2(Access, User, Server) ->
%% Check this user has access permission
- case acl:match_rule(Server, Access, jlib:make_jid(User, Server, <<"">>)) of
+ case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of
allow -> true;
deny -> false
end.
@@ -438,8 +570,76 @@ check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
tag_arguments(ArgsDefs, Args) ->
lists:zipwith(
- fun({ArgName, _ArgType}, ArgValue) ->
+ fun({ArgName, _ArgType}, ArgValue) ->
{ArgName, ArgValue}
- end,
+ end,
ArgsDefs,
Args).
+
+
+get_access_commands(undefined) ->
+ Cmds = get_commands(),
+ [{?POLICY_ACCESS, Cmds, []}];
+get_access_commands(AccessCommands) ->
+ AccessCommands.
+
+get_commands() ->
+ Opts = ejabberd_config:get_option(
+ commands,
+ fun(V) when is_list(V) -> V end,
+ []),
+ CommandsList = list_commands_policy(),
+ OpenCmds = [N || {N, _, _, open} <- CommandsList],
+ RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
+ AdminCmds = [N || {N, _, _, admin} <- CommandsList],
+ UserCmds = [N || {N, _, _, user} <- CommandsList],
+ Cmds =
+ lists:foldl(
+ fun({add_commands, L}, Acc) ->
+ Cmds = case L of
+ open -> OpenCmds;
+ restricted -> RestrictedCmds;
+ admin -> AdminCmds;
+ user -> UserCmds;
+ _ when is_list(L) -> L
+ end,
+ lists:usort(Cmds ++ Acc);
+ ({remove_commands, L}, Acc) ->
+ Cmds = case L of
+ open -> OpenCmds;
+ restricted -> RestrictedCmds;
+ admin -> AdminCmds;
+ user -> UserCmds;
+ _ when is_list(L) -> L
+ end,
+ Acc -- Cmds;
+ (_, Acc) -> Acc
+ end, AdminCmds ++ UserCmds, Opts),
+ Cmds.
+
+is_admin(_Name, noauth) ->
+ false;
+is_admin(_Name, admin) ->
+ true;
+is_admin(_Name, {_User, _Server, _, false}) ->
+ false;
+is_admin(Name, {User, Server, _, true} = Auth) ->
+ AdminAccess = ejabberd_config:get_option(
+ commands_admin_access,
+ fun(A) when is_atom(A) -> A end,
+ none),
+ case acl:match_rule(Server, AdminAccess,
+ jid:make(User, Server, <<"">>)) of
+ allow ->
+ case catch check_auth(get_command_definition(Name), Auth) of
+ {ok, _, _} -> true;
+ _ -> false
+ end;
+ deny -> false
+ end.
+
+opt_type(commands_admin_access) ->
+ fun(A) when is_atom(A) -> A end;
+opt_type(commands) ->
+ fun(V) when is_list(V) -> V end;
+opt_type(_) -> [commands, commands_admin_access].
diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl
new file mode 100644
index 00000000..dc00c5d2
--- /dev/null
+++ b/src/ejabberd_commands_doc.erl
@@ -0,0 +1,565 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_commands_doc.erl
+%%% Author : Badlop <badlop@process-one.net>
+%%% Purpose : Management of ejabberd commands
+%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2015 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_commands_doc).
+-author('pawel@process-one.net').
+
+-export([generate_html_output/3]).
+-export([generate_md_output/3]).
+
+-include("ejabberd_commands.hrl").
+-include("ejabberd.hrl").
+
+-define(RAW(V), if HTMLOutput -> fxml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
+-define(TAG(N), if HTMLOutput -> [<<"<", ??N, "/>">>]; true -> md_tag(N, <<"">>) end).
+-define(TAG(N, V), if HTMLOutput -> [<<"<", ??N, ">">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
+-define(TAG(N, C, V), if HTMLOutput -> [<<"<", ??N, " class='", C, "'>">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
+-define(TAG_R(N, V), ?TAG(N, ?RAW(V))).
+-define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))).
+-define(SPAN(N, V), ?TAG_R(span, ??N, V)).
+
+-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])).
+-define(NUM(A), ?SPAN(num,jlib:integer_to_binary(A))).
+-define(FIELD(A), ?SPAN(field,A)).
+-define(ID(A), ?SPAN(id,A)).
+-define(OP(A), ?SPAN(op,A)).
+-define(ARG(A), ?FIELD(atom_to_list(A))).
+-define(KW(A), ?SPAN(kw,A)).
+-define(BR, <<"\n">>).
+
+-define(ARG_S(A), ?STR(atom_to_list(A))).
+
+-define(RAW_L(A), ?RAW(<<A>>)).
+-define(STR_L(A), ?STR(<<A>>)).
+-define(FIELD_L(A), ?FIELD(<<A>>)).
+-define(ID_L(A), ?ID(<<A>>)).
+-define(OP_L(A), ?OP(<<A>>)).
+-define(KW_L(A), ?KW(<<A>>)).
+
+-define(STR_A(A), ?STR(atom_to_list(A))).
+-define(ID_A(A), ?ID(atom_to_list(A))).
+
+list_join_with([], _M) ->
+ [];
+list_join_with([El|Tail], M) ->
+ lists:reverse(lists:foldl(fun(E, Acc) ->
+ [E, M | Acc]
+ end, [El], Tail)).
+
+md_tag(dt, V) ->
+ [<<"\n">>, V, <<"\n">>];
+md_tag(dd, V) ->
+ [<<"\n: ">>, V, <<"\n">>];
+md_tag(li, V) ->
+ [<<"- ">>, V, <<"\n">>];
+md_tag(pre, V) ->
+ [V, <<"\n">>];
+md_tag(p, V) ->
+ [<<"\n\n">>, V, <<"\n">>];
+md_tag(h1, V) ->
+ [<<"\n\n## ">>, V, <<"\n">>];
+md_tag(h2, V) ->
+ [<<"\n\n### ">>, V, <<"\n">>];
+md_tag(strong, V) ->
+ [<<"*">>, V, <<"*">>];
+md_tag(_, V) ->
+ V.
+
+
+%% rescode_to_int(ok) ->
+%% 0;
+%% rescode_to_int(true) ->
+%% 0;
+%% rescode_to_int(_) ->
+%% 1.
+
+perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
+ [?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
+perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
+ [?ARG(Name), ?OP_L(" => "), ?STR(Str)];
+perl_gen({Name, binary}, Str, _Indent, HTMLOutput) ->
+ [?ARG(Name), ?OP_L(" => "), ?STR(Str)];
+perl_gen({Name, atom}, Atom, _Indent, HTMLOutput) ->
+ [?ARG(Name), ?OP_L(" => "), ?STR_A(Atom)];
+perl_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
+ Res = lists:map(fun({A,B})->perl_gen(A, B, Indent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))),
+ [?ARG(Name), ?OP_L(" => {"), list_join_with(Res, [?OP_L(", ")]), ?OP_L("}")];
+perl_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
+ Res = lists:map(fun(E) -> [?OP_L("{"), perl_gen(ElDesc, E, Indent, HTMLOutput), ?OP_L("}")] end, List),
+ [?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")].
+
+perl_call(Name, ArgsDesc, Values, HTMLOutput) ->
+ {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ perl\n">>} end,
+ [Preamble,
+ Indent, ?ID_L("XMLRPC::Lite"), ?OP_L("->"), ?ID_L("proxy"), ?OP_L("("), ?ID_L("$url"), ?OP_L(")->"),
+ ?ID_L("call"), ?OP_L("("), ?STR_A(Name), ?OP_L(", {"), ?BR, Indent, <<" ">>,
+ list_join_with(lists:map(fun({A,B})->perl_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, Indent, <<" ">>]),
+ ?BR, Indent, ?OP_L("})->"), ?ID_L("results"), ?OP_L("()")].
+
+java_gen_map(Vals, Indent, HTMLOutput) ->
+ {Split, NL} = case Indent of
+ none -> {<<" ">>, <<" ">>};
+ _ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]}
+ end,
+ [?KW_L("new "), ?ID_L("HashMap"), ?OP_L("<"), ?ID_L("String"), ?OP_L(", "), ?ID_L("Object"),
+ ?OP_L(">() {{"), Split, list_join_with(Vals, Split), NL, ?OP_L("}}")].
+
+java_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Integer"), ?OP_L("("), ?NUM(Int), ?OP_L("));")];
+java_gen({Name, string}, Str, _Indent, HTMLOutput) ->
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
+java_gen({Name, binary}, Str, _Indent, HTMLOutput) ->
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
+java_gen({Name, atom}, Atom, _Indent, HTMLOutput) ->
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR_A(Atom), ?OP_L(");")];
+java_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
+ NewIndent = <<" ", Indent/binary>>,
+ Res = lists:map(fun({A, B}) -> [java_gen(A, B, NewIndent, HTMLOutput)] end, lists:zip(Fields, tuple_to_list(Tuple))),
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), java_gen_map(Res, Indent, HTMLOutput), ?OP_L(")")];
+java_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
+ {NI, NI2, I} = case List of
+ [_] -> {" ", " ", Indent};
+ _ -> {[?BR, <<" ", Indent/binary>>],
+ [?BR, <<" ", Indent/binary>>],
+ <<" ", Indent/binary>>}
+ end,
+ Res = lists:map(fun(E) -> java_gen_map([java_gen(ElDesc, E, I, HTMLOutput)], none, HTMLOutput) end, List),
+ [?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Object"), ?OP_L("[] {"), NI,
+ list_join_with(Res, [?OP_L(","), NI]), NI2, ?OP_L("});")].
+
+java_call(Name, ArgsDesc, Values, HTMLOutput) ->
+ {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ java\n">>} end,
+ [Preamble,
+ Indent, ?ID_L("XmlRpcClientConfigImpl config"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClientConfigImpl"), ?OP_L("();"), ?BR,
+ Indent, ?ID_L("config"), ?OP_L("."), ?ID_L("setServerURL"), ?OP_L("("), ?ID_L("url"), ?OP_L(");"), ?BR, Indent, ?BR,
+ Indent, ?ID_L("XmlRpcClient client"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClient"), ?OP_L("();"), ?BR,
+ Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, Indent, ?BR,
+ Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "),
+ java_gen_map(lists:map(fun({A,B})->java_gen(A, B, Indent, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Indent, HTMLOutput),
+ ?OP_L(");")].
+
+-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V).
+-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")).
+-define(XML(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?BR, Indent, ?XML_E(N)).
+-define(XML(N, Indent, D, V), ?XML(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
+-define(XML_L(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?XML_E(N)).
+-define(XML_L(N, Indent, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
+
+xml_gen({Name, integer}, Int, Indent, HTMLOutput) ->
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1,
+ [?XML_L(integer, Indent, 2, ?ID(jlib:integer_to_binary(Int)))])])];
+xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1,
+ [?XML_L(string, Indent, 2, ?ID(Str))])])];
+xml_gen({Name, binary}, Str, Indent, HTMLOutput) ->
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1,
+ [?XML_L(string, Indent, 2, ?ID(Str))])])];
+xml_gen({Name, atom}, Atom, Indent, HTMLOutput) ->
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1,
+ [?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])];
+xml_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
+ NewIndent = <<" ", Indent/binary>>,
+ Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent, HTMLOutput) end, lists:zip(Fields, tuple_to_list(Tuple))),
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])];
+xml_gen({Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
+ Ind1 = <<" ", Indent/binary>>,
+ Ind2 = <<" ", Ind1/binary>>,
+ Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2, HTMLOutput))])] end, List),
+ [?XML(member, Indent,
+ [?XML_L(name, Indent, 1, ?ID_A(Name)),
+ ?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])].
+
+xml_call(Name, ArgsDesc, Values, HTMLOutput) ->
+ {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ xml">>} end,
+ Res = lists:map(fun({A, B}) -> xml_gen(A, B, <<Indent/binary, " ">>, HTMLOutput) end, lists:zip(ArgsDesc, Values)),
+ [Preamble,
+ ?XML(methodCall, Indent,
+ [?XML_L(methodName, Indent, 1, ?ID_A(Name)),
+ ?XML(params, Indent, 1,
+ [?XML(param, Indent, 2,
+ [?XML(value, Indent, 3,
+ [?XML(struct, Indent, 4, Res)])])])])].
+
+% [?ARG_S(Name), ?OP_L(": "), ?STR(Str)];
+json_gen({_Name, integer}, Int, _Indent, HTMLOutput) ->
+ [?NUM(Int)];
+json_gen({_Name, string}, Str, _Indent, HTMLOutput) ->
+ [?STR(Str)];
+json_gen({_Name, binary}, Str, _Indent, HTMLOutput) ->
+ [?STR(Str)];
+json_gen({_Name, atom}, Atom, _Indent, HTMLOutput) ->
+ [?STR_A(Atom)];
+json_gen({_Name, rescode}, Val, _Indent, HTMLOutput) ->
+ [?ID_A(Val == ok orelse Val == true)];
+json_gen({_Name, restuple}, {Val, Str}, _Indent, HTMLOutput) ->
+ [?OP_L("{"), ?STR_L("res"), ?OP_L(": "), ?ID_A(Val == ok orelse Val == true), ?OP_L(", "),
+ ?STR_L("text"), ?OP_L(": "), ?STR(Str), ?OP_L("}")];
+json_gen({_Name, {list, {_, {tuple, [{_, atom}, ValFmt]}}}}, List, Indent, HTMLOutput) ->
+ Indent2 = <<" ", Indent/binary>>,
+ Res = lists:map(fun({N, V})->[?STR_A(N), ?OP_L(": "), json_gen(ValFmt, V, Indent2, HTMLOutput)] end, List),
+ [?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")];
+json_gen({_Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
+ Indent2 = <<" ", Indent/binary>>,
+ Res = lists:map(fun({{N, _} = A, B})->[?STR_A(N), ?OP_L(": "), json_gen(A, B, Indent2, HTMLOutput)] end,
+ lists:zip(Fields, tuple_to_list(Tuple))),
+ [?OP_L("{"), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("}")];
+json_gen({_Name, {list, ElDesc}}, List, Indent, HTMLOutput) ->
+ Indent2 = <<" ", Indent/binary>>,
+ Res = lists:map(fun(E) -> json_gen(ElDesc, E, Indent2, HTMLOutput) end, List),
+ [?OP_L("["), ?BR, Indent2, list_join_with(Res, [?OP_L(","), ?BR, Indent2]), ?BR, Indent, ?OP_L("]")].
+
+json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
+ {Indent, Preamble} = if HTMLOutput -> {<<"">>, []}; true -> {<<" ">>, <<"~~~ json\n">>} end,
+ {Code, ResultStr} = case {ResultDesc, Result} of
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, [?STR_L("")]};
+ {{_, rescode}, _} ->
+ {500, [?STR_L("")]};
+ {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
+ {200, [?STR(Text1)]};
+ {{_, restuple}, {_, Text2}} ->
+ {500, [?STR(Text2)]};
+ {{_, {list, _}}, _} ->
+ {200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
+ {{_, {tuple, _}}, _} ->
+ {200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
+ {{Name0, _}, _} ->
+ {200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
+ json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
+ end,
+ CodeStr = case Code of
+ 200 -> <<" 200 OK">>;
+ 500 -> <<" 500 Internal Server Error">>
+ end,
+ [Preamble,
+ Indent, ?ID_L("POST /api/"), ?ID_A(Name), ?BR,
+ Indent, ?OP_L("{"), ?BR, Indent, <<" ">>,
+ list_join_with(lists:map(fun({{N,_}=A,B})->[?STR_A(N), ?OP_L(": "), json_gen(A, B, <<Indent/binary, " ">>, HTMLOutput)] end,
+ lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, Indent, <<" ">>]),
+ ?BR, Indent, ?OP_L("}"), ?BR, Indent, ?BR, Indent,
+ ?ID_L("HTTP/1.1"), ?ID(CodeStr), ?BR, Indent,
+ ResultStr
+ ].
+
+generate_example_input({_Name, integer}, {LastStr, LastNum}) ->
+ {LastNum+1, {LastStr, LastNum+1}};
+generate_example_input({_Name, string}, {LastStr, LastNum}) ->
+ {string:chars(LastStr+1, 5), {LastStr+1, LastNum}};
+generate_example_input({_Name, binary}, {LastStr, LastNum}) ->
+ {iolist_to_binary(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
+generate_example_input({_Name, atom}, {LastStr, LastNum}) ->
+ {list_to_atom(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
+generate_example_input({_Name, rescode}, {LastStr, LastNum}) ->
+ {ok, {LastStr, LastNum}};
+generate_example_input({_Name, restuple}, {LastStr, LastNum}) ->
+ {{ok, <<"Success">>}, {LastStr, LastNum}};
+generate_example_input({_Name, {tuple, Fields}}, Data) ->
+ {R, D} = lists:foldl(fun(Field, {Res2, Data2}) ->
+ {Res3, Data3} = generate_example_input(Field, Data2),
+ {[Res3 | Res2], Data3}
+ end, {[], Data}, Fields),
+ {list_to_tuple(lists:reverse(R)), D};
+generate_example_input({_Name, {list, Desc}}, Data) ->
+ {R1, D1} = generate_example_input(Desc, Data),
+ {R2, D2} = generate_example_input(Desc, D1),
+ {[R1, R2], D2}.
+
+gen_calls(#ejabberd_commands{args_example=none, args=ArgsDesc} = C, HTMLOutput, Langs) ->
+ {R, _} = lists:foldl(fun(Arg, {Res, Data}) ->
+ {Res3, Data3} = generate_example_input(Arg, Data),
+ {[Res3 | Res], Data3}
+ end, {[], {$a-1, 0}}, ArgsDesc),
+ gen_calls(C#ejabberd_commands{args_example=lists:reverse(R)}, HTMLOutput, Langs);
+gen_calls(#ejabberd_commands{result_example=none, result=ResultDesc} = C, HTMLOutput, Langs) ->
+ {R, _} = generate_example_input(ResultDesc, {$a-1, 0}),
+ gen_calls(C#ejabberd_commands{result_example=R}, HTMLOutput, Langs);
+gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
+ result_example=Result, result=ResultDesc,
+ name=Name}, HTMLOutput, Langs) ->
+ Perl = perl_call(Name, ArgsDesc, Values, HTMLOutput),
+ Java = java_call(Name, ArgsDesc, Values, HTMLOutput),
+ XML = xml_call(Name, ArgsDesc, Values, HTMLOutput),
+ JSON = json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput),
+ if HTMLOutput ->
+ [?TAG(ul, "code-samples-names",
+ [case lists:member(<<"java">>, Langs) of true -> ?TAG(li, <<"Java">>); _ -> [] end,
+ case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, <<"Perl">>); _ -> [] end,
+ case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, <<"XML">>); _ -> [] end,
+ case lists:member(<<"json">>, Langs) of true -> ?TAG(li, <<"JSON">>); _ -> [] end]),
+ ?TAG(ul, "code-samples",
+ [case lists:member(<<"java">>, Langs) of true -> ?TAG(li, ?TAG(pre, Java)); _ -> [] end,
+ case lists:member(<<"perl">>, Langs) of true -> ?TAG(li, ?TAG(pre, Perl)); _ -> [] end,
+ case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end,
+ case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
+ true ->
+ [<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
+ case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
+ case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
+ case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
+ <<"{: .code-samples-labels}\n">>,
+ case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
+ case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
+ case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
+ case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
+ <<"{: .code-samples-tabs}\n\n">>]
+ end.
+
+gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
+ args=Args, args_desc=ArgsDesc,
+ result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
+ LDesc = case LongDesc of
+ "" -> Desc;
+ _ -> LongDesc
+ end,
+ ArgsText = case ArgsDesc of
+ none ->
+ [?TAG(ul, "args-list", lists:map(fun({AName, Type}) ->
+ [?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
+ ?RAW(io_lib:format("~p", [Type]))])]
+ end, Args))];
+ _ ->
+ [?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) ->
+ [?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
+ ?RAW(io_lib:format("~p", [Type]))]),
+ ?TAG(dd, ?RAW(ADesc))]
+ end, lists:zip(Args, ArgsDesc)))]
+ end,
+ ResultText = case ResultDesc of
+ none ->
+ [?RAW(io_lib:format("~p", [Result]))];
+ _ ->
+ [?TAG(dl, [
+ ?TAG(dt, io_lib:format("~p", [Result])),
+ ?TAG_R(dd, ResultDesc)])]
+ end,
+
+ [?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
+ ?TAG(p, ?RAW(LDesc)),
+ ?TAG(h2, <<"Arguments:">>),
+ ArgsText,
+ ?TAG(h2, <<"Result:">>),
+ ResultText,
+ ?TAG(h2, <<"Examples:">>),
+ gen_calls(Cmd, HTMLOutput, Langs)].
+
+find_commands_definitions() ->
+ case code:lib_dir(ejabberd, ebin) of
+ {error, _} ->
+ lists:map(fun({N, _, _}) ->
+ ejabberd_commands:get_command_definition(N)
+ end, ejabberd_commands:list_commands());
+ Path ->
+ lists:flatmap(fun(P) ->
+ Mod = list_to_atom(filename:rootname(P)),
+ code:ensure_loaded(Mod),
+ case erlang:function_exported(Mod, get_commands_spec, 0) of
+ true ->
+ apply(Mod, get_commands_spec, []);
+ _ ->
+ []
+ end
+ end, filelib:wildcard("*.beam", Path))
+ end.
+
+generate_html_output(File, RegExp, Languages) ->
+ Cmds = find_commands_definitions(),
+ {ok, RE} = re:compile(RegExp),
+ Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) ->
+ re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
+ re:run(atom_to_list(Module), RE, [{capture, none}]) == match
+ end, Cmds),
+ Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) ->
+ N1 =< N2
+ end, Cmds2),
+ Langs = binary:split(Languages, <<",">>, [global]),
+ Out = lists:map(fun(C) -> gen_doc(C, true, Langs) end, Cmds3),
+ {ok, Fh} = file:open(File, [write]),
+ io:format(Fh, "~s", [[html_pre(), Out, html_post()]]),
+ file:close(Fh),
+ ok.
+
+generate_md_output(File, RegExp, Languages) ->
+ Cmds = find_commands_definitions(),
+ {ok, RE} = re:compile(RegExp),
+ Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) ->
+ re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
+ re:run(atom_to_list(Module), RE, [{capture, none}]) == match
+ end, Cmds),
+ Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) ->
+ N1 =< N2
+ end, Cmds2),
+ Langs = binary:split(Languages, <<",">>, [global]),
+ Header = <<"---\ntitle: Administration API reference\nbodyclass: nocomment\n---">>,
+ Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds3),
+ {ok, Fh} = file:open(File, [write]),
+ io:format(Fh, "~s~s", [Header, Out]),
+ file:close(Fh),
+ ok.
+
+html_pre() ->
+ "<!DOCTYPE>
+<html>
+ <head>
+ <meta http-equiv='content-type' content='text/html; charset=utf-8' />
+ <style>
+ body {
+ margin: 0 auto;
+ font-family: Georgia, Palatino, serif;
+ color: #000;
+ line-height: 1;
+ max-width: 80%;
+ padding: 10px;
+ }
+ h1, h2, h3, h4 {
+ color: #111111;
+ font-weight: 400;
+ }
+ h1, h2, h3, h4, h5, p {
+ margin-bottom: 24px;
+ padding: 0;
+ }
+ h1 {
+ margin-top: 80px;
+ font-size: 36px;
+ }
+ h2 {
+ font-size: 24px;
+ margin: 24px 0 6px;
+ }
+ ul, ol {
+ padding: 0;
+ margin: 0;
+ }
+ li {
+ line-height: 24px;
+ }
+ li ul, li ul {
+ margin-left: 24px;
+ }
+ p, ul, ol {
+ font-size: 16px;
+ line-height: 24px;
+ max-width: 80%;
+ }
+ .id {color: #bbb}
+ .lit {color: #aaa}
+ .op {color: #9f9}
+ .str {color: #f00}
+ .num {color: white}
+ .field {color: #faa}
+ .kw {font-weight: bold; color: #ff6}
+ .code-samples li {
+ font-family: Consolas, Monaco, Andale Mono, monospace;
+ line-height: 1.5;
+ font-size: 13px;
+ background: #333;
+ overflow: auto;
+ margin: 0;
+ padding: 0;
+ }
+ .code-samples pre {
+ margin: 0;
+ padding: 0.5em 0.5em;
+ }
+ .code-samples {
+ position: relative;
+ }
+ .code-samples-names li {
+ display: block;
+ }
+ .code-samples-names li {
+ color: white;
+ background: #9c1;
+ float: left;
+ margin: 0 1px -4px 0;
+ position: relative;
+ z-index: 1;
+ border: 4px solid #9c1;
+ border-bottom: 0;
+ border-radius: 9px 9px 0 0;
+ padding: 0.2em 1em 4px 1em;
+ cursor: pointer;
+ }
+ .code-samples-names li.selected {
+ background: #333;
+ }
+ .code-samples {
+ clear: both;
+ }
+ .code-samples li {
+ display: block;
+ border: 4px solid #9c1;
+ border-radius: 9px;
+ border-top-left-radius: 0;
+ width: 100%;
+ }
+ .args-list li {
+ display: block;
+ }
+ </style>
+</head>
+ <body>
+ <script>
+ function changeTab2(tab, addClickHandlers) {
+ var els = tab.parentNode.childNodes;
+ var els2 = tab.parentNode.nextSibling.childNodes;
+ for (var i = 0; i < els.length; i++) {
+ if (addClickHandlers)
+ els[i].addEventListener('click', changeTab, false);
+
+ if (els[i] == tab) {
+ els[i].setAttribute('class', 'selected');
+ els2[i].style.display = 'block';
+ } else {
+ els[i].removeAttribute('class');
+ els2[i].style.display = 'none';
+ }
+ }
+ }
+ function changeTab(event) {
+ changeTab2(event.target);
+ }
+ </script>".
+
+html_post() ->
+"<script>
+ var ul = document.getElementsByTagName('ul');
+ for (var i = 0; i < ul.length; i++) {
+ if (ul[i].className == 'code-samples-names')
+ changeTab2(ul[i].firstChild, true);
+ }
+ </script>
+ </body>
+</html>".
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 5d1df505..6f7368b7 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -5,7 +5,7 @@
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -36,13 +36,16 @@
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1,
convert_to_yaml/1, convert_to_yaml/2,
- env_binary_to_list/2]).
+ env_binary_to_list/2, opt_type/1, may_hide_data/1]).
+
+-export([start/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
-include_lib("kernel/include/file.hrl").
+-callback opt_type(atom()) -> function() | [atom()].
%% @type macro() = {macro_key(), macro_value()}
@@ -51,8 +54,48 @@
%% @type macro_value() = term().
-
start() ->
+ mnesia_init(),
+ Config = get_ejabberd_config_path(),
+ State0 = read_file(Config),
+ State1 = hosts_to_start(State0),
+ State2 = validate_opts(State1),
+ %% This start time is used by mod_last:
+ UnixTime = p1_time_compat:system_time(seconds),
+ SharedKey = case erlang:get_cookie() of
+ nocookie ->
+ p1_sha:sha(randoms:get_string());
+ Cookie ->
+ p1_sha:sha(jlib:atom_to_binary(Cookie))
+ end,
+ State3 = set_option({node_start, global}, UnixTime, State2),
+ State4 = set_option({shared_key, global}, SharedKey, State3),
+ set_opts(State4).
+
+%% When starting ejabberd for testing, we sometimes want to start a
+%% subset of hosts from the one define in the config file.
+%% This function override the host list read from config file by the
+%% one we provide.
+%% Hosts to start are defined in an ejabberd application environment
+%% variable 'hosts' to make it easy to ignore some host in config
+%% file.
+hosts_to_start(State) ->
+ case application:get_env(ejabberd, hosts) of
+ undefined ->
+ %% Start all hosts as defined in config file
+ State;
+ {ok, Hosts} ->
+ set_hosts_in_options(Hosts, State)
+ end.
+
+%% @private
+%% At the moment, these functions are mainly used to setup unit tests.
+-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
+start(Hosts, Opts) ->
+ mnesia_init(),
+ set_opts(#state{hosts = Hosts, opts = Opts}).
+
+mnesia_init() ->
case catch mnesia:table_info(local_config, storage_type) of
disc_copies ->
mnesia:delete_table(local_config);
@@ -63,21 +106,7 @@ start() ->
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, local_config)}]),
- mnesia:add_table_copy(local_config, node(), ram_copies),
- Config = get_ejabberd_config_path(),
- State = read_file(Config),
- %% This start time is used by mod_last:
- {MegaSecs, Secs, _} = now(),
- UnixTime = MegaSecs*1000000 + Secs,
- SharedKey = case erlang:get_cookie() of
- nocookie ->
- p1_sha:sha(randoms:get_string());
- Cookie ->
- p1_sha:sha(jlib:atom_to_binary(Cookie))
- end,
- State1 = set_option({node_start, global}, UnixTime, State),
- State2 = set_option({shared_key, global}, SharedKey, State1),
- set_opts(State2).
+ mnesia:add_table_copy(local_config, node(), ram_copies).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
@@ -111,10 +140,11 @@ get_env_config() ->
%% @doc Read the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% This function will crash if finds some error in the configuration file.
-%% @spec (File::string()) -> #state{}.
+%% @spec (File::string()) -> #state{}
read_file(File) ->
read_file(File, [{replace_macros, true},
- {include_files, true}]).
+ {include_files, true},
+ {include_modules_configs, true}]).
read_file(File, Opts) ->
Terms1 = get_plain_terms_file(File, Opts),
@@ -160,7 +190,7 @@ convert_to_yaml(File, Output) ->
fun({Host, Opts1}) ->
{host_config, [{Host, Opts1}]}
end, HOpts),
- Data = p1_yaml:encode(lists:reverse(NewOpts)),
+ Data = fast_yaml:encode(lists:reverse(NewOpts)),
case Output of
stdout ->
io:format("~s~n", [Data]);
@@ -189,7 +219,6 @@ env_binary_to_list(Application, Parameter) ->
%% Returns a list of plain terms,
%% in which the options 'include_config_file' were parsed
%% and the terms in those files were included.
-%% @spec(string()) -> [term()]
%% @spec(iolist()) -> [term()]
get_plain_terms_file(File) ->
get_plain_terms_file(File, [{include_files, true}]).
@@ -200,7 +229,17 @@ get_plain_terms_file(File1, Opts) ->
File = get_absolute_path(File1),
case consult(File) of
{ok, Terms} ->
- BinTerms = strings_to_binary(Terms),
+ BinTerms1 = strings_to_binary(Terms),
+ ModInc = case proplists:get_bool(include_modules_configs, Opts) of
+ true ->
+ Files = [{filename:rootname(filename:basename(F)), F}
+ || F <- filelib:wildcard(ext_mod:config_dir() ++ "/*.{yml,yaml}")
+ ++ filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.{yml,yaml}")],
+ [proplists:get_value(F,Files) || F <- proplists:get_keys(Files)];
+ _ ->
+ []
+ end,
+ BinTerms = BinTerms1 ++ [{include_config_file, list_to_binary(V)} || V <- ModInc],
case proplists:get_bool(include_files, Opts) of
true ->
include_config_files(BinTerms);
@@ -215,14 +254,14 @@ get_plain_terms_file(File1, Opts) ->
consult(File) ->
case filename:extension(File) of
Ex when (Ex == ".yml") or (Ex == ".yaml") ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} ->
{ok, []};
{ok, [Document|_]} ->
{ok, parserl(Document)};
{error, Err} ->
Msg1 = "Cannot load " ++ File ++ ": ",
- Msg2 = p1_yaml:format_error(Err),
+ Msg2 = fast_yaml:format_error(Err),
{error, Msg1 ++ Msg2}
end;
_ ->
@@ -266,7 +305,7 @@ search_hosts(Term, State) ->
{host, Host} ->
if
State#state.hosts == [] ->
- add_hosts_to_option([Host], State);
+ set_hosts_in_options([Host], State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -275,7 +314,7 @@ search_hosts(Term, State) ->
{hosts, Hosts} ->
if
State#state.hosts == [] ->
- add_hosts_to_option(Hosts, State);
+ set_hosts_in_options(Hosts, State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -285,16 +324,19 @@ search_hosts(Term, State) ->
State
end.
-add_hosts_to_option(Hosts, State) ->
+set_hosts_in_options(Hosts, State) ->
PrepHosts = normalize_hosts(Hosts),
- set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts}).
+ NewOpts = lists:filter(fun({local_config,{hosts,global},_}) -> false;
+ (_) -> true
+ end, State#state.opts),
+ set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts, opts = NewOpts}).
normalize_hosts(Hosts) ->
normalize_hosts(Hosts,[]).
normalize_hosts([], PrepHosts) ->
lists:reverse(PrepHosts);
normalize_hosts([Host|Hosts], PrepHosts) ->
- case jlib:nodeprep(iolist_to_binary(Host)) of
+ case jid:nodeprep(iolist_to_binary(Host)) of
error ->
?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]),
@@ -358,6 +400,62 @@ exit_or_halt(ExitText) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for 'include_config_file'
+get_config_option_key(Name, Val) ->
+ if Name == listen ->
+ case Val of
+ {{Port, IP, Trans}, _Mod, _Opts} ->
+ {Port, IP, Trans};
+ {{Port, Trans}, _Mod, _Opts} when Trans == tcp; Trans == udp ->
+ {Port, {0,0,0,0}, Trans};
+ {{Port, IP}, _Mod, _Opts} ->
+ {Port, IP, tcp};
+ {Port, _Mod, _Opts} ->
+ {Port, {0,0,0,0}, tcp};
+ V when is_list(V) ->
+ lists:foldl(
+ fun({port, Port}, {_, IP, T}) ->
+ {Port, IP, T};
+ ({ip, IP}, {Port, _, T}) ->
+ {Port, IP, T};
+ ({transport, T}, {Port, IP, _}) ->
+ {Port, IP, T};
+ (_, Res) ->
+ Res
+ end, {5222, {0,0,0,0}, tcp}, Val)
+ end;
+ is_tuple(Val) ->
+ element(1, Val);
+ true ->
+ Val
+ end.
+
+maps_to_lists(IMap) ->
+ maps:fold(fun(Name, Map, Res) when Name == host_config orelse Name == append_host_config ->
+ [{Name, [{Host, maps_to_lists(SMap)} || {Host,SMap} <- maps:values(Map)]} | Res];
+ (Name, Map, Res) when is_map(Map) ->
+ [{Name, maps:values(Map)} | Res];
+ (Name, Val, Res) ->
+ [{Name, Val} | Res]
+ end, [], IMap).
+
+merge_configs(Terms, ResMap) ->
+ lists:foldl(fun({Name, Val}, Map) when is_list(Val), Name =/= auth_method ->
+ Old = maps:get(Name, Map, #{}),
+ New = lists:foldl(fun(SVal, OMap) ->
+ NVal = if Name == host_config orelse Name == append_host_config ->
+ {Host, Opts} = SVal,
+ {_, SubMap} = maps:get(Host, OMap, {Host, #{}}),
+ {Host, merge_configs(Opts, SubMap)};
+ true ->
+ SVal
+ end,
+ maps:put(get_config_option_key(Name, SVal), NVal, OMap)
+ end, Old, Val),
+ maps:put(Name, New, Map);
+ ({Name, Val}, Map) ->
+ maps:put(Name, Val, Map)
+ end, ResMap, Terms).
+
%% @doc Include additional configuration files in the list of terms.
%% @spec ([term()]) -> [term()]
include_config_files(Terms) ->
@@ -374,7 +472,10 @@ include_config_files(Terms) ->
fun({File, Opts}) ->
include_config_file(File, Opts)
end, lists:flatten(FileOpts)),
- Terms1 ++ Terms2.
+
+ M1 = merge_configs(transform_terms(Terms1), #{}),
+ M2 = merge_configs(transform_terms(Terms2), M1),
+ maps_to_lists(M2).
transform_include_option({include_config_file, File}) when is_list(File) ->
case is_string(File) of
@@ -452,11 +553,11 @@ split_terms_macros(Terms) ->
lists:foldl(
fun(Term, {TOs, Ms}) ->
case Term of
- {define_macro, Key, Value} ->
+ {define_macro, Key, Value} ->
case is_correct_macro({Key, Value}) of
- true ->
+ true ->
{TOs, Ms++[{Key, Value}]};
- false ->
+ false ->
exit({macro_not_properly_defined, Term})
end;
{define_macro, KeyVals} ->
@@ -686,6 +787,46 @@ get_option(Opt, F, Default) ->
end
end.
+get_modules_with_options() ->
+ {ok, Mods} = application:get_key(ejabberd, modules),
+ ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
+ lists:foldl(
+ fun(Mod, D) ->
+ case catch Mod:opt_type('') of
+ Opts when is_list(Opts) ->
+ lists:foldl(
+ fun(Opt, Acc) ->
+ dict:append(Opt, Mod, Acc)
+ end, D, Opts);
+ {'EXIT', {undef, _}} ->
+ D
+ end
+ end, dict:new(), [?MODULE|ExtMods++Mods]).
+
+validate_opts(#state{opts = Opts} = State) ->
+ ModOpts = get_modules_with_options(),
+ NewOpts = lists:filter(
+ fun(#local_config{key = {Opt, _Host}, value = Val}) ->
+ case dict:find(Opt, ModOpts) of
+ {ok, [Mod|_]} ->
+ VFun = Mod:opt_type(Opt),
+ case catch VFun(Val) of
+ {'EXIT', _} ->
+ ?ERROR_MSG("ignoring option '~s' with "
+ "invalid value: ~p",
+ [Opt, Val]),
+ false;
+ _ ->
+ true
+ end;
+ _ ->
+ ?ERROR_MSG("unknown option '~s' will be likely"
+ " ignored", [Opt]),
+ true
+ end
+ end, Opts),
+ State#state{opts = NewOpts}.
+
-spec get_vh_by_auth_method(atom()) -> [binary()].
%% Return the list of hosts handled by a given module
@@ -739,20 +880,31 @@ replace_module(mod_roster_odbc) -> {mod_roster, odbc};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
+replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
false -> Module
end.
-replace_modules(Modules) -> lists:map( fun({Module, Opts}) -> case
- replace_module(Module) of {NewModule, DBType} ->
- emit_deprecation_warning(Module, NewModule, DBType), NewOpts =
- [{db_type, DBType} | lists:keydelete(db_type, 1, Opts)],
- {NewModule, transform_module_options(Module, NewOpts)}; NewModule
- -> if Module /= NewModule -> emit_deprecation_warning(Module,
- NewModule); true -> ok end, {NewModule,
- transform_module_options(Module, Opts)} end end, Modules).
+replace_modules(Modules) ->
+ lists:map(
+ fun({Module, Opts}) ->
+ case replace_module(Module) of
+ {NewModule, DBType} ->
+ emit_deprecation_warning(Module, NewModule, DBType),
+ NewOpts = [{db_type, DBType} |
+ lists:keydelete(db_type, 1, Opts)],
+ {NewModule, transform_module_options(Module, NewOpts)};
+ NewModule ->
+ if Module /= NewModule ->
+ emit_deprecation_warning(Module, NewModule);
+ true ->
+ ok
+ end,
+ {NewModule, transform_module_options(Module, Opts)}
+ end
+ end, Modules).
%% Elixir module naming
%% ====================
@@ -1062,3 +1214,33 @@ emit_deprecation_warning(Module, NewModule) ->
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
[Module, NewModule])
end.
+
+opt_type(hide_sensitive_log_data) ->
+ fun (H) when is_boolean(H) -> H end;
+opt_type(hosts) ->
+ fun(L) when is_list(L) ->
+ lists:map(
+ fun(H) ->
+ iolist_to_binary(H)
+ end, L)
+ end;
+opt_type(language) ->
+ fun iolist_to_binary/1;
+opt_type(_) ->
+ [hide_sensitive_log_data, hosts, language].
+
+-spec may_hide_data(string()) -> string();
+ (binary()) -> binary().
+
+may_hide_data(Data) ->
+ case ejabberd_config:get_option(
+ hide_sensitive_log_data,
+ fun(false) -> false;
+ (true) -> true
+ end,
+ false) of
+ false ->
+ Data;
+ true ->
+ "hidden_by_ejabberd"
+ end.
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 6ab383b1..7a26644f 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -44,14 +44,13 @@
-module(ejabberd_ctl).
+
+-behaviour(ejabberd_config).
-author('alexey@process-one.net').
--export([start/0,
- init/0,
- process/1,
- process2/2,
- register_commands/3,
- unregister_commands/3]).
+-export([start/0, init/0, process/1, process2/2,
+ register_commands/3, unregister_commands/3,
+ opt_type/1]).
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
@@ -64,37 +63,40 @@
%%-----------------------------
start() ->
- case init:get_plain_arguments() of
- [SNode | Args] ->
- SNode1 = case string:tokens(SNode, "@") of
- [_Node, _Server] ->
- SNode;
- _ ->
- case net_kernel:longnames() of
- true ->
- lists:flatten([SNode, "@", inet_db:gethostname(),
- ".", inet_db:res_option(domain)]);
- false ->
- lists:flatten([SNode, "@", inet_db:gethostname()]);
- _ ->
- SNode
- end
- end,
- Node = list_to_atom(SNode1),
- Status = case rpc:call(Node, ?MODULE, process, [Args]) of
- {badrpc, Reason} ->
- print("Failed RPC connection to the node ~p: ~p~n",
- [Node, Reason]),
- %% TODO: show minimal start help
- ?STATUS_BADRPC;
- S ->
- S
- end,
- halt(Status);
- _ ->
- print_usage(),
- halt(?STATUS_USAGE)
- end.
+ [SNode, Timeout, Args] = case init:get_plain_arguments() of
+ [SNode2, "--no-timeout" | Args2] ->
+ [SNode2, infinity, Args2];
+ [SNode3 | Args3] ->
+ [SNode3, 60000, Args3];
+ _ ->
+ print_usage(),
+ halt(?STATUS_USAGE)
+ end,
+ SNode1 = case string:tokens(SNode, "@") of
+ [_Node, _Server] ->
+ SNode;
+ _ ->
+ case net_kernel:longnames() of
+ true ->
+ lists:flatten([SNode, "@", inet_db:gethostname(),
+ ".", inet_db:res_option(domain)]);
+ false ->
+ lists:flatten([SNode, "@", inet_db:gethostname()]);
+ _ ->
+ SNode
+ end
+ end,
+ Node = list_to_atom(SNode1),
+ Status = case rpc:call(Node, ?MODULE, process, [Args], Timeout) of
+ {badrpc, Reason} ->
+ print("Failed RPC connection to the node ~p: ~p~n",
+ [Node, Reason]),
+ %% TODO: show minimal start help
+ ?STATUS_BADRPC;
+ S ->
+ S
+ end,
+ halt(Status).
init() ->
ets:new(ejabberd_ctl_cmds, [named_table, set, public]),
@@ -210,9 +212,9 @@ process(Args) ->
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
- process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass)}, AccessCommands);
+ process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
process2(Args, AccessCommands) ->
- process2(Args, noauth, AccessCommands).
+ process2(Args, admin, AccessCommands).
process2(Args, Auth, AccessCommands) ->
case try_run_ctp(Args, Auth, AccessCommands) of
@@ -284,7 +286,7 @@ call_command([CmdString | Args], Auth, AccessCommands) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
- case ejabberd_commands:get_command_format(Command) of
+ case ejabberd_commands:get_command_format(Command, Auth) of
{error, command_unknown} ->
{error, command_unknown};
{ArgsFormat, ResultFormat} ->
@@ -322,7 +324,7 @@ format_args(Args, ArgsFormat) ->
format_arg(Arg, integer) ->
format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
- list_to_binary(format_arg(Arg, string));
+ unicode:characters_to_binary(Arg, utf8);
format_arg("", string) ->
"";
format_arg(Arg, string) ->
@@ -347,12 +349,21 @@ format_result(Atom, {_Name, atom}) ->
format_result(Int, {_Name, integer}) ->
io_lib:format("~p", [Int]);
-format_result(String, {_Name, string}) when is_list(String) ->
+format_result([A|_]=String, {_Name, string}) when is_list(String) and is_integer(A) ->
io_lib:format("~s", [String]);
format_result(Binary, {_Name, string}) when is_binary(Binary) ->
io_lib:format("~s", [binary_to_list(Binary)]);
+format_result(Atom, {_Name, string}) when is_atom(Atom) ->
+ io_lib:format("~s", [atom_to_list(Atom)]);
+
+format_result(Integer, {_Name, string}) when is_integer(Integer) ->
+ io_lib:format("~s", [integer_to_list(Integer)]);
+
+format_result(Other, {_Name, string}) ->
+ io_lib:format("~p", [Other]);
+
format_result(Code, {_Name, rescode}) ->
make_status(Code);
@@ -384,7 +395,10 @@ format_result(ElementsTuple, {_Name, {tuple, ElementsDef}}) ->
fun({Element, ElementDef}) ->
["\t" | format_result(Element, ElementDef)]
end,
- ElementsAndDef)].
+ ElementsAndDef)];
+
+format_result(404, {_Name, _}) ->
+ make_status(not_found).
make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
@@ -404,7 +418,8 @@ get_list_commands() ->
end.
%% Return: {string(), [string()], string()}
-tuple_command_help({Name, Args, Desc}) ->
+tuple_command_help({Name, _Args, Desc}) ->
+ {Args, _} = ejabberd_commands:get_command_format(Name, admin),
Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args],
Prepend = case is_supported_args(Args) of
true -> "";
@@ -458,7 +473,7 @@ print_usage(HelpMode, MaxC, ShCode) ->
get_list_ctls(),
print(
- ["Usage: ", ?B("ejabberdctl"), " [--node ", ?U("nodename"), "] [--auth ",
+ ["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
?U("command"), " [", ?U("options"), "]\n"
"\n"
@@ -715,12 +730,13 @@ print_usage_command(Cmd, C, MaxC, ShCode) ->
tags = TagsAtoms,
desc = Desc,
longdesc = LongDesc,
- args = ArgsDef,
result = ResultDef} = C,
NameFmt = [" ", ?B("Command Name"), ": ", Cmd, "\n"],
%% Initial indentation of result is 13 = length(" Arguments: ")
+ {ArgsDef, _} = ejabberd_commands:get_command_format(
+ C#ejabberd_commands.name, admin),
Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef],
ArgsMargin = lists:duplicate(13, $\s),
ArgsListFmt = case Args of
@@ -792,3 +808,7 @@ print(Format, Args) ->
%%format_usage_xmlrpc(ArgsDef, ResultDef) ->
%% ["aaaa bbb ccc"].
+
+opt_type(ejabberdctl_access_commands) ->
+ fun (V) when is_list(V) -> V end;
+opt_type(_) -> [ejabberdctl_access_commands].
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index b169572b..b8e706f2 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -58,10 +58,6 @@
%%====================================================================
%% API
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link(Module, SockMod, Socket, Opts, Receiver) ->
gen_server:start_link(?MODULE,
[Module, SockMod, Socket, Opts, Receiver], []).
@@ -137,18 +133,10 @@ peername(_FsmRef) ->
%%gen_server:call(FsmRef, peername).
{ok, {{0, 0, 0, 0}, 0}}.
-
%%====================================================================
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Module, SockMod, Socket, Opts, Receiver]) ->
Node = ejabberd_node_groups:get_closest_node(backend),
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
@@ -159,32 +147,22 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
socket = Socket2,
receiver = Receiver}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call({starttls, TLSOpts}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
Reply = ok,
- {reply, Reply, State#state{socket = TLSSocket, sockmod = p1_tls},
+ {reply, Reply, State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
handle_call({starttls, TLSOpts, Data}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply,
- State#state{socket = TLSSocket, sockmod = p1_tls},
+ State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
-
handle_call({compress, Data}, _From, State) ->
{ok, ZlibSocket} =
ejabberd_receiver:compress(State#state.receiver, Data),
@@ -209,10 +187,10 @@ handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
- Reply = p1_tls:get_peer_certificate(State#state.socket),
+ Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_verify_result, _From, State) ->
- Reply = p1_tls:get_verify_result(State#state.socket),
+ Reply = fast_tls:get_verify_result(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver),
@@ -238,21 +216,9 @@ handle_call(peername, _From, State) ->
handle_call(_Request, _From, State) ->
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop,
[?MODULE, [], State]),
@@ -260,34 +226,19 @@ handle_info(timeout, State) ->
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, _State) -> ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
check_starttls(SockMod, Socket, Receiver, Opts) ->
TLSEnabled = proplists:get_bool(tls, Opts),
TLSOpts = lists:filter(fun({certfile, _}) -> true;
(_) -> false
end, Opts),
- if
- TLSEnabled ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket, TLSOpts),
+ if TLSEnabled ->
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(Receiver, TLSSocket),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true ->
{SockMod, Socket}
end.
diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl
index d136ba70..c1daa4c0 100644
--- a/src/ejabberd_hooks.erl
+++ b/src/ejabberd_hooks.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -58,9 +58,6 @@
-include("logger.hrl").
-%% Timeout of 5 seconds in calls to distributed hooks
--define(TIMEOUT_DISTRIBUTED_HOOK, 5000).
-
-record(state, {}).
-type local_hook() :: { Seq :: integer(), Module :: atom(), Function :: atom()}.
-type distributed_hook() :: { Seq :: integer(), Node :: atom(), Module :: atom(), Function :: atom()}.
@@ -310,7 +307,7 @@ run1([], _Hook, _Args) ->
%% It is not attempted again in case of failure. Next hook will be executed
run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) ->
%% MR: Should we have a safe rpc, like we have a safe apply or is bad_rpc enough ?
- case rpc:call(Node, Module, Function, Args, ?TIMEOUT_DISTRIBUTED_HOOK) of
+ case ejabberd_cluster:call(Node, Module, Function, Args) of
timeout ->
?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
[Node, {Hook, Args}]),
@@ -344,7 +341,7 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
run_fold1([], _Hook, Val, _Args) ->
Val;
run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) ->
- case rpc:call(Node, Module, Function, [Val | Args], ?TIMEOUT_DISTRIBUTED_HOOK) of
+ case ejabberd_cluster:call(Node, Module, Function, [Val | Args]) of
{badrpc, Reason} ->
?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
[Node, Reason, {Hook, Args}]),
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index 4e7f4b55..d8d1ddd4 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -5,7 +5,7 @@
%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_http).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
%% External exports
@@ -32,8 +34,7 @@
socket_type/0, receive_headers/1, url_encode/1,
transform_listen_option/2]).
-%% Callbacks
--export([init/2]).
+-export([init/2, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -83,8 +84,9 @@
"">>).
start(SockData, Opts) ->
- supervisor:start_child(ejabberd_http_sup,
- [SockData, Opts]).
+ {ok,
+ proc_lib:spawn(ejabberd_http, init,
+ [SockData, Opts])}.
start_link(SockData, Opts) ->
{ok,
@@ -95,6 +97,7 @@ init({SockMod, Socket}, Opts) ->
TLSEnabled = proplists:get_bool(tls, Opts),
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
({ciphers, _}) -> true;
+ ({dhfile, _}) -> true;
(_) -> false
end,
Opts),
@@ -114,9 +117,9 @@ init({SockMod, Socket}, Opts) ->
TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
inet:setopts(Socket, [{recbuf, 8192}]),
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket,
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
TLSOpts),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true -> {SockMod, Socket}
end,
Captcha = case proplists:get_bool(captcha, Opts) of
@@ -158,9 +161,14 @@ init({SockMod, Socket}, Opts) ->
default_host = DefaultHost,
options = Opts,
request_handlers = RequestHandlers},
- receive_headers(State).
+ try receive_headers(State) of
+ V -> V
+ catch
+ {error, _} -> State
+ end.
-become_controller(_Pid) -> ok.
+become_controller(_Pid) ->
+ ok.
socket_type() ->
raw.
@@ -195,22 +203,20 @@ parse_headers(#state{request_method = Method,
trail = Data} =
State) ->
PktType = case Method of
- undefined -> http_bin;
- _ -> httph_bin
- end,
+ undefined -> http_bin;
+ _ -> httph_bin
+ end,
case erlang:decode_packet(PktType, Data, []) of
- {ok, Pkt, Rest} ->
- NewState = process_header(State#state{trail = Rest}, {ok, Pkt}),
+ {ok, Pkt, Rest} ->
+ NewState = process_header(State#state{trail = Rest}, {ok, Pkt}),
case NewState#state.end_of_request of
- true ->
- ok;
- _ ->
- parse_headers(NewState)
+ true -> ok;
+ _ -> parse_headers(NewState)
end;
- {more, _} ->
- receive_headers(State#state{trail = Data});
- _ ->
- ok
+ {more, _} ->
+ receive_headers(State#state{trail = Data});
+ _ ->
+ ok
end.
process_header(State, Data) ->
@@ -260,10 +266,8 @@ process_header(State, Data) ->
State#state{request_host = Host,
request_headers = add_header(Name, Host, State)};
{ok, {http_header, _, Name, _, Value}} when is_binary(Name) ->
- State#state{request_headers =
- add_header(normalize_header_name(Name),
- Value,
- State)};
+ State#state{request_headers =
+ add_header(normalize_header_name(Name), Value, State)};
{ok, {http_header, _, Name, _, Value}} ->
State#state{request_headers =
add_header(Name, Value, State)};
@@ -284,16 +288,18 @@ process_header(State, Data) ->
HostProvided),
State2 = State#state{request_host = Host,
request_port = Port, request_tp = TP},
- Out = process_request(State2),
- send_text(State2, Out),
- case State2#state.request_keepalive of
+ {State3, Out} = process_request(State2),
+ send_text(State3, Out),
+ case State3#state.request_keepalive of
true ->
#state{sockmod = SockMod, socket = Socket,
+ trail = State3#state.trail,
options = State#state.options,
default_host = State#state.default_host,
request_handlers = State#state.request_handlers};
_ ->
#state{end_of_request = true,
+ trail = State3#state.trail,
options = State#state.options,
default_host = State#state.default_host,
request_handlers = State#state.request_handlers}
@@ -316,22 +322,14 @@ get_host_really_served(Default, Provided) ->
false -> Default
end.
-%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP}
-%% where
-%% SockMod = gen_tcp | tls
-%% HostPort = string()
-%% TP = http | https
-%% @doc Given a socket and hostport header, return data of transfer protocol.
-%% Note that HostPort can be a string of a host like "example.org",
-%% or a string of a host and port like "example.org:5280".
get_transfer_protocol(SockMod, HostPort) ->
[Host | PortList] = str:tokens(HostPort, <<":">>),
case {SockMod, PortList} of
{gen_tcp, []} -> {Host, 80, http};
{gen_tcp, [Port]} ->
{Host, jlib:binary_to_integer(Port), http};
- {p1_tls, []} -> {Host, 443, https};
- {p1_tls, [Port]} ->
+ {fast_tls, []} -> {Host, 443, https};
+ {fast_tls, [Port]} ->
{Host, jlib:binary_to_integer(Port), https}
end.
@@ -371,20 +369,20 @@ process(Handlers, Request, Socket, SockMod, Trail) ->
end.
extract_path_query(#state{request_method = Method,
- request_path = {abs_path, Path}})
+ request_path = {abs_path, Path}} = State)
when Method =:= 'GET' orelse
Method =:= 'HEAD' orelse
Method =:= 'DELETE' orelse Method =:= 'OPTIONS' ->
case catch url_decode_q_split(Path) of
- {'EXIT', _} -> false;
- {NPath, Query} ->
- LPath = normalize_path([NPE
- || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
- LQuery = case catch parse_urlencoded(Query) of
- {'EXIT', _Reason} -> [];
- LQ -> LQ
- end,
- {LPath, LQuery, <<"">>}
+ {'EXIT', _} -> {State, false};
+ {NPath, Query} ->
+ LPath = normalize_path([NPE
+ || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
+ LQuery = case catch parse_urlencoded(Query) of
+ {'EXIT', _Reason} -> [];
+ LQ -> LQ
+ end,
+ {State, {LPath, LQuery, <<"">>}}
end;
extract_path_query(#state{request_method = Method,
request_path = {abs_path, Path},
@@ -393,45 +391,49 @@ extract_path_query(#state{request_method = Method,
socket = _Socket} = State)
when (Method =:= 'POST' orelse Method =:= 'PUT') andalso
is_integer(Len) ->
- Data = recv_data(State, Len),
+ {NewState, Data} = recv_data(State, Len),
?DEBUG("client data: ~p~n", [Data]),
case catch url_decode_q_split(Path) of
- {'EXIT', _} -> false;
- {NPath, _Query} ->
- LPath = normalize_path([NPE
- || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
- LQuery = case catch parse_urlencoded(Data) of
- {'EXIT', _Reason} -> [];
- LQ -> LQ
- end,
- {LPath, LQuery, Data}
+ {'EXIT', _} -> {NewState, false};
+ {NPath, _Query} ->
+ LPath = normalize_path([NPE
+ || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
+ LQuery = case catch parse_urlencoded(Data) of
+ {'EXIT', _Reason} -> [];
+ LQ -> LQ
+ end,
+ {NewState, {LPath, LQuery, Data}}
end;
-extract_path_query(_State) ->
- false.
+extract_path_query(State) ->
+ {State, false}.
process_request(#state{request_method = Method,
- request_auth = Auth,
- request_lang = Lang,
- sockmod = SockMod,
- socket = Socket,
- options = Options,
- request_host = Host,
- request_port = Port,
- request_tp = TP,
- request_headers = RequestHeaders,
- request_handlers = RequestHandlers,
- trail = Trail} = State) ->
+ request_auth = Auth,
+ request_lang = Lang,
+ sockmod = SockMod,
+ socket = Socket,
+ options = Options,
+ request_host = Host,
+ request_port = Port,
+ request_tp = TP,
+ request_headers = RequestHeaders,
+ request_handlers = RequestHandlers,
+ trail = Trail} = State) ->
case extract_path_query(State) of
- false ->
- make_bad_request(State);
- {LPath, LQuery, Data} ->
- {ok, IPHere} =
+ {State2, false} ->
+ {State2, make_bad_request(State)};
+ {State2, {LPath, LQuery, Data}} ->
+ PeerName =
case SockMod of
gen_tcp ->
inet:peername(Socket);
_ ->
SockMod:peername(Socket)
end,
+ IPHere = case PeerName of
+ {ok, V} -> V;
+ {error, _} = E -> throw(E)
+ end,
XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []),
IP = analyze_ip_xff(IPHere, XFF, Host),
Request = #request{method = Method,
@@ -446,27 +448,27 @@ process_request(#state{request_method = Method,
opts = Options,
headers = RequestHeaders,
ip = IP},
- case process(RequestHandlers, Request, Socket, SockMod, Trail) of
- El when is_record(El, xmlel) ->
- make_xhtml_output(State, 200, [], El);
- {Status, Headers, El}
- when is_record(El, xmlel) ->
- make_xhtml_output(State, Status, Headers, El);
- Output when is_binary(Output) or is_list(Output) ->
- make_text_output(State, 200, [], Output);
- {Status, Headers, Output}
- when is_binary(Output) or is_list(Output) ->
- make_text_output(State, Status, Headers, Output);
- {Status, Reason, Headers, Output}
- when is_binary(Output) or is_list(Output) ->
- make_text_output(State, Status, Reason, Headers, Output);
- _ ->
- none
- end
+ Res = case process(RequestHandlers, Request, Socket, SockMod, Trail) of
+ El when is_record(El, xmlel) ->
+ make_xhtml_output(State, 200, [], El);
+ {Status, Headers, El}
+ when is_record(El, xmlel) ->
+ make_xhtml_output(State, Status, Headers, El);
+ Output when is_binary(Output) or is_list(Output) ->
+ make_text_output(State, 200, [], Output);
+ {Status, Headers, Output}
+ when is_binary(Output) or is_list(Output) ->
+ make_text_output(State, Status, Headers, Output);
+ {Status, Reason, Headers, Output}
+ when is_binary(Output) or is_list(Output) ->
+ make_text_output(State, Status, Reason, Headers, Output);
+ _ ->
+ none
+ end,
+ {State2, Res}
end.
make_bad_request(State) ->
-%% Support for X-Forwarded-From
make_xhtml_output(State, 400, [],
ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>,
attrs = [],
@@ -480,7 +482,8 @@ analyze_ip_xff({IPLast, Port}, XFF, Host) ->
[jlib:ip_to_list(IPLast)],
TrustedProxies = ejabberd_config:get_option(
{trusted_proxies, Host},
- fun(TPs) ->
+ fun(all) -> all;
+ (TPs) ->
[iolist_to_binary(TP) || TP <- TPs]
end, []),
IPClient = case is_ipchain_trusted(ProxiesIPs,
@@ -503,32 +506,37 @@ is_ipchain_trusted(UserIPs, TrustedIPs) ->
recv_data(State, Len) -> recv_data(State, Len, <<>>).
-recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc));
+recv_data(State, 0, Acc) -> {State, Acc};
+recv_data(#state{trail = Trail} = State, Len, <<>>) when byte_size(Trail) > Len ->
+ <<Data:Len/binary, Rest/binary>> = Trail,
+ {State#state{trail = Rest}, Data};
recv_data(State, Len, Acc) ->
case State#state.trail of
- <<>> ->
- case (State#state.sockmod):recv(State#state.socket, Len,
- 300000)
- of
- {ok, Data} ->
- recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>);
- _ -> <<"">>
- end;
- _ ->
- Trail = (State#state.trail),
- recv_data(State#state{trail = <<>>},
- Len - byte_size(Trail), <<Acc/binary, Trail/binary>>)
+ <<>> ->
+ case (State#state.sockmod):recv(State#state.socket,
+ min(Len, 16#4000000), 300000)
+ of
+ {ok, Data} ->
+ recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>);
+ Err ->
+ ?DEBUG("Cannot receive HTTP data: ~p", [Err]),
+ <<"">>
+ end;
+ _ ->
+ Trail = (State#state.trail),
+ recv_data(State#state{trail = <<>>},
+ Len - byte_size(Trail), <<Acc/binary, Trail/binary>>)
end.
make_xhtml_output(State, Status, Headers, XHTML) ->
Data = case lists:member(html, Headers) of
- true ->
- iolist_to_binary([?HTML_DOCTYPE,
- xml:element_to_binary(XHTML)]);
- _ ->
- iolist_to_binary([?XHTML_DOCTYPE,
- xml:element_to_binary(XHTML)])
- end,
+ true ->
+ iolist_to_binary([?HTML_DOCTYPE,
+ fxml:element_to_binary(XHTML)]);
+ _ ->
+ iolist_to_binary([?XHTML_DOCTYPE,
+ fxml:element_to_binary(XHTML)])
+ end,
Headers1 = case lists:keysearch(<<"Content-Type">>, 1,
Headers)
of
@@ -756,6 +764,9 @@ parse_auth(<<"Basic ", Auth64/binary>>) ->
{User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1),
{User, Pass}
end;
+parse_auth(<<"Bearer ", SToken/binary>>) ->
+ Token = str:strip(SToken),
+ {oauth, Token, []};
parse_auth(<<_/binary>>) -> undefined.
parse_urlencoded(S) ->
@@ -870,3 +881,8 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
[{request_handlers, Hs1} | Opts];
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
+
+opt_type(trusted_proxies) ->
+ fun (all) -> all;
+ (TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
+opt_type(_) -> [trusted_proxies].
diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl
index 234ccf35..ea8cd792 100644
--- a/src/ejabberd_http_bind.erl
+++ b/src/ejabberd_http_bind.erl
@@ -1,19 +1,38 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_http_bind.erl
%%% Author : Stefan Strigler <steve@zeank.in-berlin.de>
-%%% Purpose : Implements XMPP over BOSH (XEP-0206) (formerly known as
-%%% HTTP Binding)
+%%% Purpose : Implements XMPP over BOSH (XEP-0206)
%%% Created : 21 Sep 2005 by Stefan Strigler <steve@zeank.in-berlin.de>
%%% Modified: may 2009 by Mickael Remond, Alexey Schepin
-%%% Id : $Id: ejabberd_http_bind.erl 953 2009-05-07 10:40:40Z alexey $
+%%%
+%%%
+%%% 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_http_bind).
+-protocol({xep, 124, '1.11'}).
+-protocol({xep, 206, '1.4'}).
+
-behaviour(gen_fsm).
%% External exports
--export([start_link/3,
+-export([start_link/4,
init/1,
handle_event/3,
handle_sync_event/4,
@@ -32,13 +51,13 @@
change_shaper/2,
monitor/1,
close/1,
- start/4,
+ start/5,
handle_session_start/8,
handle_http_put/7,
http_put/7,
http_get/2,
prepare_response/4,
- process_request/2]).
+ process_request/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -75,7 +94,7 @@
wait_timer,
ctime = 0,
timer,
- pause=0,
+ pause = 0,
unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID}
req_list = [], % list of requests (cache)
max_inactivity,
@@ -129,25 +148,21 @@
-define(PROCESS_DELAY_MAX, 1000).
-%% Line copied from mod_http_bind.erl
-define(PROCNAME_MHB, ejabberd_mod_http_bind).
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
-%% TODO: If compile with no supervisor option, start the session without
-%% supervisor
-start(XMPPDomain, Sid, Key, IP) ->
+start(XMPPDomain, Sid, Key, IP, HOpts) ->
?DEBUG("Starting session", []),
- SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB),
- case catch supervisor:start_child(SupervisorProc, [Sid, Key, IP]) of
- {ok, Pid} -> {ok, Pid};
+ case catch gen_fsm:start(?MODULE,
+ [Sid, Key, IP, HOpts],
+ ?FSMOPTS)
+ of
+ {ok, Pid} -> {ok, Pid};
_ -> check_bind_module(XMPPDomain),
{error, "Cannot start HTTP bind session"}
end.
-start_link(Sid, Key, IP) ->
- gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS).
+start_link(Sid, Key, IP, HOpts) ->
+ gen_fsm:start_link(?MODULE, [Sid, Key, IP, HOpts], ?FSMOPTS).
send({http_bind, FsmRef, _IP}, Packet) ->
gen_fsm:sync_send_all_state_event(FsmRef,
@@ -194,7 +209,7 @@ peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
%% Entry point for data coming from client through ejabberd HTTP server:
-process_request(Data, IP) ->
+process_request(Data, IP, HOpts) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
@@ -209,7 +224,7 @@ process_request(Data, IP) ->
of
%% No existing session:
{ok, {<<"">>, Rid, Attrs, Payload}} ->
- case xml:get_attr_s(<<"to">>, Attrs) of
+ case fxml:get_attr_s(<<"to">>, Attrs) of
<<"">> ->
?DEBUG("Session not created (Improper addressing)", []),
{200, ?HEADER,
@@ -217,8 +232,9 @@ process_request(Data, IP) ->
"dressing' xmlns='",
(?NS_HTTP_BIND)/binary, "'/>">>};
XmppDomain ->
- Sid = p1_sha:sha(term_to_binary({now(), make_ref()})),
- case start(XmppDomain, Sid, <<"">>, IP) of
+ NXmppDomain = jid:nameprep(XmppDomain),
+ Sid = p1_sha:sha(term_to_binary({p1_time_compat:monotonic_time(), make_ref()})),
+ case start(NXmppDomain, Sid, <<"">>, IP, HOpts) of
{error, _} ->
{500, ?HEADER,
<<"<body type='terminate' condition='internal-se"
@@ -226,19 +242,19 @@ process_request(Data, IP) ->
(?NS_HTTP_BIND)/binary,
"'>Internal Server Error</body>">>};
{ok, Pid} ->
- handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
+ handle_session_start(Pid, NXmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP)
end
end;
%% Existing session
{ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ StreamStart = case fxml:get_attr_s(<<"xmpp:restart">>,
Attrs)
of
<<"true">> -> true;
_ -> false
end,
- Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ Payload2 = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"terminate">> ->
Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
_ -> Payload1
@@ -264,7 +280,7 @@ process_request(Data, IP) ->
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Wait = case str:to_integer(fxml:get_attr_s(<<"wait">>,
Attrs))
of
{error, _} -> ?MAX_WAIT;
@@ -273,7 +289,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
true -> CWait
end
end,
- Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Hold = case str:to_integer(fxml:get_attr_s(<<"hold">>,
Attrs))
of
{error, _} -> (?MAX_REQUESTS) - 1;
@@ -283,7 +299,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
end
end,
Pdelay = case
- str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ str:to_integer(fxml:get_attr_s(<<"process-delay">>,
Attrs))
of
{error, _} -> ?PROCESS_DELAY_DEFAULT;
@@ -296,12 +312,12 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
?PROCESS_DELAY_MIN])
end,
Version = case catch
- list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ list_to_float(binary_to_list(fxml:get_attr_s(<<"ver">>, Attrs)))
of
{'EXIT', _} -> 0.0;
V -> V
end,
- XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
+ XmppVersion = fxml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
mnesia:dirty_write(
#http_bind{id = Sid,
@@ -319,17 +335,18 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
-init([Sid, Key, IP]) ->
+init([Sid, Key, IP, HOpts]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
- Opts = [{xml_socket, true} | Opts1],
+ SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
+ ({max_ack_queue, _}) -> true;
+ ({resume_timeout, _}) -> true;
+ ({max_resume_timeout, _}) -> true;
+ ({resend_on_timeout, _}) -> true;
+ (_) -> false
+ end, HOpts),
+
+ Opts = [{xml_socket, true} | SOpts ++ Opts1],
Shaper = none,
ShaperState = shaper:new(Shaper),
Socket = {http_bind, self(), IP},
@@ -343,12 +360,6 @@ init([Sid, Key, IP]) ->
max_pause = ?MAX_PAUSE,
timer = Timer}}.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event({become_controller, C2SPid}, StateName, StateData) ->
case StateData#state.input of
cancel ->
@@ -369,15 +380,6 @@ handle_event({change_shaper, Shaper}, StateName,
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{http_receiver = undefined} = StateData) ->
Output = [Packet | StateData#state.output],
@@ -446,10 +448,8 @@ handle_sync_event(#http_put{payload_size =
shaper_timer = NewShaperTimer});
%% HTTP GET: send packets to the client
handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
- %% setup timer
- TNow = tnow(),
- if
- (Hold > 0) and
+ TNow = p1_time_compat:system_time(micro_seconds),
+ if (Hold > 0) and
((StateData#state.output == []) or (StateData#state.rid < Rid)) and
((TNow - StateData#state.ctime) < (Wait*1000*1000)) and
(StateData#state.rid =< Rid) and
@@ -457,7 +457,6 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
cancel_timer(StateData#state.wait_timer),
WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
- %% MR: Not sure we should cancel the state timer here.
cancel_timer(StateData#state.timer),
{next_state, StateName, StateData#state{
http_receiver = From,
@@ -467,34 +466,30 @@ handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) ->
true ->
cancel_timer(StateData#state.timer),
Reply = {ok, StateData#state.output},
- %% save request
ReqList = [#hbr{rid = Rid,
key = StateData#state.key,
- out = StateData#state.output
- } |
- [El || El <- StateData#state.req_list,
- El#hbr.rid /= Rid ]
- ],
- if
- (StateData#state.http_receiver /= undefined) and
- StateData#state.out_of_order_receiver ->
- {reply, Reply, StateName, StateData#state{
- output = [],
- timer = undefined,
- req_list = ReqList,
- out_of_order_receiver = false}};
- true ->
- send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
- cancel_timer(StateData#state.wait_timer),
- Timer = set_inactivity_timer(StateData#state.pause,
- StateData#state.max_inactivity),
- {reply, Reply, StateName,
- StateData#state{output = [],
- http_receiver = undefined,
- wait_timer = undefined,
- timer = Timer,
- req_list = ReqList}}
- end
+ out = StateData#state.output}
+ | [El
+ || El <- StateData#state.req_list,
+ El#hbr.rid /= Rid]],
+ if (StateData#state.http_receiver /= undefined) and
+ StateData#state.out_of_order_receiver ->
+ {reply, Reply, StateName,
+ StateData#state{output = [], timer = undefined,
+ req_list = ReqList,
+ out_of_order_receiver = false}};
+ true ->
+ send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
+ cancel_timer(StateData#state.wait_timer),
+ Timer = set_inactivity_timer(StateData#state.pause,
+ StateData#state.max_inactivity),
+ {reply, Reply, StateName,
+ StateData#state{output = [],
+ http_receiver = undefined,
+ wait_timer = undefined,
+ timer = Timer,
+ req_list = ReqList}}
+ end
end;
handle_sync_event(peername, _From, StateName,
StateData) ->
@@ -507,13 +502,6 @@ handle_sync_event(_Event, _From, StateName,
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-%% We reached the max_inactivity timeout:
handle_info({timeout, Timer, _}, _StateName,
#state{id = SID, timer = Timer} = StateData) ->
?INFO_MSG("Session timeout. Closing the HTTP bind "
@@ -546,11 +534,6 @@ handle_info({timeout, ShaperTimer, _}, StateName,
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
?DEBUG("terminate: Deleting session ~s",
[StateData#state.id]),
@@ -606,8 +589,8 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs,
Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- Key = xml:get_attr_s(<<"key">>, Attrs),
- NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ Key = fxml:get_attr_s(<<"key">>, Attrs),
+ NewKey = fxml:get_attr_s(<<"newkey">>, Attrs),
KeyAllow = case RidAllow of
repeat -> true;
false -> false;
@@ -623,7 +606,7 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs,
end
end
end,
- TNow = tnow(),
+ TNow = p1_time_compat:system_time(micro_seconds),
LastPoll = if Payload == [] -> TNow;
true -> 0
end,
@@ -792,19 +775,22 @@ http_put(Sid, Rid, Attrs, Payload, PayloadSize,
?DEBUG("Looking for session: ~p", [Sid]),
case mnesia:dirty_read({http_bind, Sid}) of
[] ->
- {error, not_exists};
- [#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] ->
- NewStream =
- case StreamStart of
- true ->
- {To, StreamVersion};
- _ ->
- <<"">>
- end,
- {gen_fsm:sync_send_all_state_event(
- FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload,
- payload_size = PayloadSize, hold = Hold,
- stream = NewStream, ip = IP}, 30000), Sess}
+ {error, not_exists};
+ [#http_bind{pid = FsmRef, hold=Hold,
+ to= {To, StreamVersion}} = Sess] ->
+ NewStream = case StreamStart of
+ true -> {To, StreamVersion};
+ _ -> <<"">>
+ end,
+ {gen_fsm:sync_send_all_state_event(
+ FsmRef, #http_put{rid = Rid,
+ attrs = Attrs,
+ payload = Payload,
+ payload_size = PayloadSize,
+ hold = Hold,
+ stream = NewStream,
+ ip = IP},
+ 30000), Sess}
end.
handle_http_put_error(Reason,
@@ -815,7 +801,7 @@ handle_http_put_error(Reason,
case Reason of
not_exists ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -824,7 +810,7 @@ handle_http_put_error(Reason,
children = []})};
bad_key ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -833,7 +819,7 @@ handle_http_put_error(Reason,
children = []})};
polling_too_frequently ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -869,7 +855,7 @@ rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
%% We did not miss any packet, we can process it immediately:
NewRid == OldRid + 1 ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"pause">>,
Attrs))
of
{'EXIT', _} -> {true, 0};
@@ -943,9 +929,9 @@ prepare_outpacket_response(#http_bind{id = Sid,
_Rid, OutPacket, true) ->
case OutPacket of
[{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
- From = xml:get_attr_s(<<"from">>, OutAttrs),
- Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ AuthID = fxml:get_attr_s(<<"id">>, OutAttrs),
+ From = fxml:get_attr_s(<<"from">>, OutAttrs),
+ Version = fxml:get_attr_s(<<"version">>, OutAttrs),
OutEls = case Els of
[] -> [];
[{xmlstreamelement,
@@ -982,7 +968,7 @@ prepare_outpacket_response(#http_bind{id = Sid,
MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
MaxPause = get_max_pause(To),
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"sid">>, Sid},
@@ -1046,7 +1032,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
TypedEls = lists:foldl(fun ({xmlstreamelement, El},
Acc) ->
Acc ++
- [xml:element_to_binary(check_default_xmlns(El))];
+ [fxml:element_to_binary(check_default_xmlns(El))];
({xmlstreamraw, R}, Acc) ->
Acc ++ [R]
end,
@@ -1081,7 +1067,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
|| {xmlstreamelement, OEl} <- StreamTail]
end,
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>,
?NS_HTTP_BIND}],
@@ -1128,14 +1114,14 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
?DEBUG("--- incoming data --- ~n~s~n --- END "
"--- ",
[Data]),
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
#xmlel{name = <<"body">>, attrs = Attrs,
children = Els} ->
- Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ Xmlns = fxml:get_attr_s(<<"xmlns">>, Attrs),
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
true ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
Attrs))
of
{'EXIT', _} -> {error, bad_request};
@@ -1147,7 +1133,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
end
end,
Els),
- Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ Sid = fxml:get_attr_s(<<"sid">>, Attrs),
if PayloadSize =< MaxStanzaSize ->
{ok, {Sid, Rid, Attrs, FixedEls}};
true -> {size_limit, Sid}
@@ -1177,10 +1163,9 @@ set_inactivity_timer(Pause, _MaxInactivity)
set_inactivity_timer(_Pause, MaxInactivity) ->
erlang:start_timer(MaxInactivity, self(), []).
-%% TODO: Use tail recursion and list reverse ?
elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [xml:element_to_binary(El) | elements_to_string(Els)].
+ [fxml:element_to_binary(El) | elements_to_string(Els)].
%% @spec (To, Default::integer()) -> integer()
%% where To = [] | {Host::string(), Version::string()}
@@ -1200,15 +1185,10 @@ get_max_pause({Host, _}) ->
?MAX_PAUSE);
get_max_pause(_) -> ?MAX_PAUSE.
-%% Current time as integer
-tnow() ->
- {TMegSec, TSec, TMSec} = now(),
- (TMegSec * 1000000 + TSec) * 1000000 + TMSec.
-
check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
children = Els} =
El) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
<<"">> ->
#xmlel{name = Name,
attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index a0cc31e2..e66cf33a 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -5,7 +5,7 @@
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,16 +24,17 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http_ws).
+-behaviour(ejabberd_config).
+
-author('ecestari@process-one.net').
-behaviour(gen_fsm).
-% External exports
-export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, code_change/4, handle_info/3,
- terminate/3, send_xml/2, setopts/2, sockname/1, peername/1,
- controlling_process/2, become_controller/2, close/1,
- socket_handoff/6]).
+ terminate/3, send_xml/2, setopts/2, sockname/1,
+ peername/1, controlling_process/2, become_controller/2,
+ close/1, socket_handoff/6, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -74,7 +75,7 @@
-export_type([ws_socket/0]).
start(WS) ->
- supervisor:start_child(ejabberd_wsloop_sup, [WS]).
+ gen_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) ->
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
@@ -110,8 +111,15 @@ socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
%%% Internal
-init([{#ws{ip = IP}, _} = WS]) ->
- Opts = [{xml_socket, true} | ejabberd_c2s_config:get_c2s_limits()],
+init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
+ SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
+ ({max_ack_queue, _}) -> true;
+ ({resume_timeout, _}) -> true;
+ ({max_resume_timeout, _}) -> true;
+ ({resend_on_timeout, _}) -> true;
+ (_) -> false
+ end, HOpts),
+ Opts = [{xml_socket, true} | ejabberd_c2s_config:get_c2s_limits() ++ SOpts],
PingInterval = ejabberd_config:get_option(
{websocket_ping_interval, ?MYNAME},
fun(I) when is_integer(I), I>=0 -> I end,
@@ -163,11 +171,11 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
{true, {xmlstreamelement, #xmlel{name=Name2} = El2}} ->
El3 = case Name2 of
<<"stream:", _/binary>> ->
- xml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
+ fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
_ ->
- case xml:get_tag_attr_s(<<"xmlns">>, El2) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
<<"">> ->
- xml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
+ fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
_ ->
El2
end
@@ -178,12 +186,12 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
end,
case Packet2 of
{xmlstreamstart, Name, Attrs3} ->
- B = xml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
+ B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
WsPid ! {send, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
{xmlstreamend, Name} ->
WsPid ! {send, <<"</", Name/binary, ">">>};
{xmlstreamelement, El} ->
- WsPid ! {send, xml:element_to_binary(El)};
+ WsPid ! {send, fxml:element_to_binary(El)};
{xmlstreamraw, Bin} ->
WsPid ! {send, Bin};
{xmlstreamcdata, Bin2} ->
@@ -202,7 +210,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
- WsPid ! {send, xml:element_to_binary(Close)},
+ WsPid ! {send, fxml:element_to_binary(Close)},
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@@ -262,10 +270,9 @@ setup_timers(StateData) ->
Timer = erlang:start_timer(StateData#state.timeout,
self(), []),
cancel_timer(StateData#state.ping_timer),
- PingTimer = case {StateData#state.ping_interval, StateData#state.rfc_compilant} of
- {0, _} -> StateData#state.ping_timer;
- {_, false} -> StateData#state.ping_timer;
- {V, _} -> erlang:start_timer(V, self(), [])
+ PingTimer = case StateData#state.ping_interval of
+ 0 -> StateData#state.ping_timer;
+ V -> erlang:start_timer(V, self(), [])
end,
StateData#state{timer = Timer, ping_timer = PingTimer,
pong_expected = false}.
@@ -309,9 +316,9 @@ get_human_html_xmlel() ->
parse(#state{rfc_compilant = C} = State, Data) ->
case C of
undefined ->
- P = xml_stream:new(self()),
- P2 = xml_stream:parse(P, Data),
- xml_stream:close(P2),
+ P = fxml_stream:new(self()),
+ P2 = fxml_stream:parse(P, Data),
+ fxml_stream:close(P2),
case parsed_items([]) of
error ->
{State#state{rfc_compilant = true}, <<"parse error">>};
@@ -323,7 +330,7 @@ parse(#state{rfc_compilant = C} = State, Data) ->
parse(State#state{rfc_compilant = false}, Data)
end;
true ->
- El = xml_stream:parse_element(Data),
+ El = fxml_stream:parse_element(Data),
case El of
#xmlel{name = <<"open">>, attrs = Attrs} ->
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
@@ -353,3 +360,10 @@ parsed_items(List) ->
after 0 ->
lists:reverse(List)
end.
+
+opt_type(websocket_ping_interval) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+opt_type(websocket_timeout) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(_) ->
+ [websocket_ping_interval, websocket_timeout].
diff --git a/src/idna.erl b/src/ejabberd_idna.erl
index dd19ad98..f889b411 100644
--- a/src/idna.erl
+++ b/src/ejabberd_idna.erl
@@ -1,11 +1,11 @@
%%%----------------------------------------------------------------------
-%%% File : idna.erl
+%%% File : ejabberd_idna.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Support for IDNA (RFC3490)
%%% Created : 10 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(idna).
+-module(ejabberd_idna).
-author('alexey@process-one.net').
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 0cfca0aa..a9cc441e 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -5,7 +5,7 @@
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,20 +24,15 @@
%%%----------------------------------------------------------------------
-module(ejabberd_listener).
+
+-behaviour(ejabberd_config).
-author('alexey@process-one.net').
--export([start_link/0, init/1, start/3,
- init/3,
- start_listeners/0,
- start_listener/3,
- stop_listeners/0,
- stop_listener/2,
- parse_listener_portip/2,
- add_listener/3,
- delete_listener/2,
- transform_options/1,
- validate_cfg/1
- ]).
+-export([start_link/0, init/1, start/3, init/3,
+ start_listeners/0, start_listener/3, stop_listeners/0,
+ stop_listener/2, parse_listener_portip/2,
+ add_listener/3, delete_listener/2, transform_options/1,
+ validate_cfg/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -191,8 +186,8 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
case ets:lookup(listen_sockets, PortIP) of
[{PortIP, ListenSocket}] ->
- ?INFO_MSG("Reusing listening port for ~p", [Port]),
- ets:delete(listen_sockets, Port),
+ ?INFO_MSG("Reusing listening port for ~p", [PortIP]),
+ ets:delete(listen_sockets, PortIP),
ListenSocket;
_ ->
Res = gen_tcp:listen(Port, [binary,
@@ -230,8 +225,8 @@ listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
%% so the option inet/inet6 is only used when no IP is specified at all.
parse_listener_portip(PortIP, Opts) ->
{IPOpt, Opts2} = strip_ip_option(Opts),
- {IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of
- true -> {inet6, Opts2 -- [inet6]};
+ {IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of
+ true -> {inet6, proplists:delete(inet6, Opts2)};
false -> {inet, Opts2}
end,
{Port, IPT, IPS, Proto} =
@@ -297,12 +292,46 @@ get_ip_tuple(IPOpt, _IPVOpt) ->
IPOpt.
accept(ListenSocket, Module, Opts) ->
+ IntervalOpt =
+ case proplists:get_value(accept_interval, Opts) of
+ [{linear, [I1_, T1_, T2_, I2_]}] ->
+ {linear, I1_, T1_, T2_, I2_};
+ I_ -> I_
+ end,
+ Interval =
+ case IntervalOpt of
+ undefined ->
+ 0;
+ I when is_integer(I), I >= 0 ->
+ I;
+ {linear, I1, T1, T2, I2}
+ when is_integer(I1),
+ is_integer(T1),
+ is_integer(T2),
+ is_integer(I2),
+ I1 >= 0,
+ I2 >= 0,
+ T2 > 0 ->
+ {MSec, Sec, _USec} = os:timestamp(),
+ TS = MSec * 1000000 + Sec,
+ {linear, I1, TS + T1, T2, I2};
+ I ->
+ ?WARNING_MSG("There is a problem in the configuration: "
+ "~p is a wrong accept_interval value. "
+ "Using 0 as fallback",
+ [I]),
+ 0
+ end,
+ accept(ListenSocket, Module, Opts, Interval).
+
+accept(ListenSocket, Module, Opts, Interval) ->
+ NewInterval = check_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
case {inet:sockname(Socket), inet:peername(Socket)} of
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
?INFO_MSG("(~w) Accepted connection ~s:~p -> ~s:~p",
- [Socket, inet_parse:ntoa(PAddr), PPort,
+ [Socket, ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), PPort,
inet_parse:ntoa(Addr), Port]);
_ ->
ok
@@ -312,11 +341,11 @@ accept(ListenSocket, Module, Opts) ->
false -> ejabberd_socket
end,
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
- accept(ListenSocket, Module, Opts);
+ accept(ListenSocket, Module, Opts, NewInterval);
{error, Reason} ->
?ERROR_MSG("(~w) Failed TCP accept: ~w",
[ListenSocket, Reason]),
- accept(ListenSocket, Module, Opts)
+ accept(ListenSocket, Module, Opts, NewInterval)
end.
udp_recv(Socket, Module, Opts) ->
@@ -560,6 +589,31 @@ format_error(Reason) ->
ReasonStr
end.
+check_rate_limit(Interval) ->
+ NewInterval = receive
+ {rate_limit, AcceptInterval} ->
+ AcceptInterval
+ after 0 ->
+ Interval
+ end,
+ case NewInterval of
+ 0 -> ok;
+ Ms when is_integer(Ms) ->
+ timer:sleep(Ms);
+ {linear, I1, T1, T2, I2} ->
+ {MSec, Sec, _USec} = os:timestamp(),
+ TS = MSec * 1000000 + Sec,
+ I =
+ if
+ TS =< T1 -> I1;
+ TS >= T1 + T2 -> I2;
+ true ->
+ round((I2 - I1) * (TS - T1) / T2 + I1)
+ end,
+ timer:sleep(I)
+ end,
+ NewInterval.
+
-define(IS_CHAR(C), (is_integer(C) and (C >= 0) and (C =< 255))).
-define(IS_UINT(U), (is_integer(U) and (U >= 0) and (U =< 65535))).
-define(IS_PORT(P), (is_integer(P) and (P > 0) and (P =< 65535))).
@@ -574,11 +628,8 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) ->
Opts1 = lists:map(
fun({ip, IPT}) when is_tuple(IPT) ->
{ip, list_to_binary(inet_parse:ntoa(IP))};
- (tls) -> {tls, true};
(ssl) -> {tls, true};
- (zlib) -> {zlib, true};
- (starttls) -> {starttls, true};
- (starttls_required) -> {starttls_required, true};
+ (A) when is_atom(A) -> {A, true};
(Opt) -> Opt
end, Opts),
Opts2 = lists:foldl(
@@ -598,11 +649,11 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) ->
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_option({{Port, Transport}, Mod, Opts})
when ?IS_TRANSPORT(Transport) ->
- transform_option({{Port, {0,0,0,0}, Transport}, Mod, Opts});
+ transform_option({{Port, all_zero_ip(Opts), Transport}, Mod, Opts});
transform_option({{Port, IP}, Mod, Opts}) ->
transform_option({{Port, IP, tcp}, Mod, Opts});
transform_option({Port, Mod, Opts}) ->
- transform_option({{Port, {0,0,0,0}, tcp}, Mod, Opts});
+ transform_option({{Port, all_zero_ip(Opts), tcp}, Mod, Opts});
transform_option(Opt) ->
Opt.
@@ -638,7 +689,7 @@ validate_cfg(L) ->
{Port, prepare_mod(Mod), Opts};
(Opt, {Port, Mod, Opts}) ->
{Port, Mod, [Opt|Opts]}
- end, {{5222, {0,0,0,0}, tcp}, ejabberd_c2s, []}, LOpts)
+ end, {{5222, all_zero_ip(LOpts), tcp}, ejabberd_c2s, []}, LOpts)
end, L).
prepare_ip({A, B, C, D} = IP)
@@ -660,3 +711,12 @@ prepare_mod(sip) ->
esip_socket;
prepare_mod(Mod) when is_atom(Mod) ->
Mod.
+
+all_zero_ip(Opts) ->
+ case proplists:get_bool(inet6, Opts) of
+ true -> {0,0,0,0,0,0,0,0};
+ false -> {0,0,0,0}
+ end.
+
+opt_type(listen) -> fun validate_cfg/1;
+opt_type(_) -> [listen].
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index cec39ced..7c30f3b6 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -5,7 +5,7 @@
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -32,7 +32,7 @@
%% API
-export([start_link/0]).
--export([route/3, route_iq/4, route_iq/5,
+-export([route/3, route_iq/4, route_iq/5, process_iq/3,
process_iq_reply/3, register_iq_handler/4,
register_iq_handler/5, register_iq_response_handler/4,
register_iq_response_handler/5, unregister_iq_handler/2,
@@ -175,16 +175,10 @@ bounce_resource_packet(From, To, Packet) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([]) ->
lists:foreach(fun (Host) ->
ejabberd_router:register_route(Host,
+ Host,
{apply, ?MODULE,
route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
@@ -200,28 +194,7 @@ init([]) ->
mnesia:add_table_copy(iq_response, node(), ram_copies),
{ok, #state{}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
Reply = ok, {reply, Reply, State}.
handle_cast(_Msg, State) -> {noreply, State}.
@@ -272,26 +245,18 @@ handle_info(refresh_iq_handlers, State) ->
handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID),
{noreply, State};
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-handle_info(_Info, State) -> {noreply, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
-terminate(_Reason, _State) -> ok.
+terminate(_Reason, _State) ->
+ ok.
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
@@ -308,7 +273,7 @@ do_route(From, To, Packet) ->
end;
true ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index a00ac994..795d4f39 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -1,12 +1,11 @@
%%%-------------------------------------------------------------------
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2013, Evgeniy Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 12 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2013-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2013-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
@@ -25,8 +24,10 @@
%%%-------------------------------------------------------------------
-module(ejabberd_logger).
+-behaviour(ejabberd_config).
+
%% API
--export([start/0, reopen_log/0, get/0, set/1, get_log_path/0]).
+-export([start/0, reopen_log/0, rotate_log/0, get/0, set/1, get_log_path/0, opt_type/1]).
-include("ejabberd.hrl").
@@ -35,6 +36,7 @@
-spec start() -> ok.
-spec get_log_path() -> string().
-spec reopen_log() -> ok.
+-spec rotate_log() -> ok.
-spec get() -> {loglevel(), atom(), string()}.
-spec set(loglevel() | {loglevel(), list()}) -> {module, module()}.
@@ -48,6 +50,7 @@
%% "ejabberd.log" in current directory.
%% Note: If the directory where to place the ejabberd log file to not exist,
%% it is not created and no log file will be generated.
+%% @spec () -> string()
get_log_path() ->
case ejabberd_config:env_binary_to_list(ejabberd, log_path) of
{ok, Path} ->
@@ -61,7 +64,16 @@ get_log_path() ->
end
end.
--ifdef(LAGER).
+opt_type(log_rotate_date) ->
+ fun(S) -> binary_to_list(iolist_to_binary(S)) end;
+opt_type(log_rotate_size) ->
+ fun(I) when is_integer(I), I >= 0 -> I end;
+opt_type(log_rotate_count) ->
+ fun(I) when is_integer(I), I >= 0 -> I end;
+opt_type(log_rate_limit) ->
+ fun(I) when is_integer(I), I >= 0 -> I end;
+opt_type(_) ->
+ [log_rotate_date, log_rotate_size, log_rotate_count, log_rate_limit].
get_integer_env(Name, Default) ->
case application:get_env(ejabberd, Name) of
@@ -88,7 +100,33 @@ get_string_env(Name, Default) ->
Default
end.
+%% @spec () -> ok
start() ->
+ StartedApps = application:which_applications(5000),
+ case lists:keyfind(logger, 1, StartedApps) of
+ %% Elixir logger is started. We assume everything is in place
+ %% to use lager to Elixir logger bridge.
+ {logger, _, _} ->
+ error_logger:info_msg("Ignoring ejabberd logger options, using Elixir Logger.", []),
+ %% Do not start lager, we rely on Elixir Logger
+ do_start_for_logger();
+ _ ->
+ do_start()
+ end.
+
+do_start_for_logger() ->
+ application:load(sasl),
+ application:set_env(sasl, sasl_error_logger, false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, false),
+ application:set_env(lager, error_logger_whitelist, ['Elixir.Logger.ErrorHandler']),
+ application:set_env(lager, crash_log, false),
+ application:set_env(lager, handlers, [{elixir_logger_backend, [{level, info}]}]),
+ ejabberd:start_app(lager),
+ ok.
+
+%% Start lager
+do_start() ->
application:load(sasl),
application:set_env(sasl, sasl_error_logger, false),
application:load(lager),
@@ -115,7 +153,13 @@ start() ->
ejabberd:start_app(lager),
ok.
+%% @spec () -> ok
reopen_log() ->
+ %% Lager detects external log rotation automatically.
+ ok.
+
+%% @spec () -> ok
+rotate_log() ->
lager_crash_log ! rotate,
lists:foreach(
fun({lager_file_backend, File}) ->
@@ -124,8 +168,9 @@ reopen_log() ->
ok
end, gen_event:which_handlers(lager_event)).
+%% @spec () -> {loglevel(), atom(), string()}
get() ->
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
none -> {0, no_log, "No log"};
emergency -> {1, critical, "Critical"};
alert -> {1, critical, "Critical"};
@@ -137,6 +182,7 @@ get() ->
debug -> {5, debug, "Debug"}
end.
+%% @spec (loglevel() | {loglevel(), list()}) -> {module, module()}
set(LogLevel) when is_integer(LogLevel) ->
LagerLogLevel = case LogLevel of
0 -> none;
@@ -144,9 +190,10 @@ set(LogLevel) when is_integer(LogLevel) ->
2 -> error;
3 -> warning;
4 -> info;
- 5 -> debug
+ 5 -> debug;
+ E -> throw({wrong_loglevel, E})
end,
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
LagerLogLevel ->
ok;
_ ->
@@ -156,6 +203,8 @@ set(LogLevel) when is_integer(LogLevel) ->
lager:set_loglevel(H, LagerLogLevel);
(lager_console_backend = H) ->
lager:set_loglevel(H, LagerLogLevel);
+ (elixir_logger_backend = H) ->
+ lager:set_loglevel(H, LagerLogLevel);
(_) ->
ok
end, gen_event:which_handlers(lager_event))
@@ -165,56 +214,21 @@ set({_LogLevel, _}) ->
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
{module, lager}.
--else.
-
-start() ->
- set(4),
- LogPath = get_log_path(),
- error_logger:add_report_handler(p1_logger_h, LogPath),
- ok.
-
-reopen_log() ->
- %% TODO: Use the Reopen log API for logger_h ?
- p1_logger_h:reopen_log(),
- reopen_sasl_log().
-
-get() ->
- p1_loglevel:get().
-
-set(LogLevel) ->
- p1_loglevel:set(LogLevel).
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-reopen_sasl_log() ->
- case application:get_env(sasl,sasl_error_logger) of
- {ok, {file, SASLfile}} ->
- error_logger:delete_report_handler(sasl_report_file_h),
- rotate_sasl_log(SASLfile),
- error_logger:add_report_handler(sasl_report_file_h,
- {SASLfile, get_sasl_error_logger_type()});
- _ -> false
- end,
- ok.
-
-rotate_sasl_log(Filename) ->
- case file:read_file_info(Filename) of
- {ok, _FileInfo} ->
- file:rename(Filename, [Filename, ".0"]),
- ok;
- {error, _Reason} ->
- ok
+get_lager_loglevel() ->
+ Handlers = get_lager_handlers(),
+ lists:foldl(fun(lager_console_backend, _Acc) ->
+ lager:get_loglevel(lager_console_backend);
+ (elixir_logger_backend, _Acc) ->
+ lager:get_loglevel(elixir_logger_backend);
+ (_, Acc) ->
+ Acc
+ end,
+ none, Handlers).
+
+get_lager_handlers() ->
+ case catch gen_event:which_handlers(lager_event) of
+ {'EXIT',noproc} ->
+ [];
+ Result ->
+ Result
end.
-
-%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
-get_sasl_error_logger_type () ->
- case application:get_env (sasl, errlog_type) of
- {ok, error} -> error;
- {ok, progress} -> progress;
- {ok, all} -> all;
- {ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
- _ -> all
- end.
-
--endif.
diff --git a/src/ejabberd_node_groups.erl b/src/ejabberd_node_groups.erl
index da0bffe9..352757dd 100644
--- a/src/ejabberd_node_groups.erl
+++ b/src/ejabberd_node_groups.erl
@@ -5,7 +5,7 @@
%%% Created : 1 Nov 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,6 +24,8 @@
%%%----------------------------------------------------------------------
-module(ejabberd_node_groups).
+
+-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -35,9 +37,8 @@
get_members/1,
get_closest_node/1]).
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3, opt_type/1]).
-define(PG2, pg2).
@@ -163,3 +164,10 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
+opt_type(node_type) ->
+ fun (frontend) -> frontend;
+ (backend) -> backend;
+ (generic) -> generic
+ end;
+opt_type(_) -> [node_type].
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
new file mode 100644
index 00000000..d8e0a435
--- /dev/null
+++ b/src/ejabberd_oauth.erl
@@ -0,0 +1,490 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_oauth.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : OAUTH2 support
+%%% Created : 20 Mar 2015 by Alexey Shchepin <alexey@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., 59 Temple Place, Suite 330, Boston, MA
+%%% 02111-1307 USA
+%%%
+%%%-------------------------------------------------------------------
+
+-module(ejabberd_oauth).
+
+-behaviour(gen_server).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-export([start/0,
+ start_link/0,
+ get_client_identity/2,
+ verify_redirection_uri/3,
+ authenticate_user/2,
+ authenticate_client/2,
+ verify_resowner_scope/3,
+ associate_access_code/3,
+ associate_access_token/3,
+ associate_refresh_token/3,
+ check_token/4,
+ check_token/2,
+ process/2,
+ opt_type/1]).
+
+-include("jlib.hrl").
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-include("ejabberd_http.hrl").
+-include("ejabberd_web_admin.hrl").
+
+-record(oauth_token, {
+ token = {<<"">>, <<"">>} :: {binary(), binary()},
+ us = {<<"">>, <<"">>} :: {binary(), binary()},
+ scope = [] :: [binary()],
+ expire :: integer()
+ }).
+
+-define(EXPIRE, 3600).
+
+start() ->
+ init_db(mnesia, ?MYNAME),
+ Expire = expire(),
+ application:set_env(oauth2, backend, ejabberd_oauth),
+ application:set_env(oauth2, expiry_time, Expire),
+ application:start(oauth2),
+ ChildSpec = {?MODULE, {?MODULE, start_link, []},
+ temporary, 1000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec),
+ ok.
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+init([]) ->
+ erlang:send_after(expire() * 1000, self(), clean),
+ {ok, ok}.
+
+handle_call(_Request, _From, State) ->
+ {reply, bad_request, State}.
+
+handle_cast(_Msg, State) -> {noreply, State}.
+
+handle_info(clean, State) ->
+ {MegaSecs, Secs, MiniSecs} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ F = fun() ->
+ Ts = mnesia:select(
+ oauth_token,
+ [{#oauth_token{expire = '$1', _ = '_'},
+ [{'<', '$1', TS}],
+ ['$_']}]),
+ lists:foreach(fun mnesia:delete_object/1, Ts)
+ end,
+ mnesia:async_dirty(F),
+ erlang:send_after(trunc(expire() * 1000 * (1 + MiniSecs / 1000000)),
+ self(), clean),
+ {noreply, State};
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason, _State) -> ok.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+
+init_db(mnesia, _Host) ->
+ mnesia:create_table(oauth_token,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, oauth_token)}]),
+ mnesia:add_table_copy(oauth_token, node(), disc_copies);
+init_db(_, _) ->
+ ok.
+
+
+get_client_identity(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
+
+verify_redirection_uri(_, _, Ctx) -> {ok, Ctx}.
+
+authenticate_user({User, Server}, {password, Password} = Ctx) ->
+ case jid:make(User, Server, <<"">>) of
+ #jid{} = JID ->
+ Access =
+ ejabberd_config:get_option(
+ {oauth_access, JID#jid.lserver},
+ fun(A) when is_atom(A) -> A end,
+ none),
+ case acl:match_rule(JID#jid.lserver, Access, JID) of
+ allow ->
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
+ true ->
+ {ok, {Ctx, {user, User, Server}}};
+ false ->
+ {error, badpass}
+ end;
+ deny ->
+ {error, badpass}
+ end;
+ error ->
+ {error, badpass}
+ end.
+
+authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
+
+verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
+ Cmds = ejabberd_commands:get_commands(),
+ Cmds1 = [sasl_auth | Cmds],
+ RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
+ case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
+ oauth2_priv_set:new(RegisteredScope)) of
+ true ->
+ {ok, {Ctx, Scope}};
+ false ->
+ {error, badscope}
+ end;
+verify_resowner_scope(_, _, _) ->
+ {error, badscope}.
+
+
+associate_access_code(_AccessCode, _Context, AppContext) ->
+ %put(?ACCESS_CODE_TABLE, AccessCode, Context),
+ {ok, AppContext}.
+
+associate_access_token(AccessToken, Context, AppContext) ->
+ {user, User, Server} =
+ proplists:get_value(<<"resource_owner">>, Context, <<"">>),
+ Scope = proplists:get_value(<<"scope">>, Context, []),
+ Expire = proplists:get_value(<<"expiry_time">>, Context, 0),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ R = #oauth_token{
+ token = AccessToken,
+ us = {LUser, LServer},
+ scope = Scope,
+ expire = Expire
+ },
+ mnesia:dirty_write(R),
+ {ok, AppContext}.
+
+associate_refresh_token(_RefreshToken, _Context, AppContext) ->
+ %put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
+ {ok, AppContext}.
+
+
+check_token(User, Server, Scope, Token) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case catch mnesia:dirty_read(oauth_token, Token) of
+ [#oauth_token{us = {LUser, LServer},
+ scope = TokenScope,
+ expire = Expire}] ->
+ {MegaSecs, Secs, _} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ oauth2_priv_set:is_member(
+ Scope, oauth2_priv_set:new(TokenScope)) andalso
+ Expire > TS;
+ _ ->
+ false
+ end.
+
+check_token(Scope, Token) ->
+ case catch mnesia:dirty_read(oauth_token, Token) of
+ [#oauth_token{us = {LUser, LServer},
+ scope = TokenScope,
+ expire = Expire}] ->
+ {MegaSecs, Secs, _} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ case oauth2_priv_set:is_member(
+ Scope, oauth2_priv_set:new(TokenScope)) andalso
+ Expire > TS of
+ true -> {ok, LUser, LServer};
+ false -> false
+ end;
+ _ ->
+ false
+ end.
+
+
+expire() ->
+ ejabberd_config:get_option(
+ oauth_expire,
+ fun(I) when is_integer(I) -> I end,
+ ?EXPIRE).
+
+-define(DIV(Class, Els),
+ ?XAE(<<"div">>, [{<<"class">>, Class}], Els)).
+-define(INPUTID(Type, Name, Value),
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}, {<<"id">>, Name}])).
+-define(LABEL(ID, Els),
+ ?XAE(<<"label">>, [{<<"for">>, ID}], Els)).
+
+process(_Handlers,
+ #request{method = 'GET', q = Q, lang = Lang,
+ path = [_, <<"authorization_token">>]}) ->
+ ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
+ RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
+ Scope = proplists:get_value(<<"scope">>, Q, <<"">>),
+ State = proplists:get_value(<<"state">>, Q, <<"">>),
+ Form =
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"authorization_token">>},
+ {<<"method">>, <<"post">>}],
+ [?LABEL(<<"username">>, [?CT(<<"User">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"text">>, <<"username">>, <<"">>),
+ ?BR,
+ ?LABEL(<<"server">>, [?CT(<<"Server">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"text">>, <<"server">>, <<"">>),
+ ?BR,
+ ?LABEL(<<"password">>, [?CT(<<"Password">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"password">>, <<"password">>, <<"">>),
+ ?INPUT(<<"hidden">>, <<"response_type">>, ResponseType),
+ ?INPUT(<<"hidden">>, <<"client_id">>, ClientId),
+ ?INPUT(<<"hidden">>, <<"redirect_uri">>, RedirectURI),
+ ?INPUT(<<"hidden">>, <<"scope">>, Scope),
+ ?INPUT(<<"hidden">>, <<"state">>, State),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"">>, <<"Accept">>)
+ ]),
+ Top =
+ ?DIV(<<"section">>,
+ [?DIV(<<"block">>,
+ [?A(<<"https://www.ejabberd.im">>,
+ [?XA(<<"img">>,
+ [{<<"height">>, <<"32">>},
+ {<<"src">>, logo()}])]
+ )])]),
+ Middle =
+ ?DIV(<<"white section">>,
+ [?DIV(<<"block">>,
+ [?XC(<<"h1">>, <<"Authorization request">>),
+ ?XE(<<"p">>,
+ [?C(<<"Application ">>),
+ ?XC(<<"em">>, ClientId),
+ ?C(<<" wants to access scope ">>),
+ ?XC(<<"em">>, Scope)]),
+ Form
+ ])]),
+ Bottom =
+ ?DIV(<<"section">>,
+ [?DIV(<<"block">>,
+ [?XAC(<<"a">>,
+ [{<<"href">>, <<"https://www.ejabberd.im">>},
+ {<<"title">>, <<"ejabberd XMPP server">>}],
+ <<"ejabberd">>),
+ ?C(" is maintained by "),
+ ?XAC(<<"a">>,
+ [{<<"href">>, <<"https://www.process-one.net">>},
+ {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
+ <<"ProcessOne">>)
+ ])]),
+ Body = ?DIV(<<"container">>, [Top, Middle, Bottom]),
+ ejabberd_web:make_xhtml(web_head(), [Body]);
+process(_Handlers,
+ #request{method = 'POST', q = Q, lang = _Lang,
+ path = [_, <<"authorization_token">>]}) ->
+ _ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
+ RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
+ SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
+ Username = proplists:get_value(<<"username">>, Q, <<"">>),
+ Server = proplists:get_value(<<"server">>, Q, <<"">>),
+ Password = proplists:get_value(<<"password">>, Q, <<"">>),
+ State = proplists:get_value(<<"state">>, Q, <<"">>),
+ Scope = str:tokens(SScope, <<" ">>),
+ case oauth2:authorize_password({Username, Server},
+ ClientId,
+ RedirectURI,
+ Scope,
+ {password, Password}) of
+ {ok, {_AppContext, Authorization}} ->
+ {ok, {_AppContext2, Response}} =
+ oauth2:issue_token(Authorization, none),
+ {ok, AccessToken} = oauth2_response:access_token(Response),
+ {ok, Type} = oauth2_response:token_type(Response),
+ {ok, Expires} = oauth2_response:expires_in(Response),
+ {ok, VerifiedScope} = oauth2_response:scope(Response),
+ %oauth2_wrq:redirected_access_token_response(ReqData,
+ % RedirectURI,
+ % AccessToken,
+ % Type,
+ % Expires,
+ % VerifiedScope,
+ % State,
+ % Context);
+ {302, [{<<"Location">>,
+ <<RedirectURI/binary,
+ "?access_token=", AccessToken/binary,
+ "&token_type=", Type/binary,
+ "&expires_in=", (integer_to_binary(Expires))/binary,
+ "&scope=", (str:join(VerifiedScope, <<" ">>))/binary,
+ "&state=", State/binary>>
+ }],
+ ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])};
+ {error, Error} when is_atom(Error) ->
+ %oauth2_wrq:redirected_error_response(
+ % ReqData, RedirectURI, Error, State, Context)
+ {302, [{<<"Location">>,
+ <<RedirectURI/binary,
+ "?error=", (atom_to_binary(Error, utf8))/binary,
+ "&state=", State/binary>>
+ }],
+ ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
+ end;
+process(_Handlers, _Request) ->
+ ejabberd_web:error(not_found).
+
+
+
+
+web_head() ->
+ [?XA(<<"meta">>, [{<<"http-equiv">>, <<"X-UA-Compatible">>},
+ {<<"content">>, <<"IE=edge">>}]),
+ ?XA(<<"meta">>, [{<<"name">>, <<"viewport">>},
+ {<<"content">>,
+ <<"width=device-width, initial-scale=1">>}]),
+ ?XC(<<"title">>, <<"Authorization request">>),
+ ?XC(<<"style">>, css())
+ ].
+
+css() ->
+ <<"
+ body {
+ margin: 0;
+ padding: 0;
+
+ font-family: sans-serif;
+ color: #fff;
+ }
+
+ h1 {
+ font-size: 3em;
+ color: #444;
+ }
+
+ p {
+ line-height: 1.5em;
+ color: #888;
+ }
+
+ a {
+ color: #fff;
+ }
+ a:hover,
+ a:active {
+ text-decoration: underline;
+ }
+
+ em {
+ display: inline-block;
+ padding: 0 5px;
+
+ background: #f4f4f4;
+ border-radius: 5px;
+
+ font-style: normal;
+ font-weight: bold;
+ color: #444;
+ }
+
+ form {
+ color: #444;
+ }
+ label {
+ display: block;
+ font-weight: bold;
+ }
+
+ input[type=text],
+ input[type=password] {
+ margin-bottom: 1em;
+ padding: 0.4em;
+
+ max-width: 330px;
+ width: 100%;
+
+ border: 1px solid #c4c4c4;
+ border-radius: 5px;
+ outline: 0;
+
+ font-size: 1.2em;
+ }
+ input[type=text]:focus,
+ input[type=password]:focus,
+ input[type=text]:active,
+ input[type=password]:active {
+ border-color: #41AFCA;
+ }
+
+ input[type=submit] {
+ font-size: 1em;
+ }
+
+ .container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ background: #424A55;
+ background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ }
+
+ .section {
+ padding: 3em;
+ }
+ .white.section {
+ background: #fff;
+ border-bottom: 4px solid #41AFCA;
+ }
+
+ .white.section a {
+ text-decoration: none;
+ color: #41AFCA;
+ }
+ .white.section a:hover,
+ .white.section a:active {
+ text-decoration: underline;
+ }
+
+ .container > .section {
+ background: #424A55;
+ }
+
+ .block {
+ margin: 0 auto;
+ max-width: 900px;
+ width: 100%;
+ }
+">>.
+
+logo() ->
+ <<"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABACAYAAACgPErgAAAVtklEQVR42uydeXhU1d2ADzuyCcii4FZBZdEqVYtaUGqlikVwaRWsrWBRKIh8KgXcEAX9VECtoljcURAJICJWQLaKLAEjIHtBSNhC9oQkk2SW+36/52P+iIE5d5kzk4Fn3ud5H/7gIbnD3PPOveeeuVedygD9xLlQMh9yJ0D+GeokJAg1xcEWzOWYj0DBaQm0fXdaMCu8bROgpJVKksQj0+F84OXw/jRd7KVOdbbCFI6jdDNktlAnESVQNwSzOI6yhZBaV1UzAXiB4wj8CBtbqiRJXDIILtgL+zmOb8aoU5Xh0MMiEr5x6iSiAIZYROLo/aoayYMeocjbNlklSeKC9VAjBDM5IcEQfNZFnYq8AM8SkcJV6iSiCGYRkT1LVDWSA08Tkfy9MLaeOsUA2ot9xJHiW2KK+LW4Mvxnijg1/Pd9xQvFGiqJLUOhcT5kE5Etg0/VYI3TBOvbkyxYKURk2dpqDZb2gyEjC1SjUyBQdcQe4mtimujDHWXiD+Lr4g1iXZXkhISgBZBLZIaoU5GR0I2IVDxxkgVrNhH5z+pqDZb2g+FAJjQ4aYMFtBAfEzdils3iP8TkhYkqlMEZFuQQmcHqVGUBTOI40lLhz02TwUoGKxJAI/FxMYPYckB8Smyikvw/vlM/WHqAP4ifiHPF0fBsYyUkg5UM1okAbhV/JL5sE29TSZLBOhHJYCWDVRXgNPFVqpfXxQbJYCWDlQxWMlgRAc4VV5EYrBZ/kQxWMljJYFVLsBomdLCAS8RdJBZ7xMuSwTIcLKCO2EG8VrxdvEfsLV4F1vkJstq9BlidwN8hVleSLLgUuFzsEv6zs9g4EYIFNBAvFi87tm3WZWI7oHZ8glWnkWbb6ortCG/bsT+tDsBpcYrVpeIhzBHAHJni5XGOd5PwvnKJif0DaCh2/Pn7y/linbgFKx/qrjkWpffEHZo3qVhMBWuC2EVVA4ehay6sAYJgBeDocuh9iYqCL+F04DbxLTFNPMzxWGI6WCuBJ8Rfxi5YK34WLKC5BX3FVzj22n8S/fwcX/i9+1wcLraLVbAqr8OqgNOA31nwPLBc3HWCNU1+cbe4WBwjXhbD08A9eOcn8SNxmHij+EuxQ3iwdxP/Jr4pbsU7+2J5ehiCNuI9FkwN78sZol+0xDSYd6OH75R2BcZasALYKwZO0IWd4fd3LIS6g1XHeLBWQV1gsLgJ9wTBWgSBm1ScuBYuAvI4jrx9cOEZyiXzoUUIxhbDPm+fvFlL4PPe5oO14FslvAjnrYeXgYO4p0T8UrzJbLB2HlbCZMqbfwOjgrDD41HLCrE/LK1pKFYNxFS8sVDsK1Y5ctQeRf42HLcy3JMmNjIc6+ss+ETMRUuRH9Zd7SBS9QMwCFiDNzZBYAT4G5dTUcuC7KiClQ3dj8B6osYSAzOgoq2KMatgChF5e5RyQQXcVwL7MMNXsKOTuWAd2FBG9uhyyMMI6XPBd7GZYOUd8pP+UCn8hBEOr4KCbgYG7Ju4Z43YU0VB+LRoLu6ZpgyQAhflw2e4Y7lYS0XADzdZ8ANGsLaX88PgACHvwfLBY4Afo4T2Q9lNMZ68W0lE/J8oB5RDkxDMwDhF+XC4v6tgxZdcKLov+mBhYR4/FD8VRTRu93CE97RYTxkCuFfMxR13qSjIhoE+Tx9qPh9Mb6WqcBRqBuBli5hguQ4WUGsPvGsRKyw/hAaoGJEPy4lI4Uxlw3XHzu/XEVNKHk3QYAmWWDEecmraBqtaCE6HrCYeJpX34Jwc8eYYXp3cjHPSxWbKA9/Da3im+CeY1kBV4gNoUQILiB/6YD0JNYFP4jMoGFINwZqhNHwKLbfB5ji9/kdMBss8vk9MBcs8pasht7GLSIzHOQfFX6kYApyFuw/FFz38jveJitxBqhI3QMsS2IiQMMFaDFOJHyF45fZECdYOqA0sIX5YMPkOk8Eyz96xpoJlnvIZDgduazEfZ+SJVyoNhqO1DWcUim1dfJf2DTxTVgxFY1Ql+kOtivDYSJhgTYX7cIx1EJgmDhSvEbuK12XAiHyYCxThCKsQHmiXCMHa5+5T+BBY74qVX39vcUIhbLBwSkUO7DonBsEqBCtFfBjoHt6+34mjLfg34HO4feL8mw0HqwKsr8XRwA2E9x3xYY693jzHvWfZCAdheAZnWG6+1wecKd4oDhFHicPFvm6WI4SXRBTijOcdzi3+BefkifPAehLoI14Pqcdt/1x3YyNb/BisYUC38PvbU3wivOQhGHWw+sK5ASjAltBB8D8MQe05NXCBBZOBCmxJXwbTa1RnsDbCL4Pgx5ajeeJjYDVXEZgDNSugVwhW44j1sw0GKwjrX4El2kGzHDqVwzvO1x3d2dhMsPbMgjnaRZGroW0OjHcY1VIYeZHNMoZ0nPFP5YDwIJwp5miWinwt3u7w5z3g4lS1sc2c1Tl+Z0eTRXBkLKy1PWp7Ga4EAthSXAhHn4BQa5vX2zUIX1rRBKvU0fluYDaUt3F5xa5rNmzFlsfurs5grYP52JK7AVZcpBxSCrU3wvPOPtgzuxsI1gG3l9/90Cfg6Ihm/ONRBssn3udyceNVftiBLXNnaAZHbxcLNZs6uPXMFJdHCF84OeICluIIfQTzHI3jrI0Q7Kwc4myaxEqDtR2VC9bCCMDvOliZ0NGCCv1pwb7XlEdmQCtgOVp2bYU69aojWB/AVUEIoWcpfNlMeQD4q/0bs/GL6IK1JR3Wd/T4/lxZDJloKTgMrzXzFqyDJfBNT+WB2dD2kO1Eb9AP/zrhAAQ+xhl/d7A6fm0U977qbvPzr3YYwlkqArnQ2f4swb8SZjZXDtkG3SxsWSZ6HRt9RJ+rYP0H/omWdVNUlGyDxpm2b/i4P1VHsPbYnhrl7IQ/NlNRAPzDfqnH0o4eg1UEEy+N8mkl3e1P378e4CFYFnzRR0VBVzgP26BumqyqANQX92JPhm5FOXC2uJXoKBR7KA3AQuzZLzaMcJbwqv2pfXFL5YKV8BFaCvfCH1uqKDgKA0NOgzUAGuRp7664eaUyxD3Qpkz7u0oWxDtY/aBhFhzUD7hBN6goeRVq7IRVNkEY4y1YRx5VBiiHV9ByaK77YOW+pwxQCn9DS/4W2Fy7SgCuEEPYM1ETkXriSsxwRLxQe7RhjyV2VVW4E+ofhj1oSe2rXDAaGhTBAbQM72vo6VDzHQXrX9r7nxeXwqrLYWcNoFa0hj8F7rQiTuAV5cHIFvEM1jvQ3UJH+QJliN9Dj5B2APkWuw/Wrm3QrJ6hZyC20q/C9qfDsgbOg1WYDxe2NbRtdfTfZbXKIbW9x8ns30QISA1xEmbZLDbRzJEdwp6hqgqvwVWWdt9avVy5ZDL8Wj9VstbYwUwpdALKbIP1HxhJRAJlwPfhHWWzATeJafpz9bd/H89grYBRaHn+RmWQn2CdZtsyYWhjd8HaPVQZZKl2HV7AgvEXOw9W1pvKIDNhGFrevbVKACY6vPLWSHN0NUp8ThxnyJfECzRHWSlermaugfvRcuQu5ZLv4F60pN+lDLIZUmyDtRRmklAUPBzPYK2GT3Vf4IX+jZRBPoYniUioHA62dx6sQAVkt1MGSYXeFjoC3ZwFKyRm/14Z5Bu4OKidZwsOqjL4P8eeZSqBAMZgz0JVhQ3wOhEpLoLxZyqXHIAJRKS0CF48UxnkWehnG6wj8BUJxfK3VZQUuAiW/ntRwa+UYR6BP+gH+f6eLoK1CQprKoMchIv82itNGfc5DFY+FLUyvG0NS2E/Ecl8qcrg/w573kuwYP0Re1JVFd7RfvAWbYIU1/vJ2/AhETm6HlJqKIOshA4hKNMGK5RwwZrygYFHqC9zGqygNlgrP4vBjQW7hcCKHKy9tzh/kGrqd8ow+6CVX3tPovShKkw2PKuJx2G4tKEyyF6o4YPvicjhN1UlHD5TcGKCBetG7Nkq/ixA+doPNv9i5YFp2iuE1mJlmBxoEbR7kKqVcMGaOU1FyTpYq7tbg/NgbZ+tDLMLrrcJVi9ViTLtKfsq48E6CK392p0m/e8qTDE8RUQOHobmRoN1AGqWQZomWFMq33FE/C/2vJBgwboee/aKdVzMfXk6U3hLGyyMBysXWga1i5jTBqmchAvWY31VlGRqbyrmn64qka8NVsj4/MZg6IPgNFj7tV9iLfsRVtdSBsmATgEIaoJ/mwqzEwYTkWABbDhLGSQdGpdp12OVPF7lmQO7HU1gJxDAI9izs+oRVlAfrH8rDyzQXoAp3gxv1zJ8UaVTSDtHec9f1bqEmXQPiuUfwjdR/Se8AG0sOEpEUidVmax8n4iU58OKFoaPsJ5xE6x/wwT9pPuTRifdv4K79Nv3QZdK8yb90PLazcogH8HlIQgQkfl3ewjWpwn2yLH92JOmhFgHqwSGE5EyH7x/vjJICtyjX362s6f6Cf6hX9tSOB8Q+SJGfimmQPogZYCXoD9avh1WZYAORcvwfsogpbDeTbBmwW1oKR6mDJIGs4iIvxAeOLPSpGxn/dGYb6rhJRdPod2jn7nGQ7DWaQJSQzxLbC22iqFtxDvE7ThjRTyCNQ1uRkvqYGWQQzBH/yV3+R7zFOhhab8u8smt6iThN1AjB1brd+rnuqhKvAFX6hdz/rgBLqqpDJAHPS2w3ARrIpzth2IiEtgBM+spA2RBB/33unZ9qyrxDNTLg52a11MAS842dDrYRH/XhfxDMLyhh2AViK0jBKum+K5YKObE0CLc8WE8gvUStC7XbtuWrXBeHWWA7tDJp933dq+F8TXVHmgS1M4LFByE/q3VScB826OrwG7IqqsqcQvU3Q+70OJ7SEVJO6iVARsQHAcrTBosR8vrI5UBNsBctBQ+raowD6ag5ev3lQFmw3i0FM9SgiZYOiJ+KHMs4iUkFk+aC5aeLbAULYERygBB+BIt+8ZV3lHfQ8uRRaDqqASmF7Sxf8xV6SR1AjbBi+gphPIuUcb0aQQvwfoWBqDFKoEXuqooWAID0RLyw56OJ7j319WAhZZp/aOMVTegDC0ZfaII1sdKA9CPxOLWeAVrMgy0Hxu9ukS5NvEh+33v6Q6VT4uusCCElpnz4Ie6KgFZAWctgTS0lPjg2osiLDRtFwAfWoKHIfRrj/NCI0oAr8EqgAZ+2IOew7DkWuWBAPQvBT9ayuZo5uUWo8cH6z1FKwTXFkEOWio2Q1ndKIJVILa1XYGeGBSJbeIVrObQKBPS0VJ6ABZ7itY2eLAIO1JmqKqUwzzsWQyB9soFwGliQxUj/HCNs/tiH5hqM2hfxxarCPwPughBI80dEBwHK3wUOMDCjtKjbp5GlAn1vodxgIWeoO6hDGvhmqDtzygXA0/BN3VcPF/yAYfzO3crQRcsE+uxgIfEUqqXVUowGyw9I2EY9uRCxV0uls803AwTsacC3uisqjIEOgMV2GLlAeMgdK7NbW47Aq+Ke8UD4jsw4yyDj9ruIE6yoAxbyo/AjjOVhnehVQFk4ojQIgjeDNSNsGO3FP9mwVYEE8FqAbUOwwoccWAh+HuCVS/C9jUNwZ+BNByx9A0Hd4+ciiOOpELx3WBFumNB/RD0smARjti5FPrVNBCsQvECB9G6SvwaZ/xX/F/xf8S5YojoGRXvYM2C+j7nT8uZC9ZvI42NhdA8AAOALTgi5TkVic/gSRxjFQDzxKFiH7G7eJv4WAhWBqCc49i/Gya2US7hWPzuFf8iTrBgGVCOY4r/5HCupF8AV2wDazrwiDhEfEFcKGYRxkiwwlRAeyAb5+wQPxFHioPEseLn4gEcU7gdLm3q4PubTVw+ay9DTBGfCm/bKPFTcSfOyYe+FyrBQLAQPnfzyHdxSvg1F4hlYrGYHn5d94hNTnC/qxK8Uya2Mx8se0rhCsCHc7aCVXnfG2fB/Ao4hGPSV8O59VQkfgs1N8IiYkrBK8oFW+E5wIdndkx2eVXkeYxgPljh7bs1CCHigr8Ull7u4r26pBgKiBtb7lCCqWCFeVi5AKgtthE7iL8QGykNwGi8s0AJcQ9WmOnwIHEjlAFp5yg7noNmQCoxo2yNizmWoUG8Yon73oOzayiXAG8mZLDC/BkGlIBFTMkvg1v6eFhPc31mzKNVIf59hBJiECy/2FvFCOCcKObBesQjWNV/8aFiPxR0cbNRrcVVxITgauWAR6H+IdiNZxZPinJdyKSEDFaY5dAXyCY2HIJPeyqPvAlXANuJDT74/n4leA6WPUfFXjEKVjMxD/css/m5c7TBMsgmGBGI2VG+9T0UtvdyI7cme+EzjPPhROWACmiJp0/q8lzY/RdlgFUwDCjGGFZYXbDSb3Fx4aGzH77DKBmrIPdCFSUcWyU9B6Pk7ICMbkowFiz9fNFAZRjgGjGIOwLi1UrDBv0R1hJlmCLoFYIMs2Mj8D74m6poAIZbkGtmg/gIbmumHLID5uOK0GzIb294B7skCAssoiYLsh6FdWIk/D7Ycp7LZ7vV2Qejifo9CmYBo+Hb2oY/jQcC+4iKUAnwCmxvrgTjwdLzttjS0L7U3uOjwqYoG3ZqF19WvKVigA/aBGEaUEFUWFsh+CeTa50uCMAUC3LwRioEb1MueRw62a/DsXLE9yH4GxVD/HCjBfPEYlxxdDsUjgPrbCXMprTF/oiPnip9QnnEgnND8KwFu3DHdigfC4vaqhgBNA3CwxZsxB37xFdhUSelQRssM+wVh4iNPb7+RhwLyhHcs8PJM/+OHLv1ziaOw5cNmReoGAL8KgRvBWC/5e4IZh2EBomnqVgQgrYWDBUXApkRlhaUillgLRVfFm8AaiqPAN3ENWJ+2ExxJVjTIHQXBM9UcSQI51twLzBNXMSxQOSIeeEdchNYC469dq6HH+qd4DYpvwA+FPeIeeF/86Ch9+g0C34HTBC/FLeK2eHfkyWmiZ+LT4rXhddqxYUQ1LLgGmCMOF/8QcwKb1tOeFsXhNcu3QyWLhDmg2XPLvFZ8UpRO8iAVuL14nPiTrxRLnZVDjkC54TveJAfdjEcuUzFia/h9FLoDbwifhV+3XmVxsZ6cQ5Yj0LoChVPkI0TO4q9wLoDuFW8TrxAbGr4d9WwoAVwhni6SiCA+mIzsXn4/6S2i3/bIPzvasdw++pW2r6mYi2VIAC1wtvUPLyNdZUGz8EyTyj8s78KnzK+LL4kviPOE9eJeUTP/R5f+xlgnZEIY8OC5pXGRg2VJEkSTbBOXsaoJEmSJIN1EvC0SvJ/7dSxigEAHIDxf5FisAilbNbbWGSi5OZ7BgZPcS/hAZTNwFtYSVltit2iU/c9gxv8u75ffa/wSQ4ruR+ahySHldyZJiHJYSW3pnZIcliJHekrJDmsxMM60JQqIUkJh3WhJX1SKSTpzcN60J1udKQNfdOIaiFJLwxrTwua0Zj61PtjXfqgDtWpEJL04rCetKKBM5GUeVgnGoYkJR1Wka60o0ZIUuJhVWlLzZCk5MMqUyv0r/wCSDD/4sxS1q8AAAAASUVORK5CYII=">>.
+
+opt_type(oauth_expire) ->
+ fun(I) when is_integer(I), I >= 0 -> I end;
+opt_type(oauth_access) ->
+ fun(A) when is_atom(A) -> A end;
+opt_type(_) -> [oauth_expire, oauth_access].
diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl
index 09f17a63..e9866586 100644
--- a/src/ejabberd_odbc.erl
+++ b/src/ejabberd_odbc.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_odbc).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
@@ -39,11 +41,16 @@
sql_bloc/2,
escape/1,
escape_like/1,
+ escape_like_arg/1,
to_bool/1,
sqlite_db/1,
sqlite_file/1,
- encode_term/1,
- decode_term/1,
+ encode_term/1,
+ decode_term/1,
+ odbc_config/0,
+ freetds_config/0,
+ odbcinst_config/0,
+ init_mssql/1,
keep_alive/1]).
%% gen_fsm callbacks
@@ -51,20 +58,22 @@
handle_info/3, terminate/3, print_state/1,
code_change/4]).
-%% gen_fsm states
-export([connecting/2, connecting/3,
- session_established/2, session_established/3]).
+ session_established/2, session_established/3,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
-record(state,
{db_ref = self() :: pid(),
- db_type = odbc :: pgsql | mysql | sqlite | odbc,
- start_interval = 0 :: non_neg_integer(),
- host = <<"">> :: binary(),
+ db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql,
+ db_version = undefined :: undefined | non_neg_integer(),
+ start_interval = 0 :: non_neg_integer(),
+ host = <<"">> :: binary(),
max_pending_requests_len :: non_neg_integer(),
- pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
+ pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
-define(STATE_KEY, ejabberd_odbc_state).
@@ -76,13 +85,17 @@
-define(MYSQL_PORT, 3306).
+-define(MSSQL_PORT, 1433).
+
-define(MAX_TRANSACTION_RESTARTS, 10).
-define(TRANSACTION_TIMEOUT, 60000).
-define(KEEPALIVE_TIMEOUT, 60000).
--define(KEEPALIVE_QUERY, <<"SELECT 1;">>).
+-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
+
+-define(PREPARE_KEY, ejabberd_odbc_prepare).
%%-define(DBGFSM, true).
@@ -108,16 +121,18 @@ start_link(Host, StartInterval) ->
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
--type sql_query() :: [sql_query() | binary()].
+-type sql_query() :: [sql_query() | binary()] | #sql_query{} |
+ fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query_result() :: {updated, non_neg_integer()} |
{error, binary()} |
{selected, [binary()],
- [[binary()]]}.
+ [[binary()]]} |
+ {selected, [any()]}.
-spec sql_query(binary(), sql_query()) -> sql_query_result().
sql_query(Host, Query) ->
- sql_call(Host, {sql_query, Query}).
+ check_error(sql_call(Host, {sql_query, Query}), Query).
%% SQL transaction based on a list of queries
%% This function automatically
@@ -145,7 +160,8 @@ sql_call(Host, Msg) ->
case ejabberd_odbc_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
- (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg, now()},
+ (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
+ p1_time_compat:monotonic_time(milli_seconds)},
?TRANSACTION_TIMEOUT)
end;
_State -> nested_op(Msg)
@@ -153,7 +169,8 @@ sql_call(Host, Msg) ->
keep_alive(PID) ->
(?GEN_FSM):sync_send_event(PID,
- {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, now()},
+ {sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
+ p1_time_compat:monotonic_time(milli_seconds)},
?KEEPALIVE_TIMEOUT).
-spec sql_query_t(sql_query()) -> sql_query_result().
@@ -184,6 +201,13 @@ escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
+escape_like_arg(S) when is_binary(S) ->
+ << <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
+escape_like_arg($%) -> <<"\\%">>;
+escape_like_arg($_) -> <<"\\_">>;
+escape_like_arg($\\) -> <<"\\\\">>;
+escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
+
to_bool(<<"t">>) -> true;
to_bool(<<"true">>) -> true;
to_bool(<<"1">>) -> true;
@@ -193,7 +217,8 @@ to_bool(_) -> false.
encode_term(Term) ->
escape(list_to_binary(
- erl_prettypr:format(erl_syntax:abstract(Term)))).
+ erl_prettypr:format(erl_syntax:abstract(Term),
+ [{paper, 65535}, {ribbon, 65535}]))).
decode_term(Bin) ->
Str = binary_to_list(<<Bin/binary, ".">>),
@@ -248,15 +273,16 @@ connecting(connect, #state{host = Host} = State) ->
end,
{_, PendingRequests} = State#state.pending_requests,
case ConnectRes of
- {ok, Ref} ->
- erlang:monitor(process, Ref),
- lists:foreach(fun (Req) ->
- (?GEN_FSM):send_event(self(), Req)
- end,
- queue:to_list(PendingRequests)),
- {next_state, session_established,
- State#state{db_ref = Ref,
- pending_requests = {0, queue:new()}}};
+ {ok, Ref} ->
+ erlang:monitor(process, Ref),
+ lists:foreach(fun (Req) ->
+ (?GEN_FSM):send_event(self(), Req)
+ end,
+ queue:to_list(PendingRequests)),
+ State1 = State#state{db_ref = Ref,
+ pending_requests = {0, queue:new()}},
+ State2 = get_db_version(State1),
+ {next_state, session_established, State2};
{error, Reason} ->
?INFO_MSG("~p connection failed:~n** Reason: ~p~n** "
"Retry after: ~p seconds",
@@ -364,7 +390,7 @@ print_state(State) -> State.
%%%----------------------------------------------------------------------
run_sql_cmd(Command, From, State, Timestamp) ->
- case timer:now_diff(now(), Timestamp) div 1000 of
+ case p1_time_compat:monotonic_time(milli_seconds) - Timestamp of
Age when Age < (?TRANSACTION_TIMEOUT) ->
put(?NESTING_KEY, ?TOP_LEVEL_TXN),
put(?STATE_KEY, State),
@@ -429,13 +455,13 @@ outer_transaction(F, NRestarts, _Reason) ->
[T]),
erlang:exit(implementation_faulty)
end,
- sql_query_internal(<<"begin;">>),
+ sql_query_internal([<<"begin;">>]),
put(?NESTING_KEY, PreviousNestingLevel + 1),
Result = (catch F()),
put(?NESTING_KEY, PreviousNestingLevel),
case Result of
{aborted, Reason} when NRestarts > 0 ->
- sql_query_internal(<<"rollback;">>),
+ sql_query_internal([<<"rollback;">>]),
outer_transaction(F, NRestarts - 1, Reason);
{aborted, Reason} when NRestarts =:= 0 ->
?ERROR_MSG("SQL transaction restarts exceeded~n** "
@@ -444,11 +470,11 @@ outer_transaction(F, NRestarts, _Reason) ->
"== ~p",
[?MAX_TRANSACTION_RESTARTS, Reason,
erlang:get_stacktrace(), get(?STATE_KEY)]),
- sql_query_internal(<<"rollback;">>),
+ sql_query_internal([<<"rollback;">>]),
{aborted, Reason};
{'EXIT', Reason} ->
- sql_query_internal(<<"rollback;">>), {aborted, Reason};
- Res -> sql_query_internal(<<"commit;">>), {atomic, Res}
+ sql_query_internal([<<"rollback;">>]), {aborted, Reason};
+ Res -> sql_query_internal([<<"commit;">>]), {atomic, Res}
end.
execute_bloc(F) ->
@@ -458,8 +484,74 @@ execute_bloc(F) ->
Res -> {atomic, Res}
end.
+execute_fun(F) when is_function(F, 0) ->
+ F();
+execute_fun(F) when is_function(F, 2) ->
+ State = get(?STATE_KEY),
+ F(State#state.db_type, State#state.db_version).
+
+sql_query_internal([{_, _} | _] = Queries) ->
+ State = get(?STATE_KEY),
+ case select_sql_query(Queries, State) of
+ undefined ->
+ {error, <<"no matching query for the current DBMS found">>};
+ Query ->
+ sql_query_internal(Query)
+ end;
+sql_query_internal(#sql_query{} = Query) ->
+ State = get(?STATE_KEY),
+ Res =
+ try
+ case State#state.db_type of
+ odbc ->
+ generic_sql_query(Query);
+ pgsql ->
+ Key = {?PREPARE_KEY, Query#sql_query.hash},
+ case get(Key) of
+ undefined ->
+ case pgsql_prepare(Query, State) of
+ {ok, _, _, _} ->
+ put(Key, prepared);
+ {error, Error} ->
+ ?ERROR_MSG("PREPARE failed for SQL query "
+ "at ~p: ~p",
+ [Query#sql_query.loc, Error]),
+ put(Key, ignore)
+ end;
+ _ ->
+ ok
+ end,
+ case get(Key) of
+ prepared ->
+ pgsql_execute_sql_query(Query, State);
+ _ ->
+ generic_sql_query(Query)
+ end;
+ mysql ->
+ generic_sql_query(Query);
+ sqlite ->
+ generic_sql_query(Query)
+ end
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Internal error while processing SQL query: ~p",
+ [{Class, Reason, ST}]),
+ {error, <<"internal error">>}
+ end,
+ case Res of
+ {error, <<"No SQL-driver information available.">>} ->
+ {updated, 0};
+ _Else -> Res
+ end;
+sql_query_internal(F) when is_function(F) ->
+ case catch execute_fun(F) of
+ {'EXIT', Reason} -> {error, Reason};
+ Res -> Res
+ end;
sql_query_internal(Query) ->
State = get(?STATE_KEY),
+ ?DEBUG("SQL: \"~s\"", [Query]),
Res = case State#state.db_type of
odbc ->
to_odbc(odbc:sql_query(State#state.db_ref, Query,
@@ -467,10 +559,6 @@ sql_query_internal(Query) ->
pgsql ->
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
mysql ->
- ?DEBUG("MySQL, Send query~n~p~n", [Query]),
- %%squery to be able to specify result_type = binary
- %%[Query] because p1_mysql_conn expect query to be a list (elements can be binaries, or iolist)
- %% but doesn't accept just a binary
R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
[Query], self(),
[{timeout, (?TRANSACTION_TIMEOUT) - 1000},
@@ -487,6 +575,93 @@ sql_query_internal(Query) ->
_Else -> Res
end.
+select_sql_query(Queries, State) ->
+ select_sql_query(
+ Queries, State#state.db_type, State#state.db_version, undefined).
+
+select_sql_query([], _Type, _Version, undefined) ->
+ undefined;
+select_sql_query([], _Type, _Version, Query) ->
+ Query;
+select_sql_query([{any, Query} | _], _Type, _Version, _) ->
+ Query;
+select_sql_query([{Type, Query} | _], Type, _Version, _) ->
+ Query;
+select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) ->
+ select_sql_query(Rest, Type, undefined, Query1);
+select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) ->
+ if
+ Version >= Version1 ->
+ Query1;
+ true ->
+ select_sql_query(Rest, Type, Version, Query)
+ end;
+select_sql_query([{_, _} | Rest], Type, Version, Query) ->
+ select_sql_query(Rest, Type, Version, Query).
+
+generic_sql_query(SQLQuery) ->
+ sql_query_format_res(
+ sql_query_internal(generic_sql_query_format(SQLQuery)),
+ SQLQuery).
+
+generic_sql_query_format(SQLQuery) ->
+ Args = (SQLQuery#sql_query.args)(generic_escape()),
+ (SQLQuery#sql_query.format_query)(Args).
+
+generic_escape() ->
+ #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
+ integer = fun(X) -> integer_to_binary(X) end,
+ boolean = fun(true) -> <<"1">>;
+ (false) -> <<"0">>
+ end
+ }.
+
+pgsql_prepare(SQLQuery, State) ->
+ Escape = #sql_escape{_ = fun(X) -> X end},
+ N = length((SQLQuery#sql_query.args)(Escape)),
+ Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)],
+ Query = (SQLQuery#sql_query.format_query)(Args),
+ pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query).
+
+pgsql_execute_escape() ->
+ #sql_escape{string = fun(X) -> X end,
+ integer = fun(X) -> [integer_to_binary(X)] end,
+ boolean = fun(true) -> "1";
+ (false) -> "0"
+ end
+ }.
+
+pgsql_execute_sql_query(SQLQuery, State) ->
+ Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()),
+ ExecuteRes =
+ pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args),
+% {T, ExecuteRes} =
+% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]),
+% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]),
+ Res = pgsql_execute_to_odbc(ExecuteRes),
+ sql_query_format_res(Res, SQLQuery).
+
+
+sql_query_format_res({selected, _, Rows}, SQLQuery) ->
+ Res =
+ lists:flatmap(
+ fun(Row) ->
+ try
+ [(SQLQuery#sql_query.format_res)(Row)]
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Error while processing "
+ "SQL query result: ~p~n"
+ "row: ~p",
+ [{Class, Reason, ST}, Row]),
+ []
+ end
+ end, Rows),
+ {selected, Res};
+sql_query_format_res(Res, _SQLQuery) ->
+ Res.
+
%% Generate the OTP callback return tuple depending on the driver result.
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
@@ -509,7 +684,10 @@ abort_on_driver_error(Reply, From) ->
%% Open an ODBC database connection
odbc_connect(SQLServer) ->
ejabberd:start_app(odbc),
- odbc:connect(binary_to_list(SQLServer), [{scrollable_cursors, off}]).
+ odbc:connect(binary_to_list(SQLServer),
+ [{scrollable_cursors, off},
+ {tuple_row, off},
+ {binary_strings, on}]).
%% == Native SQLite code
@@ -595,20 +773,33 @@ pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
pgsql_item_to_odbc({error, Error}) -> {error, Error};
pgsql_item_to_odbc(_) -> {updated, undefined}.
+pgsql_execute_to_odbc({ok, {<<"SELECT", _/binary>>, Rows}}) ->
+ {selected, [], [[Field || {_, Field} <- Row] || Row <- Rows]};
+pgsql_execute_to_odbc({ok, {'INSERT', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'DELETE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'UPDATE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({error, Error}) -> {error, Error};
+pgsql_execute_to_odbc(_) -> {updated, undefined}.
+
+
%% == Native MySQL code
%% part of init/1
%% Open a database connection to MySQL
mysql_connect(Server, Port, DB, Username, Password) ->
case p1_mysql_conn:start(binary_to_list(Server), Port,
- binary_to_list(Username), binary_to_list(Password),
- binary_to_list(DB), fun log/3)
+ binary_to_list(Username),
+ binary_to_list(Password),
+ binary_to_list(DB), fun log/3)
of
- {ok, Ref} ->
- p1_mysql_conn:fetch(Ref, [<<"set names 'utf8';">>],
- self()),
- {ok, Ref};
- Err -> Err
+ {ok, Ref} ->
+ p1_mysql_conn:fetch(
+ Ref, [<<"set names 'utf8mb4' collate 'utf8mb4_bin';">>], self()),
+ {ok, Ref};
+ Err -> Err
end.
%% Convert MySQL query result to Erlang ODBC result formalism
@@ -634,10 +825,36 @@ mysql_item_to_odbc(Columns, Recs) ->
{selected, [element(2, Column) || Column <- Columns], Recs}.
to_odbc({selected, Columns, Recs}) ->
- {selected, Columns, [tuple_to_list(Rec) || Rec <- Recs]};
+ Rows = [lists:map(
+ fun(I) when is_integer(I) ->
+ jlib:integer_to_binary(I);
+ (B) ->
+ B
+ end, Row) || Row <- Recs],
+ {selected, [list_to_binary(C) || C <- Columns], Rows};
+to_odbc({error, Reason}) when is_list(Reason) ->
+ {error, list_to_binary(Reason)};
to_odbc(Res) ->
Res.
+get_db_version(#state{db_type = pgsql} = State) ->
+ case pgsql:squery(State#state.db_ref,
+ <<"select current_setting('server_version_num')">>) of
+ {ok, [{_, _, [[SVersion]]}]} ->
+ case catch binary_to_integer(SVersion) of
+ Version when is_integer(Version) ->
+ State#state{db_version = Version};
+ Error ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Error]),
+ State
+ end;
+ Res ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Res]),
+ State
+ end;
+get_db_version(State) ->
+ State.
+
log(Level, Format, Args) ->
case Level of
debug -> ?DEBUG(Format, Args);
@@ -650,6 +867,7 @@ db_opts(Host) ->
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
+ (mssql) -> mssql;
(odbc) -> odbc
end, odbc),
Server = ejabberd_config:get_option({odbc_server, Host},
@@ -665,6 +883,7 @@ db_opts(Host) ->
{odbc_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
case Type of
+ mssql -> ?MSSQL_PORT;
mysql -> ?MYSQL_PORT;
pgsql -> ?PGSQL_PORT
end),
@@ -677,9 +896,80 @@ db_opts(Host) ->
Pass = ejabberd_config:get_option({odbc_password, Host},
fun iolist_to_binary/1,
<<"">>),
- [Type, Server, Port, DB, User, Pass]
+ case Type of
+ mssql ->
+ [odbc, <<"DSN=", Host/binary, ";UID=", User/binary,
+ ";PWD=", Pass/binary>>];
+ _ ->
+ [Type, Server, Port, DB, User, Pass]
+ end
end.
+init_mssql(Host) ->
+ Server = ejabberd_config:get_option({odbc_server, Host},
+ fun iolist_to_binary/1,
+ <<"localhost">>),
+ Port = ejabberd_config:get_option(
+ {odbc_port, Host},
+ fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
+ ?MSSQL_PORT),
+ DB = ejabberd_config:get_option({odbc_database, Host},
+ fun iolist_to_binary/1,
+ <<"ejabberd">>),
+ FreeTDS = io_lib:fwrite("[~s]~n"
+ "\thost = ~s~n"
+ "\tport = ~p~n"
+ "\ttds version = 7.1~n",
+ [Host, Server, Port]),
+ ODBCINST = io_lib:fwrite("[freetds]~n"
+ "Description = MSSQL connection~n"
+ "Driver = libtdsodbc.so~n"
+ "Setup = libtdsS.so~n"
+ "UsageCount = 1~n"
+ "FileUsage = 1~n", []),
+ ODBCINI = io_lib:fwrite("[~s]~n"
+ "Description = MS SQL~n"
+ "Driver = freetds~n"
+ "Servername = ~s~n"
+ "Database = ~s~n"
+ "Port = ~p~n",
+ [Host, Host, DB, Port]),
+ ?DEBUG("~s:~n~s", [freetds_config(), FreeTDS]),
+ ?DEBUG("~s:~n~s", [odbcinst_config(), ODBCINST]),
+ ?DEBUG("~s:~n~s", [odbc_config(), ODBCINI]),
+ case filelib:ensure_dir(freetds_config()) of
+ ok ->
+ try
+ ok = file:write_file(freetds_config(), FreeTDS, [append]),
+ ok = file:write_file(odbcinst_config(), ODBCINST),
+ ok = file:write_file(odbc_config(), ODBCINI, [append]),
+ os:putenv("ODBCSYSINI", tmp_dir()),
+ os:putenv("FREETDS", freetds_config()),
+ os:putenv("FREETDSCONF", freetds_config()),
+ ok
+ catch error:{badmatch, {error, Reason} = Err} ->
+ ?ERROR_MSG("failed to create temporary files in ~s: ~s",
+ [tmp_dir(), file:format_error(Reason)]),
+ Err
+ end;
+ {error, Reason} = Err ->
+ ?ERROR_MSG("failed to create temporary directory ~s: ~s",
+ [tmp_dir(), file:format_error(Reason)]),
+ Err
+ end.
+
+tmp_dir() ->
+ filename:join(["/tmp", "ejabberd"]).
+
+odbc_config() ->
+ filename:join(tmp_dir(), "odbc.ini").
+
+freetds_config() ->
+ filename:join(tmp_dir(), "freetds.conf").
+
+odbcinst_config() ->
+ filename:join(tmp_dir(), "odbcinst.ini").
+
max_fsm_queue() ->
ejabberd_config:get_option(
max_fsm_queue,
@@ -690,3 +980,40 @@ fsm_limit_opts() ->
N when is_integer(N) -> [{max_queue, N}];
_ -> []
end.
+
+check_error({error, Why} = Err, #sql_query{} = Query) ->
+ ?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
+ [Query#sql_query.hash, Query#sql_query.loc, Why]),
+ Err;
+check_error({error, Why} = Err, Query) ->
+ case catch iolist_to_binary(Query) of
+ SQuery when is_binary(SQuery) ->
+ ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]);
+ _ ->
+ ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why])
+ end,
+ Err;
+check_error(Result, _Query) ->
+ Result.
+
+opt_type(max_fsm_queue) ->
+ fun (N) when is_integer(N), N > 0 -> N end;
+opt_type(odbc_database) -> fun iolist_to_binary/1;
+opt_type(odbc_keepalive_interval) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(odbc_password) -> fun iolist_to_binary/1;
+opt_type(odbc_port) ->
+ fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
+opt_type(odbc_server) -> fun iolist_to_binary/1;
+opt_type(odbc_type) ->
+ fun (mysql) -> mysql;
+ (pgsql) -> pgsql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(odbc_username) -> fun iolist_to_binary/1;
+opt_type(_) ->
+ [max_fsm_queue, odbc_database, odbc_keepalive_interval,
+ odbc_password, odbc_port, odbc_server, odbc_type,
+ odbc_username].
diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_odbc_sup.erl
index 37128e26..65fa3c1c 100644
--- a/src/ejabberd_odbc_sup.erl
+++ b/src/ejabberd_odbc_sup.erl
@@ -5,7 +5,7 @@
%%% Created : 22 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,13 @@
-module(ejabberd_odbc_sup).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-%% API
-export([start_link/1, init/1, add_pid/2, remove_pid/2,
- get_pids/1, get_random_pid/1, transform_options/1]).
+ get_pids/1, get_random_pid/1, transform_options/1,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -71,11 +73,14 @@ init([Host]) ->
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
+ (mssql) -> mssql;
(odbc) -> odbc
end, odbc),
case Type of
sqlite ->
check_sqlite_db(Host);
+ mssql ->
+ ejabberd_odbc:init_mssql(Host);
_ ->
ok
end,
@@ -97,7 +102,7 @@ get_pids(Host) ->
get_random_pid(Host) ->
case get_pids(Host) of
[] -> none;
- Pids -> lists:nth(erlang:phash(now(), length(Pids)), Pids)
+ Pids -> lists:nth(erlang:phash(p1_time_compat:unique_integer(), length(Pids)), Pids)
end.
add_pid(Host, Pid) ->
@@ -205,3 +210,17 @@ read_lines(Fd, File, Acc) ->
?ERROR_MSG("Failed read from lite.sql, reason: ~p", [Err]),
[]
end.
+
+opt_type(odbc_pool_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(odbc_start_interval) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(odbc_type) ->
+ fun (mysql) -> mysql;
+ (pgsql) -> pgsql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(_) ->
+ [odbc_pool_size, odbc_start_interval, odbc_type].
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 641db497..123189dd 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -5,11 +5,10 @@
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
%%%-------------------------------------------------------------------
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2012, Evgeniy Khramtsov
%%% @doc
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -38,8 +37,12 @@
-module(ejabberd_piefxis).
-%% API
--export([import_file/1, export_server/1, export_host/2]).
+-behaviour(ejabberd_config).
+
+-protocol({xep, 227, '1.0'}).
+
+-export([import_file/1, export_server/1, export_host/2,
+ opt_type/1]).
-define(CHUNK_SIZE, 1024*20). %20k
@@ -61,7 +64,7 @@
-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>).
-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>).
--record(state, {xml_stream_state :: xml_stream:xml_stream_state(),
+-record(state, {xml_stream_state :: fxml_stream:xml_stream_state(),
user = <<"">> :: binary(),
server = <<"">> :: binary(),
fd :: file:io_device(),
@@ -77,12 +80,11 @@ import_file(FileName) ->
import_file(FileName, #state{}).
-spec import_file(binary(), state()) -> ok | {error, atom()}.
-
import_file(FileName, State) ->
case file:open(FileName, [read, binary]) of
{ok, Fd} ->
Dir = filename:dirname(FileName),
- XMLStreamState = xml_stream:new(self(), infinity),
+ XMLStreamState = fxml_stream:new(self(), infinity),
Res = process(State#state{xml_stream_state = XMLStreamState,
fd = Fd,
dir = Dir}),
@@ -94,72 +96,14 @@ import_file(FileName, State) ->
{error, Reason}
end.
-%%%==================================
-%%%% Process Elements
-%%%==================================
-%%%% Process Element
-%%%==================================
-%%%% Add user
-%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
-%% -> ok | {error, ErrorText::string()}
-%% @doc Add a new user to the database.
-%% If user already exists, it will be only updated.
-spec export_server(binary()) -> any().
-
-%% @spec (User::string(), Password::string(), Domain::string())
-%% -> ok | {atomic, exists} | {error, not_allowed}
-%% @doc Create a new user
export_server(Dir) ->
export_hosts(?MYHOSTS, Dir).
-%%%==================================
-%%%% Populate user
-%% @spec (User::string(), Domain::string(), El::xml())
-%% -> ok | {error, not_found}
-%%
-%% @doc Add a new user from a XML file with a roster list.
-%%
-%% Example of a file:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='juliet' password='s3crEt'>
-%% <query xmlns='jabber:iq:roster'>
-%% <item jid='romeo@montague.net'
-%% name='Romeo'
-%% subscription='both'>
-%% <group>Friends</group>
-%% </item>
-%% </query>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
-spec export_host(binary(), binary()) -> any().
-
export_host(Dir, Host) ->
export_hosts([Host], Dir).
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with vCard tags values
-%% @ret ok | {error, not_found}
-%% @doc Read vcards from the XML and send it to the server
-%%
-%% Example:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='admin' password='s3crEt'>
-%% <vCard xmlns='vcard-temp'>
-%% <FN>Admin</FN>
-%% </vCard>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -191,11 +135,6 @@ export_hosts(Hosts, Dir) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with offline messages values
-%% @ret ok | {error, not_found}
-%% @doc Read off-line message from the XML and send it to the server
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
case file:open(DFn, [raw, write]) of
@@ -220,11 +159,6 @@ export_host(Dir, FnH, Host) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with private storage values
-%% @ret ok | {error, not_found}
-%% @doc Private storage parsing
export_users([{User, _S}|Users], Server, Fd) ->
case export_user(User, Server, Fd) of
ok ->
@@ -235,11 +169,9 @@ export_users([{User, _S}|Users], Server, Fd) ->
export_users([], _Server, _Fd) ->
ok.
-%%%==================================
-%%%% Utilities
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
Pass = case Password of
{_,_,_,_} ->
@@ -254,7 +186,7 @@ export_user(User, Server, Fd) ->
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
- print(Fd, xml:element_to_binary(
+ print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User},
{<<"password">>, Pass}],
@@ -278,7 +210,7 @@ parse_scram_password(PassData) ->
}.
get_vcard(User, Server) ->
- JID = jlib:make_jid(User, Server, <<>>),
+ JID = jid:make(User, Server, <<>>),
case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of
#iq{type = result, sub_el = [_|_] = VCardEls} ->
VCardEls;
@@ -286,7 +218,6 @@ get_vcard(User, Server) ->
[]
end.
-%%%==================================
get_offline(User, Server) ->
case mod_offline:get_offline_els(User, Server) of
[] ->
@@ -303,7 +234,6 @@ get_offline(User, Server) ->
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
end.
-%%%% Export hosts
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
@@ -330,9 +260,8 @@ get_privacy(User, Server) ->
[]
end.
-%% @spec (Dir::string(), Hosts::[string()]) -> ok
get_roster(User, Server) ->
- JID = jlib:make_jid(User, Server, <<>>),
+ JID = jid:make(User, Server, <<>>),
case mod_roster:get_roster(User, Server) of
[_|_] = Items ->
Subs =
@@ -346,8 +275,8 @@ get_roster(User, Server) ->
[#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
- jlib:jid_to_string(R#roster.jid)},
- {<<"to">>, jlib:jid_to_string(JID)},
+ jid:to_string(R#roster.jid)},
+ {<<"to">>, jid:to_string(JID)},
{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, <<"subscribe">>}],
children =
@@ -384,17 +313,17 @@ get_private(User, Server) ->
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
case file:read(Fd, ?CHUNK_SIZE) of
{ok, Data} ->
- NewXMLStreamState = xml_stream:parse(XMLStreamState, Data),
+ NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data),
case process_els(State#state{xml_stream_state =
NewXMLStreamState}) of
{ok, NewState} ->
process(NewState);
Err ->
- xml_stream:close(NewXMLStreamState),
+ fxml_stream:close(NewXMLStreamState),
Err
end;
eof ->
- xml_stream:close(XMLStreamState),
+ fxml_stream:close(XMLStreamState),
ok
end.
@@ -412,7 +341,7 @@ process_els(State) ->
end.
process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_PIEFXIS ->
{ok, State};
?NS_PIE ->
@@ -427,7 +356,7 @@ process_el({xmlstreamcdata, _}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
attrs = Attrs}},
#state{dir = Dir, user = <<"">>} = State) ->
- FileName = xml:get_attr_s(<<"href">>, Attrs),
+ FileName = fxml:get_attr_s(<<"href">>, Attrs),
case import_file(filename:join([Dir, FileName]), State) of
ok ->
{ok, State};
@@ -440,8 +369,8 @@ process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"host">>,
attrs = Attrs,
children = Els}}, State) ->
- JIDS = xml:get_attr_s(<<"jid">>, Attrs),
- case jlib:string_to_jid(JIDS) of
+ JIDS = fxml:get_attr_s(<<"jid">>, Attrs),
+ case jid:from_string(JIDS) of
#jid{lserver = S} ->
case lists:member(S, ?MYHOSTS) of
true ->
@@ -483,8 +412,8 @@ process_users([], State) ->
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
#state{server = LServer} = State) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
- Password = xml:get_attr_s(<<"password">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
+ Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
Pass = case PasswordFormat of
scram ->
@@ -496,7 +425,7 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
_ -> Password
end,
- case jlib:nodeprep(Name) of
+ case jid:nodeprep(Name) of
error ->
stop("Invalid 'user': ~s", [Name]);
LUser ->
@@ -522,7 +451,7 @@ process_user_els([], State) ->
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
- case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of
+ case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(El, State);
{<<"query">>, ?NS_PRIVACY} ->
@@ -573,15 +502,13 @@ process_roster(El, State = #state{user = U, server = S}) ->
stop("Failed to write roster: ~p", [Err])
end.
-%%%==================================
-%%%% Export server
process_privacy(El, State = #state{user = U, server = S}) ->
- JID = jlib:make_jid(U, S, <<"">>),
+ JID = jid:make(U, S, <<"">>),
case mod_privacy:process_iq_set(
[], JID, JID, #iq{type = set, sub_el = El}) of
{error, Error} = Err ->
#xmlel{children = Els} = El,
- Name = case xml:remove_cdata(Els) of
+ Name = case fxml:remove_cdata(Els) of
[#xmlel{name = N}] -> N;
_ -> undefined
end,
@@ -600,9 +527,8 @@ process_privacy(El, State = #state{user = U, server = S}) ->
{ok, State}
end.
-%% @spec (Dir::string()) -> ok
process_private(El, State = #state{user = U, server = S}) ->
- JID = jlib:make_jid(U, S, <<"">>),
+ JID = jid:make(U, S, <<"">>),
case mod_private:process_sm_iq(
JID, JID, #iq{type = set, sub_el = El}) of
#iq{type = result} ->
@@ -611,10 +537,8 @@ process_private(El, State = #state{user = U, server = S}) ->
stop("Failed to write private: ~p", [Err])
end.
-%%%==================================
-%%%% Export host
process_vcard(El, State = #state{user = U, server = S}) ->
- JID = jlib:make_jid(U, S, <<"">>),
+ JID = jid:make(U, S, <<"">>),
case mod_vcard:process_sm_iq(
JID, JID, #iq{type = set, sub_el = El}) of
#iq{type = result} ->
@@ -623,12 +547,11 @@ process_vcard(El, State = #state{user = U, server = S}) ->
stop("Failed to write vcard: ~p", [Err])
end.
-%% @spec (Dir::string(), Host::string()) -> ok
process_offline_msg(El, State = #state{user = U, server = S}) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
- case jlib:string_to_jid(FromS) of
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ case jid:from_string(FromS) of
#jid{} = From ->
- To = jlib:make_jid(U, S, <<>>),
+ To = jid:make(U, S, <<>>),
NewEl = jlib:replace_from_to(From, To, El),
case catch mod_offline:store_packet(From, To, NewEl) of
{'EXIT', _} = Err ->
@@ -640,12 +563,11 @@ process_offline_msg(El, State = #state{user = U, server = S}) ->
stop("Invalid 'from' = ~s", [FromS])
end.
-%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
process_presence(El, #state{user = U, server = S} = State) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
- case jlib:string_to_jid(FromS) of
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ case jid:from_string(FromS) of
#jid{} = From ->
- To = jlib:make_jid(U, S, <<>>),
+ To = jid:make(U, S, <<>>),
NewEl = jlib:replace_from_to(From, To, El),
ejabberd_router:route(From, To, NewEl),
{ok, State};
@@ -710,28 +632,9 @@ make_xinclude(Fn) ->
Base = filename:basename(Fn),
io_lib:format("<xi:include href='~s'/>", [Base]).
-%%%==================================
-%%%% Export user
-%% @spec (Fd, Username::string(), Host::string()) -> ok
-%% @doc Extract user information and print it.
-%% @spec (Username::string(), Host::string()) -> string()
-%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
-%%%==================================
-%%%% Interface with ejabberd offline storage
-%% Copied from mod_offline.erl and customized
-%%%==================================
-%%%% Interface with ejabberd private storage
-%%%==================================
-%%%% Disk file access
-%% @spec () -> string()
-%% @spec (Dir::string(), FnT::string()) -> string()
-%% @spec (FnT::string(), Host::string()) -> FnH::string()
-%% @doc Make the filename for the host.
-%% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"''
-%% @spec (Fn::string()) -> {ok, Fd}
-%% @spec (Fd) -> ok
-%% @spec (Fd, String::string()) -> ok
print(Fd, String) ->
-%%%==================================
-%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker:
file:write(Fd, String).
+
+opt_type(auth_password_format) -> fun (X) -> X end;
+opt_type(_) -> [auth_password_format].
+
diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl
index 93bd9fc4..ae405db0 100644
--- a/src/ejabberd_rdbms.erl
+++ b/src/ejabberd_rdbms.erl
@@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,14 +25,19 @@
-module(ejabberd_rdbms).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
--export([start/0]).
+-export([start/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
start() ->
+ file:delete(ejabberd_odbc:freetds_config()),
+ file:delete(ejabberd_odbc:odbc_config()),
+ file:delete(ejabberd_odbc:odbcinst_config()),
case lists:any(fun(H) -> needs_odbc(H) /= false end,
?MYHOSTS) of
true ->
@@ -70,16 +75,27 @@ start_odbc(Host, App) ->
%% Returns {true, App} if we have configured odbc for the given host
needs_odbc(Host) ->
- LHost = jlib:nameprep(Host),
+ LHost = jid:nameprep(Host),
case ejabberd_config:get_option({odbc_type, LHost},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
+ (mssql) -> mssql;
(odbc) -> odbc
end, undefined) of
mysql -> {true, p1_mysql};
pgsql -> {true, p1_pgsql};
sqlite -> {true, sqlite3};
+ mssql -> {true, odbc};
odbc -> {true, odbc};
undefined -> false
end.
+
+opt_type(odbc_type) ->
+ fun (mysql) -> mysql;
+ (pgsql) -> pgsql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(_) -> [odbc_type].
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index f63ae1cc..0a33e30e 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -5,7 +5,7 @@
%%% Created : 10 Nov 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -48,23 +48,17 @@
-include("logger.hrl").
-record(state,
- {socket :: inet:socket() | p1_tls:tls_socket() | ezlib:zlib_socket(),
- sock_mod = gen_tcp :: gen_tcp | p1_tls | ezlib,
+ {socket :: inet:socket() | fast_tls:tls_socket() | ezlib:zlib_socket(),
+ sock_mod = gen_tcp :: gen_tcp | fast_tls | ezlib,
shaper_state = none :: shaper:shaper(),
c2s_pid :: pid(),
max_stanza_size = infinity :: non_neg_integer() | infinity,
- xml_stream_state :: xml_stream:xml_stream_state(),
+ xml_stream_state :: fxml_stream:xml_stream_state(),
timeout = infinity:: timeout()}).
--define(HIBERNATE_TIMEOUT, 90000).
+-define(HIBERNATE_TIMEOUT, ejabberd_config:get_option(receiver_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
+
-%%====================================================================
-%% API
-%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
-spec start_link(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> ignore |
{error, any()} |
@@ -76,10 +70,6 @@ start_link(Socket, SockMod, Shaper, MaxStanzaSize) ->
-spec start(inet:socket(), atom(), shaper:shaper()) -> undefined | pid().
-%%--------------------------------------------------------------------
-%% Function: start() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start(Socket, SockMod, Shaper) ->
start(Socket, SockMod, Shaper, infinity).
@@ -87,9 +77,8 @@ start(Socket, SockMod, Shaper) ->
non_neg_integer() | infinity) -> undefined | pid().
start(Socket, SockMod, Shaper, MaxStanzaSize) ->
- {ok, Pid} =
- supervisor:start_child(ejabberd_receiver_sup,
- [Socket, SockMod, Shaper, MaxStanzaSize]),
+ {ok, Pid} = gen_server:start(ejabberd_receiver,
+ [Socket, SockMod, Shaper, MaxStanzaSize], []),
Pid.
-spec change_shaper(pid(), shaper:shaper()) -> ok.
@@ -101,7 +90,7 @@ change_shaper(Pid, Shaper) ->
reset_stream(Pid) -> do_call(Pid, reset_stream).
--spec starttls(pid(), p1_tls:tls_socket()) -> ok.
+-spec starttls(pid(), fast_tls:tls_socket()) -> ok.
starttls(Pid, TLSSocket) ->
do_call(Pid, {starttls, TLSSocket}).
@@ -127,13 +116,6 @@ close(Pid) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
ShaperState = shaper:new(Shaper),
Timeout = case SockMod of
@@ -145,66 +127,42 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
shaper_state = ShaperState,
max_stanza_size = MaxStanzaSize, timeout = Timeout}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
-handle_call({starttls, TLSSocket}, _From,
- #state{xml_stream_state = XMLStreamState,
- c2s_pid = C2SPid,
- max_stanza_size = MaxStanzaSize} = State) ->
- close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid,
- MaxStanzaSize),
- NewState = State#state{socket = TLSSocket,
- sock_mod = p1_tls,
- xml_stream_state = NewXMLStreamState},
- case p1_tls:recv_data(TLSSocket, <<"">>) of
+handle_call({starttls, TLSSocket}, _From, State) ->
+ State1 = reset_parser(State),
+ NewState = State1#state{socket = TLSSocket,
+ sock_mod = fast_tls},
+ case fast_tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} ->
- {reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
+ {reply, ok,
+ process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call({compress, Data}, _From,
- #state{xml_stream_state = XMLStreamState,
- c2s_pid = C2SPid, socket = Socket, sock_mod = SockMod,
- max_stanza_size = MaxStanzaSize} =
+ #state{socket = Socket, sock_mod = SockMod} =
State) ->
+ ejabberd:start_app(ezlib),
{ok, ZlibSocket} = ezlib:enable_zlib(SockMod,
Socket),
if Data /= undefined -> do_send(State, Data);
true -> ok
end,
- close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid,
- MaxStanzaSize),
- NewState = State#state{socket = ZlibSocket,
- sock_mod = ezlib,
- xml_stream_state = NewXMLStreamState},
+ State1 = reset_parser(State),
+ NewState = State1#state{socket = ZlibSocket,
+ sock_mod = ezlib},
case ezlib:recv_data(ZlibSocket, <<"">>) of
{ok, ZlibData} ->
- {reply, {ok, ZlibSocket},
- process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
- {error, _Reason} -> {stop, normal, ok, NewState}
+ {reply, {ok, ZlibSocket},
+ process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
+ {error, _Reason} ->
+ {stop, normal, ok, NewState}
end;
-handle_call(reset_stream, _From,
- #state{xml_stream_state = XMLStreamState,
- c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize} =
- State) ->
- close_stream(XMLStreamState),
- NewXMLStreamState = xml_stream:new(C2SPid,
- MaxStanzaSize),
+handle_call(reset_stream, _From, State) ->
+ NewState = reset_parser(State),
Reply = ok,
- {reply, Reply,
- State#state{xml_stream_state = NewXMLStreamState},
- ?HIBERNATE_TIMEOUT};
+ {reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
- XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
+ XMLStreamState = fxml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
@@ -213,12 +171,6 @@ handle_call({become_controller, C2SPid}, _From, State) ->
handle_call(_Request, _From, State) ->
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper),
{noreply, State#state{shaper_state = NewShaperState},
@@ -227,25 +179,19 @@ handle_cast(close, State) -> {stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info({Tag, _TCPSocket, Data},
#state{socket = Socket, sock_mod = SockMod} = State)
when (Tag == tcp) or (Tag == ssl) or
(Tag == ejabberd_xml) ->
case SockMod of
- p1_tls ->
- case p1_tls:recv_data(Socket, Data) of
+ fast_tls ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
{error, Reason} ->
if is_binary(Reason) ->
- ?ERROR_MSG("TLS error = ~s", [Reason]);
+ ?DEBUG("TLS error = ~s", [Reason]);
true ->
ok
end,
@@ -280,13 +226,6 @@ handle_info(timeout, State) ->
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid} =
@@ -299,10 +238,6 @@ terminate(_Reason,
catch (State#state.sock_mod):close(State#state.socket),
ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
@@ -347,7 +282,12 @@ process_data(Data,
shaper_state = ShaperState, c2s_pid = C2SPid} =
State) ->
?DEBUG("Received XML on stream = ~p", [(Data)]),
- XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
+ XMLStreamState1 = case XMLStreamState of
+ undefined ->
+ XMLStreamState;
+ _ ->
+ fxml_stream:parse(XMLStreamState, Data)
+ end,
{NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
if
C2SPid == undefined ->
@@ -371,7 +311,25 @@ element_wrapper(Element) -> Element.
close_stream(undefined) -> ok;
close_stream(XMLStreamState) ->
- xml_stream:close(XMLStreamState).
+ fxml_stream:close(XMLStreamState).
+
+reset_parser(#state{xml_stream_state = undefined} = State) ->
+ State;
+reset_parser(#state{c2s_pid = C2SPid,
+ max_stanza_size = MaxStanzaSize,
+ xml_stream_state = XMLStreamState}
+ = State) ->
+ NewStreamState = try fxml_stream:reset(XMLStreamState)
+ catch error:_ ->
+ close_stream(XMLStreamState),
+ case C2SPid of
+ undefined ->
+ undefined;
+ _ ->
+ fxml_stream:new(C2SPid, MaxStanzaSize)
+ end
+ end,
+ State#state{xml_stream_state = NewStreamState}.
do_send(State, Data) ->
(State#state.sock_mod):send(State#state.socket, Data).
diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl
index 86ebd184..b79774e3 100644
--- a/src/ejabberd_regexp.erl
+++ b/src/ejabberd_regexp.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Dec 2011 by Badlop
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl
index c8084674..575810ac 100644
--- a/src/ejabberd_riak.erl
+++ b/src/ejabberd_riak.erl
@@ -4,7 +4,9 @@
%%% Interface for Riak database
%%% @end
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
-%%% @copyright (C) 2002-2015 ProcessOne
+%%% @copyright (C) 2002-2016 ProcessOne
+%%%
+%%% 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
@@ -26,7 +28,7 @@
-behaviour(gen_server).
%% API
--export([start_link/4, get_proc/1, make_bucket/1, put/2, put/3,
+-export([start_link/5, get_proc/1, make_bucket/1, put/2, put/3,
get/2, get/3, get_by_index/4, delete/1, delete/2,
count_by_index/3, get_by_index_range/5,
get_keys/1, get_keys_by_index/3, is_connected/0,
@@ -66,12 +68,20 @@
%%% API
%%%===================================================================
%% @private
-start_link(Num, Server, Port, _StartInterval) ->
- gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port], []).
+start_link(Num, Server, Port, _StartInterval, Options) ->
+ gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port, Options], []).
%% @private
is_connected() ->
- catch riakc_pb_socket:is_connected(get_random_pid()).
+ lists:all(
+ fun({_Id, Pid, _Type, _Modules}) when is_pid(Pid) ->
+ case catch riakc_pb_socket:is_connected(get_riak_pid(Pid)) of
+ true -> true;
+ _ -> false
+ end;
+ (_) ->
+ false
+ end, supervisor:which_children(ejabberd_riak_sup)).
%% @private
get_proc(I) ->
@@ -427,10 +437,8 @@ map_key(Obj, _, _) ->
%%% gen_server API
%%%===================================================================
%% @private
-init([Server, Port]) ->
- case riakc_pb_socket:start(
- Server, Port,
- [auto_reconnect]) of
+init([Server, Port, Options]) ->
+ case riakc_pb_socket:start(Server, Port, Options) of
{ok, Pid} ->
erlang:monitor(process, Pid),
{ok, #state{pid = Pid}};
@@ -515,6 +523,9 @@ make_invalid_object(Val) ->
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
+ get_riak_pid(PoolPid).
+
+get_riak_pid(PoolPid) ->
case catch gen_server:call(PoolPid, get_pid) of
{ok, Pid} ->
Pid;
diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl
index bb36eb44..af811441 100644
--- a/src/ejabberd_riak_sup.erl
+++ b/src/ejabberd_riak_sup.erl
@@ -5,7 +5,7 @@
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,17 +24,13 @@
%%%----------------------------------------------------------------------
-module(ejabberd_riak_sup).
+
+-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-%% API
--export([start/0,
- start_link/0,
- init/1,
- get_pids/0,
- transform_options/1,
- get_random_pid/0,
- get_random_pid/1
- ]).
+-export([start/0, start_link/0, init/1, get_pids/0,
+ transform_options/1, get_random_pid/0, get_random_pid/1,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -107,12 +103,27 @@ init([]) ->
StartInterval = get_start_interval(),
Server = get_riak_server(),
Port = get_riak_port(),
+ CACertFile = get_riak_cacertfile(),
+ Username = get_riak_username(),
+ Password = get_riak_password(),
+ Options = lists:filter(
+ fun(X) -> X /= nil end,
+ [auto_reconnect,
+ {keepalive, true},
+ if CACertFile /= nil -> {cacertfile ,CACertFile};
+ true -> nil
+ end,
+ if (Username /= nil) and (Password /= nil) ->
+ {credentials, Username, Password};
+ true -> nil
+ end
+ ]),
{ok, {{one_for_one, PoolSize*10, 1},
lists:map(
fun(I) ->
{ejabberd_riak:get_proc(I),
{ejabberd_riak, start_link,
- [I, Server, Port, StartInterval*1000]},
+ [I, Server, Port, StartInterval*1000, Options]},
transient, 2000, worker, [?MODULE]}
end, lists:seq(1, PoolSize))}}.
@@ -135,6 +146,27 @@ get_riak_server() ->
binary_to_list(iolist_to_binary(S))
end, ?DEFAULT_RIAK_HOST).
+get_riak_cacertfile() ->
+ ejabberd_config:get_option(
+ riak_cacertfile,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_username() ->
+ ejabberd_config:get_option(
+ riak_username,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_password() ->
+ ejabberd_config:get_option(
+ riak_password,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
get_riak_port() ->
ejabberd_config:get_option(
riak_port,
@@ -145,7 +177,7 @@ get_pids() ->
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
get_random_pid() ->
- get_random_pid(now()).
+ get_random_pid(p1_time_compat:monotonic_time()).
get_random_pid(Term) ->
I = erlang:phash2(Term, get_pool_size()) + 1,
@@ -158,3 +190,17 @@ transform_options({riak_server, {S, P}}, Opts) ->
[{riak_server, S}, {riak_port, P}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
+
+opt_type(modules) -> fun (L) when is_list(L) -> L end;
+opt_type(riak_pool_size) ->
+ fun (N) when is_integer(N), N >= 1 -> N end;
+opt_type(riak_port) -> fun (_) -> true end;
+opt_type(riak_server) -> fun (_) -> true end;
+opt_type(riak_start_interval) ->
+ fun (N) when is_integer(N), N >= 1 -> N end;
+opt_type(riak_cacertfile) -> fun iolist_to_binary/1;
+opt_type(riak_username) -> fun iolist_to_binary/1;
+opt_type(riak_password) -> fun iolist_to_binary/1;
+opt_type(_) ->
+ [modules, riak_pool_size, riak_port, riak_server,
+ riak_start_interval, riak_cacertfile, riak_username, riak_password].
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index 76ef71dc..e29d6acf 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -5,7 +5,7 @@
%%% Created : 27 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_router).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -34,7 +36,9 @@
route_error/4,
register_route/1,
register_route/2,
+ register_route/3,
register_routes/1,
+ host_of_route/1,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@@ -43,9 +47,8 @@
-export([start_link/0]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -54,7 +57,7 @@
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
--record(route, {domain, pid, local_hint}).
+-record(route, {domain, server_host, pid, local_hint}).
-record(state, {}).
@@ -85,7 +88,7 @@ route(From, To, Packet) ->
route_error(From, To, ErrPacket, OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
- case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
+ case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
end.
@@ -93,19 +96,29 @@ route_error(From, To, ErrPacket, OrigPacket) ->
-spec register_route(binary()) -> term().
register_route(Domain) ->
- register_route(Domain, undefined).
+ ?WARNING_MSG("~s:register_route/1 is deprected, "
+ "use ~s:register_route/2 instead",
+ [?MODULE, ?MODULE]),
+ register_route(Domain, ?MYNAME).
--spec register_route(binary(), local_hint()) -> term().
+-spec register_route(binary(), binary()) -> term().
-register_route(Domain, LocalHint) ->
- case jlib:nameprep(Domain) of
- error -> erlang:error({invalid_domain, Domain});
- LDomain ->
+register_route(Domain, ServerHost) ->
+ register_route(Domain, ServerHost, undefined).
+
+-spec register_route(binary(), binary(), local_hint()) -> term().
+
+register_route(Domain, ServerHost, LocalHint) ->
+ case {jid:nameprep(Domain), jid:nameprep(ServerHost)} of
+ {error, _} -> erlang:error({invalid_domain, Domain});
+ {_, error} -> erlang:error({invalid_domain, ServerHost});
+ {LDomain, LServerHost} ->
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun () ->
mnesia:write(#route{domain = LDomain, pid = Pid,
+ server_host = LServerHost,
local_hint = LocalHint})
end,
mnesia:transaction(F);
@@ -114,53 +127,49 @@ register_route(Domain, LocalHint) ->
case mnesia:wread({route, LDomain}) of
[] ->
mnesia:write(#route{domain = LDomain,
+ server_host = LServerHost,
pid = Pid,
local_hint = 1}),
- lists:foreach(fun (I) ->
- mnesia:write(#route{domain
- =
- LDomain,
- pid
- =
- undefined,
- local_hint
- =
- I})
- end,
- lists:seq(2, N));
+ lists:foreach(
+ fun (I) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = undefined,
+ server_host = LServerHost,
+ local_hint = I})
+ end,
+ lists:seq(2, N));
Rs ->
- lists:any(fun (#route{pid = undefined,
- local_hint = I} =
- R) ->
- mnesia:write(#route{domain =
- LDomain,
- pid =
- Pid,
- local_hint
- =
- I}),
- mnesia:delete_object(R),
- true;
- (_) -> false
- end,
- Rs)
+ lists:any(
+ fun (#route{pid = undefined,
+ local_hint = I} = R) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = Pid,
+ server_host = LServerHost,
+ local_hint = I}),
+ mnesia:delete_object(R),
+ true;
+ (_) -> false
+ end,
+ Rs)
end
end,
mnesia:transaction(F)
end
end.
--spec register_routes([binary()]) -> ok.
+-spec register_routes([{binary(), binary()}]) -> ok.
register_routes(Domains) ->
- lists:foreach(fun (Domain) -> register_route(Domain)
+ lists:foreach(fun ({Domain, ServerHost}) -> register_route(Domain, ServerHost)
end,
Domains).
-spec unregister_route(binary()) -> term().
unregister_route(Domain) ->
- case jlib:nameprep(Domain) of
+ case jid:nameprep(Domain) of
error -> erlang:error({invalid_domain, Domain});
LDomain ->
Pid = self(),
@@ -182,7 +191,9 @@ unregister_route(Domain) ->
of
[R] ->
I = R#route.local_hint,
+ ServerHost = R#route.server_host,
mnesia:write(#route{domain = LDomain,
+ server_host = ServerHost,
pid = undefined,
local_hint = I}),
mnesia:delete_object(R);
@@ -210,6 +221,20 @@ dirty_get_all_routes() ->
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
+-spec host_of_route(binary()) -> binary().
+
+host_of_route(Domain) ->
+ case jid:nameprep(Domain) of
+ error ->
+ erlang:error({invalid_domain, Domain});
+ LDomain ->
+ case mnesia:dirty_read(route, LDomain) of
+ [#route{server_host = ServerHost}|_] ->
+ ServerHost;
+ [] ->
+ erlang:error({unregistered_route, Domain})
+ end
+ end.
%%====================================================================
%% gen_server callbacks
@@ -225,7 +250,8 @@ dirty_get_all_domains() ->
init([]) ->
update_tables(),
mnesia:create_table(route,
- [{ram_copies, [node()]}, {type, bag},
+ [{ram_copies, [node()]},
+ {type, bag},
{attributes, record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies),
mnesia:subscribe({table, route, simple}),
@@ -281,8 +307,11 @@ handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
if is_integer(E#route.local_hint) ->
LDomain = E#route.domain,
I = E#route.local_hint,
+ ServerHost = E#route.server_host,
mnesia:write(#route{domain =
LDomain,
+ server_host =
+ ServerHost,
pid =
undefined,
local_hint =
@@ -342,17 +371,17 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
end;
Rs ->
Value = case
- ejabberd_config:get_local_option({domain_balancing,
- LDstDomain}, fun(D) when is_atom(D) -> D end)
+ ejabberd_config:get_option({domain_balancing,
+ LDstDomain}, fun(D) when is_atom(D) -> D end)
of
- undefined -> now();
- random -> now();
- source -> jlib:jid_tolower(From);
- destination -> jlib:jid_tolower(To);
+ undefined -> p1_time_compat:monotonic_time();
+ random -> p1_time_compat:monotonic_time();
+ source -> jid:tolower(From);
+ destination -> jid:tolower(To);
bare_source ->
- jlib:jid_remove_resource(jlib:jid_tolower(From));
+ jid:remove_resource(jid:tolower(From));
bare_destination ->
- jlib:jid_remove_resource(jlib:jid_tolower(To))
+ jid:remove_resource(jid:tolower(To))
end,
case get_component_number(LDstDomain) of
undefined ->
@@ -392,12 +421,10 @@ get_component_number(LDomain) ->
undefined).
update_tables() ->
- case catch mnesia:table_info(route, attributes) of
- [domain, node, pid] -> mnesia:delete_table(route);
- [domain, pid] -> mnesia:delete_table(route);
- [domain, pid, local_hint] -> ok;
- [domain, pid, local_hint|_] -> mnesia:delete_table(route);
- {'EXIT', _} -> ok
+ try
+ mnesia:transform_table(route, ignore, record_info(fields, route))
+ catch exit:{aborted, {no_exists, _}} ->
+ ok
end,
case lists:member(local_route,
mnesia:system_info(tables))
@@ -406,3 +433,13 @@ update_tables() ->
false -> ok
end.
+opt_type(domain_balancing) ->
+ fun (random) -> random;
+ (source) -> source;
+ (destination) -> destination;
+ (bare_source) -> bare_source;
+ (bare_destination) -> bare_destination
+ end;
+opt_type(domain_balancing_component_number) ->
+ fun (N) when is_integer(N), N > 1 -> N end;
+opt_type(_) -> [domain_balancing, domain_balancing_component_number].
diff --git a/src/ejabberd_router_multicast.erl b/src/ejabberd_router_multicast.erl
index 9cd7dd3d..fa32c8ed 100644
--- a/src/ejabberd_router_multicast.erl
+++ b/src/ejabberd_router_multicast.erl
@@ -3,6 +3,24 @@
%%% Author : Badlop <badlop@process-one.net>
%%% Purpose : Multicast router
%%% Created : 11 Aug 2007 by Badlop <badlop@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_router_multicast).
@@ -51,7 +69,7 @@ route_multicast(From, Domain, Destinations, Packet) ->
end.
register_route(Domain) ->
- case jlib:nameprep(Domain) of
+ case jid:nameprep(Domain) of
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
@@ -64,7 +82,7 @@ register_route(Domain) ->
end.
unregister_route(Domain) ->
- case jlib:nameprep(Domain) of
+ case jid:nameprep(Domain) of
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
@@ -191,9 +209,9 @@ code_change(_OldVsn, State, _Extra) ->
do_route(From, Domain, Destinations, Packet) ->
?DEBUG("route_multicast~n\tfrom ~s~n\tdomain ~s~n\tdestinations ~p~n\tpacket ~p~n",
- [jlib:jid_to_string(From),
+ [jid:to_string(From),
Domain,
- [jlib:jid_to_string(To) || To <- Destinations],
+ [jid:to_string(To) || To <- Destinations],
Packet]),
%% Try to find an appropriate multicast service
@@ -202,14 +220,18 @@ do_route(From, Domain, Destinations, Packet) ->
%% If no multicast service is available in this server, send manually
[] -> do_route_normal(From, Destinations, Packet);
- %% If available, send the packet using multicast service
- [R] ->
- case R#route_multicast.pid of
- Pid when is_pid(Pid) ->
- Pid ! {route_trusted, From, Destinations, Packet};
- _ -> do_route_normal(From, Destinations, Packet)
- end
+ %% If some is available, send the packet using multicast service
+ Rs when is_list(Rs) ->
+ Pid = pick_multicast_pid(Rs),
+ Pid ! {route_trusted, From, Destinations, Packet}
end.
+pick_multicast_pid(Rs) ->
+ List = case [R || R <- Rs, node(R#route_multicast.pid) == node()] of
+ [] -> Rs;
+ RLocals -> RLocals
+ end,
+ (hd(List))#route_multicast.pid.
+
do_route_normal(From, Destinations, Packet) ->
[ejabberd_router:route(From, To, Packet) || To <- Destinations].
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index a7b3234f..0eab4633 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -5,7 +5,7 @@
%%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,27 +25,32 @@
-module(ejabberd_s2s).
+-protocol({xep, 220, '1.1'}).
+
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
-export([start_link/0, route/3, have_connection/1,
- has_key/2, get_connections_pids/1, try_register/1,
- remove_connection/3, find_connection/2,
+ make_key/2, get_connections_pids/1, try_register/1,
+ remove_connection/2, find_connection/2,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
- check_peer_certificate/3]).
+ check_peer_certificate/3,
+ get_commands_spec/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-%% ejabberd API
--export([get_info_s2s_connections/1, transform_options/1]).
+-export([get_info_s2s_connections/1,
+ transform_options/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -71,23 +76,15 @@
%% once a server is temporarly blocked, it stay blocked for 60 seconds
-record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_',
- pid = self() :: pid() | '_' | '$1',
- key = <<"">> :: binary() | '_'}).
+ pid = self() :: pid() | '_' | '$1'}).
-record(state, {}).
-record(temporarily_blocked, {host = <<"">> :: binary(),
- timestamp = now() :: erlang:timestamp()}).
+ timestamp :: integer()}).
-type temporarily_blocked() :: #temporarily_blocked{}.
-%%====================================================================
-%% API
-%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
@@ -117,9 +114,9 @@ external_host_overloaded(Host) ->
"seconds",
[Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
mnesia:transaction(fun () ->
+ Time = p1_time_compat:monotonic_time(),
mnesia:write(#temporarily_blocked{host = Host,
- timestamp =
- now()})
+ timestamp = Time})
end).
-spec is_temporarly_blocked(binary()) -> boolean().
@@ -128,7 +125,8 @@ is_temporarly_blocked(Host) ->
case mnesia:dirty_read(temporarily_blocked, Host) of
[] -> false;
[#temporarily_blocked{timestamp = T} = Entry] ->
- case timer:now_diff(now(), T) of
+ Diff = p1_time_compat:monotonic_time() - T,
+ case p1_time_compat:convert_time_unit(Diff, native, micro_seconds) of
N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
mnesia:dirty_delete_object(Entry), false;
_ -> true
@@ -136,19 +134,15 @@ is_temporarly_blocked(Host) ->
end.
-spec remove_connection({binary(), binary()},
- pid(), binary()) -> {atomic, ok} |
- ok |
- {aborted, any()}.
+ pid()) -> {atomic, ok} | ok | {aborted, any()}.
-remove_connection(FromTo, Pid, Key) ->
+remove_connection(FromTo, Pid) ->
case catch mnesia:dirty_match_object(s2s,
- #s2s{fromto = FromTo, pid = Pid,
- _ = '_'})
+ #s2s{fromto = FromTo, pid = Pid})
of
- [#s2s{pid = Pid, key = Key}] ->
+ [#s2s{pid = Pid}] ->
F = fun () ->
- mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid,
- key = Key})
+ mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
mnesia:transaction(F);
_ -> ok
@@ -158,25 +152,12 @@ remove_connection(FromTo, Pid, Key) ->
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
- [_] ->
+ [_] ->
true;
_ ->
false
end.
--spec has_key({binary(), binary()}, binary()) -> boolean().
-
-has_key(FromTo, Key) ->
- case mnesia:dirty_select(s2s,
- [{#s2s{fromto = FromTo, key = Key, _ = '_'},
- [],
- ['$_']}]) of
- [] ->
- false;
- _ ->
- true
- end.
-
-spec get_connections_pids({binary(), binary()}) -> [pid()].
get_connections_pids(FromTo) ->
@@ -187,10 +168,9 @@ get_connections_pids(FromTo) ->
[]
end.
--spec try_register({binary(), binary()}) -> {key, binary()} | false.
+-spec try_register({binary(), binary()}) -> boolean().
try_register(FromTo) ->
- Key = randoms:get_string(),
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
@@ -200,9 +180,8 @@ try_register(FromTo) ->
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
- mnesia:write(#s2s{fromto = FromTo, pid = self(),
- key = Key}),
- {key, Key};
+ mnesia:write(#s2s{fromto = FromTo, pid = self()}),
+ true;
true -> false
end
end,
@@ -221,7 +200,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
{ok, Cert} ->
case SockMod:get_verify_result(Sock) of
0 ->
- case idna:domain_utf8_to_ascii(Peer) of
+ case ejabberd_idna:domain_utf8_to_ascii(Peer) of
false ->
{error, <<"Cannot decode remote server name">>};
AsciiPeer ->
@@ -235,61 +214,44 @@ check_peer_certificate(SockMod, Sock, Peer) ->
end
end;
VerifyRes ->
- {error, p1_tls:get_cert_verify_string(VerifyRes, Cert)}
+ {error, fast_tls:get_cert_verify_string(VerifyRes, Cert)}
end;
+ {error, _Reason} ->
+ {error, <<"Cannot get peer certificate">>};
error ->
- {error, <<"Cannot get peer certificate">>}
+ {error, <<"Cannot get peer certificate">>}
end.
+make_key({From, To}, StreamID) ->
+ Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end),
+ p1_sha:to_hexlist(
+ crypto:hmac(sha256, p1_sha:to_hexlist(crypto:hash(sha256, Secret)),
+ [To, " ", From, " ", StreamID])).
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([]) ->
update_tables(),
- mnesia:create_table(s2s, [{ram_copies, [node()]}, {type, bag},
- {attributes, record_info(fields, s2s)}]),
+ mnesia:create_table(s2s,
+ [{ram_copies, [node()]},
+ {type, bag},
+ {attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
- ejabberd_commands:register_commands(commands()),
- mnesia:create_table(temporarily_blocked, [{ram_copies, [node()]}, {attributes, record_info(fields, temporarily_blocked)}]),
+ ejabberd_commands:register_commands(get_commands_spec()),
+ mnesia:create_table(temporarily_blocked,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, temporarily_blocked)}]),
{ok, #state{}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
+ {reply, ok, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
@@ -303,27 +265,17 @@ handle_info({route, From, To, Packet}, State) ->
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
- ejabberd_commands:unregister_commands(commands()),
+ ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
@@ -347,8 +299,8 @@ do_route(From, To, Packet) ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
NewAttrs =
- jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To), Attrs),
+ jlib:replace_from_to_attrs(jid:to_string(From),
+ jid:to_string(To), Attrs),
#jid{lserver = MyServer} = From,
ejabberd_hooks:run(s2s_send_packet, MyServer,
[From, To, Packet]),
@@ -356,7 +308,7 @@ do_route(From, To, Packet) ->
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
ok;
{aborted, _Reason} ->
- case xml:get_tag_attr_s(<<"type">>, Packet) of
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
@@ -422,7 +374,7 @@ choose_pid(From, Pids) ->
Ps -> Ps
end,
Pid =
- lists:nth(erlang:phash(jlib:jid_remove_resource(From),
+ lists:nth(erlang:phash(jid:remove_resource(From),
length(Pids1)),
Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
@@ -442,17 +394,15 @@ open_several_connections(N, MyServer, Server, From,
new_connection(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) ->
- Key = randoms:get_string(),
{ok, Pid} = ejabberd_s2s_out:start(
- MyServer, Server, {new, Key}),
+ MyServer, Server, new),
F = fun() ->
L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
- mnesia:write(#s2s{fromto = FromTo, pid = Pid,
- key = Key}),
+ mnesia:write(#s2s{fromto = FromTo, pid = Pid}),
?INFO_MSG("New s2s connection started ~p", [Pid]),
Pid;
true -> choose_connection(From, L)
@@ -467,7 +417,7 @@ new_connection(MyServer, Server, From, FromTo,
max_s2s_connections_number({From, To}) ->
case acl:match_rule(From, max_s2s_connections,
- jlib:make_jid(<<"">>, To, <<"">>))
+ jid:make(<<"">>, To, <<"">>))
of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
@@ -475,7 +425,7 @@ max_s2s_connections_number({From, To}) ->
max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule(From, max_s2s_connections_per_node,
- jlib:make_jid(<<"">>, To, <<"">>))
+ jid:make(<<"">>, To, <<"">>))
of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
@@ -514,17 +464,19 @@ parent_domains(Domain) ->
end,
[], lists:reverse(str:tokens(Domain, <<".">>))).
-send_element(Pid, El) -> Pid ! {send_element, El}.
+send_element(Pid, El) ->
+ Pid ! {send_element, El}.
%%%----------------------------------------------------------------------
%%% ejabberd commands
-commands() ->
+get_commands_spec() ->
[#ejabberd_commands{name = incoming_s2s_number,
tags = [stats, s2s],
desc =
"Number of incoming s2s connections on "
"the node",
+ policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number,
@@ -532,6 +484,7 @@ commands() ->
desc =
"Number of outgoing s2s connections on "
"the node",
+ policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}}].
@@ -552,16 +505,17 @@ update_tables() ->
end,
case catch mnesia:table_info(s2s, attributes) of
[fromto, node, key] ->
- mnesia:transform_table(s2s, ignore, [fromto, pid, key]),
+ mnesia:transform_table(s2s, ignore, [fromto, pid]),
mnesia:clear_table(s2s);
- [fromto, pid, key] -> ok;
+ [fromto, pid, key] ->
+ mnesia:transform_table(s2s, ignore, [fromto, pid]),
+ mnesia:clear_table(s2s);
+ [fromto, pid] -> ok;
{'EXIT', _} -> ok
end,
case lists:member(local_s2s, mnesia:system_info(tables)) of
- true ->
- mnesia:delete_table(local_s2s);
- false ->
- ok
+ true -> mnesia:delete_table(local_s2s);
+ false -> ok
end.
%% Check if host is in blacklist or white list
@@ -585,7 +539,7 @@ allow_host1(MyHost, S2SHost) ->
s2s_access,
fun(A) when is_atom(A) -> A end,
all),
- JID = jlib:make_jid(<<"">>, S2SHost, <<"">>),
+ JID = jid:make(<<"">>, S2SHost, <<"">>),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
allow ->
@@ -655,10 +609,15 @@ get_s2s_state(S2sPid) ->
[{s2s_pid, S2sPid} | Infos].
get_cert_domains(Cert) ->
- {rdnSequence, Subject} =
- (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
- Extensions =
- (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions,
+ TBSCert = Cert#'Certificate'.tbsCertificate,
+ Subject = case TBSCert#'TBSCertificate'.subject of
+ {rdnSequence, Subj} -> lists:flatten(Subj);
+ _ -> []
+ end,
+ Extensions = case TBSCert#'TBSCertificate'.extensions of
+ Exts when is_list(Exts) -> Exts;
+ _ -> []
+ end,
lists:flatmap(fun (#'AttributeTypeAndValue'{type =
?'id-at-commonName',
value = Val}) ->
@@ -669,7 +628,7 @@ get_cert_domains(Cert) ->
true -> error
end,
if D /= error ->
- case jlib:string_to_jid(D) of
+ case jid:from_string(D) of
#jid{luser = <<"">>, lserver = LD,
lresource = <<"">>} ->
[LD];
@@ -681,7 +640,7 @@ get_cert_domains(Cert) ->
end;
(_) -> []
end,
- lists:flatten(Subject))
+ Subject)
++
lists:flatmap(fun (#'Extension'{extnID =
?'id-ce-subjectAltName',
@@ -705,7 +664,7 @@ get_cert_domains(Cert) ->
when
is_binary(D) ->
case
- jlib:string_to_jid((D))
+ jid:from_string((D))
of
#jid{luser =
<<"">>,
@@ -714,7 +673,7 @@ get_cert_domains(Cert) ->
lresource =
<<"">>} ->
case
- idna:domain_utf8_to_ascii(LD)
+ ejabberd_idna:domain_utf8_to_ascii(LD)
of
false ->
[];
@@ -728,7 +687,7 @@ get_cert_domains(Cert) ->
({dNSName, D})
when is_list(D) ->
case
- jlib:string_to_jid(list_to_binary(D))
+ jid:from_string(list_to_binary(D))
of
#jid{luser = <<"">>,
lserver = LD,
@@ -771,3 +730,11 @@ match_labels([DL | DLabels], [PL | PLabels]) ->
end;
false -> false
end.
+
+opt_type(route_subdomains) ->
+ fun (s2s) -> s2s;
+ (local) -> local
+ end;
+opt_type(s2s_access) ->
+ fun (A) when is_atom(A) -> A end;
+opt_type(_) -> [route_subdomains, s2s_access].
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 1b40f03c..c8d3cd04 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -5,7 +5,7 @@
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_s2s_in).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(p1_fsm).
@@ -32,11 +34,10 @@
%% External exports
-export([start/2, start_link/2, socket_type/0]).
-%% gen_fsm callbacks
-export([init/1, wait_for_stream/2,
wait_for_feature_request/2, stream_established/2,
handle_event/3, handle_sync_event/4, code_change/4,
- handle_info/3, print_state/1, terminate/3]).
+ handle_info/3, print_state/1, terminate/3, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -73,21 +74,6 @@
-endif.
-%% Module start with or without supervisor:
--ifdef(NO_TRANSIENT_SUPERVISORS).
-
--define(SUPERVISOR_START,
- p1_fsm:start(ejabberd_s2s_in, [SockData, Opts],
- ?FSMOPTS ++ fsm_limit_opts(Opts))).
-
--else.
-
--define(SUPERVISOR_START,
- supervisor:start_child(ejabberd_s2s_in_sup,
- [SockData, Opts])).
-
--endif.
-
-define(STREAM_HEADER(Version),
<<"<?xml version='1.0'?><stream:stream "
"xmlns:stream='http://etherx.jabber.org/stream"
@@ -99,21 +85,20 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
- xml:element_to_binary(?SERR_INVALID_FROM)).
+ fxml:element_to_binary(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
-start(SockData, Opts) -> ?SUPERVISOR_START.
+start(SockData, Opts) ->
+ supervisor:start_child(ejabberd_s2s_in_sup,
+ [SockData, Opts]).
start_link(SockData, Opts) ->
p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts],
@@ -125,13 +110,6 @@ socket_type() -> xml_stream.
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?DEBUG("started: ~p", [{SockMod, Socket}]),
Shaper = case lists:keysearch(shaper, 1, Opts) of
@@ -184,9 +162,14 @@ init([{SockMod, Socket}, Opts]) ->
undefined -> TLSOpts2;
ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
end,
+ TLSOpts4 = case ejabberd_config:get_option(
+ s2s_dhfile, fun iolist_to_binary/1) of
+ undefined -> TLSOpts3;
+ DHFile -> [{dhfile, DHFile} | TLSOpts3]
+ end,
TLSOpts = case proplists:get_bool(tls_compression, Opts) of
- false -> [compression_none | TLSOpts3];
- true -> TLSOpts3
+ false -> [compression_none | TLSOpts4];
+ true -> TLSOpts4
end,
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
{ok, wait_for_stream,
@@ -205,10 +188,10 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
{<<"jabber:server">>, _, Server, true}
when StateData#state.tls and
@@ -216,7 +199,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
send_text(StateData,
?STREAM_HEADER(<<" version='1.0'">>)),
Auth = if StateData#state.tls_enabled ->
- case jlib:nameprep(xml:get_attr_s(<<"from">>, Attrs)) of
+ case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of
From when From /= <<"">>, From /= error ->
{Result, Message} =
ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
@@ -251,7 +234,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_text(StateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertError)))/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
@@ -323,7 +306,7 @@ wait_for_feature_request({xmlstreamelement, El},
TLSEnabled = StateData#state.tls_enabled,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_TLS, <<"starttls">>}
when TLS == true, TLSEnabled == false,
SockMod == gen_tcp ->
@@ -348,7 +331,7 @@ wait_for_feature_request({xmlstreamelement, El},
end,
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
- xml:element_to_binary(#xmlel{name
+ fxml:element_to_binary(#xmlel{name
=
<<"proceed">>,
attrs
@@ -362,7 +345,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state{socket = TLSSocket, streamid = new_id(),
tls_enabled = true, tls_options = TLSOpts}};
{?NS_SASL, <<"auth">>} when TLSEnabled ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
case Mech of
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
AuthDomain = StateData#state.auth_domain,
@@ -377,7 +360,7 @@ wait_for_feature_request({xmlstreamelement, El},
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
[AuthDomain, StateData#state.tls_enabled]),
change_shaper(StateData, <<"">>,
- jlib:make_jid(<<"">>, AuthDomain, <<"">>)),
+ jid:make(<<"">>, AuthDomain, <<"">>)),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true}};
@@ -420,8 +403,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
case is_key_packet(El) of
{key, To, From, Id, Key} ->
?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
- LTo = jlib:nameprep(To),
- LFrom = jlib:nameprep(From),
+ LTo = jid:nameprep(To),
+ LFrom = jid:nameprep(From),
case {ejabberd_s2s:allow_host(LTo, LFrom),
lists:member(LTo,
ejabberd_router:dirty_get_all_domains())}
@@ -435,7 +418,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
wait_for_verification,
StateData#state.connections),
change_shaper(StateData, LTo,
- jlib:make_jid(<<"">>, LFrom, <<"">>)),
+ jid:make(<<"">>, LFrom, <<"">>)),
{next_state, stream_established,
StateData#state{connections = Conns, timer = Timer}};
{_, false} ->
@@ -447,11 +430,11 @@ stream_established({xmlstreamelement, El}, StateData) ->
end;
{verify, To, From, Id, Key} ->
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
- LTo = jlib:nameprep(To),
- LFrom = jlib:nameprep(From),
- Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of
- true -> <<"valid">>;
- _ -> <<"invalid">>
+ LTo = jid:nameprep(To),
+ LFrom = jid:nameprep(From),
+ Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of
+ Key -> <<"valid">>;
+ _ -> <<"invalid">>
end,
send_element(StateData,
#xmlel{name = <<"db:verify">>,
@@ -464,10 +447,10 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From_s = xml:get_attr_s(<<"from">>, Attrs),
- From = jlib:string_to_jid(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
- To = jlib:string_to_jid(To_s),
+ 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),
if (To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
LTo = To#jid.lserver,
@@ -517,8 +500,8 @@ stream_established({valid, From, To}, StateData) ->
children = []}),
?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
[From, StateData#state.tls_enabled]),
- LFrom = jlib:nameprep(From),
- LTo = jlib:nameprep(To),
+ LFrom = jid:nameprep(From),
+ LTo = jid:nameprep(To),
NSD = StateData#state{connections =
(?DICT):store({LFrom, LTo}, established,
StateData#state.connections)},
@@ -530,8 +513,8 @@ stream_established({invalid, From, To}, StateData) ->
[{<<"from">>, To}, {<<"to">>, From},
{<<"type">>, <<"invalid">>}],
children = []}),
- LFrom = jlib:nameprep(From),
- LTo = jlib:nameprep(To),
+ LFrom = jid:nameprep(From),
+ LTo = jid:nameprep(To),
NSD = StateData#state{connections =
(?DICT):erase({LFrom, LTo},
StateData#state.connections)},
@@ -561,20 +544,8 @@ stream_established(closed, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: The associated StateData for this connection
-%% {reply, Reply, NextStateName, NextStateData}
-%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
-%%----------------------------------------------------------------------
handle_sync_event(get_state_infos, _From, StateName,
StateData) ->
@@ -615,12 +586,6 @@ handle_sync_event(_Event, _From, StateName,
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
@@ -630,11 +595,6 @@ handle_info({timeout, Timer, _}, _StateName,
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
?DEBUG("terminated: ~p", [Reason]),
case Reason of
@@ -655,11 +615,6 @@ get_external_hosts(StateData) ->
|| {{D, _}, established} <- dict:to_list(Connections)]
end.
-%%----------------------------------------------------------------------
-%% Func: print_state/1
-%% Purpose: Prepare the state to be printed on error log
-%% Returns: State to print
-%%----------------------------------------------------------------------
print_state(State) -> State.
%%%----------------------------------------------------------------------
@@ -671,7 +626,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper,
@@ -688,15 +643,15 @@ cancel_timer(Timer) ->
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:result">> ->
- {key, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {key, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(_) -> false.
fsm_limit_opts(Opts) ->
@@ -710,3 +665,32 @@ fsm_limit_opts(Opts) ->
N -> [{max_queue, N}]
end
end.
+
+opt_type(domain_certfile) -> fun iolist_to_binary/1;
+opt_type(max_fsm_queue) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(s2s_certfile) -> fun iolist_to_binary/1;
+opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
+opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
+opt_type(s2s_protocol_options) ->
+ fun (Options) ->
+ [_ | O] = lists:foldl(fun (X, Acc) -> X ++ Acc end, [],
+ [["|" | binary_to_list(Opt)]
+ || Opt <- Options, is_binary(Opt)]),
+ iolist_to_binary(O)
+ end;
+opt_type(s2s_tls_compression) ->
+ fun (true) -> true;
+ (false) -> false
+ end;
+opt_type(s2s_use_starttls) ->
+ fun (false) -> false;
+ (true) -> true;
+ (optional) -> optional;
+ (required) -> required;
+ (required_trusted) -> required_trusted
+ end;
+opt_type(_) ->
+ [domain_certfile, max_fsm_queue, s2s_certfile,
+ s2s_ciphers, s2s_dhfile, s2s_protocol_options,
+ s2s_tls_compression, s2s_use_starttls].
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index 6196f136..594fbb2c 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -5,7 +5,7 @@
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_s2s_out).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(p1_fsm).
@@ -35,28 +37,16 @@
start_connection/1,
terminate_if_waiting_delay/2,
stop_connection/1,
- transform_options/1]).
-
-%% p1_fsm callbacks (same as gen_fsm)
--export([init/1,
- open_socket/2,
- wait_for_stream/2,
- wait_for_validation/2,
- wait_for_features/2,
- wait_for_auth_result/2,
- wait_for_starttls_proceed/2,
- relay_to_bridge/2,
- reopen_socket/2,
- wait_before_retry/2,
- stream_established/2,
- handle_event/3,
- handle_sync_event/4,
- handle_info/3,
- terminate/3,
- print_state/1,
- code_change/4,
- test_get_addr_port/1,
- get_addr_port/1]).
+ transform_options/1]).
+
+-export([init/1, open_socket/2, wait_for_stream/2,
+ wait_for_validation/2, wait_for_features/2,
+ wait_for_auth_result/2, wait_for_starttls_proceed/2,
+ relay_to_bridge/2, reopen_socket/2, wait_before_retry/2,
+ stream_established/2, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3,
+ print_state/1, code_change/4, test_get_addr_port/1,
+ get_addr_port/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -66,6 +56,7 @@
-record(state,
{socket :: ejabberd_socket:socket_state(),
streamid = <<"">> :: binary(),
+ remote_streamid = <<"">> :: binary(),
use_v10 = true :: boolean(),
tls = false :: boolean(),
tls_required = false :: boolean(),
@@ -79,7 +70,7 @@
server = <<"">> :: binary(),
queue = queue:new() :: ?TQUEUE,
delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(),
- new = false :: false | binary(),
+ new = false :: boolean(),
verify = false :: false | {pid(), binary(), binary()},
bridge :: {atom(), atom()},
timer = make_ref() :: reference()}).
@@ -96,15 +87,6 @@
-endif.
-%% Module start with or without supervisor:
--ifdef(NO_TRANSIENT_SUPERVISORS).
--define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type],
- fsm_limit_opts() ++ ?FSMOPTS)).
--else.
--define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_out_sup,
- [From, Host, Type])).
--endif.
-
-define(FSMTIMEOUT, 30000).
%% We do not block on send anymore.
@@ -123,13 +105,13 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@@ -137,7 +119,8 @@
%%% API
%%%----------------------------------------------------------------------
start(From, Host, Type) ->
- ?SUPERVISOR_START.
+ supervisor:start_child(ejabberd_s2s_out_sup,
+ [From, Host, Type]).
start_link(From, Host, Type) ->
p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
@@ -151,13 +134,6 @@ stop_connection(Pid) -> p1_fsm:send_event(Pid, closed).
%%% Callback functions from p1_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([From, Server, Type]) ->
process_flag(trap_exit, true),
?DEBUG("started: ~p", [{From, Server, Type}]),
@@ -207,16 +183,21 @@ init([From, Server, Type]) ->
undefined -> TLSOpts2;
ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
end,
+ TLSOpts4 = case ejabberd_config:get_option(
+ s2s_dhfile, fun iolist_to_binary/1) of
+ undefined -> TLSOpts3;
+ DHFile -> [{dhfile, DHFile} | TLSOpts3]
+ end,
TLSOpts = case ejabberd_config:get_option(
{s2s_tls_compression, From},
fun(true) -> true;
(false) -> false
end, true) of
- false -> [compression_none | TLSOpts3];
- true -> TLSOpts3
+ false -> [compression_none | TLSOpts4];
+ true -> TLSOpts4
end,
{New, Verify} = case Type of
- {new, Key} -> {Key, false};
+ new -> {true, false};
{verify, Pid, Key, SID} ->
start_connection(self()), {false, {Pid, Key, SID}}
end,
@@ -227,12 +208,6 @@ init([From, Server, Type]) ->
tls_options = TLSOpts, queue = queue:new(), myname = From,
server = Server, new = New, verify = Verify, timer = Timer}}.
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
open_socket(init, StateData) ->
log_s2s_out(StateData#state.new, StateData#state.myname,
StateData#state.server, StateData#state.tls),
@@ -240,7 +215,7 @@ open_socket(init, StateData) ->
[{StateData#state.myname, StateData#state.server,
StateData#state.new, StateData#state.verify}]),
AddrList = case
- idna:domain_utf8_to_ascii(StateData#state.server)
+ ejabberd_idna:domain_utf8_to_ascii(StateData#state.server)
of
false -> [];
ASCIIAddr -> get_addr_port(ASCIIAddr)
@@ -297,8 +272,6 @@ open_socket(timeout, StateData) ->
open_socket(_, StateData) ->
{next_state, open_socket, StateData}.
-%%----------------------------------------------------------------------
-%% IPv4
open_socket1({_, _, _, _} = Addr, Port) ->
open_socket2(inet, Addr, Port);
%% IPv6
@@ -338,7 +311,7 @@ open_socket2(Type, Addr, Port) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- {CertCheckRes, CertCheckMsg, NewStateData} =
+ {CertCheckRes, CertCheckMsg, StateData0} =
if StateData#state.tls_certverify, StateData#state.tls_enabled ->
{Res, Msg} =
ejabberd_s2s:check_peer_certificate(ejabberd_socket,
@@ -350,13 +323,15 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
true ->
{no_verify, <<"Not verified">>, StateData}
end,
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ RemoteStreamID = fxml:get_attr_s(<<"id">>, Attrs),
+ NewStateData = StateData0#state{remote_streamid = RemoteStreamID},
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
_ when CertCheckRes == error ->
send_text(NewStateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertCheckMsg)))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
@@ -520,7 +495,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_SASL ->
@@ -533,7 +508,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
=
Els2}) ->
case
- xml:get_cdata(Els2)
+ fxml:get_cdata(Els2)
of
<<"EXTERNAL">> ->
true;
@@ -558,13 +533,13 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
_STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_TLS ->
Req =
case
- xml:get_subtag(El1,
+ fxml:get_subtag(El1,
<<"required">>)
of
#xmlel{} ->
@@ -635,7 +610,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -662,7 +637,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"success">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("auth: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -678,7 +653,7 @@ wait_for_auth_result({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -686,7 +661,7 @@ wait_for_auth_result({xmlstreamelement, El},
{stop, normal, StateData}
end;
#xmlel{name = <<"failure">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("restarted: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -695,7 +670,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -704,7 +679,7 @@ wait_for_auth_result({xmlstreamelement, El},
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -732,7 +707,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"proceed">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_TLS ->
?DEBUG("starttls: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -762,7 +737,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -877,19 +852,7 @@ stream_established(closed, StateData) ->
%% Reply = ok,
%% {reply, Reply, state_name, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: The associated StateData for this connection
-%% {reply, Reply, NextStateName, NextStateData}
-%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
-%%----------------------------------------------------------------------
{next_state, StateName, StateData,
get_timeout_interval(StateName)}.
@@ -938,12 +901,6 @@ handle_sync_event(_Event, _From, StateName,
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
cancel_timer(StateData#state.timer),
@@ -1000,19 +957,14 @@ handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData,
get_timeout_interval(StateName)}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
?DEBUG("terminated: ~p", [{Reason, StateName}]),
case StateData#state.new of
false -> ok;
- Key ->
+ true ->
ejabberd_s2s:remove_connection({StateData#state.myname,
StateData#state.server},
- self(), Key)
+ self())
end,
bounce_queue(StateData#state.queue,
?ERR_REMOTE_SERVER_NOT_FOUND),
@@ -1023,11 +975,6 @@ terminate(Reason, StateName, StateData) ->
end,
ok.
-%%----------------------------------------------------------------------
-%% Func: print_state/1
-%% Purpose: Prepare the state to be printed on error log
-%% Returns: State to print
-%%----------------------------------------------------------------------
print_state(State) -> State.
%%%----------------------------------------------------------------------
@@ -1038,7 +985,7 @@ send_text(StateData, Text) ->
ejabberd_socket:send(StateData#state.socket, Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_queue(StateData, Q) ->
case queue:out(Q) of
@@ -1050,14 +997,14 @@ send_queue(StateData, Q) ->
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(El, Error),
- From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_tag_attr_s(<<"from">>,
El)),
- To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_tag_attr_s(<<"to">>,
El)),
ejabberd_router:route(To, From, Err)
end.
@@ -1085,19 +1032,18 @@ bounce_messages(Error) ->
send_db_request(StateData) ->
Server = StateData#state.server,
New = case StateData#state.new of
- false ->
- case ejabberd_s2s:try_register({StateData#state.myname,
- Server})
- of
- {key, Key} -> Key;
- false -> false
- end;
- Key -> Key
+ false ->
+ ejabberd_s2s:try_register({StateData#state.myname, Server});
+ true ->
+ true
end,
NewStateData = StateData#state{new = New},
try case New of
- false -> ok;
- Key1 ->
+ false -> ok;
+ true ->
+ Key1 = ejabberd_s2s:make_key(
+ {StateData#state.myname, Server},
+ StateData#state.remote_streamid),
send_element(StateData,
#xmlel{name = <<"db:result">>,
attrs =
@@ -1124,16 +1070,16 @@ send_db_request(StateData) ->
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:result">> ->
- {result, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {result, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(_) -> false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1153,8 +1099,7 @@ get_addr_port(Server) ->
?DEBUG("srv lookup of '~s': ~p~n",
[Server, HEnt#hostent.h_addr_list]),
AddrList = HEnt#hostent.h_addr_list,
- {A1, A2, A3} = now(),
- random:seed(A1, A2, A3),
+ random:seed(p1_time_compat:timestamp()),
case catch lists:map(fun ({Priority, Weight, Port,
Host}) ->
N = case Weight of
@@ -1329,14 +1274,10 @@ wait_before_reconnect(StateData) ->
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
undefined_delay ->
- {_, _, MicroSecs} = now(), MicroSecs rem 14000 + 1000;
+ {_, _, MicroSecs} = p1_time_compat:timestamp(), MicroSecs rem 14000 + 1000;
D1 -> lists:min([D1 * 2, get_max_retry_delay()])
end,
Timer = erlang:start_timer(Delay, self(), []),
-%% @doc Get the maximum allowed delay for retry to reconnect (in miliseconds).
-%% The default value is 5 minutes.
-%% The option {s2s_max_retry_delay, Seconds} can be used (in seconds).
-%% @spec () -> integer()
{next_state, wait_before_retry,
StateData#state{timer = Timer, delay_to_retry = Delay,
queue = queue:new()}}.
@@ -1365,3 +1306,55 @@ fsm_limit_opts() ->
undefined -> [];
N -> [{max_queue, N}]
end.
+
+opt_type(domain_certfile) -> fun iolist_to_binary/1;
+opt_type(max_fsm_queue) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(outgoing_s2s_families) ->
+ fun (Families) ->
+ true = lists:all(fun (ipv4) -> true;
+ (ipv6) -> true
+ end,
+ Families),
+ Families
+ end;
+opt_type(outgoing_s2s_port) ->
+ fun (I) when is_integer(I), I > 0, I =< 65536 -> I end;
+opt_type(outgoing_s2s_timeout) ->
+ fun (TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
+ TimeOut;
+ (infinity) -> infinity
+ end;
+opt_type(s2s_certfile) -> fun iolist_to_binary/1;
+opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
+opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
+opt_type(s2s_dns_retries) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+opt_type(s2s_dns_timeout) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+opt_type(s2s_max_retry_delay) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(s2s_protocol_options) ->
+ fun (Options) ->
+ [_ | O] = lists:foldl(fun (X, Acc) -> X ++ Acc end, [],
+ [["|" | binary_to_list(Opt)]
+ || Opt <- Options, is_binary(Opt)]),
+ iolist_to_binary(O)
+ end;
+opt_type(s2s_tls_compression) ->
+ fun (true) -> true;
+ (false) -> false
+ end;
+opt_type(s2s_use_starttls) ->
+ fun (true) -> true;
+ (false) -> false;
+ (optional) -> optional;
+ (required) -> required;
+ (required_trusted) -> required_trusted
+ end;
+opt_type(_) ->
+ [domain_certfile, max_fsm_queue, outgoing_s2s_families,
+ outgoing_s2s_port, outgoing_s2s_timeout, s2s_certfile,
+ s2s_ciphers, s2s_dhfile, s2s_dns_retries, s2s_dns_timeout,
+ s2s_max_retry_delay, s2s_protocol_options,
+ s2s_tls_compression, s2s_use_starttls].
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index 1fbc18ff..5caae610 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -5,7 +5,7 @@
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,8 +25,12 @@
-module(ejabberd_service).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
+-protocol({xep, 114, '1.6'}).
+
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
@@ -35,11 +39,10 @@
-export([start/2, start_link/2, send_text/2,
send_element/2, socket_type/0, transform_listen_option/2]).
-%% gen_fsm callbacks
-export([init/1, wait_for_stream/2,
wait_for_handshake/2, stream_established/2,
handle_event/3, handle_sync_event/4, code_change/4,
- handle_info/3, terminate/3, print_state/1]).
+ handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -50,8 +53,8 @@
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
- hosts = [] :: [binary()],
- password = <<"">> :: binary(),
+ host_opts = dict:new() :: ?TDICT,
+ host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean()}).
@@ -88,10 +91,10 @@
"m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% API
@@ -123,18 +126,21 @@ init([{SockMod, Socket}, Opts]) ->
{value, {_, A}} -> A;
_ -> all
end,
- %% This should be improved probably
- {Hosts, HostOpts} = case lists:keyfind(hosts, 1, Opts) of
- {_, HOpts} ->
- {[H || {H, _} <- HOpts],
- lists:flatten(
- [O || {_, O} <- HOpts])};
- _ ->
- {[], []}
- end,
- Password = gen_mod:get_opt(password, HostOpts,
- fun iolist_to_binary/1,
- p1_sha:sha(crypto:rand_bytes(20))),
+ HostOpts = case lists:keyfind(hosts, 1, Opts) of
+ {hosts, HOpts} ->
+ lists:foldl(
+ fun({H, Os}, D) ->
+ P = proplists:get_value(
+ password, Os,
+ p1_sha:sha(crypto:rand_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))),
+ dict:from_list([{global, Pass}])
+ end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
@@ -148,7 +154,7 @@ init([{SockMod, Socket}, Opts]) ->
SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream,
#state{socket = Socket, sockmod = SockMod,
- streamid = new_id(), hosts = Hosts, password = Password,
+ streamid = new_id(), host_opts = HostOpts,
access = Access, check_from = CheckFrom}}.
%%----------------------------------------------------------------------
@@ -160,13 +166,36 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"jabber:component:accept">> ->
- To = xml:get_attr_s(<<"to">>, Attrs),
- Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, xml:crypt(To)]),
- send_text(StateData, Header),
- {next_state, wait_for_handshake, StateData};
+ To = fxml:get_attr_s(<<"to">>, Attrs),
+ Host = jid:nameprep(To),
+ if Host == error ->
+ Header = io_lib:format(?STREAM_HEADER,
+ [<<"none">>, ?MYNAME]),
+ send_text(StateData,
+ <<(list_to_binary(Header))/binary,
+ (?INVALID_XML_ERR)/binary,
+ (?STREAM_TRAILER)/binary>>),
+ {stop, normal, StateData};
+ true ->
+ Header = io_lib:format(?STREAM_HEADER,
+ [StateData#state.streamid, fxml:crypt(To)]),
+ send_text(StateData, Header),
+ HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
+ true ->
+ StateData#state.host_opts;
+ false ->
+ case dict:find(global, StateData#state.host_opts) of
+ {ok, GlobalPass} ->
+ dict:from_list([{Host, GlobalPass}]);
+ error ->
+ StateData#state.host_opts
+ end
+ end,
+ {next_state, wait_for_handshake,
+ StateData#state{host = Host, host_opts = HostOpts}}
+ end;
_ ->
send_text(StateData, ?INVALID_HEADER_ERR),
{stop, normal, StateData}
@@ -183,23 +212,28 @@ wait_for_stream(closed, StateData) ->
wait_for_handshake({xmlstreamelement, El}, StateData) ->
#xmlel{name = Name, children = Els} = El,
- case {Name, xml:get_cdata(Els)} of
+ case {Name, fxml:get_cdata(Els)} of
{<<"handshake">>, Digest} ->
- case p1_sha:sha(<<(StateData#state.streamid)/binary,
- (StateData#state.password)/binary>>)
- of
- Digest ->
- send_text(StateData, <<"<handshake/>">>),
- lists:foreach(fun (H) ->
- ejabberd_router:register_route(H),
- ?INFO_MSG("Route registered for service ~p~n",
- [H])
- end,
- StateData#state.hosts),
- {next_state, stream_established, StateData};
- _ ->
- send_text(StateData, ?INVALID_HANDSHAKE_ERR),
- {stop, normal, StateData}
+ case dict:find(StateData#state.host, StateData#state.host_opts) of
+ {ok, Password} ->
+ case p1_sha:sha(<<(StateData#state.streamid)/binary,
+ Password/binary>>) of
+ Digest ->
+ send_text(StateData, <<"<handshake/>">>),
+ lists:foreach(
+ fun (H) ->
+ ejabberd_router:register_route(H, ?MYNAME),
+ ?INFO_MSG("Route registered for service ~p~n",
+ [H])
+ end, dict:fetch_keys(StateData#state.host_opts)),
+ {next_state, stream_established, StateData};
+ _ ->
+ send_text(StateData, ?INVALID_HANDSHAKE_ERR),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ send_text(StateData, ?INVALID_HANDSHAKE_ERR),
+ {stop, normal, StateData}
end;
_ -> {next_state, wait_for_handshake, StateData}
end;
@@ -216,29 +250,29 @@ wait_for_handshake(closed, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From = xml:get_attr_s(<<"from">>, Attrs),
+ From = fxml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
%% behalf of the server users.
- false -> jlib:string_to_jid(From);
+ false -> jid:from_string(From);
%% The default is the standard behaviour in XEP-0114
_ ->
- FromJID1 = jlib:string_to_jid(From),
+ FromJID1 = jid:from_string(From),
case FromJID1 of
#jid{lserver = Server} ->
- case lists:member(Server, StateData#state.hosts) of
+ case dict:is_key(Server, StateData#state.host_opts) of
true -> FromJID1;
false -> error
end;
_ -> error
end
end,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> error;
- _ -> jlib:string_to_jid(To)
+ _ -> jid:from_string(To)
end,
if ((Name == <<"iq">>) or (Name == <<"message">>) or
(Name == <<"presence">>))
@@ -320,9 +354,9 @@ handle_info({route, From, To, Packet}, StateName,
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Attrs2 =
- jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To), Attrs),
- Text = xml:element_to_binary(#xmlel{name = Name,
+ jlib:replace_from_to_attrs(jid:to_string(From),
+ jid:to_string(To), Attrs),
+ Text = fxml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
@@ -346,7 +380,7 @@ terminate(Reason, StateName, StateData) ->
lists:foreach(fun (H) ->
ejabberd_router:unregister_route(H)
end,
- StateData#state.hosts);
+ dict:fetch_keys(StateData#state.host_opts));
_ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
@@ -368,7 +402,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
new_id() -> randoms:get_string().
@@ -402,3 +436,7 @@ fsm_limit_opts(Opts) ->
N -> [{max_queue, N}]
end
end.
+
+opt_type(max_fsm_queue) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(_) -> [max_fsm_queue].
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 67845295..218e657f 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -5,7 +5,7 @@
%%% Created : 24 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,8 @@
-module(ejabberd_sm).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -33,6 +35,7 @@
-export([start/0,
start_link/0,
route/3,
+ process_iq/3,
open_session/5,
open_session/6,
close_session/4,
@@ -48,6 +51,7 @@
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_session_number/1,
+ get_vh_by_backend/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
@@ -61,12 +65,12 @@
get_user_ip/3,
get_max_user_sessions/2,
get_all_pids/0,
- is_existing_resource/3
+ is_existing_resource/3,
+ get_commands_spec/0
]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -94,10 +98,6 @@
%%====================================================================
%% API
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
-export_type([sid/0]).
start() ->
@@ -106,8 +106,7 @@ start() ->
supervisor:start_child(ejabberd_sup, ChildSpec).
start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [],
- []).
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
@@ -124,7 +123,7 @@ route(From, To, Packet) ->
open_session(SID, User, Server, Resource, Priority, Info) ->
set_session(SID, User, Server, Resource, Priority, Info),
check_for_sessions_to_replace(User, Server, Resource),
- JID = jlib:make_jid(User, Server, Resource),
+ JID = jid:make(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
@@ -136,18 +135,21 @@ open_session(SID, User, Server, Resource, Info) ->
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) ->
- Mod = get_sm_backend(),
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
{ok, #session{info = I}} -> I;
{error, notfound} -> []
end,
- JID = jlib:make_jid(User, Server, Resource),
+ JID = jid:make(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
+-spec check_in_subscription(any(), binary(), binary(),
+ any(), any(), any()) -> any().
+
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
true -> Acc;
@@ -165,21 +167,21 @@ bounce_offline_message(From, To, Packet) ->
-spec disconnect_removed_user(binary(), binary()) -> ok.
disconnect_removed_user(User, Server) ->
- ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
- jlib:make_jid(User, Server, <<"">>),
+ ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
+ jid:make(User, Server, <<"">>),
{broadcast, {exit, <<"User removed">>}}).
get_user_resources(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- Mod = get_sm_backend(),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -187,10 +189,10 @@ get_user_present_resources(LUser, LServer) ->
-spec get_user_ip(binary(), binary(), binary()) -> ip().
get_user_ip(User, Server, Resource) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
- Mod = get_sm_backend(),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
undefined;
@@ -202,10 +204,10 @@ get_user_ip(User, Server, Resource) ->
-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
get_user_info(User, Server, Resource) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
- Mod = get_sm_backend(),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
offline;
@@ -225,7 +227,7 @@ set_presence(SID, User, Server, Resource, Priority,
set_session(SID, User, Server, Resource, Priority,
Info),
ejabberd_hooks:run(set_presence_hook,
- jlib:nameprep(Server),
+ jid:nameprep(Server),
[User, Server, Resource, Presence]).
-spec unset_presence(sid(), binary(), binary(),
@@ -236,7 +238,7 @@ unset_presence(SID, User, Server, Resource, Status,
set_session(SID, User, Server, Resource, undefined,
Info),
ejabberd_hooks:run(unset_presence_hook,
- jlib:nameprep(Server),
+ jid:nameprep(Server),
[User, Server, Resource, Status]).
-spec close_session_unset_presence(sid(), binary(), binary(),
@@ -246,16 +248,16 @@ close_session_unset_presence(SID, User, Server,
Resource, Status) ->
close_session(SID, User, Server, Resource),
ejabberd_hooks:run(unset_presence_hook,
- jlib:nameprep(Server),
+ jid:nameprep(Server),
[User, Server, Resource, Status]).
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
- Mod = get_sm_backend(),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
@@ -264,40 +266,49 @@ get_session_pid(User, Server, Resource) ->
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
- Mod = get_sm_backend(),
- [S#session.usr || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S#session.usr || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
+
+-spec dirty_get_my_sessions_list() -> [#session{}].
dirty_get_my_sessions_list() ->
- Mod = get_sm_backend(),
- [S || S <- Mod:get_sessions(), node(element(2, S#session.sid)) == node()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S || S <- Mod:get_sessions(),
+ node(element(2, S#session.sid)) == node()]
+ end, get_sm_backends()).
-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_list(Server) ->
- LServer = jlib:nameprep(Server),
- Mod = get_sm_backend(),
+ LServer = jid:nameprep(Server),
+ Mod = get_sm_backend(LServer),
[S#session.usr || S <- Mod:get_sessions(LServer)].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
- Mod = get_sm_backend(),
- [element(2, S#session.sid) || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [element(2, S#session.sid) || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
+
+-spec get_vh_session_number(binary()) -> non_neg_integer().
get_vh_session_number(Server) ->
- LServer = jlib:nameprep(Server),
- Mod = get_sm_backend(),
+ LServer = jid:nameprep(Server),
+ Mod = get_sm_backend(LServer),
length(Mod:get_sessions(LServer)).
register_iq_handler(Host, XMLNS, Module, Fun) ->
- ejabberd_sm !
- {register_iq_handler, Host, XMLNS, Module, Fun}.
+ ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
- ejabberd_sm !
- {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
+ ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
-spec unregister_iq_handler(binary(), binary()) -> any().
@@ -309,16 +320,8 @@ unregister_iq_handler(Host, XMLNS) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([]) ->
- Mod = get_sm_backend(),
- Mod:init(),
+ lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
@@ -329,35 +332,14 @@ init([]) ->
ejabberd_hooks:add(remove_user, Host,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
- ejabberd_commands:register_commands(commands()),
+ ejabberd_commands:register_commands(get_commands_spec()),
{ok, #state{}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok, {reply, Reply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast(_Msg, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@@ -387,34 +369,26 @@ handle_info({unregister_iq_handler, Host, XMLNS},
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
- ejabberd_commands:unregister_commands(commands()),
+ ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+-spec set_session(sid(), binary(), binary(), binary(),
+ prio(), info()) -> ok.
+
set_session(SID, User, Server, Resource, Priority, Info) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
@@ -425,13 +399,13 @@ do_route(From, To, {broadcast, _} = Packet) ->
<<"">> ->
lists:foreach(fun(R) ->
do_route(From,
- jlib:jid_replace_resource(To, R),
+ jid:replace_resource(To, R),
Packet)
end,
get_user_resources(To#jid.user, To#jid.server));
_ ->
- {U, S, R} = jlib:jid_tolower(To),
- Mod = get_sm_backend(),
+ {U, S, R} = jid:tolower(To),
+ Mod = get_sm_backend(S),
case Mod:get_sessions(U, S, R) of
[] ->
?DEBUG("packet dropped~n", []);
@@ -453,10 +427,10 @@ do_route(From, To, #xmlel{} = Packet) ->
<<"">> ->
case Name of
<<"presence">> ->
- {Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs)
+ {Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> ->
- Reason = xml:get_path_s(Packet,
+ Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
@@ -509,7 +483,7 @@ do_route(From, To, #xmlel{} = Packet) ->
PResources = get_user_present_resources(LUser, LServer),
lists:foreach(fun ({_, R}) ->
do_route(From,
- jlib:jid_replace_resource(To,
+ jid:replace_resource(To,
R),
Packet)
end,
@@ -517,7 +491,7 @@ do_route(From, To, #xmlel{} = Packet) ->
true -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
@@ -532,13 +506,15 @@ do_route(From, To, #xmlel{} = Packet) ->
_ -> ok
end;
_ ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
case Name of
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
+ <<"normal">> -> route_message(From, To, Packet, normal);
+ <<"">> -> route_message(From, To, Packet, normal);
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
@@ -546,7 +522,7 @@ do_route(From, To, #xmlel{} = Packet) ->
ejabberd_router:route(To, From, Err)
end;
<<"iq">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
@@ -596,8 +572,8 @@ route_message(From, To, Packet, Type) ->
when is_integer(Priority), Priority >= 0 ->
lists:foreach(fun ({P, R}) when P == Priority;
(P >= 0) and (Type == headline) ->
- LResource = jlib:resourceprep(R),
- Mod = get_sm_backend(),
+ LResource = jid:resourceprep(R),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer,
LResource) of
[] ->
@@ -652,9 +628,9 @@ clean_session_list([S1, S2 | Rest], Res) ->
%% On new session, check if some existing connections need to be replace
check_for_sessions_to_replace(User, Server, Resource) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
@@ -676,14 +652,14 @@ is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource).
get_resource_sessions(User, Server, Resource) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LResource = jlib:resourceprep(Resource),
- Mod = get_sm_backend(),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
check_max_sessions(LUser, LServer) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
@@ -696,7 +672,7 @@ check_max_sessions(LUser, LServer) ->
%% Defaults to infinity
get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions,
- jlib:make_jid(LUser, Host, <<"">>))
+ jid:make(LUser, Host, <<"">>))
of
Max when is_integer(Max) -> Max;
infinity -> infinity;
@@ -735,17 +711,17 @@ process_iq(From, To, Packet) ->
-spec force_update_presence({binary(), binary()}) -> any().
force_update_presence({LUser, LServer}) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
- Pid ! {force_update_presence, LUser}
+ Pid ! {force_update_presence, LUser, LServer}
end,
Ss).
--spec get_sm_backend() -> module().
+-spec get_sm_backend(binary()) -> module().
-get_sm_backend() ->
- DBType = ejabberd_config:get_option(sm_db_type,
+get_sm_backend(Host) ->
+ DBType = ejabberd_config:get_option({sm_db_type, Host},
fun(mnesia) -> mnesia;
(internal) -> mnesia;
(odbc) -> odbc;
@@ -753,25 +729,41 @@ get_sm_backend() ->
end, mnesia),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
+-spec get_sm_backends() -> [module()].
+
+get_sm_backends() ->
+ lists:usort([get_sm_backend(Host) || Host <- ?MYHOSTS]).
+
+-spec get_vh_by_backend(module()) -> [binary()].
+
+get_vh_by_backend(Mod) ->
+ lists:filter(
+ fun(Host) ->
+ get_sm_backend(Host) == Mod
+ end, ?MYHOSTS).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
-commands() ->
+get_commands_spec() ->
[#ejabberd_commands{name = connected_users,
tags = [session],
desc = "List all established sessions",
+ policy = admin,
module = ?MODULE, function = connected_users, args = [],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
tags = [session, stats],
desc = "Get the number of established sessions",
+ policy = admin,
module = ?MODULE, function = connected_users_number,
args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
tags = [session],
desc = "List user's connected resources",
+ policy = user,
module = ?MODULE, function = user_resources,
- args = [{user, binary}, {host, binary}],
+ args = [],
result = {resources, {list, {resource, string}}}},
#ejabberd_commands{name = kick_user,
tags = [session],
@@ -803,3 +795,11 @@ kick_user(User, Server) ->
PID ! kick
end, Resources),
length(Resources).
+
+opt_type(sm_db_type) ->
+ fun (mnesia) -> mnesia;
+ (internal) -> mnesia;
+ (odbc) -> odbc;
+ (redis) -> redis
+ end;
+opt_type(_) -> [sm_db_type].
diff --git a/src/ejabberd_sm_mnesia.erl b/src/ejabberd_sm_mnesia.erl
index 7acc1022..b900da31 100644
--- a/src/ejabberd_sm_mnesia.erl
+++ b/src/ejabberd_sm_mnesia.erl
@@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2015, Evgeny Khramtsov
+%%% @copyright (C) 2015-2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_odbc.erl
index 946f58ff..698763b5 100644
--- a/src/ejabberd_sm_odbc.erl
+++ b/src/ejabberd_sm_odbc.erl
@@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2015, Evgeny Khramtsov
+%%% @copyright (C) 2015-2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
@@ -43,7 +43,7 @@ init() ->
end;
(_, Err) ->
Err
- end, ok, ?MYHOSTS).
+ end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
@@ -90,7 +90,7 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
get_sessions(LServer) ->
case ejabberd_odbc:sql_query(
diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl
index 0283f9c3..bf9e0eff 100644
--- a/src/ejabberd_sm_redis.erl
+++ b/src/ejabberd_sm_redis.erl
@@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2015, Evgeny Khramtsov
+%%% @copyright (C) 2015-2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
@@ -8,16 +8,13 @@
%%%-------------------------------------------------------------------
-module(ejabberd_sm_redis).
+-behaviour(ejabberd_config).
+
-behaviour(ejabberd_sm).
-%% API
--export([init/0,
- set_session/1,
- delete_session/4,
- get_sessions/0,
- get_sessions/1,
- get_sessions/2,
- get_sessions/3]).
+-export([init/0, set_session/1, delete_session/4,
+ get_sessions/0, get_sessions/1, get_sessions/2,
+ get_sessions/3, opt_type/1]).
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
@@ -110,7 +107,7 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
-spec get_sessions(binary()) -> [#session{}].
get_sessions(LServer) ->
@@ -134,7 +131,8 @@ get_sessions(LUser, LServer) ->
[]
end.
--spec get_sessions(binary(), binary(), binary()) -> [#session{}].
+-spec get_sessions(binary(), binary(), binary()) ->
+ [#session{}].
get_sessions(LUser, LServer, LResource) ->
USKey = us_to_key({LUser, LServer}),
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
@@ -206,4 +204,18 @@ clean_table() ->
?ERROR_MSG("failed to clean redis table for "
"server ~s: ~p", [LServer, Err])
end
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
+
+opt_type(redis_connect_timeout) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(redis_db) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+opt_type(redis_password) -> fun iolist_to_list/1;
+opt_type(redis_port) ->
+ fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
+opt_type(redis_reconnect_timeout) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(redis_server) -> fun iolist_to_list/1;
+opt_type(_) ->
+ [redis_connect_timeout, redis_db, redis_password,
+ redis_port, redis_reconnect_timeout, redis_server].
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 29c7774e..887b4a0f 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -52,10 +52,10 @@
-type sockmod() :: ejabberd_http_bind |
ejabberd_http_ws |
- gen_tcp | p1_tls | ezlib.
+ gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
- p1_tls:tls_socket() |
+ fast_tls:tls_socket() |
ezlib:zlib_socket() |
ejabberd_http_bind:bind_socket().
@@ -67,15 +67,12 @@
-export_type([socket_state/0, sockmod/0]).
--spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
%%====================================================================
%% API
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function:
-%% Description:
-%%--------------------------------------------------------------------
+-spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
+
start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of
xml_stream ->
@@ -148,15 +145,15 @@ connect(Addr, Port, Opts, Timeout) ->
end.
starttls(SocketData, TLSOpts) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
starttls(SocketData, TLSOpts, Data) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
send(SocketData, Data),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
compress(SocketData) -> compress(SocketData, undefined).
@@ -219,10 +216,10 @@ get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
get_peer_certificate(SocketData) ->
- p1_tls:get_peer_certificate(SocketData#socket_state.socket).
+ fast_tls:get_peer_certificate(SocketData#socket_state.socket).
get_verify_result(SocketData) ->
- p1_tls:get_verify_result(SocketData#socket_state.socket).
+ fast_tls:get_verify_result(SocketData#socket_state.socket).
close(SocketData) ->
ejabberd_receiver:close(SocketData#socket_state.receiver).
@@ -241,7 +238,3 @@ peername(#socket_state{sockmod = SockMod,
_ -> SockMod:peername(Socket)
end.
-%%====================================================================
-%% Internal functions
-%%====================================================================
-%====================================================================
diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl
new file mode 100644
index 00000000..660eac19
--- /dev/null
+++ b/src/ejabberd_sql_pt.erl
@@ -0,0 +1,544 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_sql_pt.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Description : Parse transform for SQL queries
+%%%
+%%% Created : 20 Jan 2016 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+-module(ejabberd_sql_pt).
+
+%% API
+-export([parse_transform/2]).
+
+-export([parse/2]).
+
+-include("ejabberd_sql_pt.hrl").
+
+-record(state, {loc,
+ 'query' = [],
+ params = [],
+ param_pos = 0,
+ args = [],
+ res = [],
+ res_vars = [],
+ res_pos = 0}).
+
+-define(QUERY_RECORD, "sql_query").
+
+-define(ESCAPE_RECORD, "sql_escape").
+-define(ESCAPE_VAR, "__SQLEscape").
+
+-define(MOD, sql__module_).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+parse_transform(AST, _Options) ->
+ %io:format("PT: ~p~nOpts: ~p~n", [AST, Options]),
+ NewAST = top_transform(AST),
+ %io:format("NewPT: ~p~n", [NewAST]),
+ NewAST.
+
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+
+transform(Form) ->
+ case erl_syntax:type(Form) of
+ application ->
+ case erl_syntax_lib:analyze_application(Form) of
+ {?SQL_MARK, 1} ->
+ case erl_syntax:application_arguments(Form) of
+ [Arg] ->
+ case erl_syntax:type(Arg) of
+ string ->
+ S = erl_syntax:string_value(Arg),
+ Pos = erl_syntax:get_pos(Arg),
+ ParseRes = parse(S, Pos),
+ set_pos(make_sql_query(ParseRes), Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL argument must be "
+ "a constant string"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL args"})
+ end;
+ {?SQL_UPSERT_MARK, 2} ->
+ case erl_syntax:application_arguments(Form) of
+ [TableArg, FieldsArg] ->
+ case {erl_syntax:type(TableArg),
+ erl_syntax:is_proper_list(FieldsArg)}of
+ {string, true} ->
+ Table = erl_syntax:string_value(TableArg),
+ ParseRes =
+ parse_upsert(
+ erl_syntax:list_elements(FieldsArg)),
+ Pos = erl_syntax:get_pos(Form),
+ set_pos(
+ make_sql_upsert(Table, ParseRes, Pos),
+ Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL_UPSERT arguments must be "
+ "a constant string and a list"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL_UPSERT args"})
+ end;
+ _ ->
+ Form
+ end;
+ attribute ->
+ case erl_syntax:atom_value(erl_syntax:attribute_name(Form)) of
+ module ->
+ case erl_syntax:attribute_arguments(Form) of
+ [M | _] ->
+ Module = erl_syntax:atom_value(M),
+ %io:format("module ~p~n", [Module]),
+ put(?MOD, Module),
+ Form;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end.
+
+top_transform(Forms) when is_list(Forms) ->
+ lists:map(
+ fun(Form) ->
+ try
+ Form2 = erl_syntax_lib:map(
+ fun(Node) ->
+ %io:format("asd ~p~n", [Node]),
+ transform(Node)
+ end, Form),
+ Form3 = erl_syntax:revert(Form2),
+ Form3
+ catch
+ throw:{error, Line, Error} ->
+ {error, {Line, erl_parse, Error}}
+ end
+ end, Forms).
+
+parse(S, Loc) ->
+ parse1(S, [], #state{loc = Loc}).
+
+parse(S, ParamPos, Loc) ->
+ parse1(S, [], #state{loc = Loc, param_pos = ParamPos}).
+
+parse1([], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ State1#state{'query' = lists:reverse(State1#state.'query'),
+ params = lists:reverse(State1#state.params),
+ args = lists:reverse(State1#state.args),
+ res = lists:reverse(State1#state.res),
+ res_vars = lists:reverse(State1#state.res_vars)
+ };
+parse1([$@, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = "__V" ++ integer_to_list(State2#state.res_pos),
+ EVar = erl_syntax:variable(Var),
+ Convert =
+ case Type of
+ integer ->
+ erl_syntax:application(
+ erl_syntax:atom(binary_to_integer),
+ [EVar]);
+ string ->
+ EVar;
+ boolean ->
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_odbc),
+ erl_syntax:atom(to_bool),
+ [EVar])
+ end,
+ State3 = append_string(Name, State2),
+ State4 = State3#state{res_pos = State3#state.res_pos + 1,
+ res = [Convert | State3#state.res],
+ res_vars = [EVar | State3#state.res_vars]},
+ parse1(S1, [], State4);
+parse1([$%, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = State2#state.param_pos,
+ Convert =
+ erl_syntax:application(
+ erl_syntax:record_access(
+ erl_syntax:variable(?ESCAPE_VAR),
+ erl_syntax:atom(?ESCAPE_RECORD),
+ erl_syntax:atom(Type)),
+ [erl_syntax:variable(Name)]),
+ State3 = State2,
+ State4 =
+ State3#state{'query' = [{var, Var} | State3#state.'query'],
+ args = [Convert | State3#state.args],
+ params = [Var | State3#state.params],
+ param_pos = State3#state.param_pos + 1},
+ parse1(S1, [], State4);
+parse1([C | S], Acc, State) ->
+ parse1(S, [C | Acc], State).
+
+append_string([], State) ->
+ State;
+append_string(S, State) ->
+ State#state{query = [{str, S} | State#state.query]}.
+
+parse_name(S, State) ->
+ parse_name(S, [], 0, State).
+
+parse_name([], _Acc, _Depth, State) ->
+ throw({error, State#state.loc,
+ "expected ')', found end of string"});
+parse_name([$), T | S], Acc, 0, State) ->
+ Type =
+ case T of
+ $d -> integer;
+ $s -> string;
+ $b -> boolean;
+ _ ->
+ throw({error, State#state.loc,
+ ["unknown type specifier '", T, "'"]})
+ end,
+ {lists:reverse(Acc), Type, S, State};
+parse_name([$)], _Acc, 0, State) ->
+ throw({error, State#state.loc,
+ "expected type specifier, found end of string"});
+parse_name([$( = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth + 1, State);
+parse_name([$) = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth - 1, State);
+parse_name([C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth, State).
+
+
+make_var(V) ->
+ Var = "__V" ++ integer_to_list(V),
+ erl_syntax:variable(Var).
+
+
+make_sql_query(State) ->
+ Hash = erlang:phash2(State#state{loc = undefined}),
+ SHash = <<"Q", (integer_to_binary(Hash))/binary>>,
+ Query = pack_query(State#state.'query'),
+ EQuery =
+ lists:map(
+ fun({str, S}) ->
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(S))]);
+ ({var, V}) -> make_var(V)
+ end, Query),
+ erl_syntax:record_expr(
+ erl_syntax:atom(?QUERY_RECORD),
+ [erl_syntax:record_field(
+ erl_syntax:atom(hash),
+ %erl_syntax:abstract(SHash)
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(binary_to_list(SHash)))])),
+ erl_syntax:record_field(
+ erl_syntax:atom(args),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:variable(?ESCAPE_VAR)],
+ none,
+ [erl_syntax:list(State#state.args)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_query),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(lists:map(fun make_var/1, State#state.params))],
+ none,
+ [erl_syntax:list(EQuery)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_res),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(State#state.res_vars)],
+ none,
+ [erl_syntax:tuple(State#state.res)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(loc),
+ erl_syntax:abstract({get(?MOD), State#state.loc}))
+ ]).
+
+pack_query([]) ->
+ [];
+pack_query([{str, S1}, {str, S2} | Rest]) ->
+ pack_query([{str, S1 ++ S2} | Rest]);
+pack_query([X | Rest]) ->
+ [X | pack_query(Rest)].
+
+
+parse_upsert(Fields) ->
+ {Fs, _} =
+ lists:foldr(
+ fun(F, {Acc, Param}) ->
+ case erl_syntax:type(F) of
+ string ->
+ V = erl_syntax:string_value(F),
+ {_, _, State} = Res =
+ parse_upsert_field(
+ V, Param, erl_syntax:get_pos(F)),
+ {[Res | Acc], State#state.param_pos};
+ _ ->
+ throw({error, erl_syntax:get_pos(F),
+ "?SQL_UPSERT field must be "
+ "a constant string"})
+ end
+ end, {[], 0}, Fields),
+ %io:format("asd ~p~n", [{Fields, Fs}]),
+ Fs.
+
+parse_upsert_field([$! | S], ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, true, ParseState};
+parse_upsert_field(S, ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, false, ParseState}.
+
+parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
+ throw({error, Loc,
+ "?SQL_UPSERT fields must have the "
+ "following form: \"[!]name=value\""});
+parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
+ {lists:reverse(Acc), parse(S, ParamPos, Loc)};
+parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
+ parse_upsert_field1(S, [C | Acc], ParamPos, Loc).
+
+
+make_sql_upsert(Table, ParseRes, Pos) ->
+ check_upsert(ParseRes, Pos),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
+ [erl_syntax:infix_expr(
+ erl_syntax:variable("__Version"),
+ erl_syntax:operator('>='),
+ erl_syntax:integer(90100))],
+ [make_sql_upsert_pgsql901(Table, ParseRes),
+ erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore(), erl_syntax:underscore()],
+ none,
+ [make_sql_upsert_generic(Table, ParseRes)])
+ ]).
+
+make_sql_upsert_generic(Table, ParseRes) ->
+ Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)),
+ Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)),
+ InsertBranch =
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_odbc),
+ erl_syntax:atom(sql_query_t),
+ [Insert]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:variable("__UpdateRes")],
+ none,
+ [erl_syntax:variable("__UpdateRes")])]),
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_odbc),
+ erl_syntax:atom(sql_query_t),
+ [Update]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore()],
+ none,
+ [InsertBranch])]).
+
+make_sql_upsert_update(Table, ParseRes) ->
+ WPairs =
+ lists:flatmap(
+ fun({_Field, false, _ST}) ->
+ [];
+ ({Field, true, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Where = join_states(WPairs, " AND "),
+ SPairs =
+ lists:flatmap(
+ fun({_Field, true, _ST}) ->
+ [];
+ ({Field, false, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Set = join_states(SPairs, ", "),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "UPDATE "}, {str, Table}, {str, " SET "}]},
+ Set,
+ #state{'query' = [{str, " WHERE "}]},
+ Where
+ ]),
+ State.
+
+make_sql_upsert_insert(Table, ParseRes) ->
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") VALUES ("}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, ")"}]}
+ ]),
+ State.
+
+make_sql_upsert_pgsql901(Table, ParseRes) ->
+ Update = make_sql_upsert_update(Table, ParseRes),
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ Insert =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") SELECT "}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, " WHERE NOT EXISTS (SELECT * FROM upsert)"}]}
+ ]),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "WITH upsert AS ("}]},
+ Update,
+ #state{'query' = [{str, " RETURNING *) "}]},
+ Insert
+ ]),
+ Upsert = make_sql_query(State),
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_odbc),
+ erl_syntax:atom(sql_query_t),
+ [Upsert]).
+
+
+check_upsert(ParseRes, Pos) ->
+ Set =
+ lists:filter(
+ fun({_Field, Match, _ST}) ->
+ not Match
+ end, ParseRes),
+ case Set of
+ [] ->
+ throw({error, Pos,
+ "No ?SQL_UPSERT fields to set, use INSERT instead"});
+ _ ->
+ ok
+ end,
+ ok.
+
+
+concat_states(States) ->
+ lists:foldr(
+ fun(ST11, ST2) ->
+ ST1 = resolve_vars(ST11, ST2),
+ ST1#state{
+ 'query' = ST1#state.'query' ++ ST2#state.'query',
+ params = ST1#state.params ++ ST2#state.params,
+ args = ST1#state.args ++ ST2#state.args,
+ res = ST1#state.res ++ ST2#state.res,
+ res_vars = ST1#state.res_vars ++ ST2#state.res_vars,
+ loc = case ST1#state.loc of
+ undefined -> ST2#state.loc;
+ _ -> ST1#state.loc
+ end
+ }
+ end, #state{}, States).
+
+resolve_vars(ST1, ST2) ->
+ Max = lists:max([0 | ST1#state.params ++ ST2#state.params]),
+ {Map, _} =
+ lists:foldl(
+ fun(Var, {Acc, New}) ->
+ case lists:member(Var, ST2#state.params) of
+ true ->
+ {dict:store(Var, New, Acc), New + 1};
+ false ->
+ {Acc, New}
+ end
+ end, {dict:new(), Max + 1}, ST1#state.params),
+ NewParams =
+ lists:map(
+ fun(Var) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ New;
+ error ->
+ Var
+ end
+ end, ST1#state.params),
+ NewQuery =
+ lists:map(
+ fun({var, Var}) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ {var, New};
+ error ->
+ {var, Var}
+ end;
+ (S) -> S
+ end, ST1#state.'query'),
+ ST1#state{params = NewParams, 'query' = NewQuery}.
+
+
+join_states([], _Sep) ->
+ #state{};
+join_states([H | T], Sep) ->
+ J = [[H] | [[#state{'query' = [{str, Sep}]}, X] || X <- T]],
+ concat_states(lists:append(J)).
+
+
+set_pos(Tree, Pos) ->
+ erl_syntax_lib:map(
+ fun(Node) ->
+ case erl_syntax:get_pos(Node) of
+ 0 -> erl_syntax:set_pos(Node, Pos);
+ _ -> Node
+ end
+ end, Tree).
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
index 11347d60..3c91117d 100644
--- a/src/ejabberd_stun.erl
+++ b/src/ejabberd_stun.erl
@@ -6,7 +6,7 @@
%%% @end
%%% Created : 8 May 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2013-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2013-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
@@ -25,8 +25,11 @@
%%%-------------------------------------------------------------------
-module(ejabberd_stun).
-%% API
--export([tcp_init/2, udp_init/2, udp_recv/5, start/2, socket_type/0]).
+-protocol({rfc, 5766}).
+-protocol({xep, 176, '1.0'}).
+
+-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
+ socket_type/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -35,11 +38,11 @@
%%% API
%%%===================================================================
tcp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:tcp_init(Socket, prepare_turn_opts(Opts)).
udp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:udp_init(Socket, prepare_turn_opts(Opts)).
udp_recv(Socket, Addr, Port, Packet, Opts) ->
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index da25af2c..56dccdcd 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -97,21 +97,6 @@ init([]) ->
infinity,
supervisor,
[ejabberd_listener]},
- ReceiverSupervisor =
- {ejabberd_receiver_sup,
- {ejabberd_tmp_sup, start_link,
- [ejabberd_receiver_sup, ejabberd_receiver]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
- C2SSupervisor =
- {ejabberd_c2s_sup,
- {ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
S2SInSupervisor =
{ejabberd_s2s_in_sup,
{ejabberd_tmp_sup, start_link,
@@ -136,14 +121,6 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
- HTTPSupervisor =
- {ejabberd_http_sup,
- {ejabberd_tmp_sup, start_link,
- [ejabberd_http_sup, ejabberd_http]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
FrontendSocketSupervisor =
{ejabberd_frontend_socket_sup,
{ejabberd_tmp_sup, start_link,
@@ -169,12 +146,9 @@ init([]) ->
S2S,
Local,
Captcha,
- ReceiverSupervisor,
- C2SSupervisor,
S2SInSupervisor,
S2SOutSupervisor,
ServiceSupervisor,
- HTTPSupervisor,
IQSupervisor,
FrontendSocketSupervisor,
Listener]}}.
diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl
index df8d9d51..3f6c0566 100644
--- a/src/ejabberd_system_monitor.erl
+++ b/src/ejabberd_system_monitor.erl
@@ -5,7 +5,7 @@
%%% Created : 21 Mar 2007 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,17 +25,18 @@
-module(ejabberd_system_monitor).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
--export([start_link/0, process_command/3,
+-export([start_link/0, process_command/3, register_hook/1,
process_remote_command/1]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -67,10 +68,10 @@ process_command(From, To, Packet) ->
case Name of
<<"message">> ->
LFrom =
- jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
true ->
- Body = xml:get_path_s(Packet,
+ Body = fxml:get_path_s(Packet,
[{elem, <<"body">>}, cdata]),
spawn(fun () ->
process_flag(priority, high),
@@ -84,6 +85,10 @@ process_command(From, To, Packet) ->
_ -> ok
end.
+register_hook(Host) ->
+ ejabberd_hooks:add(local_send_to_resource_hook, Host,
+ ?MODULE, process_command, 50).
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -99,11 +104,7 @@ init(Opts) ->
LH = proplists:get_value(large_heap, Opts),
process_flag(priority, high),
erlang:system_monitor(self(), [{large_heap, LH}]),
- lists:foreach(fun (Host) ->
- ejabberd_hooks:add(local_send_to_resource_hook, Host,
- ?MODULE, process_command, 50)
- end,
- ?MYHOSTS),
+ lists:foreach(fun register_hook/1, ?MYHOSTS),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -184,26 +185,32 @@ process_large_heap(Pid, Info) ->
io_lib:format("(~w) The process ~w is consuming too "
"much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo])),
- From = jlib:make_jid(<<"">>, Host, <<"watchdog">>),
+ From = jid:make(<<"">>, Host, <<"watchdog">>),
+ Hint = [#xmlel{name = <<"no-permanent-store">>,
+ attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
lists:foreach(fun (JID) ->
- send_message(From, jlib:make_jid(JID), Body)
+ send_message(From, jid:make(JID), Body, Hint)
end, JIDs).
send_message(From, To, Body) ->
+ send_message(From, To, Body, []).
+
+send_message(From, To, Body, ExtraEls) ->
ejabberd_router:route(From, To,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
children =
[#xmlel{name = <<"body">>, attrs = [],
children =
- [{xmlcdata, Body}]}]}).
+ [{xmlcdata, Body}]}
+ | ExtraEls]}).
get_admin_jids() ->
ejabberd_config:get_option(
watchdog_admins,
fun(JIDs) ->
- [jlib:jid_tolower(
- jlib:string_to_jid(
+ [jid:tolower(
+ jid:from_string(
iolist_to_binary(S))) || S <- JIDs]
end, []).
@@ -244,8 +251,9 @@ s2s_out_info(Pid) ->
[<<"Process type: s2s_out">>,
case FromTo of
[{From, To}] ->
- list_to_binary(io_lib:format("\nS2S connection: from ~s to ~s",
- [From, To]));
+ <<"\n",
+ (io_lib:format("S2S connection: from ~s to ~s",
+ [From, To]))/binary>>;
_ -> <<"">>
end,
check_send_queue(Pid), <<"\n">>,
@@ -309,7 +317,7 @@ help() ->
"<node>\n setlh <node> <integer>">>.
remote_command(Node, Args, From, To) ->
- Message = case rpc:call(Node, ?MODULE,
+ Message = case ejabberd_cluster:call(Node, ?MODULE,
process_remote_command, [Args])
of
{badrpc, Reason} ->
@@ -331,3 +339,12 @@ process_remote_command([setlh, NewValue]) ->
io_lib:format("Result of set large heap: ~p --> ~p",
[OldLH, NewLH]);
process_remote_command(_) -> throw(unknown_command).
+
+opt_type(watchdog_admins) ->
+ fun (JIDs) ->
+ [jid:tolower(jid:from_string(iolist_to_binary(S)))
+ || S <- JIDs]
+ end;
+opt_type(watchdog_large_heap) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(_) -> [watchdog_admins, watchdog_large_heap].
diff --git a/src/ejabberd_tmp_sup.erl b/src/ejabberd_tmp_sup.erl
index 5852e666..8b9f4fc1 100644
--- a/src/ejabberd_tmp_sup.erl
+++ b/src/ejabberd_tmp_sup.erl
@@ -5,7 +5,7 @@
%%% Created : 18 Jul 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/ejabberd_update.erl b/src/ejabberd_update.erl
index afcb6222..75ccc3de 100644
--- a/src/ejabberd_update.erl
+++ b/src/ejabberd_update.erl
@@ -5,7 +5,7 @@
%%% Created : 27 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -67,9 +67,6 @@ update(ModulesToUpdate) ->
{error, Reason}
end.
-%% OTP R14B03 and older provided release_handler_1:eval_script/3
-%% But OTP R14B04 and newer provide release_handler_1:eval_script/5
-%% Dialyzer reports a call to missing function; don't worry.
eval_script(Script, Apps, LibDirs) ->
release_handler_1:eval_script(Script, Apps, LibDirs, [], []).
@@ -138,17 +135,17 @@ build_script(Dir, UpdatedBeams) ->
LowLevelScript,
[{ejabberd, "", filename:join(Dir, "..")}]),
Check1 = case Check of
- {ok, []} ->
- ?DEBUG("script: ~p~n", [Script]),
- ?DEBUG("low level script: ~p~n", [LowLevelScript]),
- ?DEBUG("check: ~p~n", [Check]),
- ok;
- _ ->
- ?ERROR_MSG("script: ~p~n", [Script]),
- ?ERROR_MSG("low level script: ~p~n", [LowLevelScript]),
- ?ERROR_MSG("check: ~p~n", [Check]),
- error
- end,
+ {ok, []} ->
+ ?DEBUG("script: ~p~n", [Script]),
+ ?DEBUG("low level script: ~p~n", [LowLevelScript]),
+ ?DEBUG("check: ~p~n", [Check]),
+ ok;
+ _ ->
+ ?ERROR_MSG("script: ~p~n", [Script]),
+ ?ERROR_MSG("low level script: ~p~n", [LowLevelScript]),
+ ?ERROR_MSG("check: ~p~n", [Check]),
+ error
+ end,
{Script, LowLevelScript, Check1}.
%% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl
diff --git a/src/ejabberd_web.erl b/src/ejabberd_web.erl
index 61cd079a..459423aa 100644
--- a/src/ejabberd_web.erl
+++ b/src/ejabberd_web.erl
@@ -6,7 +6,7 @@
%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index 7cf15210..f525a4d3 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -5,7 +5,7 @@
%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,12 +27,13 @@
-module(ejabberd_web_admin).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-%% External exports
-export([process/2, list_users/4,
list_users_in_diapason/4, pretty_print_xml/1,
- term_to_id/1]).
+ term_to_id/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -94,7 +95,7 @@ is_acl_match(Host, Rules, Jid) ->
get_jid(Auth, HostHTTP, Method) ->
case get_auth_admin(Auth, HostHTTP, [], Method) of
{ok, {User, Server}} ->
- jlib:make_jid(User, Server, <<"">>);
+ jid:make(User, Server, <<"">>);
{unauthorized, Error} ->
?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
throw({unauthorized, Auth})
@@ -166,15 +167,15 @@ process([<<"doc">>, LocalFile], _Request) ->
?DEBUG("Delivering content.", []),
{200, [{<<"Server">>, <<"ejabberd">>}], FileContents};
{error, Error} ->
- ?DEBUG("Delivering error: ~p", [Error]),
Help = <<" ", FileName/binary,
" - Try to specify the path to ejabberd "
"documentation with the environment variable "
"EJABBERD_DOC_PATH. Check the ejabberd "
"Guide for more information.">>,
+ ?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
case Error of
eacces -> {403, [], <<"Forbidden", Help/binary>>};
- enoent -> {404, [], <<"Not found", Help/binary>>};
+ enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>};
_Else ->
{404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>}
end
@@ -183,7 +184,7 @@ process([<<"server">>, SHost | RPath] = Path,
#request{auth = Auth, lang = Lang, host = HostHTTP,
method = Method} =
Request) ->
- Host = jlib:nameprep(SHost),
+ Host = jid:nameprep(SHost),
case lists:member(Host, ?MYHOSTS) of
true ->
case get_auth_admin(Auth, HostHTTP, Path, Method) of
@@ -202,7 +203,7 @@ process([<<"server">>, SHost | RPath] = Path,
{unauthorized, Error} ->
{BadUser, _BadPass} = Auth,
{IPT, _Port} = Request#request.ip,
- IPS = jlib:ip_to_list(IPT),
+ IPS = ejabberd_config:may_hide_data(jlib:ip_to_list(IPT)),
?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
[BadUser, IPS, Error]),
{401,
@@ -234,7 +235,7 @@ process(RPath,
{unauthorized, Error} ->
{BadUser, _BadPass} = Auth,
{IPT, _Port} = Request#request.ip,
- IPS = jlib:ip_to_list(IPT),
+ IPS = ejabberd_config:may_hide_data(jlib:ip_to_list(IPT)),
?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
[BadUser, IPS, Error]),
{401,
@@ -249,7 +250,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) ->
case Auth of
{SJID, Pass} ->
{HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
- case jlib:string_to_jid(SJID) of
+ case jid:from_string(SJID) of
error -> {unauthorized, <<"badformed-jid">>};
#jid{user = <<"">>, server = User} ->
get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
@@ -266,7 +267,7 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case is_acl_match(HostOfRule, AccessRule,
- jlib:make_jid(User, Server, <<"">>))
+ jid:make(User, Server, <<"">>))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
@@ -295,7 +296,7 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
- {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}],
+ {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang),
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
@@ -339,8 +340,15 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
[{xmlcdata, <<"">>}])]),
?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
[?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
- [?XC(<<"p">>,
- <<"ejabberd (c) 2002-2015 ProcessOne">>)])])])]}}.
+ [?XE(<<"p">>,
+ [?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>),
+ ?C(<<" (c) 2002-2016 ">>),
+ ?AC(<<"https://www.process-one.net/">>, <<"ProcessOne, leader in messaging and push solutions">>)]
+ )])])])]}}.
+
+direction(ltr) -> [{<<"dir">>, <<"ltr">>}];
+direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}];
+direction(_) -> [].
get_base_path(global, cluster) -> <<"/admin/">>;
get_base_path(Host, cluster) ->
@@ -368,212 +376,291 @@ additions_js() ->
css(Host) ->
Base = get_base_path(Host, cluster),
- <<"\nhtml,body {\n background: white;\n "
- " margin: 0;\n padding: 0;\n height: "
- "100%;\n}\n\n#container {\n padding: "
- "0;\n margin: 0;\n min-height: 100%;\n "
- " height: 100%;\n margin-bottom: -30px;\n}\n\n"
- "html>body #container {\n height: auto;\n}\n\n"
- "#header h1 {\n width: 100%;\n height: "
- "55px;\n padding: 0;\n margin: 0;\n "
- " background: transparent url(\"",
- Base/binary,
- "logo-fill.png\");\n}\n\n#header h1 a "
- "{\n position: absolute;\n top: 0;\n "
- " left: 0;\n width: 100%;\n height: "
- "55px;\n padding: 0;\n margin: 0;\n "
- " background: transparent url(\"",
- Base/binary,
- "logo.png\") no-repeat;\n display: block;\n "
- " text-indent: -700em;\n}\n\n#clearcopyright "
- "{\n display: block;\n width: 100%;\n "
- " height: 30px;\n}\n\n#copyrightouter "
- "{\n display: table;\n width: 100%;\n "
- " height: 30px;\n}\n\n#copyright {\n "
- " display: table-cell;\n vertical-align: "
- "bottom;\n width: 100%;\n height: 30px;\n}\n\n"
- "#copyright p {\n margin-left: 0;\n "
- " margin-right: 0;\n margin-top: 5px;\n "
- " margin-bottom: 0;\n padding-left: "
- "0;\n padding-right: 0;\n padding-top: "
- "1px;\n padding-bottom: 1px;\n width: "
- "100%;\n color: #ffffff;\n background-color: "
- "#fe8a00;\n font-family: Verdana, Arial, "
- "Helvetica, sans-serif; \n font-size: "
- "7pt;\n font-weight: bold;\n text-align: "
- "center;\n}\n\n#navigation ul {\n position: "
- "absolute;\n top: 65px;\n left: 0;\n "
- " padding: 0 1px 1px 1px;\n margin: "
- "0;\n font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 8pt;\n font-weigh"
- "t: bold;\n border-top: 1px solid #d47911;\n "
- " width: 17em;\n}\n\n#navigation ul li "
- "{\n list-style: none;\n margin: 0;\n "
- " text-align: left;\n display: inline;\n}\n\n"
- "#navigation ul li a {\n margin: 0;\n "
- " display: block;\n padding: 3px 6px "
- "3px 9px;\n border-left: 1em solid #ffc78c;\n "
- " border-right: 1px solid #d47911;\n "
- " border-bottom: 1px solid #d47911;\n "
- " background: #ffe3c9;\n text-decoration: "
- "none;\n}\n\n#navigation ul li a:link "
- "{\n color: #844;\n}\n\n#navigation "
- "ul li a:visited {\n color: #766;\n}\n\n#navig"
- "ation ul li a:hover {\n border-color: "
- "#fc8800;\n color: #FFF;\n background: "
- "#332;\n}\n\nul li #navhead a, ul li "
- "#navheadsub a, ul li #navheadsubsub "
- "a {\n text-align: center;\n border-top: "
- "1px solid #d47911;\n border-bottom: "
- "2px solid #d47911;\n background: #FED6A6;\n}\n\n"
- "#navheadsub, #navitemsub {\n border-left: "
- "7px solid white;\n margin-left: 2px;\n}\n\n#"
- "navheadsubsub, #navitemsubsub {\n border-lef"
- "t: 14px solid white;\n margin-left: "
- "4px;\n}\n\n#lastactivity li {\n font-weight: "
- "bold;\n border: 1px solid #d6760e;\n "
- " background-color: #fff2e8;\n padding: "
- "2px;\n margin-bottom: -1px;\n}\n\ntd.copy "
- "{\n color: #ffffff;\n background-color: "
- "#fe8a00;\n font-family: Verdana, Arial, "
- "Helvetica, sans-serif; \n font-size: "
- "7pt;\n font-weight: bold;\n text-align: "
- "center;\n}\n\ninput {\n font-family: "
- "Verdana, Arial, Helvetica, sans-serif; "
- "\n font-size: 10pt;\n border: 1px "
- "solid #d6760e;\n color: #723202;\n "
- " background-color: #fff2e8;\n vertical-align"
- ": middle;\n margin-bottom: 0px;\n "
- "padding: 0.1em;\n}\n\ninput[type=submit] "
- "{\n font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 8pt;\n font-weigh"
- "t: bold;\n color: #ffffff;\n background-col"
- "or: #fe8a00;\n border: 1px solid #d6760e;\n}\n\n"
- "textarea {\n font-family: Verdana, "
- "Arial, Helvetica, sans-serif; \n font-size: "
- "10pt;\n border: 1px solid #d6760e;\n "
- " color: #723202;\n background-color: "
- "#fff2e8;\n}\n\nselect {\n border: 1px "
- "solid #d6760e;\n color: #723202;\n "
- " background-color: #fff2e8;\n vertical-align"
- ": middle;\n margin-bottom: 0px; \n "
- " padding: 0.1em;\n}\n\nthead {\n color: "
- "#000000;\n background-color: #ffffff;\n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 10pt;\n "
- "font-weight: bold;\n}\n\ntr.head {\n "
- " color: #ffffff;\n background-color: "
- "#3b547a;\n font-family: Verdana, Arial, "
- "Helvetica, sans-serif; \n font-size: "
- "9pt;\n font-weight: bold;\n text-align: "
- "center;\n}\n\ntr.oddraw {\n color: "
- "#412c75;\n background-color: #ccd4df;\n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 9pt;\n font-weigh"
- "t: normal;\n text-align: center;\n}\n\ntr.ev"
- "enraw {\n color: #412c75;\n background-colo"
- "r: #dbe0e8;\n font-family: Verdana, "
- "Arial, Helvetica, sans-serif; \n font-size: "
- "9pt;\n font-weight: normal;\n text-align: "
- "center;\n}\n\ntd.leftheader {\n color: "
- "#412c75;\n background-color: #ccccc1;\n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 9pt;\n font-weigh"
- "t: bold;\n padding-left: 5px;\n padding-top"
- ": 2px;\n padding-bottom: 2px;\n margin-top: "
- "0px;\n margin-bottom: 0px;\n}\n\ntd.leftcont"
- "ent {\n color: #000044;\n background-color: "
- "#e6e6df;\n font-family: Verdana, Arial, "
- "Helvetica, sans-serif; \n font-size: "
- "7pt;\n font-weight: normal;\n padding-left: "
- "5px;\n padding-right: 5px;\n padding-top: "
- "2px;\n padding-bottom: 2px;\n margin-top: "
- "0px;\n margin-bottom: 0px;\n}\n\ntd.rightcon"
- "tent {\n color: #000044;\n font-family: "
- "Verdana, Arial, Helvetica, sans-serif; "
- "\n font-size: 10pt;\n font-weight: "
- "normal;\n text-align: justify;\n padding-le"
- "ft: 10px;\n padding-right: 10px;\n "
- " padding-bottom: 5px;\n}\n\n\nh1 {\n "
- " color: #000044;\n font-family: Verdana, "
- "Arial, Helvetica, sans-serif; \n font-size: "
- "14pt;\n font-weight: bold;\n text-align: "
- "center;\n padding-top: 2px;\n padding-botto"
- "m: 2px;\n margin-top: 0px;\n margin-bottom: "
- "0px;\n}\n\nh2 {\n color: #000044;\n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 12pt;\n "
- "font-weight: bold;\n text-align: center;\n "
- " padding-top: 2px;\n padding-bottom: "
- "2px;\n margin-top: 0px;\n margin-bottom: "
- "0px;\n}\n\nh3 {\n color: #000044;\n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 10pt;\n "
- "font-weight: bold;\n text-align: left;\n "
- " padding-top: 20px;\n padding-bottom: "
- "2px;\n margin-top: 0px;\n margin-bottom: "
- "0px;\n}\n\n#content a:link {\n color: "
- "#990000; \n font-family: Verdana, Arial, "
- "Helvetica, sans-serif; \n font-size: "
- "10pt;\n font-weight: bold;\n text-decoratio"
- "n: underline;\n}\n#content a:visited "
- "{\n color: #990000; \n font-family: "
- "Verdana, Arial, Helvetica, sans-serif; "
- "\n font-size: 10pt;\n font-weight: "
- "bold;\n text-decoration: underline;\n}\n#con"
- "tent a:hover {\n color: #cc6600; \n "
- " font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 10pt;\n "
- "font-weight: bold;\n text-decoration: "
- "underline;\n}\n\n\n#content ul li {\n "
- " list-style-type: disc;\n font-size: "
- "10pt;\n /*font-size: 7pt;*/\n padding-left: "
- "10px;\n}\n\n#content ul.nolistyle>li "
- "{\n list-style-type: none;\n}\n\n#content "
- "li.big {\n font-size: 10pt;\n}\n\n#content "
- "{\n font-family: Verdana, Arial, Helvetica, "
- "sans-serif; \n font-size: 10pt;\n "
- "padding-left: 17em;\n padding-top: "
- "5px;\n}\n\ndiv.guidelink {\n text-align: "
- "right;\n padding-right: 1em;\n}\n\ntable.wit"
- "htextareas>tbody>tr>td {\n vertical-align: "
- "top;\n}\n\np.result {\n border: 1px;\n "
- " border-style: dashed;\n border-color: "
- "#FE8A02;\n padding: 1em;\n margin-right: "
- "1em;\n background: #FFE3C9;\n}\n\n*.alignrig"
- "ht {\n font-size: 10pt;\n text-align: "
- "right;\n}\n\n">>.
+ <<"html,body {\n"
+ " margin: 0;\n"
+ " padding: 0;\n"
+ " height: 100%;\n"
+ " background: #f9f9f9;\n"
+ " font-family: sans-serif;\n"
+ "}\n"
+ "body {\n"
+ " min-width: 990px;\n"
+ "}\n"
+ "a {\n"
+ " text-decoration: none;\n"
+ " color: #3eaffa;\n"
+ "}\n"
+ "a:hover,\n"
+ "a:active {\n"
+ " text-decoration: underline;\n"
+ "}\n"
+ "#container {\n"
+ " position: relative;\n"
+ " padding: 0;\n"
+ " margin: 0 auto;\n"
+ " max-width: 1280px;\n"
+ " min-height: 100%;\n"
+ " height: 100%;\n"
+ " margin-bottom: -30px;\n"
+ " z-index: 1;\n"
+ "}\n"
+ "html>body #container {\n"
+ " height: auto;\n"
+ "}\n"
+ "#header h1 {\n"
+ " width: 100%;\n"
+ " height: 50px;\n"
+ " padding: 0;\n"
+ " margin: 0;\n"
+ " background-color: #49cbc1;\n"
+ "}\n"
+ "#header h1 a {\n"
+ " position: absolute;\n"
+ " top: 0;\n"
+ " left: 0;\n"
+ " width: 100%;\n"
+ " height: 50px;\n"
+ " padding: 0;\n"
+ " margin: 0;\n"
+ " background: url('",Base/binary,"logo.png') 10px center no-repeat transparent;\n"
+ " background-size: auto 25px;\n"
+ " display: block;\n"
+ " text-indent: -9999px;\n"
+ "}\n"
+ "#clearcopyright {\n"
+ " display: block;\n"
+ " width: 100%;\n"
+ " height: 30px;\n"
+ "}\n"
+ "#copyrightouter {\n"
+ " position: relative;\n"
+ " display: table;\n"
+ " width: 100%;\n"
+ " height: 30px;\n"
+ " z-index: 2;\n"
+ "}\n"
+ "#copyright {\n"
+ " display: table-cell;\n"
+ " vertical-align: bottom;\n"
+ " width: 100%;\n"
+ " height: 30px;\n"
+ "}\n"
+ "#copyright a {\n"
+ " font-weight: bold;\n"
+ " color: #fff;\n"
+ "}\n"
+ "#copyright p {\n"
+ " margin-left: 0;\n"
+ " margin-right: 0;\n"
+ " margin-top: 5px;\n"
+ " margin-bottom: 0;\n"
+ " padding-left: 0;\n"
+ " padding-right: 0;\n"
+ " padding-top: 5px;\n"
+ " padding-bottom: 5px;\n"
+ " width: 100%;\n"
+ " color: #fff;\n"
+ " background-color: #30353E;\n"
+ " font-size: 0.75em;\n"
+ " text-align: center;\n"
+ "}\n"
+ "#navigation {\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
+ " width: 30%;\n"
+ "}\n"
+ "#navigation ul {\n"
+ " padding: 0;\n"
+ " margin: 0;\n"
+ " width: 90%;\n"
+ " background: #fff;\n"
+ "}\n"
+ "#navigation ul li {\n"
+ " list-style: none;\n"
+ " margin: 0;\n"
+ "\n"
+ " border-bottom: 1px solid #f9f9f9;\n"
+ " text-align: left;\n"
+ "}\n"
+ "#navigation ul li a {\n"
+ " margin: 0;\n"
+ " display: inline-block;\n"
+ " padding: 10px;\n"
+ " color: #333;\n"
+ "}\n"
+ "ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {\n"
+ " font-size: 1.5em;\n"
+ " color: inherit;\n"
+ "}\n"
+ "#navitemsub {\n"
+ " border-left: 0.5em solid #424a55;\n"
+ "}\n"
+ "#navitemsubsub {\n"
+ " border-left: 2em solid #424a55;\n"
+ "}\n"
+ "#navheadsub,\n"
+ "#navheadsubsub {\n"
+ " padding-left: 0.5em;\n"
+ "}\n"
+ "#navhead,\n"
+ "#navheadsub,\n"
+ "#navheadsubsub {\n"
+ " border-top: 3px solid #49cbc1;\n"
+ " background: #424a55;\n"
+ " color: #fff;\n"
+ "}\n"
+ "#lastactivity li {\n"
+ " padding: 2px;\n"
+ " margin-bottom: -1px;\n"
+ "}\n"
+ "thead tr td {\n"
+ " background: #3eaffa;\n"
+ " color: #fff;\n"
+ "}\n"
+ "thead tr td a {\n"
+ " color: #fff;\n"
+ "}\n"
+ "td.copy {\n"
+ " text-align: center;\n"
+ "}\n"
+ "tr.head {\n"
+ " color: #fff;\n"
+ " background-color: #3b547a;\n"
+ " text-align: center;\n"
+ "}\n"
+ "tr.oddraw {\n"
+ " color: #412c75;\n"
+ " background-color: #ccd4df;\n"
+ " text-align: center;\n"
+ "}\n"
+ "tr.evenraw {\n"
+ " color: #412c75;\n"
+ " background-color: #dbe0e8;\n"
+ " text-align: center;\n"
+ "}\n"
+ "td.leftheader {\n"
+ " color: #412c75;\n"
+ " background-color: #ccccc1;\n"
+ " padding-left: 5px;\n"
+ " padding-top: 2px;\n"
+ " padding-bottom: 2px;\n"
+ " margin-top: 0px;\n"
+ " margin-bottom: 0px;\n"
+ "}\n"
+ "td.leftcontent {\n"
+ " color: #000044;\n"
+ " background-color: #e6e6df;\n"
+ " padding-left: 5px;\n"
+ " padding-right: 5px;\n"
+ " padding-top: 2px;\n"
+ " padding-bottom: 2px;\n"
+ " margin-top: 0px;\n"
+ " margin-bottom: 0px;\n"
+ "}\n"
+ "td.rightcontent {\n"
+ " color: #000044;\n"
+ " text-align: justify;\n"
+ " padding-left: 10px;\n"
+ " padding-right: 10px;\n"
+ " padding-bottom: 5px;\n"
+ "}\n"
+ "\n"
+ "h1 {\n"
+ " color: #000044;\n"
+ " padding-top: 2px;\n"
+ " padding-bottom: 2px;\n"
+ " margin-top: 0px;\n"
+ " margin-bottom: 0px;\n"
+ "}\n"
+ "h2 {\n"
+ " color: #000044;\n"
+ " text-align: center;\n"
+ " padding-top: 2px;\n"
+ " padding-bottom: 2px;\n"
+ " margin-top: 0px;\n"
+ " margin-bottom: 0px;\n"
+ "}\n"
+ "h3 {\n"
+ " color: #000044;\n"
+ " text-align: left;\n"
+ " padding-top: 20px;\n"
+ " padding-bottom: 2px;\n"
+ " margin-top: 0px;\n"
+ " margin-bottom: 0px;\n"
+ "}\n"
+ "#content ul {\n"
+ " padding-left: 1.1em;\n"
+ " margin-top: 1em;\n"
+ "}\n"
+ "#content ul li {\n"
+ " list-style-type: disc;\n"
+ " padding: 5px;\n"
+ "}\n"
+ "#content ul.nolistyle>li {\n"
+ " list-style-type: none;\n"
+ "}\n"
+ "#content {\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
+ " padding-top: 25px;\n"
+ " width: 70%;\n"
+ "}\n"
+ "div.guidelink,\n"
+ "p[dir=ltr] {\n"
+ " display: inline-block;\n"
+ " float: right;\n"
+ "\n"
+ " margin: 0;\n"
+ " margin-right: 1em;\n"
+ "}\n"
+ "div.guidelink a,\n"
+ "p[dir=ltr] a {\n"
+ " display: inline-block;\n"
+ " border-radius: 3px;\n"
+ " padding: 3px;\n"
+ "\n"
+ " background: #3eaffa;\n"
+ "\n"
+ " text-transform: uppercase;\n"
+ " font-size: 0.75em;\n"
+ " color: #fff;\n"
+ "}\n"
+ "table {\n"
+ " margin-top: 1em;\n"
+ "}\n"
+ "table tr td {\n"
+ " padding: 0.5em;\n"
+ "}\n"
+ "table tr:nth-child(odd) {\n"
+ " background: #fff;\n"
+ "}\n"
+ "table.withtextareas>tbody>tr>td {\n"
+ " vertical-align: top;\n"
+ "}\n"
+ "textarea {\n"
+ " margin-bottom: 1em;\n"
+ "}\n"
+ "input,\n"
+ "select {\n"
+ " font-size: 1em;\n"
+ "}\n"
+ "p.result {\n"
+ " border: 1px;\n"
+ " border-style: dashed;\n"
+ " border-color: #FE8A02;\n"
+ " padding: 1em;\n"
+ " margin-right: 1em;\n"
+ " background: #FFE3C9;\n"
+ "}\n"
+ "*.alignright {\n"
+ " text-align: right;\n"
+ "}">>.
favicon() ->
- jlib:decode_base64(<<"AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAA"
- "AEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJf+cAAI"
- "PsAAGC8gAVhecAAIr8ACiR7wBBmOcAUKPsAFun8ABhqeo"
- "AgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhICAk"
- "JCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkX"
- "rRCQkJCMgI7kiAjICAUFF2swkFBQRQUXazCQUFBAgI7ki"
- "AgICAkJF60QkJCQgICOpiHkyAgJCRevdvlQkICAjdndnM"
- "gICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- "AAAAAAAAAAAAAAAAAAA">>).
+ jlib:decode_base64(<<"AAABAAEAEBAAAAEAIAAoBQAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAA1AwMAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMARQUEA+oFAwCOBAQAaAQEAGkEBABpBAQAaQQEAGoFAgBcBAAAOQAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wUFA/wEBAHOBQICXgAAAAAAAAAAAAAAAAAAAAADAwBCBwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8DAwCUAAAABwAAAAAAAAAAAwMAQgcGBf8HBgX/BwYF/wcGBf8FBQPMBAAAaAQAAD8DAwNOAwMDlgUFA/QHBgX/BwYF/wQEAHkAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8EBAGeAAAACAAAAAAAAAASAAAABQAAAAAFBQGxBwYF/wcGBf8FBAPvAAAAKAAAAAADAwBCBwYF/wcGBf8EBAHPAAAADQAAACEFBQGuBQQD8AUEAeEFBQGuBQQB9QcGBf8HBgX/BwYF/wQEAH8AAAAAAwMAQgcGBf8HBgX/BgQAbwAAAAADAwOXBQQB3gUFAdgFBQHZBQQB3QUFAdYFBAHhBQUD/gcGBf8EBAK8AAAAAAMDAEIHBgX/BwYF/wQAAD0AAAAAAAAABQAAAAEAAAABAAAAAQAAAAEAAAAFAAAAEQUFArwKBgX/BQMDxQAAAAADAwBCBwYF/wcGBf8DAwBKAAAAAwYDAFAGAwBVBgMAVAYDAFQFAgJZAAAALwAAAAAFBQGuCgYF/wUDA8QAAAAAAAAAKwUEA/QHBgX/AwMDlgAAAAAFAwOIBwYF/wcGBf8HBgX/BQQB5wAAADMAAAAWBQUD5wcGBf8EBAGbAAAAAAAAAAYFBAG9BwYF/wUFA/EDAABAAAAAAAMDA1QDAwOYBQUAhQAAACQAAAAABAQBnQcGBf8HBgX/AwMATQAAAAAAAAAAAwAAQwUFA/oHBgX/BQQB5QYDA1UAAAAAAAAAAAAAAAAAAAAXAwMAlwcGBf8HBgX/BQUBtQAAAAcAAAAAAAAAAAAAAAAEBABzBQUD/gcGBf8HBgX/BQMDyQQEAZwGBAGqBQQB5AcGBf8HBgX/BAQB0QAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAUFAmQFBAHlBwYF/wcGBf8HBgX/BwYF/wcGBf8FBQP+BQUBsAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwUFA40FBAHrBwYF/wUFA/4FAwPGBgMAUgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">>).
logo() ->
- jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEA"
- "AAAAXNSR0IArs4c6QAAAEtQTFRFcTIA1XcE/YsA/40E/p"
- "IH/JYc/5kg/54i/KIu/6U6/apE/61H/61P/bFX/7Vh/bd"
- "a/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA"
- "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3t"
- "qO/DhxihMg33VJ7JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcM"
- "UVV1xxLXIlRfPAZptYrbf5YeW618PWyvG8w/g9ZwquuJ6"
- "Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/"
- "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmV"
- "pb5NN9LUyddu7nnLYkrrrjiimuVK6mZB+6VuFbiXJk8v/"
- "bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xxxRXXKldSMw+8KPG"
- "gxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx"
- "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8j"
- "O4Ot9uTEq8KrrjiiiuuZa6kZh74UFpli3sO61btMfyHyW"
- "Gv/RMs7wB67ne32/BdwRVXXHHFtcyV1MwDn0qrbHHvyPT"
- "/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI"
- "F5dWoNvcLcs/AAAAAElFTkSuQmCC">>).
+ jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAA64AAADICAYAAADoQ7yoAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AABAAElEQVR4Ae19CbgdVZVu1c0EGclMBsgNDUHRACKgQpjs0D6FqK0tCCqE9mt8HezXdjO07XvdBAdsIXwP+zUI2D4GWxB8ditBWwFBQoBWEAjYCjTk3oQMZCBzbpKb5Nb717nnhDucc24Ne+3au+rf37fuuadq1xr+tavOXnuvvSsMWIiA5whEUTQMJgytmjEYn/uCINwRhkHkuWlU3zME0BZDFLY7z/yWRl36Og1qvIYIEAEiQATKjID8dor97CuVuRWU0HY0/OGgRXvxp0m5G+cmlhAemqyMANrVNNDdTdpfG87PB8lACovHCMCH9LXH/qPqRIAIEAEikC8C+B0dBVoI2gyqV9pwUM6PyldTSicCCghUGzc+4pbOWxXUIMsSIoAWN2x7FD0ft+VV680vIVTem0xfe+9CGkAEiAARIAI5I7ALg/zJ+kzss+fsMoo3hcAazLKuS9b6e9SWK9cMN6UL+ZQPATSg2T0aVMJ/Ny0uH2L+Wkxf++s7ak4EiAARIAIOIICMs/R99k34GWaf3QEvUoW0CKAFj8WoTcayDdfzRkjrgzJfh5TguRkbHy5/g8GrB42IvvbASVSRCBABIkAE3EUgilrSB6213lal1z/WXSOpGRFohAAWcme/AWo3wvLNjcTwOBGoh0BH9xrHWgPK+LlxQT0ZPOYGAvS1G36gFkSACBABIuAvAq8lX1LVoG8lM69Bi79IUPNSIrAcmzA1aNEpD2+YV0ogaXQqBFanbGX1L5PtnJZy84FUntC/iL7Wx5gSiAARIAJEoLgIbDSSodazB7WC2WrFbS4FtAzrWqWrb7as5utKCthUNEzCA3iB2bYn3PgQ1vBVVp70dVYEeT0RIAJEgAiUHQFzGZK9el+tZceV9nuCQHsUXdmr6Zr7MssTCKhmjggoPYClFXPWNUe/1hNNX9dDhceIABEgAkSACMRDwPxsa63T3/ZYTQPmDdeQ4KerCCitCVx5oasGUy83EFiLdwBPUlPlzc+osSbjxAjQ14kh4wVEgAgQASJABHohsDUIFvU6YOzLmNOx1jUUdoON8SQjImAaAexKNi4IWk2z7ea392J8LtThTa5FQGBXEJylZ8euS8H7Zj3+5JwEAfo6CVqsWyYEMN8hu3oeXaUZ+BwNkoyRkdVP+b8n4WuwvQ/t6PF9G/5fAXoF9HIYhpvwyUIEiIDvCGAjVTwsjtMxQx5D0SFBEG5m4KqDMLmaQSDaZ4ZPHS7DMDDEQgQaI4B0lAmNz2Y+Ix1AFkcQoK8dcQTVyA0BBKhHQPhskASpspSmFqxOxP9Jy5S4F0DuRtR9uQ+9iIB2eVwerEcEiED+CCzFYJaMYCmWMeDNwFURYLJ2GoE9cgOwEIGcEBhziGzxHoZBV04KUKw1BOhra1BTUGwEEDAehsqSVfL+6ufhsS82W1EGCIVO7ckW+r2O74+AHpVPBLLynYUIEAFHEZgWBONtzIbakOEoxFSLCBABIkAEiAARIALFRwCBoCzZrwWp8nmk41ZLYC1LeoQC6P8qPipBrHwikF0nx1mIABFwA4HJQbDXhiYMXG2gTBmpEdBroEwVTu0UXkgEiAARIALOI4BgbwSU/BhINoP7Q1ALyNcigbbQn4G6YJvMxn4X9K8IYmUNLQsRIAIlQMDnh1gJ3FNyE8MwwkLUZToo7P+eDl9yJQJEgAgQASKQDwII6FpAZ4PuggYyKymfZ4OK1N8TW+aC7gS9AVu/W7W5SDbCNBYiQAT6IsCbvC8i/O4UAgcHwY06CkU/0OFLrkSACBABIkAE7CKAwO0Y0HWQKmtBHwTJLKvMuBa9iI2fBonNrwOD60HvKLrRtI8IlBUBBq5l9bwndmPHhrtkX32zZdWWMJzZbpYnuREBIkAEiAARsIsAgrSTQT+G1N+CrgRNtauBU9LE9itALwKT+0HvcUo7KkMEiEBmBBi4ZoaQDFQRCMOuziC4xKyMSNKmWIgAESACRIAIeIkAgrIzQQ9B+V+BPgwKvTRER2nBYh7oP4DRw6CzdMSQKxEgArYRYOBqG3HKS4zAhDC8oy0ItiS+sO4Fa5eE4eHP1D3Fg0SACBABIkAEHEYAQdiHQE9ARdlhV9Z5sjRHQDalegSYPQk6p3lVniUCRMB1BBi4uu4h6ldB4IggmLg+MxYrwGHqmZnZkAERIAJEgAgQAYsIIOiSDZeehcifgE6xKLooot4HQx4Ahs+B/qgoRtEOIlA2BBi4ls3jvtobhvsmY897rHddks6E9diduLUlDIMo3fW8iggQASJABIiAXQQQZE0D3QupD4LeZVd6IaUdD6t+DkzvA00vpIU0iggUGAEGrgV2bhFNGx2GZ8Cus/clM+6SMJx8PIPWZKCxNhEgAkSACOSDAIKqwaDLIf0l0Hn5aFFoqZ+Adb8HxlcI1oW2lMYRgQIhwMC1QM4siymYeH14CP7A3rMxfdroPa/tOH8ZSKregU8WIkAEiAARIALOI4BA6jQo+RxoEWik8wr7q6Bgez3oeWB+ur9mUHMiUB4EOMpUHl8XzlIJYGGUpP0E+NHpOQgT4RxTggvncRpEBIgAESguAvgdwxvgghtAFxXXSictk/e+Pgb8/wWff4X+w0YntaRSRIAIBD07+4SDCHiLAH5ounoQg1ZvPUnFiQARIALlQwBBk8yyPg9i0Jqf+z8N0cvgC86+5ucDSiYCTRFg4NoUHp4kAkSACBABIkAEiIAOAgiSQtCXwP1R0DQdKeSaAIGpqCuvz/lfIPaREwDHqkTABgK8KW2gTBlEgAgQASJABIgAEeiBAAKjSfj6M9DXQIN6nOK/+SIgvvgKSHYfnpyvKpROBIhATwQYuPZEg/8TASJABIgAESACREAZAQREZ0KEpAbznaLKWGdgP1d8BF+dlYEHLyUCRMAgAgxcDYJJVkSACBABIkAEiAARaIYAAqG/w/lfgKY0q8dzTiBwKLR4GD77e5C8zYCFCBCBHBFg4Joj+BRNBIgAESACRIAIlAMBBD7ybtbbYe2XQex/+eN28dU1oDvEh/6oTU2JQPEQ4IOzeD6lRUSACBABIkAEiIBDCCDgGQ51fgSa75BaVCUZArLj84+rvkx2JWsTASJgBAEGrkZgJBMiQASIABEgAkSACPRHAIHOOByV946f0/8sj3iGwIeg7y/g0/Ge6U11iUAhEGDgWgg30ggiQASIABEgAkTANQQQ4EyHTo+D3ueabtQnNQLvxZWPw7eHpebAC4kAEUiFAAPXVLDxIiJABIgAESACRIAINEYAgc0xOPskSD5ZioXA22HOk/DxO4plFq0hAm4jwMDVbf9QOyJABIgAESACRMAzBKoBjcy0clbOM98lUFdm05fA1+9McA2rEgEikAGBQuyOhoeGbFF+MEhe5i0PkqmgCdXv+AhmgdaAdoB2gjaAVoFWVv/fGoZhF/5nIQKlQAD3jAxaCQ1qYvBe3hdN0PH0VAPf74c5XfS3p05toDZ93QAY5cPA/XCI+DlI1rayFBsB8fHP4fNT8PxcUWxTaZ0rCKC9Sdwj1LcfJ7/l+9EWI1d0Na2Hl4ErHDYWQMwBXQD6IOgQUKYSRXtx/eAl+HMv6KeglezEAQVHC9qA7NA4H3TpviA4bnCwd0sQDAHta636cRHu6Z+EYVDKAYnqQ03uiz8AnQyS++WYbqzwX8wCPlITuAYvgJ4GPQZ6FrSW9wdQcLDAZ8Oglrwfcjbo3VWSGYFWUNPS7W+0kmBwO/48BVoK+jXoNdCWIv8Ywj7vCn3tnsvgExk0fxA0zT3t1DTqBOdtoO2gXaARoNGgUSAv+5nQO0mRyZIH4ftT8YzcmORC1m2OADCVvt500NEg+T2TiShJvR8Dkj6OkPRR5LfqG8BfPgtRYLsEpbIJ2HEg6cOJ/Ql+yyu4rMA1vwM9V6Xf43MdcJIfei+LROvOFzhPHnxngv4SdC7IVlkGQdeC/h1OlgcyS94IoC1gOOku3M0yaBGnnA/f3Renos91qveIrLn5OOhiUCtIuVQGC74DIbeBXgXOhRokaI+iBTOC4CYdEOVxMnpQ1oGVqt+PALOPgD4Fkh84zSLPxO+BfgBaAZ8XYlSXvq7bZArp67qWGjqI+1ECtkdAMlhYpCLZai+BpNNbI/m+AbQdzwEJXOsWYCLZcBLEHgaSgKMnzcR36ZwXpTwDQ84CHoIXS0IE0FZk0FUC0wz9mMrA69nwwcMJxedevfp7Lv24izGV9tkhBiblGhtVwekBnP8h6CHQGmCW6fe8I4qm4WZf1Vhm5jMzoWN7Zi5qDDANDifO6Yqi5/HpQOncDCXOA8mNxZIDAsB+9t5ULWET2lAlpSIHrfVEAorhIGmTjtwj0WPQZQ6oEB0RCWZgi1LZBr7pOmy4cBpoYWcUyTMp7yI+nwuSwUVvC30dqxkVwtdajRQIDgFJymgRyh4Y8Qjoi6B3gVQmOcB3NOiPQbeAcBsWosjM61CtdlY0vsBK6fdsw2M+YAX7R4Hmg9pAeRfpS4ouktWauEjgqmxAa2KlbFwAo8WJi5SNz8h+n/yAuwmgDSflIGN3FM3L5rR1uDxdoJCDuY1Fdg/ozEWvoi0bHupXL4aE1saGuH/GpWAGWMoPwq3qXssmQJ6Lkp7sXaGvEzveW19rNE6gF4LuToyiWxfsgDrfAZ0Lkplj6wVyjwZ9AfQsyOdyD5RXCfatO0VBILCp/J7tVffwakhwr98HpQaDztvpxuBzAy/skuNXgiTdP1axFbg6MzMCcGbJ7CrQkXUSl8dCKbdKg06H6LYoqszCzstNjZIIRttoxTT3/dnMnYTL18g6PS8LMJDZ1UXI4+iCAQ9hOLfVcUMkpR/3iMwsyqBD8Wa81fHv7gzPk5lVyJL0m0vVZWYTIM/FF8ThKDL4GPsHL5vYAlxNX/vuxGthQNzlK67ZKv2uBaCpSMP7LOgBkGxiab1A7sugG0EnQPhJoG+DfEy7/ST0/gcQSxUB/B4MAy2QcAiHKr9n+mk6svS4HavL3CgwXQL2xdAGmcDBvcNVU4Gz2nyQMLgOhE5cl8zEnpiVo6nrcw9cAcasrd0ds5cxPKW9PssUblU+Q2RR+P3Y2AlmROcZZk52ggA6dOsRAJkBY0prFG2UH2hvCtrV8P0YyYfC0pFwfECnHqyV2AWDDnsxLiWptwxg66HU6xhSrYHVldVBivt117n0kmzyi7RV/OBVslMmmmRcKF70tffuxL16Doz4G88Mkc78XaD3IEh8F+hbIJk0cKZAn2dAMlgnG819DvSKM8rFU0Rmq2zuyRJPK8u1gMG0fVEkabu7QTdVwiGrOsxAN/J16UPlVoDBiTK7CgUkYPewTYQSmz1dm4jIDciq4NwCVzhyLNIdZaTvZazalwDQ41IZN7o3iipT63M8NsQ51ZcHwfUyV2qujLgJwZP765S7U0kWwe6duEl9Hcnv4bbKPXJTNYCd3+ME/60h0D3rJgGrdCplpLMApZKdsr4awKZaN1MAEPqbQF/3x8TDI+jHTIfad4Iw7u5Fkdmu74OOQVB4MejXrmsNHXeAZAPAY0DzQegWeFGkTdyJNnK4F9oaVhJ2z5IlTWC7alAQSDZOjmX6BVG0YZZtBYDB7GrA+vRw7+McQa97IqIa68y1jWdNnv3AtfsHWzrkm4Z6N8Nag63RZ2Us6fEo2tKGBstOWiOY4h5H8IahVsOzjOKj5f8YV4U86qHtnIfehaSSGLY9D2v6yqwEsLdzkKc3LvD5XDi8C0cLErD2tg+vC5aOyybYiXW67q056qut5nf6WhNde7zhR3mYSRA43p7UTJJ+hKuPQxB4Aci32csAOsu7KWWQ4GiQzMSuBLlexkHB71fbiuu6GtEPtk6sTUqhj99qhKkRJjtfNsImBhNgUJuYe6EYAWtfoyuxzkNRtFU2iGzte1b7u9XAFQbO3tXdOStgh7ynq8aII6WTdmXPo/w/GQJrguC9ldsj2WUxao+41MWUVbSXidW0+XtjGOF5lYpnMcizXdZO4NletrIPHbFA0qfHvtmdQvRQJaQvPgzocEbIfo9KtDcAfV3QZv012HWqB7b9VvRE0PfHoBc90LepirBhH+jbqHQU6O9AkoLqcnkflPu6ywoa0Q2TUtiP4VbwWo+A1cFlf5Iy3Km+ThO/bQuBQQEn5uq1ktGSLdsWRfuxMV0QPhIEHfVqmT5mLXCtNugXdAIR07AY43ddFL0pIxLcpCQFpLgDFqS4LMYlknzcIav2nSnVh916/9Pmk0I6Un7gkE1TpkBGMBqLH9HK2uVNGJL3fKlEUp9X6mNvgMqghftp+6nM63kRfV00X+Pe/RA87PrAtAR0XwKdgEDvyZ4tsgj/w6ZO0Fdhy2zQLxy36XK0GQ/XNsZDFbadKBlD2I8BA5Mul7VXa2kHDFqxqYOk4qvJ0NI9O9+WC7ClRRcW+0tWh3pRD1zhx7Ey9eh+g9bCepx0SqU9l2iGwQyW6NEeY4ZTPS4dZ9c7avsY2sWoyot6Svmw64U2ApkNj7k4E95LS7Nf8LAvc6kMWuBNV9HcEqBAX2NmrAi+hg2yrvUukMvrWh+Gfu9EYPd1EGKK4hbY9ypIniEXgTY4amltvethjuqXTi3MsmLUeTEuftqPjKGDVAYPsD3rQmDQVu4Zqor1siRIvagGrnjAS7C2iYs9K35Ex3zr3eoeLZCATlVbduS+0ynuD9nIa5vZzadUQVNmPgEPvU2SPlvu578yyg6yl7UyfDY66BgFlYrg61uAi6vrWuVn8/MI5M4GvabgP2dZwt7vQrl3gB50VElZ7yqptIUo+J1ulaV/WOejEgzqgDRJMp2M9f3Aa9gbWOqDoL2Es6w6HorDVS1w3d6d635/HCXKU2c0djarpA6XID2uPF5NY2l1hO7xNNcW+5rKMJdkKMwutp20rjcC8myU3IM16AexFBsBf32NBvpR+EZef+NiaYNSpyCAu8lF5WzoBNtlxvWDoL8HddmQmVDGB9GGPpbwGueqw4b5UKrNv6V/kj0fymtpMhdgMGsfskgml3OpT2b8sjAwH7gidWANtsAe6XyuexbYslwrqcO7JWVqWhYuvNZfBJA6v5gjdAP674Uo2nPegLVYoUAISO7BRFnvPKtARtGUugj452u0SxlU+WZdc/I/KGvLZC3rb/JXJV8NgEEX6CvQQpYDrctXm7rSb0RbGlH3jAcHq6nBt3ugah0V18mmiIg3sxX4bz44vOxHenQ2W1282mjgioB1+CaMck1xagtsF2GvjFOtQuNX3+HMRevLrJMM6mBO0aPUmjy9NRTvRt7h+gYoeQJUQNmVrsDLHLQooGv7meSdr2UW7/B+ZuR7IIL4LyJQkx2Dt+SrilvSgccj0OhdoGfc0iyQda7+pZbi9U+yA75fqcF9Pb/3gb5Hkn6vZpN6GrgntdbN+sYCVwRhY9Eh3wliiY/A08BN1gGzFB0BZCLIWggO6iR19AjszL12UdKrWN93BGTQYvtC362g/nEQcN/X+J1+Oyz56zjWWKwjmy5dhADtGxZleiUK2KyFwmeBHnJM8S+gTcl6XC+K9O+xnnUvFukiY9DnMuOa1NqjD4fFLM8zmzQ1gsYuNBK4olG3QqNN/uW7G8MxCyNs2rRzQRYGvNZxBCRoxf3BtRBp/XQoXiWwhcFrWvi8vW7k1VG0WmaUWAqPgPO+vhkuwMsRnCk7ock8BGb/4oxGjioCjHZAtXNB9zqkorQlL9YiF6d/37YlDIc+k6YNLMWGkfgh6sICBwffT5vGIr+vyRy4Vht1m98w5K398JuiaCOD17zdoCT/dQatBpAdg+CVM3AGgPSMxVTsArmGvy+eeS2dum76Gn2cT8GeM9PZpHKVbEB0FgKyn6twLyBTYCW7LV8I+ieHzDsDbeszDunTTxXoJ/sNFOT5G0raeOIiSyDfjbc/JL6QF6ghkClwRaOeCM0K0qjVMI7JeDyC1zfPi1mZ1TxBQFJLpnufXuMK2DIrwwEeV7xhT48prQheH7Mnj5LyQ8AtX6OPIzNjX88Pj36S38SRMxGIPd3vDA80RQCYyaZNf4FKNzataPfktWhjQ+2KjCcNerWi5svxartea/01YTizPbGWUdQyCEsgmU2aGDnVC1IHrjIKgY2l16tqVzrm47Cuq5MbNhXE7yuwezBTS0w7UwZ4Ns41zZX8XEdgyulc6+y6j0zp55SvL4JVspmOC0VSXj+E4Ot3LijjsQ6yVtmVFGuMawcXu4Ylgtax2Hq3IJNSa5eE4eSFaTBeEwSvoQ/H4hgC6QJXrNnjKISWJ4fIhk0yk83iMQKYaV14OHcPVvLg+Id4jyhB6zRbWeu8m5vZOe0jU8rl72s8Y9DNCb5oyqKMfCTVVXYO/nVGPqW/HBhGAOES0E8dAeOL1bbmhjrYPRgvOt1U2fPbDY0yaLHnhjCcekYaBq9H0d3cTDMNcvrXpApcsWbvOY5CaDpn8/ooClL5RlMr8o6HwN4omoP74+p4tVkrHQLreY+kA87zq4ZhM7sOvgPbcy/GUz93X58PPY+Mp6tqrf3gfiECrodVpZSIObDEhGLwCdBTDph9BHS4wAE9Kiqgf7/B/7eDiHuD08LwoCvS4IrX/pw33SGfpLGhyNckDo5kJgkO5c5aqq1CHhvtr6mKIHMVBGT3OTB+XIU5mfZAQIbO2h7tcYD/lgaB3XgHNgf2yuHufHyNGbAQ+H7JEYz/BoHWDx3RpTBqANMOGCMZHCsdMOpL1TaXqyoyy4j+vcevvMECxiC4DBuAt8C/S9OA2RFF0/DaH5d2oE5jRqGvSRS47oii2Q7PJMnLt28DfRh0NAhtL5A11UKywYKQ/D8ahHszOA10FWgZyMEyAxuSrFvooGJUqQkCM/Aj6G6Kzd5G90jf+2MmTJT76AZQO8jR0op1jxuk48HSFIF9cvYBkKTHHQuSqH8EqK/fez4Xl+C8o0UG9jhoUd859HV9XBIf/SiueEfiq8xf8CN0wOU5zKKAALB9E2xl5lVSsfMs8p7gj+WpwIYomocfAGdmfvtjceDZdg3OSf/kJJD8nkl/X/osI8LwYLg0vDkMgwjfkxcMWG0PglXJL7R1xQEMEJxX7K/9ltfiHPmU33a4snJeYhyHf8uhnWpB3vs2DAk5VhZDnxNB2WIFNFbwmAZauAt/3CobZDvyUpbXsCOvni/arzQNansUXamnb2rOco/MAaW+R3BtC2g26G6QYwWJ2dGa4aZ9KfzgzwWOGZtAnT1tqHweaFQWbHD9RNCCzijajE/Hirld2OnrIIBzS+HrOPcDsHjGgcaOn8BoTBx9bdaBTsNBZ4BkbeZ3QT8BPQV6GYT4J9oNQnJe9DsQkpCiH4O+DfocSH5HEk2Y2LANOv0FKO/yrA1b68mQTDH5JXWrVDRaBJ2kzaTuv9Szt9Ex2VDTLQwq2kg/eB7IxG/5le7FOIkQb23ku7rHdYOIJIrvlA7UXJDaww+8W0GONOBNUKWcaXG6bc5s4PoAduGDoxwpnXKPyIPO/D3SPcgzZ6dTgczrz9d9aGU86GkwsxB+z/QD1wg28JU2Lh0JR4p0bJYasZW+7u11AFtYX/e2tP832H62Aw1cgr8T+mtn/wj0kIH9s0D/ByQBfdYYZyt4PAj6W5DMDDlRoMt9oLzLB/IAAynCLg1M3gonTLSNAxq1DPA7UirhpQycD9PAAXyngRyciBgQ/lbBI1bHVlIIjsh9XWtHO/Q9OgxHjEUqwMOgLjFAo4B3O2geeMuUu6Qf51gkLe6/fpyjAhQdA4F3BkFuo6VvqbdNUoFxjwyVe2Sxyj2CHRnBdylycqRhzuxwIpV4+nHlfkVOJX0IG8mEsq5nIQjZTuYL+G4GXRF2/26cX5FqXkwCjjIIP/2FBBcUoCp9bcGJf2pBxkAiLse9lutvCrqQk0F/A0VfAT0C+jzo3aCss1+jweNs0LUgjBlFGPeNPgLKyhfsMpXP4urlmThkv9h624MDrsTowSHZVc/CobI2VX7DBqHdfw60IQu3xNdikH+XE3uTdEgf7qS3Up7DPYltiXEB8F0NuhBVJbX4hhiXOFVl4MAVDxOE/PfnpzU25u4OWGcCaHmAWiuQ1wH6HASOQHdB1ojlVI48N4p2zM5JOMUOgADeaTwHa1tbB6imeLrSmcUOemMkYLV2j0BWOwJYWVtyrEqklAixlodKmplwTXUjivtSr+tJhDMqdw9e3DcEDQDfZA1NjkX2AijNe33pa2VfI3iSoOojOTZoEf0foJvz0gEYzAL9APKxwWzwD6AjFXUZBN7ngH4EWgm5V4CGKspryBqPM/kZ+/OGFeyc+DDst5YeLinC6LtcZ8e0hlIuqQZq8humNiHVUDpOvIr3+hpJ3WkmpOm5SuCOPlxlYu6ZplUNnkSb3wOSnZcPQuNfYpC1KqsBA1c49K4cHXp+GI4DrvY64/XQhvwOdNLm4dzMShhdr5L6sa0lm1lQB9SYAOwCkOMuwrvvqQYuS40ZlJAR7o8XR+MPLssxiJEJ4OV5/wAnRC5L9U0yMisPR8ywptyIIov46rWQfz3+HbE115n3/TJoIe2voIW+fsux6r6WjXoOfkue9f9kFFJmnNJtLpNBXQRMo0DyDP0t6E9AsnmbzYLXZgbyPPkt9DjXpuCaLOD+IP7/Xu17Dp8yA3aeLbkAPMe+y3YJlGT88w5b9taTsxZpyX+Q66ZUe2/DIyf1Lsj1bEp6DD7Ygz7cGbjupMo0SFIGlus3DVzzc+iGduBwEMC8zzIeTcVBn3bpKaJSDh30qUEUtS9oqiBPWkdgYxTNn2pd6gGBGKE7GO/3yy9wOaAJ/sGtIZ2O6fnNvh52udZGTT3tzP9/ean6eJldz28crQcI0KPjkO6Z98t6HLb47yTI2pj3TImSvfR1b2DVfX1Rb3nWv92I+8nqIDWCRFnDKnZLto5sWmg7YO0L8lE4IHuM/Az0tr4nLXz/K8jYZEFOIxFW2uDvsenREfktATw7DEefgbaee5wEBR5q5Ajd4xXTkRY8FANVzvThnsHNPwgdi2W6tmfj3jRw7cjFoV1XheGkmWjQKrnd2eDqvhq6SQd9pv07btxNmFnIex2ICQiLwQPrInAD3W7fmEq8IpOcuc2yNrIZOq1Grl1LPmkncmt0/HMj3QpyPPVL1bXth+9vhoyj7T8XxbKh8mxs+numbb8Cf/q6Lqg6vkaQ1Apx8pq8vMpKCF5oUzhsHgd5PwXdCTrUpuwYsj6AOs9Bx0tj1DVWBc+xDWAm6ZN5lVNh8xHawkfmkhp6oO/ysLZ9cfjLPT89l+BdsmgGy+SctbTgOHhU6mD/IEzQHb8jCK6JfY3lig1/6NvgUNw5x1nW59gwHCRBofMFDa4dIxND1gWBpOxZKpK0vVbWnLA4gMDGIPjvkqBqt0g2wjhJK8lvYnMgg5HmJmkn+Tz4ZlxQzFnXyhqYSfC7c4MVPZsD9HsFvb4R63setPK/PBvXXW5FlLoQ+ro5xGq+/gzk5ply/kXcPzub227uLDrtJ4Lbs6D/Zo6rcU6SOiu7zMprd0YY596AIfwgA9K/bnBa+7C0QWmLakX25UDAdoiagLqM3eu7vBYEj9ZVVfXgxiXVjClnJ+fE/FFYhoSPs+V/10rDwBVzFz+yp2zlh1qycF+0J9OAJKQ5HIrdVRHALDHALSaLCUiHDLBfFkuuCGC2FTtI3GRXh7V44Ek2ghtpJQPZLg++ziDAToE2i8y6hl+2KVFfloxSb8Y+WJZ3Wkxp2FSkDk/GrLvdQT1R9uDr/J91pa/jNTsVX6sGCwPY9RLO3ztAHWOnEQTKppMyCDbDGFNdRp8G+19D77friunF/e96fbP7RbUt4nfZ8trWzQ+41nfpwCthsLa11a5bN90WhhPPsCszvTT0OWRm/Nj0HHSurBu4ikPtTZ8f+KGWf7wsEzG7hAURt9lRXjrmy//RjixKaYQABisuknF/e2X9PWE41ZsHXg2XYd3r1E+qfbfzOUIGd+RGKUCRuctx2MBiKlZueFQw6y6DenioW1wrI3fkRivrw3Q8QV/Hx9WsrxEQyezjUfHlG6/5VXQSu4xz7cMQdsp6VhlwvQXk2wD4MdBZgtcz8ale4I8HIcRygHfArD+AnScf+Gbwn+pbEAxyHIiV9F3GzRuolu3zGFi9267MHddgplUGjLwquA9kQnGmS0rXDVztOdTTH+o6HhyP1+ZstfY+pCmXcta1jhMsHtpvdW3rSoxWTr7QonlGReHB9wwYWhy1q3RqZYTe8yLPx8l4r12wz1dDZK3MKqvLKfbc7idW9HVyvxn19dzk8o1d8Qo4fd8YtwaMEAhJf++fQQsaVPHh8Ego+e+w5UOWlM1z1lWlTaLvstgSdhCzzsm+C14cPLY1CE63h8NqzLSOWmhPnllJ6MO14z23mM90o/QLXO05VCZYJ2Mmwd9OWV8XYmfNK+wEr7LsY/nX+srndzsIdGJ0fpIdUZCytj0MZzg3WpnUfBm1w4KODye9Ln19o53a9GqkvlKWT+xHenA+77VLrXadCw8Lgon20mlk9/U1c+qo4fAh+jqdc4z6+v3pdDBylcy2Ip7QKwj0BoH7naA/1ZNijbN0gH4Em/5EWyL88hhkPKwtpwF/421yA97Ri+jD0trWVVvC8FAn+y7vDoL/2QBzhcOrloXhdO9mWvsCMRwbb+7F63L6Hs/je7/A1Y5Dt8PW3dIp25eH0ZoyJXjF2Pk9mjK6eVfWuvbzn75cSni9e9TaAhByn0zNM33NqI3YQm8xZt8s3BuitnRqd8w2aoBVZgcd7V16cCN8sBcANjGzmGq0x7NBPfq6UdMZ+Hh2XyMAwnYFwakDy1KpsRpcVVMWq0GrPHc/rWJBPkyHQOz3YZsNm67Nx8TgFNg3zKRsrDe5wSS/xrxkMG7QtMbnczyDdHl0nC+3o4H04Q47wY4sfSlDkT2H+OYqfUnNJfQOfOBQ3CUWHDp4emE6ZXXwnRyGF6KDrry2axQkbzinjnge0kQAPyRTre22fRDe8VWswZ3DcG+s0fRPL94b/qnXV2++rL8GI/2SPliYAnva8YN3jR2Dxp/uzxpn+jpbmzDi6/dCh+HZ9Eh99V24N1RnW6HZdaBPpNbQ3QtlFvn/Irg7S1NF+OdR8M/jeXww5L7PmG1IFccA4rnG+DVlNBjvaXVzXwbsen+UvYy5LdKHU1+73tQVhk8ivrl+bRC0G2abiF2vwHVHELwTDVu5bPxwGA6XUcZCF6THvQsdNeWyk5s0KSPclz0een8keUr6Rda1DpW1oYUrndZm34x0ai3jvyLAeuaFloVaEYcfvIV2Bi1kUK/zeCtGZRJCX2eCr3KxEV+/P7seqTmorslGUCeblf11au3cv1BmXn8IO2cpq/ptZf6N2Btrm3j2niJ3i36R1NghD+vLSScBc6CXprsy6VXShzu8kH245dizJM902V6BK9Yh/W1S1ySrL46cuDjZNZ7Wxq6aT2M7UF3tp7VG0VI7zyJdQ7zhvi0IvqKvrKTZzPhjfTn5SJiJ2Tc7KcNGOrWWQZphMaXWsmkQN8baJl3r/8y+dUkl0tdJEatfP7OvjQUH9fVreHQpZvP+q+HZjCcQzJ0MFrdmZOPD5TLfgu1ZIs15lzsgA2Ou1ouxtrk3CP5GX3sJZ1acpi8nvQSMdHw2/dVxr6z04dTXYMfVxnS9OWG4fWsQXGaab1x+bwWuSBMeGgQXxL0weT3ZnmOGxc1Zkmto+opzw3DzRtUNaQZD5bd/0LTe5NcAAaTaTLSSJrzzkqKlCPdFFBkJF8mjXb+s9Gi2QQb2wnZ9TPKTMBKbdOkvoxD79lkaVU+LJX2dFrn+16X3NYIdSRF+T3+eVo6ozbbCLsmG/DeQnQQhK3A1FXIUzsrMq6QPGy94LqMrF/yrccYDMzwZNo0YuNoANdC/xzDuuQPUMnD69RvCcA4mNR0tuN8PtbI51bqr0Ifb4ygKRtSaEIY3rzHCKTmTA4FrB3Yz0c37HnosHBklV9HvK/CO18XIB1+iZ0XHl/R4k3NPBJBK/w796e01SBWdcEdPuYX8Hxv24LVbV+nbdrDiYJxJ7WWkesb5Jjm6ygvDbWfr6zZNdhfOa93iAObR1wMAlPB0Jl+fAmEYs7deZPbuPkWp3wTvqYr8XWQta12vVFRMbaChic6YIMy+cVgbflw0p6O79Zd49QgLv+lN0BrgFAKtE2S6R7fIJF3rIl0ZbnDH4vxcdhk+ELhiPeaFelDI6PLIF/X4u80Zvx5/qDe7NOY4fzYicdtPA2mHx/LFA9XJfn6YhU59di1NcGgNgkXyiNctsruwD+n0m67BwB7GD4tfpoThhnbVwTzBsNI9OcFNNOlrs37J5OvZZnWJze1JzOJhLNR8wQydZGF90jxnLzheA/vfqaTpY+C7U4l3M7aZ22hoZXOuzsvwG+b0RkTA4aPNgDZzbuslZZmkOxy7DOexUdOBwBUOW2DGaX25yCjMjI/0PVqq75hdQgddKe9f5gB3vL1UeOZkLOZJlNdGyGYtEx7OyTz7YrEOHGuGLayTeFteqYAxMZVhrclfjVm5ENWQv2hh/c9uC52UpO6gr5MiFq9+al8fHY+/8Voqz/lqWum3jGvrD0OZPb8TOBifWMNAg6R+/jIHKEy0UaX+fQ0NGYKecEvtm6ufu9T7cJgCDGbe6ar9GnohJeADGnyb8ey+uXGTj8PcdrOK6c91tQdB9H7MCh6cnkchrsQ9o1W2y0zgFVrcyRcIWFkbMaJUa8ClXbUGwS0IXm/STcHe/QmIUukoig3ZyzaZbcW4SHmKzLq+FkXLjlBdM97ycSDq2HORvtZp5al9bSIoSGOS1vPoy1BmRhqFCnSNZFp8CSRYmC4/A8NzTDMdgF+2Noq9OfT69zXNt8tvmNOzrejDtUxQX986SGadoxoqZfjEcshX2qOoHQ+dVlv2VgLXtiCYrtdxHCPGPGTLoHLK2SczgY510IrliZVBcIzxIdxeEElmwsSf9DpUhi9h2LUxim7D8+dSPXM7zwPvz+nxz8JZ4tX912Xh4Ou1GMn8PHR/XE//0a0YMG1xp0NFXzvo62xBQTqDtuCyZ9Jd2vgqzDLKMkbJ7FrWuFZpzmCyJPoqZklNB1MSuNoumdro2iAYr9e/r0FxuKypdrpgHc4U3dkzeb533uE0CErKIc3hM2Ct+FveW/FKXzwMgg/1PsxvfiEw5hBZ51q2WRubPsKv31m68jZjHbjjI5ZKAOD583WwVgxcJ8j94VAA0xPIN/DOu8NKsba1p9XyP9b+P4nZ9kCvUyXdlAiZTJUUPxGZc6GvXfI1ApvRaBBTcmgUv0RAhX1NzBbwlHzNk81yJbeeCADjV9FuMNcTzOx5XPn/yZA5BrK3ppGDB+Bxaa6Lf82qLfgNk7bndNmIe/0wVQ1Xt4dha1l/y5/YBGxl5MxGaREh6Dh+0IYwytBCQLoDHZO1uJMv5sSC4FO6OAz5hi5/d7mjB7BCVobolcr9kUcHNYZJB/1djErFrILZkA2qs0OVN4E45Hf6Wq8hp/L1LD19mnLmjGhTeJw/+WwOGqaedd0RBMrtvMv52VbxFyYftAd1rs+hXbghEvuVIGfwAVvKVAJX/NEdiLBlTanlDHl7qc1XNn5QEIzREyEbtkx9Wo+/45zx0AMCyg+9gzHw7FqR1KK9v3BNK5v64LfnRl15O/Um+RIpTl876Ou3JXKhucovmWNFTjkg8LscZKZuqxh0V8xmEiSG350DHolFIv9mUuKLEl0w4weJqhesMjpY1iZfWrBgORyhnkpQMA85ac7qdzmpVhGUwj2iu7nBWqTaFPtl1QM1g+FBcNtAdbKdXyEbNDlWZBfpqaVMLao5AvNkyuu6N/63mqx8P+lrB32dehYrY1v6fcbreXm+COQRuKZuq0NVsarszfGqqghDzKHpxYZY1WEjmdIhspHLW7D052kZnrVRZMY11N10xoYZlIG1XOcSBR0EMBV4iO60zSAvUm100O3mOjEIHpR553KVFuVg3X00ZXfhFbpq7tRlH5c7fe2grw+P6z2D9SLwesUgP7Kyj0AegWu6toqddJEqdpweRJuwrtOPvTmwJHKrHg477wEOcm+Xt+B1UW8EgWw8p15asMr8cN1OuboNFFBBYH+6BxvRGxCBd6imCYv4KcppsgOa6EKFTsX3RcG+fQtcMLK3DqNu6P29nN+QyrZMz/JBc/R4J+FMXwtajvk6j67PCmyyo/uoS9IsWTcNAi93N+U0l6a+Jm1bjXRnwcI7U1tk80JkzY1UDeD3L7VpjquyMLt/rQ3dWrCjz14bgiiDCPiKgP49srrUKSaVdoF1rpgaUwxghimOtqZt2RM6017J6+IisOd9cWvq1qOvdfEV7ol9nTYYyGLKyiwX89r8EcDAwx5ogcklqyWPthrDQPnZ9qKEWOPKoowARuSstAdJFWYpBAKTKu8sLIQppTOitXQW1zNYN4rbo7i5Vj1rYh1zMJiOpbfRStj4rAwp0/Q1Wo1jvs4jGJBFgSz+IyBv8rJZ8mirNu3TlqU88xz9VNsAH/ijkVqZhGHg6kNroI4FRkASeZ54s8AGxjZNdxOJ2GpYqljZzMHKehBLBlFMQwTo64bQ5Hsij2CAgWu+Pjcl3YvAFTv/TR1ryuK6fKY+Wvdw6Q62ls7iegYjh/6JesdNH2tZFwQOvibCtJnkRwTSI4DcB2y8rVWkHzNnhxZ3n/gi/yqPTS9ygqiyJR72i2ApPgL0taM+ZuDqqGM8UMuLwFUfRy5z0sfYHwnHW1K1pRWCdBdvW7Kk9GJ2LfNldzffXDUBeft690hlPJTLL9AokEb4oG9tg/oSASLgLQIMXL11Xe6KM3CtuKA1d0dQAXcQQJ7wFBvatEDQUL4OxwbU2jIOPi6KgkKlfiOQcWJdYjuyEvTuEUkjDLjLJEBA4/24gMFCBIgAEbCAQB6BK7NrLDjWgggrm9D0sGNkj//5LxFwEgFM8qy1oVgLciBt34A27CqhjMrSmeK8RwrvHxsXBK0uOFJ3V2GZcY1wv7MgcD3MMRQUU8Qds5Tq0NclagNRFEmWC8ZGrZeDrEukQA0EdJeO9te4BW2Wz6j+uPCIQwg8b0mXQs3QWcLMUTGdSwr2AmTlXeCcciN/kOAO3V2Fk/sbowk/S35V3CsqWwtwfwHA5cJUAn0dt91mq+eCr6sW5LUXnEMQZPNlya+2khLZB+OuPt/5NQECellzCZQoeNWjguAEPRNlci5cKfwZuOqhbJnz6JssC6Q4Ywjs4ruUjWFpjhEejpvMcevLSSZeojw6P30Vyf078uQv1VNiUOWHbiD+9PVACJk574KvzViSmkse6cmpleWF/RHAzKcMPszuf0b1yH68P5ZLijJArLdPiSj15ocyqFaYS5G+ayVrjrsKF6LJyEjE+H8thClVI9YEwcGu/MLr77y9U3GUypNWEUUhcq+Oc0lbfb+7ZG1+uujOtIf3xrGMvo6DUvY6Lvg6uxWZOLjys5bJiJJf/AHYP8wyBpW1YJZlFkdcGEbYTWSZnkE79FiTcz8EWv4zCLb2O8oDniGw6SqkCesOKFlG5JAgGKub2jFyQ1yT8Au1fnfcyqnqdbw71WXFuijU9XdysPSfjes/kVyr4l2BuWfNAYtYafj0tZ125YKv7VjaUApThRtC482Jz+egKQPXjKBjA5gxGVk0uTz8YJOTpTmFTQMUs6e2B7U3p7ScGwRbeEf43K7WwJmt1/tsQT3d8eLss+sdN3ds+ENxeU3Frr+6OTrR1XF1KWo95HOe4NpUhP6zcdeCovozrl1PRtE03F+KZUKsdcr0taILqqxd8bW+pU0lYM9BFl8RQJrwOdD9zBz0Zzc9I+joX9yZkUWTy0edi7d6lPu97NhQFSMDioPQu9prDmhB1BMhSNhSO8BPnxCQSdaphfwhRIN0J5jDPbJTNc1kWhBFS12L26zeCGjJn7QqMI4w9WfjuNaivcIqDqw968xSH6AaEa/DR1/3dIvK/874WsW62EyxfwmLjwggaJ0Evb+Vk+7xnmM5KeeDWEw+rNfTUzaZ7tAdg9VT3gjntiA4XLkT+1RNUexJEQTYGWZF7QA/fUFAklcHT8KC/c2+aBxbT4zc4BeiNXb9xBXlN2D42iSXIbBSTKmXJNmjz0yiT9HqDgmCz7poEwYsFJ+N8pjfcKSLdtvSCTfVF/RkyX0erorLn76Oi1S6ei75Op0FRq4ahQCIm7IZgdIeE/hM1rTKPiJWNp+pYxkXUdYBJckhrBl5OEn95HU7PpL8muJcMTwIPqFrzeDnavwrgevQIPhR7QA/fUBg8zLsXTQIQesGH7RNquPvg+AduiM3mw7kysfVDTkgsTZ5icuvf71tX+l/rBxHlkbRqOlBcIiL1mJIQfnZuF1xTYiLiPbQKYoGY4haM7VIhO3vIbHpv/R1U3iynXTM19mMyXz10Zk5kIE1BBC0ToQwCXpOtSa0vyDOuPbHJNERTD4oT/LsuDKRQgWr3BkEX9I1acSPa/wrgSv+/KZ2gJ8uI9DZDu2ODcNxx9cWKbusbVrdELQqb36wH4F/sqI/Wjf1OKSNyqhu6cq0IPi4q0ZjJlh5lHbE5a7arq3XRqwVk5cC6ZUdy5K825q+1vOEa77WszQWZwausWDKvxKCVtl051egOTlrsy1n+d6LR5rDRt3IdRqW/pRzydcDUTR2uurkA4Ydgr0HsqcwyBwEePn6UuVWeQn4YyktS0IEMPteKa/i77NhOKz4GOK1KDBaeRZq0PequMb+wJDrCtmgSa+jLZw3Il12ws2xlSpIRTyE/rerpmBG8FkspQgqD0oVJSdhffOaOWE4VfsZrKJ9FqbIffv2+CwMBrw22X1OXw8IaOoKrvk6tSFmLmTgagZHFS4IVtEHD/4QJP2QU1SEJGeKJYQsmRCQvUqiaNlYtSwf6SUcJgPRCzPp6eHFxwTB/9RVe7VsQnsg/qn0x5AGuWUdpE5Sk7zzN2E48kU19mRcGASQ+3wuHizKJd5Oo72UCMM9a6Noy0zVUaWWmzDrekuRZ9N7YYovG6JoFgbOnEwTrugahh1vwO+6o4md34UsNK3ylLYoap2huo5dsJzxg0SI0teJ4Ipb2Ulfx1Vep957dNgGGASLbgPvt2vxLzBfJFwEI0HyW4QkIOfKy85p5KFCBweB3B836ak+9mr04b5cpj4cHjqDMZutnDk2WPx2oFQC18rOwlG0BEdPP3DG6D+dL8jumUnStoyKJzNvEOgMgvt1lZVNrUZiGW2q8h1cpXiDSsjeDv7Fe71RI7R3BsHPEbg6XfCQVPb7DKQY7ZhdpsE9YKq9dhhtavTKpA2Lvk6K2MD1XfX1wJqr1TgZAeZI7FGBiWjj5V/A8ZcgzEewFAgBBq4GnIlsvh+DjWLgKruzlKsPtyII/pdeJmLN6ZO+XftPPitrXOWf0arOlA758ltEDgsRaITA8ig6b2qjk8aOr2vHAIokzCcueCT1GvVJzCDWBZOvK8ta1x1RNFt/1i0W6E0rwe93Nq1g5ORmGTgsRZFZdsxgK27KJDBukPWtXUkBpa+TIta8vsu+bq656lnE8jqTBAiG5Tnyf1W1J/M8EHglD6FFk4nAdY3uOldBbJz04eQeL37BbtvI1L1a11CZbBraK2P3QOD6VBA8pCt85qUYZWzVlUHu3iKAta3I0bnXgv6p15Bineur29UVlLGr129XF+OAAGzY8oIDagyoAvLH/lPf79MPiaKNcwdUpgAVgOWv9M1ouTGNDPo6DWqNr3HZ1421tnJG1lBqlavAuJBvHNACzHG+q5Vm5x03W0E9rHPdGgTKg8Qy67pccVZXAZeULF8Pgtv1Z1srk017eqp4IHA9F+8DxZSvclnfhpEIprAoo+wje7S9q2VeXr/MuCO1jDDselP9oSfaTb+g6IM8b2J2HbOtfhRrfm95SJZU+AFKOi0xAzdvppU1za2SEpa80NfJMWtwhfO+bqC3pcPv15KDIGcTeP+VFn/ytY4A04QNQo50u0UG2TVgVZmos9OlbaCB9uGOKJqGzKkLtOVg5f41fWX06iQhr+qGvhXMfpftn15PvKOrWR3IzTUE5D2eh6unG4jVa7CcO9u7bzGW9hU7+K1psyMnBylYzI+UHRuz68aMw3uKlHfNE1Xld27Vt4wp7Roj+B04Kq9hF6NXbcF9njojjL420HA88bUBS9OywOvPoilpLx7oOrR/6WcpZ9ENpAXPG0LgJUN8yAYIHBUED6ZaK5YYvRXPJr7EowswifNbfXXFU6t/2FdOr8AVU6H/1LeC+e8ym7Rhnnm+5OgrAhi1sZQyuueGrBhhI6FfSsa9fpmKzdrWWhgZ1Lekr4Q3guDf9NNL+krN9h1rr5/UTxcWHadhScWGWdm0dfNqZFX82ygrqu2/NosY+joLet3X+uLr7Jam5oDuVvDp1FfHu/AiVFsdryprOYwAZ1xNOgdviEAfZJlJlvV5yaaLb55X/5zfR9uj6Er022UHbuWyGnvSzOnX9eoVuM4Mw/Y1ymp0s59wP0YbsWSQpewIIHd8PlJGW+3gcGj294WG4b71QfCAJX0vj6LOE+3IsiNlYxTNnYxXHtmRZlAKUkjh93sMcmzCatDLRUsZ3gu/I6vCgt9lhHZVto0A6esmbXPgU175emBzNGvM12SOWVf0z4OPg/ZoyiFvdQQsDeyr2+GMgEFB8Hk7yoy+F7FOoVKG0WeXV9ldZwe/kX9WT06vwFUq7A0CWdhvoWxfj84ZsrJYyooA3os6sRWLu+3YL+mDw42MPiPN9XI7OouU8OmiPPgewAN8jMfpayOC4C/t+F1+59YVJs1I7nNssWgpbXEldhPuP0Kb1G/0dVLEuuv76Ot0lhq56hg8208ywqkBEwSvshHaZQ1O87D7CEiC11Puq+mXhsiqeSL1WpJEpsrmwms2FWZvHywBGR8ElpaxYaogmPCLenD3C1xbg+AfZcxav0jS2OrdRZtZ0MetIBJwA6BzKC3TUjlI0qaMlIlh+MprRjjFYSIPvu148K1BvOxxgb9PCYJNYo2vZQrWR6+1sjmXIDQJa+DW3e0rVgf0xnb5du/zIz56QHaGf+jrFOB56usUlpq8ZL5JZvV4IXiV91AXd+18PaOLc+wJ+I8z5qb9id2Fu6wN6CBMDtY9Z9oE6/zw5g/M/Oy1s9xHrBt0CV5pF9Wzs1/git1r9mB9iqWUOHHoG9hglDsN13NOYY9FUYvdG0Di44lG03uR3H+2Pf/Io2LkTm9nXqv+LkK+DPx+oT2/T8J+AFu9DV7XRNFwbG+6294P3QqshwnbTfmHvo6PpO++jm+p8Zqyg7yNJf+SLfKIce3JUBuBR7UFlJX/hCC4rd/iSTUwKgPRz6ux12aMPtybmHiQiM1OkfnwCXc1ktU/cEVNbGFmMbVkMvoHq7s489rIRQU7jlH5dUGw394NIPh1NBy5SYvuhDB8GAM8Fot0//dh5jVqtSg0s6hqh9ayvzOr3ZDB8DBcvdzKxg41FUajY7vOux88GWTBQMVOu4MVEz5cQ83EJ30dD8Ui+DqepSq15Bb5MxXOPZhiQAerwCprzP+9x2H+6z4CHGzQ8hH2K9kVBNdose/PV4JX/14JWuvDjbOyGVMNta7LMNvaVfvW97Nu4CrvdG23lhInKkkYs2m/9+mQYgpLQwSkg7MNMzDyUiR7RWZbZ96pIW+k1VlXsaCSaNsGHOdp2GOaJ/ScaD94MW1Ff34YeDmn/1HNI/KD9+ZmDO55kWkNv88CGptsTCO9hXob1rCPfPGt72b+o6+b41gkXze3VPXsVcBxqKoEMEfwin568BHQ/9OWRf5GEJAJwaeNcCKTughMDoKv2nlLRE28bOuyDhN1fiz9wnNpGnbRtTwAXZlt/VYNsXqfdQNXqYhOx5/Uu0DvmAw8TpR0yFY9GeScFwLwzDf9CgAAHYZJREFU62zI3iTzhnbL4PMb5cln1cP+rOsBjWVX7kUHvjn4D/SbC7XW2w1e7ABxCmZd260O7Ild45CZsgsbtrr9fIR+86FsDq9vmHS6oGS60NeNES2arxtbqn4Gb5YILlGXAgHVmddP4l+VwVwbNpRIxuPwl50tZ0oEai9Tu98SYXHWVaTL1E0l1pEBXmcLnu8yQbLK/mj51ksG6rM3DFxlc4qV1l77UfPdgRmlBbUj/PQfAUylXwkrctjSfQ1+qMffp4kgbiDVXSGb6I5X5cgsXGR/LKCJUnJqexTdig9Lu8gOoIzSaazRPleJdRO2lWGANvjcvecj1sBsi6LHoLylXcJ7wiRrW83PttYk0Nc1JKqfBfZ1H0ttfv0i7msrfUQEQ/thmATKXwc1TMezaTxl1UXgkbpHedAoAjOC4Mv21rrWVK/c6njt3X7pG7tVsAkTZhAXQ6n77SsmffaZdwwkt2HgKhe+bnUjkl6q3hRFO6WDZneJVC8V+CUrAvDf8C1R1IZGdl1WXumun6geVB4ehs9gh+H2dPplvUpm4QLEC5VZrqzMMl8PPabJIlykUF+amZnjDOaE4fb1VtfH9AKk9nx0YtACLj8ROYj7oYzKrGcvy+t+mTSn7mFDB+nrt4Asuq/fstT6f62QeJEtqQheI9CXIO8M0HJbciknEQIPJqrNyukQwHu737SU8dBfwZbrqhMQTsQ6eL7Pxm951/BcBuYFnf2x+uxNA1f5wX4jCG7oD7aNI8NbIQX94M5bAaaVkchmVkGHsaArQW2gnkVmve4GzW52fdnOAQ+ZFdo5Jgha87F9Fd7nOPQZG7JHBMHJNuQ0kXE7dp+VdtjapI7aKcgdhln1uyFglRNPXzVLezPG+phrELzmVCrPRxm0WJjXxnaQPRaz67Jx1NP5pYS/do+p9zM3cyR9XR5fN2sHyue+hntqtLKMXuwRvC7FgeNA3+51gl/yRuBF+Mb4mv28jXJV/swwvGNFbspVJiBkzH8RKJdYB3JH7erOmHohv9/y9iVheHisPnvTwFX8OCUIrrI/jd6zBQ2R2RtZ2yVB44D69rzSxP+QOXZPd+cMb3aozBy29uErs14XgF6IIulHljuAhf1zcQNEwOOmPjhZ/CrLQlacZkugpNXnN8BTs3K0tMO2KNrzPOBvrR3V/IScUaBFkLEbN6bcA+UqmLXAoMWxORt9dRBEGDeoPB+t/OhB1kSQpBJtwuy6dHpzKvLLdOSnrQinr8vjaysNqq6QQ3H0K3XPKB5EgLQDJP2sD4KWKYpKwhoZ+pV3z8os9Jmgd4Nk35VrQE+Bil7uKrqBrtmHTuvMnHW6HPIl1sFgdIRJT/0COTIhJ5MO2xCwnq4vsZEE2SJr5h81Opvq+EYEIzDOlSKjEshKUyzI8YaM2aDF6YzeLmv8SlOA0XDQlXLHuVE2ymyv3YI2s84N46ta7JQZ2Hkgo4M9wg90IrbFkwDZo/Jqm1aDWIU1vQ4BIdkfraZtBU/x+1wM4rXh05GyVzVFuB6G9HVerjfra1gxJi9LBpC7D+ePr9f2bByDbOn7fAyU1/P9Zcj+Y9CgZvbi/CyQPHfxU1S4Im3AyBsDO7B8Rxmd1mZ+8u3ccqd+y/c9Bt+Zz+Tsvscd68NtnKvSVl7L70HW4L7b04YTElAPM2UweMnDUDp+Bop/72tKgiMAktm289zqyIrb1qgFKAPhs6N7sEOUcK3IA1DulVSjeLhOfC3XCx9Pi17gKu1itXOoVIaRpGMnA3CpZmJxnfh9HshBv6+QGd9cCn2NFmG1mPc11Hc1cBVknwSFuTTuqlCRD5IAcilIOziUjJGHQPNBQ5PYjfqngF4FFan8PAkGzeoycG2GTv1z7j3fK01b4pITQaniHVwnk0tzQCkn43ClWkn+fI//cETnB+/g3Ks71Vm/IcU42o46N4N+DFqBtJc9+BywwA/SkT8G9NcghVRHebfgEd4t+wMuPTu68kOCjMhA0pgkXefjSMQ9t2cFHHOkSLrB5hFhOLUjL4VWYcRumtObE0ka9eAl+PMA6CVQO0iKPBBxi1f2aj8Sn+8EzQXlmAoK6cbKa9h59ki1VCDcM3Kfy3ICV8sWKLYUJL5/DoRXlAbynOwEyT0ua+uOAMl6bcf9vh4qTm4ZaMt8VFIp9LUKrA2Y6vgaPhwDgXJPuFr+HP2YW1xQDljJc/NCkKTlv82QTsjMrKT83ovPH8DWtWn5Qj/pm8gu9vKbVYTyaeDxPROGSOB6MPaeMMGrAY+Z0LW9wTkvD6/FUhg0KHnwOFoO9OEehYK/Acm9I323nr/lU/Bd+utnofbpg/GPm2UN1JqW+Lc8fuAK9p2I+If480Lkdqj8W5Agsx0kReJuScGQB1wryEJpuwHB6xUWBGUSgYf/HDD4GijHPPdMJuDivSfZ2pCpmabtAHNGswo8lwMCuoGrGIQ5zrn4gSj0a4BycFwdkbum29iQqY7gA4fo6wNQKP+j42s8ol0PXLG5Z3AyggLpwzhTgJsMZMr+Ee8FvQd0JChuWY6KT1bpAdiGF1eYKdBrAjhJJ/5wMxxz4yJ91UOBjZHBdwau6fz4JrIJxwWBDKqwqCFQCcDHoa1vTioiUeAqzDEasQijEZcnFVTu+k+MDsM5teDZKSjwwD9xd667gpqCY/01YTh5oSlumfhgJh+9jp357c6WSfuCXqwfuApwfD5qN59N52u/mzmuBfR1XKTS1tPztQeBq4D2OxAGY80EMWm90Ow64Dge548CoZ9fIfkuEwTS35EZbemUyucrsAN7GOoV6PI+cJeMEncnmAY2/w7gdMnA1eLVYOAaD6d6tVYgrRajIOfWO8djJhDYd1oYDlmahlPiwFWEYAHz5plBcEgageW8ZiVGF2fMc8127P57N4IrhRRp25bKq28OO9621Gby8CPaivO5rbdtpls5z9kJXAXb17EfwPTCpFi71Fo23Yag9XMuaURfa3lD19eeBK4C7u0IZP5UC+Wi8YVf/zds+oLHduEtlOETpvRn4JoNyTcQ6+BVaIx1ssFY5+r1V2Gi6fo6J2IdShW4Bm6vd41luN1KMiX+K3dmXbHxAoY+NxXjhlwRhGFrunas3AiQTjgHQ7+PK4sh+1gI2AtcRR3+4MVySoJKK7BGuXVmggusVaWvTUOt72uPAlcB9zMIZv7FNMpF5Ae/ToJdkpIs+3L4Vh6Hn083qTQD14xoYjMkLB7dLSkELKYQWIV3rx8ma+ZTl5ZUV4bhPjhSHhAssRCQzJVpkkLjRMFTvSBBq2QhtWLZtZtlCF7ujtXy57upHbXSRADLKcY7vLuDpukKvFeAZ6tsHOVkoa9NusVtX5u0NAGvbyMgOzVB/dJWReAnj13ZpNPH8lUflS60ztjoFbHOOJl6YjGBwNolWYNW0SJd4IoL8YDYsBfrL0yYUg4eo850wU68a3Qhpi0KkPogS2h2YwfhwOlnyrAwvA+7LFzmgu+pg0UEwrALGQ1DGLxmxVwQbMUYUCC7kLpZ6GtDfvHA14YsTchGtkuQ11gck/C6slZ/wEPDn0af+kEP9S68yvDLZkw9YfUPSzYEJGidekY2Ht1Xpw5c5fKhYfgMooazTShSfB47JuZtI374xmKa/Oq89cguX4LWHbm+9iaJDVD05p1BcFWSa1i3AAggMwXB6yDZ1pwlDQISyEyWoNXpwamKZfR1Ggf3uMYjX/fQ2uK/8rqtn+E3nB3ogUF/fuAqztX4mnMaUaEDCCB4XY0vTi5VOaCk0/+swz4/ZoJWMTNT4CoM0Kt4GC8D/LD8z+I2AquC4FG3NYyjnXRwRqPZ5feu1jha9q0zMgyv58xrX1RK8B2zcdPwnMWL1tpLYK1BE9dtQdA6yIugtWY1fV1DIuGnh75OaKGh6oeBjwSv3r0b3pD9cdmo7l4cV4kE9V5E3fsT1GfVHBBA8NoOsZPcH0XNAZymIrfilaCHGt2cNnPgKvoeFIaL4Ux5txeLqwjgFS1Yi3Wcq+rF02ttOzqz8rJiL58dMvPKNa/xPF2oWmEYTQ3DmeuCwMcUthxcsR67hB86Fvd5Vw7Cs4mkrxPi57GvE1pqqPo7wOdhBK9InmJpgMD+BsddPXwtgiJ3l0K4iloOesFPGzZg4y/ZXYUlDgK7LwnDQ66IUzNJHSOBqwjEFNhSfBydRHi56k57Kk97ka54AvL0PS7rsRPZ1JnozHr9gJc1rxzk8bgZZlAdb5WXUUeud26K4Q688mby8U2reHCSvo7jpGL4Oo6lhuucAH5LEbzOMMy3KOwmeGTIK9D1Po/0Lb2qGITuwEuLByGAbS89GM0BODYMD76jeZV0Z40FriIeoxFyE47bnU6Xgl+1Cpm6+RVspDU3P+mZJZ+PzuyFmbk4wqA6yDOJ94kjDrGoBp6RN0McN7Wrjznu81Gfq3/Kv6P0dVOfFcrXTS3VOXkU2D6B4FVmYFl6I/C23l+d/nY5nhP+ZZY4DakF5eCzSciiwqty7rEgzTMRlflovP4zlBR4lWI0cBUNoezmgzEBi7Q4rFFi6UZA1mXOlH3+cyuYprw4N+GpBVdCu+loU4UbkYRNG+Q+4ahd6sbh7YXw/TNrkW60ic/Iqg8r9/nMgt7n9HWvO7W4vu5lpp0vWD4fLEHweoodcd5IeY8nmv4YzzzV5SPohw/xBAsv1RwThjKhwj1+DnivA5swjUOzDmUHVbViPHCtaIodFpEqNZajETW/7bos7xRXBEky0+NR2b4kCA7G5iyV3dw80juBqrhPZNQOw61XJbiKVQuAgKQbjcczEhvb3VAAczKYsHlZ9T5vz8DE6Uvp65p7iu/rmqUWP5G1GPwSwetfWpTprCjgIH3aTzqr4FuKdeBfdZ9hG9xVlaGit+TyP8MIoI+6GCwncd1r8OEwHGF0E6ZGrtIJXKvSOBohQEhzbv1WFZLcPvCU/EFuwpMLxg0w+gwvN2dJbmuA6Px6XMYHXwrs4l8Sbo1f115NbGwnGxccXdLOBdJFxx1flvucvi6Pr+09QSqSZFbtRgRtPwSNsSzbNXEy+zXLNaXq6PNVBDz6WXgYHN+om9mDN/2xwJcbZJ5xRxDcVj40trbDZuw9WgngrZivGriKBVVjRpc3Le4gpLrmv6HQTGzl7f77JHdjllU2qbZ3A1i5y2IIgc2VBx+qlm32FfaGLfj1OzYGTBmqtNyY4WLVS+H7V5ARIc/iksy+7sAsa+WHrnBLAAZqKPT1QAjxfAYEPoZrf4Pg9V0ZeHh7KeyWzapu9sCAl6CjtWc9NuX8jg4m66V/j9VOLDUERoWh7NEwUzVPtibMjU8MPh8yE+2gw6Y66oGrGAOjtktaHP69zKZx+cradw3sRhkuLy52ogxzFv/KbS47kGGWNUT2ZHkL7JfZ19EYuZPOfZGLjExin6rwehnYQQtQfvfe6h86DSZehwAsZPZ1En4B2p3WNbVylXnl07ABE2ZZ7f7QpVZZ40L6WgNV8uxG4A/w8R8I4r4MOqgsoMDWj8LWX4CmeGAzlo6Fnbb0xFP3n3Rkbb9Hh6/fXOHbdtmZCFYUON7pEt9L/y2XwWcB12rBA2YUOuWPj/T+naKNYJMgbNRMabyNauR2PIpCLNbvmpSbAn0Fy+tYB2PEJp/G31cb177jXpmFIOZXw4PgENd0S6/Pvgfg8wvh836Dku0wWOf9DiuxYcAMK2sv0uPS+0pAcSIAenpU78M+f7sEPr/DZwO0dKevtZBtzBeYS0pt0TeQfA02LsB992BjJPw+Az8iWaUye/nnnlhyN/zxKdu6vhpFbRjRaDUsF1t0cMa1GaZon5jwDm4CXdqsnj/nOjGhMvTsvP1uZca1p1Ng8HZMpx+PY8f267n2rOjn/1dhbSZMdDBoFTwx0o9pbwdexVGZeUHAKgM2DFobNXVg8woWDkimwkkIYD3vZO2XNHAsAxkyD3bVvfURpJ3fCIv0x2VwZIgC3/QaxbkSGD1THbU9zV/fC/bBJZIKDnvuiGN3GevQ12X0uhWbZfb15+g8fx/kw0xkIlBg0ym44GmQL0HrKuj6PxIZaajyiCA42RCrKpu22/Dc2mCWZ/G4AaN9oM/BMrjA5/WvErAGWPY4TLKl6Hc8fOZswx+/y97F0B8TY36UdVF0dz54dz0PubP9QMk9LQW7rigSDH0qi6Bs7IlDzLq2mTVu71z3PJlcI2DSui+KHjOLjRa3PW3gPCe5lbxCEAB29LVyUwDGY0BlKjth7A2gQ5WhVWcPGw4D5dSHgeR0ZS8uO1UdnCYCVkTRwnSq971qNd6uyJIGASA5GDTfo5jnVugrkycs9RAAOLP2+Ncpl4enl05dbzxAABJ1yy45eiUodvBSr33w2FsICJaCaQVZ/ONgkQDrxCgKki9FQDr7G1G02YxNu+a/hVox/gMuw0AL3PO99MsqHSMvn4cutg76Ws8rwLZsgavcn1Lk0fFN0FQ9dHU4Q+dpoG+AOkC+lat0UEnGdU3mwU9pPkvZl0sGe93aAFLaswSGjpXKwPM8KGU9I7cuUD4cBFjSKV9Y6QY55s5udTqlUz0fJLnrXhfMvCrN3lW61TK6Jy9IZ1FEABhPBF3ZaSzYA7d0RYLVOSAjDzuMSGaYXaw8PQo/sw+sxfdynxkK9MEpWRG5vM8V7+8aa+BMX9fAMPAJPMsauNbu8N34RzrNhlNIDTinDwvoiEHQ6Hsg/Mx5WX4CrZMP4vbBwdTXralnq9Fj9Ciz0BRe6nzQNoDrbNCt+cU9lWD1PPrXgLcBYmu+zoT0qNKUJBVYRiC8SQeOCz9sWgDKWHphxNG4uOAbrgcnShqKPAAlmGgDKZaKzyXjQOQZCVb7wgG+KZYRVNL2vR9U6ovFQN+B1XDQXJD4RCuQFb7S2ZUBisI9CwfC2JXzgj2Ivs7gEOBX9sAVEBwov8d/XwQ5M9AMXY4HfQUkuvlcXofy4zM0VZVLoZP0Z5OURSqKkGk/BOCUsSAJIhcrBrJt4C/LuKT/5l1/yZlRoH7e63MA4EoK2tmgy0Cn9zlt8usyMPsRCLufBr/DQmTsjVLsAmyHwcKvYSuVywdowbJB0ArQr0BPgZ6U78Co1K+wAQZOFvhV7m/ZdXE66GjQu0GzQMeAZFfNQ6qEj15F/Cy0FfQ66Deg34F+D1oF2gqfd+HTSoEd0qG6ECT3/TtBffWWe/Z7oFugV92Nn3CuVAWYya08GfR20LuqFNfvK1H/JZDg+muQ7E5q1eeQxxITAfo6JlA9qgGzMuwq3MPiWP/KM11eKfNz0GOg5/A83Y9P1QJf4CUTwQmgE0GyeeT7QDobzIOxxSK7050JDJ+wKDO+KAw4Y7Hqf8dWmTfVf2+SqD/4Bvz5e9hQ+H5wfODs1mzyfJ8BDx3SoM9e68PV+y3fAn96vU7Zm8C1b1OBMyXYkofb8SDpkEmn/DBQ7YGHzm3lxhMHSql9iiPl3aprQL8FvQp6GbQNzix9AFbFdSjwkB+sGnUBG2uBCuSyEAEiQASIABFQQQC/cwxcB0Z2G6osBUkQ+yyoDbQSfYG9+ExcqphPxIWyQZT022qB6tvwv0rWDvjmWa4AVhL4OV/gG4l/ZJB7J0h2wN0F3aUDzUIEnEPA28DVOSSpEBEgAkSACBABIuA8AgxcU7tIBrBXgdpBK0CYsKvMEMggtwQ6QkNAEqD2JRkQL0v5JgK/L5TFWNpJBGwiwMDVJtqURQSIABEgAkSACOSKAAPXXOEvuvB7YOCnELh6nY5ZdCfRPn8RKGJ6hr/eoOZEgAgQASJABIgAESACPiLwIJS+mEGrj66jzr4gwMDVF09RTyJABIgAESACRIAIEAEXEZCN7D6GoDXVGmAXDaJORMBFBBi4uugV6kQEiAARIAJEgAgQASLgAwKywec5CFplcyMWIkAEFBFg4KoILlkTASJABIgAESACRIAIFBaBlbDsjxC0biyshTSMCDiEAANXh5xBVYgAESACRIAIEAEiQAS8QOA/oeWpCFoleGUhAkTAAgIMXC2ATBFEgAgQASJABIgAESAChUHgSVhyGoJWeT0QCxEgApYQYOBqCWiKIQJEgAgQASJABIgAEfAegQdgwVwErZu9t4QGEAHPEGDg6pnDqC4RIAJEgAgQASJABIhALgjcDqkfRdC6KxfpFEoESo4AA9eSNwCaTwSIABEgAkSACBABIjAgAv+AgPVPQfsHrMkKRIAIqCAwWIUrmRIBIkAEiAARIAJEgAgQAf8RkNnVv0DA+h3/TaEFRMBvBBi4+u0/ak8EiAARIAJEgAgQASKgg8BLYHsegtYXddiTKxEgAkkQYKpwErRYlwgQASJABIgAESACRKAMCHwXRp7IoLUMrqaNviDAGVdfPEU9iQARIAJEgAgQASJABLQR6ICAzyNglY2YWIgAEXAIAQauDjmDqhABIkAEiAARIAJEgAjkhsDvIPkTCFrlk4UIEAHHEGCqsGMOoTpEgAgQASJABIgAESACVhHYC2nfAJ3EoNUq7hRGBBIhwBnXRHCxMhEgAkSACBABIkAEiECBEHgUtlyGgPX3BbKJphCBQiLAGddCupVGEQEiQASIABEgAkSACDRBYC3OXYiA9f0MWpugxFNEwCEEGLg65AyqQgSIABEgAkSACBABIqCKwH5w/ybobQhY71GVROZEgAgYRYCpwkbhJDMiQASIABEgAkSACBABRxH4KfT6WwSsLziqH9UiAkSgCQIMXJuAw1NEgAgQASJABIgAESACXiPQBe3/FXQtAtbnvLaEyhOBkiPAwLXkDYDmEwEiQASIABEgAkSggAjsg013g76OgPWlAtpHk4hA6RBg4Fo6l9NgIkAEiAARIAJEgAgUFoE9sOx20HUIWNsKayUNIwIlRICBawmdTpOJABEgAkSACBABIlAwBCQN+C7Q3QhY1xfMNppDBIgAEGDgymZABIgAESACRIAIEAEi4CMCq6D090DfRbD6nz4aQJ2JABGIjwAD1/hYsSYRIAJEgAgQASJABIhAvgjsgHjZbElmVx9FwCqbL7EQASJQAgQYuJbAyTSRCBABIkAEiAARIAKeItAJvf8D9AjoUfkfwaocYyECRKBkCDBwLZnDaS4RIAJEgAgQASJABBxGQHYDfgYkQaoEq08gUN2FTxYiQARKjgAD15I3AJpPBIgAESACRIAIEIEcEOiAzP8CvdyHXkKgKunALESACBCBXggwcO0FB78QASJABIgAESACREANgf3gLAHbzuqn/F+kNZqSwru9B0kA2vP7NnxfCZJg9XUEqBE+WYgAESACsRBg4BoLJlYiAkSACBABIkAEiEAsBPai1jLQr0GvguRdou1CCNS24JOFCBABIkAEUiDAwDUFaLyECBABIkAEiAARIAJVBGTG9CnQ/aDHQc8iQN2DTxYiQASIABEwiAADV4NgkhURIAJEgAgQASJQGgSWwNI7QYsRqG4ojdU0lAgQASJABIgAESACRIAIEAEiQAR0EYiiaAwobdmGC/8RdIyuluROBIgAESACRIAIEAEiQASIABEgAqVFAEFnmsB1J677Bmh8aYGj4USACBABIkAEiAARIAJEgAgQASJgB4EUgesduGayHe0ohQgQASJABIgAESACRIAIEAEiQARKj0CCwPUl1D2j9IARACJABIgAESACRIAIEAEiQASIABGwi0DMwPWfUW+4Xc0ojQgQASJABIgAESACRIAIEAEiQASIABAYIHDdhfMXEigiQASIABEgAkSACBABIkAEiAARIAK5IdAkcF2Hc+/NTTEKJgJEgAgQASJABIgAESACRIAIEAEiIAg0CFxX4fgsIkQEiAARIAJEgAgQASJABIgAESACRCB3BOoErqtx7IjcFaMCRIAIEAEiQASIABEgAkSACBABIkAEBIE+gesWfJ9NZIgAESACRIAIEAEiQASIABEgAkSACDiDQI/AdT/+/4AzilERIkAEiAARIAJEgAgQASJABIgAESACgkCPwPXviQgRIAJEgAgQASJABIgAESACRIAIEAHnEKgGrk/hc5BzylEhIkAEiAARIAJEgAgQASJABIgAESACCFhHgo4lEkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAFjCPx/2P3JeG4VmJoAAAAASUVORK5CYII=">>).
logo_fill() ->
jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA"
@@ -589,7 +676,7 @@ logo_fill() ->
process_admin(global,
#request{path = [], auth = {_, _, AJID},
lang = Lang}) ->
- make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>,
+ make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>,
<<"Contents">>))
++
[?XE(<<"ul">>,
@@ -658,7 +745,7 @@ process_admin(Host,
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"ACLDefinition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -667,7 +754,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"acls">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -694,7 +781,7 @@ process_admin(Host,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"ACLDefinition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -702,9 +789,9 @@ process_admin(Host,
nothing -> []
end
++
- [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
+ [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[acls_to_xhtml(ACLs), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>),
@@ -760,7 +847,7 @@ process_admin(Host,
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"AccessRights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -769,7 +856,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"access">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -793,7 +880,7 @@ process_admin(Host,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"AccessRights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -801,10 +888,10 @@ process_admin(Host,
nothing -> []
end
++
- [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
+ [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[access_rules_to_xhtml(AccessRules, Lang), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>)])],
@@ -852,7 +939,7 @@ process_admin(global,
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
- <<"virtualhost">>, <<"Virtual Hosting">>))
+ <<"virtualhosting">>, <<"Virtual Hosting">>))
++ Res,
global, Lang, AJID);
process_admin(Host,
@@ -1049,8 +1136,9 @@ term_to_string(T) ->
%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()}
term_to_paragraph(T, Cols) ->
- Paragraph = list_to_binary(erl_prettypr:format(erl_syntax:abstract(T),
- [{paper, Cols}])),
+ P1 = erl_syntax:abstract(T),
+ P2 = erl_prettypr:format(P1, [{paper, Cols}]),
+ Paragraph = list_to_binary(P2),
FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>),
NumLines = length(FieldList),
{NumLines, Paragraph}.
@@ -1114,7 +1202,7 @@ string_to_spec(<<"server_regexp">>, Val) ->
{server_regexp, Val};
string_to_spec(<<"node_regexp">>, Val) ->
#jid{luser = U, lserver = S, resource = <<"">>} =
- jlib:string_to_jid(Val),
+ jid:from_string(Val),
{node_regexp, U, S};
string_to_spec(<<"user_glob">>, Val) ->
string_to_spec2(user_glob, Val);
@@ -1122,7 +1210,7 @@ string_to_spec(<<"server_glob">>, Val) ->
{server_glob, Val};
string_to_spec(<<"node_glob">>, Val) ->
#jid{luser = U, lserver = S, resource = <<"">>} =
- jlib:string_to_jid(Val),
+ jid:from_string(Val),
{node_glob, U, S};
string_to_spec(<<"all">>, _) -> all;
string_to_spec(<<"raw">>, Val) ->
@@ -1132,7 +1220,7 @@ string_to_spec(<<"raw">>, Val) ->
string_to_spec2(ACLName, Val) ->
#jid{luser = U, lserver = S, resource = <<"">>} =
- jlib:string_to_jid(Val),
+ jid:from_string(Val),
case U of
<<"">> -> {ACLName, S};
_ -> {ACLName, {U, S}}
@@ -1348,7 +1436,7 @@ list_users_parse_query(Query, Host) ->
lists:keysearch(<<"newusername">>, 1, Query),
{value, {_, Password}} =
lists:keysearch(<<"newuserpassword">>, 1, Query),
- case jlib:string_to_jid(<<Username/binary, "@",
+ case jid:from_string(<<Username/binary, "@",
Host/binary>>)
of
error -> error;
@@ -1432,8 +1520,7 @@ get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
none -> <<"disabled">>;
_ ->
- pretty_string_int(ModOffline:get_queue_length(User,
- Server))
+ pretty_string_int(ModOffline:count_offline_messages(User,Server))
end.
get_offlinemsg_module(Server) ->
@@ -1449,10 +1536,10 @@ get_lastactivity_menuitem_list(Server) ->
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, <<"">>}).
+ jid:to_string({User, Server, <<"">>}).
su_to_list({Server, User}) ->
- jlib:jid_to_string({User, Server, <<"">>}).
+ jid:to_string({User, Server, <<"">>}).
%%%==================================
%%%% get_stats
@@ -1464,10 +1551,8 @@ get_stats(global, Lang) ->
+ Total
end,
0, ?MYHOSTS),
- S2SConns = ejabberd_s2s:dirty_get_connections(),
- S2SConnections = length(S2SConns),
- S2SServers = length(lists:usort([element(2, C)
- || C <- S2SConns])),
+ OutS2SNumber = ejabberd_s2s:outgoing_s2s_number(),
+ InS2SNumber = ejabberd_s2s:incoming_s2s_number(),
[?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
@@ -1478,10 +1563,10 @@ get_stats(global, Lang) ->
?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, <<"Outgoing s2s Connections:">>),
- ?XC(<<"td">>, (pretty_string_int(S2SConnections)))]),
+ ?XC(<<"td">>, (pretty_string_int(OutS2SNumber)))]),
?XE(<<"tr">>,
- [?XCT(<<"td">>, <<"Outgoing s2s Servers:">>),
- ?XC(<<"td">>, (pretty_string_int(S2SServers)))])])])];
+ [?XCT(<<"td">>, <<"Incoming s2s Connections:">>),
+ ?XC(<<"td">>, (pretty_string_int(InS2SNumber)))])])])];
get_stats(Host, Lang) ->
OnlineUsers =
length(ejabberd_sm:get_vh_session_list(Host)),
@@ -1509,8 +1594,8 @@ list_online_users(Host, _Lang) ->
SUsers).
user_info(User, Server, Query, Lang) ->
- LServer = jlib:nameprep(Server),
- US = {jlib:nodeprep(User), LServer},
+ LServer = jid:nameprep(Server),
+ US = {jid:nodeprep(User), LServer},
Res = user_parse_query(User, Server, Query),
Resources = ejabberd_sm:get_user_resources(User,
Server),
@@ -1549,18 +1634,24 @@ user_info(User, Server, Query, Lang) ->
c2s_compressed_tls ->
<<"tls+zlib">>;
http_bind ->
- <<"http-bind">>
+ <<"http-bind">>;
+ websocket ->
+ <<"websocket">>;
+ _ ->
+ <<"unknown">>
end,
- <<" (", ConnS/binary,
+ <<ConnS/binary,
"://",
(jlib:ip_to_list(IP))/binary,
":",
(jlib:integer_to_binary(Port))/binary,
"#",
- (jlib:atom_to_binary(Node))/binary,
- ")">>
+ (jlib:atom_to_binary(Node))/binary>>
end,
- ?LI([?C((<<R/binary, FIP/binary>>))])
+ case direction(Lang) of
+ [{_, <<"rtl">>}] -> ?LI([?C((<<FIP/binary, " - ", R/binary>>))]);
+ _ -> ?LI([?C((<<R/binary, " - ", FIP/binary>>))])
+ end
end,
lists:sort(Resources))))]
end,
@@ -1640,8 +1731,7 @@ user_parse_query1(Action, User, Server, Query) ->
end.
list_last_activity(Host, Lang, Integral, Period) ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp = MegaSecs * 1000000 + Secs,
+ TimeStamp = p1_time_compat:system_time(seconds),
case Period of
<<"all">> -> TS = 0, Days = infinity;
<<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
@@ -1780,7 +1870,7 @@ get_node(Host, Node, [], _Query, Lang) ->
<<"Modules">>)])]
++ MenuItems2))];
get_node(global, Node, [<<"db">>], Query, Lang) ->
- case rpc:call(Node, mnesia, system_info, [tables]) of
+ case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, _Reason} ->
[?XCT(<<"h1">>, <<"RPC Call Error">>)];
Tables ->
@@ -1792,7 +1882,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) ->
Rows = lists:map(fun (Table) ->
STable =
iolist_to_binary(atom_to_list(Table)),
- TInfo = case rpc:call(Node, mnesia,
+ TInfo = case ejabberd_cluster:call(Node, mnesia,
table_info,
[Table, all])
of
@@ -1871,9 +1961,7 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
(list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
end,
- (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])),
- <<"list-eja-commands">>,
- <<"List of ejabberd Commands">>))
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
@@ -2014,7 +2102,7 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
[?INPUTT(<<"submit">>, <<"import_dir">>,
<<"OK">>)])])])])])];
get_node(global, Node, [<<"ports">>], Query, Lang) ->
- Ports = rpc:call(Node, ejabberd_config,
+ Ports = ejabberd_cluster:call(Node, ejabberd_config,
get_local_option, [listen,
{ejabberd_listener, validate_cfg},
[]]),
@@ -2028,14 +2116,14 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
{error, iolist_to_binary(io_lib:format("~p", [Reason]))};
_ -> nothing
end,
- NewPorts = lists:sort(rpc:call(Node, ejabberd_config,
+ NewPorts = lists:sort(ejabberd_cluster:call(Node, ejabberd_config,
get_local_option,
[listen,
{ejabberd_listener, validate_cfg},
[]])),
H1String = <<(?T(<<"Listened Ports at ">>))/binary,
(iolist_to_binary(atom_to_list(Node)))/binary>>,
- (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>))
+ (?H1GL(H1String, <<"listeningports">>, <<"Listening Ports">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2051,7 +2139,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
[node_ports_to_xhtml(NewPorts, Lang)])];
get_node(Host, Node, [<<"modules">>], Query, Lang)
when is_binary(Host) ->
- Modules = rpc:call(Node, gen_mod,
+ Modules = ejabberd_cluster:call(Node, gen_mod,
loaded_modules_with_opts, [Host]),
Res = case catch node_modules_parse_query(Host, Node,
Modules, Query)
@@ -2060,10 +2148,10 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
{'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error;
_ -> nothing
end,
- NewModules = lists:sort(rpc:call(Node, gen_mod,
+ NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
- (?H1GL(H1String, <<"modoverview">>,
+ (?H1GL(H1String, <<"modulesoverview">>,
<<"Modules Overview">>))
++
case Res of
@@ -2076,21 +2164,21 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[node_modules_to_xhtml(NewModules, Lang)])];
get_node(global, Node, [<<"stats">>], _Query, Lang) ->
- UpTime = rpc:call(Node, erlang, statistics,
+ UpTime = ejabberd_cluster:call(Node, erlang, statistics,
[wall_clock]),
UpTimeS = list_to_binary(io_lib:format("~.3f",
[element(1, UpTime) / 1000])),
- CPUTime = rpc:call(Node, erlang, statistics, [runtime]),
+ 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),
- TransactionsCommitted = rpc:call(Node, mnesia,
+ TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_commits]),
- TransactionsAborted = rpc:call(Node, mnesia,
+ TransactionsAborted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_failures]),
- TransactionsRestarted = rpc:call(Node, mnesia,
+ TransactionsRestarted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_restarts]),
- TransactionsLogged = rpc:call(Node, mnesia, system_info,
+ TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_log_writes]),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
@@ -2125,12 +2213,12 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) ->
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(TransactionsLogged)))])])])];
get_node(global, Node, [<<"update">>], Query, Lang) ->
- rpc:call(Node, code, purge, [ejabberd_update]),
+ ejabberd_cluster:call(Node, code, purge, [ejabberd_update]),
Res = node_update_parse_query(Node, Query),
- rpc:call(Node, code, load_file, [ejabberd_update]),
+ ejabberd_cluster:call(Node, code, load_file, [ejabberd_update]),
{ok, _Dir, UpdatedBeams, Script, LowLevelScript,
Check} =
- rpc:call(Node, ejabberd_update, update_info, []),
+ ejabberd_cluster:call(Node, ejabberd_update, update_info, []),
Mods = case UpdatedBeams of
[] -> ?CT(<<"None">>);
_ ->
@@ -2199,14 +2287,14 @@ get_node(Host, Node, NPath, Query, Lang) ->
node_parse_query(Node, Query) ->
case lists:keysearch(<<"restart">>, 1, Query) of
{value, _} ->
- case rpc:call(Node, init, restart, []) of
+ case ejabberd_cluster:call(Node, init, restart, []) of
{badrpc, _Reason} -> error;
_ -> ok
end;
_ ->
case lists:keysearch(<<"stop">>, 1, Query) of
{value, _} ->
- case rpc:call(Node, init, stop, []) of
+ case ejabberd_cluster:call(Node, init, stop, []) of
{badrpc, _Reason} -> error;
_ -> ok
end;
@@ -2290,35 +2378,35 @@ node_backup_parse_query(Node, Query) ->
{value, {_, Path}} ->
Res = case Action of
<<"store">> ->
- rpc:call(Node, mnesia, backup,
+ ejabberd_cluster:call(Node, mnesia, backup,
[binary_to_list(Path)]);
<<"restore">> ->
- rpc:call(Node, ejabberd_admin,
+ ejabberd_cluster:call(Node, ejabberd_admin,
restore, [Path]);
<<"fallback">> ->
- rpc:call(Node, mnesia,
+ ejabberd_cluster:call(Node, mnesia,
install_fallback,
[binary_to_list(Path)]);
<<"dump">> ->
- rpc:call(Node, ejabberd_admin,
+ ejabberd_cluster:call(Node, ejabberd_admin,
dump_to_textfile,
[Path]);
<<"load">> ->
- rpc:call(Node, mnesia,
+ ejabberd_cluster:call(Node, mnesia,
load_textfile,
[binary_to_list(Path)]);
<<"import_piefxis_file">> ->
- rpc:call(Node, ejabberd_piefxis,
+ ejabberd_cluster:call(Node, ejabberd_piefxis,
import_file, [Path]);
<<"export_piefxis_dir">> ->
- rpc:call(Node, ejabberd_piefxis,
+ ejabberd_cluster:call(Node, ejabberd_piefxis,
export_server, [Path]);
<<"export_piefxis_host_dir">> ->
{value, {_, Host}} =
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
- rpc:call(Node, ejabberd_piefxis,
+ ejabberd_cluster:call(Node, ejabberd_piefxis,
export_host,
[Path, Host]);
<<"export_sql_file">> ->
@@ -2326,13 +2414,13 @@ node_backup_parse_query(Node, Query) ->
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
- rpc:call(Node, ejd2odbc,
+ ejabberd_cluster:call(Node, ejd2odbc,
export, [Host, Path]);
<<"import_file">> ->
- rpc:call(Node, ejabberd_admin,
+ ejabberd_cluster:call(Node, ejabberd_admin,
import_file, [Path]);
<<"import_dir">> ->
- rpc:call(Node, ejabberd_admin,
+ ejabberd_cluster:call(Node, ejabberd_admin,
import_dir, [Path])
end,
case Res of
@@ -2380,18 +2468,18 @@ node_ports_to_xhtml(Ports, Lang) ->
[?INPUTS(<<"text">>,
<<"module", SSPort/binary>>,
SModule, <<"15">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SSPort/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"35">>, SOptsClean)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"add", SSPort/binary>>,
- <<"Update">>)]),
+ <<"Restart">>)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"delete", SSPort/binary>>,
- <<"Delete">>)])])
+ <<"Stop">>)])])
end,
Ports)
++
@@ -2406,12 +2494,12 @@ node_ports_to_xhtml(Ports, Lang) ->
?XE(<<"td">>,
[?INPUTS(<<"text">>, <<"modulenew">>, <<"">>,
<<"15">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
[?INPUTT(<<"submit">>, <<"addnew">>,
- <<"Add New">>)])])]))]).
+ <<"Start">>)])])]))]).
make_netprot_html(NetProt) ->
?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}],
@@ -2457,10 +2545,10 @@ node_ports_parse_query(Node, Ports, Query) ->
{ok, Tokens, _} =
erl_scan:string(binary_to_list(SOpts) ++ "."),
{ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, ejabberd_listener,
+ ejabberd_cluster:call(Node, ejabberd_listener,
delete_listener,
[PortIpNetp2, Module1]),
- R = rpc:call(Node, ejabberd_listener,
+ R = ejabberd_cluster:call(Node, ejabberd_listener,
add_listener,
[PortIpNetp2, Module, Opts]),
throw({is_added, R});
@@ -2470,7 +2558,7 @@ node_ports_parse_query(Node, Ports, Query) ->
1, Query)
of
{value, _} ->
- rpc:call(Node, ejabberd_listener,
+ ejabberd_cluster:call(Node, ejabberd_listener,
delete_listener,
[PortIpNetp, Module1]),
throw(submitted);
@@ -2503,7 +2591,7 @@ node_ports_parse_query(Node, Ports, Query) ->
{Port2, _SPort, IP2, _SIP, _SSPort, NetProt2,
OptsClean} =
get_port_data({Port2, STIP2, NetProt2}, Opts),
- R = rpc:call(Node, ejabberd_listener, add_listener,
+ R = ejabberd_cluster:call(Node, ejabberd_listener, add_listener,
[{Port2, IP2, NetProt2}, Module, OptsClean]),
throw({is_added, R});
_ -> ok
@@ -2523,7 +2611,7 @@ node_modules_to_xhtml(Modules, Lang) ->
40),
?XE(<<"tr">>,
[?XC(<<"td">>, SModule),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SModule/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"40">>, SOpts)]),
@@ -2542,7 +2630,7 @@ node_modules_to_xhtml(Modules, Lang) ->
[?XE(<<"tr">>,
[?XE(<<"td">>,
[?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
@@ -2562,9 +2650,9 @@ node_modules_parse_query(Host, Node, Modules, Query) ->
{ok, Tokens, _} =
erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
{ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, stop_module,
+ ejabberd_cluster:call(Node, gen_mod, stop_module,
[Host, Module]),
- rpc:call(Node, gen_mod, start_module,
+ ejabberd_cluster:call(Node, gen_mod, start_module,
[Host, Module, Opts]),
throw(submitted);
_ ->
@@ -2572,7 +2660,7 @@ node_modules_parse_query(Host, Node, Modules, Query) ->
1, Query)
of
{value, _} ->
- rpc:call(Node, gen_mod, stop_module,
+ ejabberd_cluster:call(Node, gen_mod, stop_module,
[Host, Module]),
throw(submitted);
_ -> ok
@@ -2588,7 +2676,7 @@ node_modules_parse_query(Host, Node, Modules, Query) ->
Module = jlib:binary_to_atom(SModule),
{ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)),
{ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, gen_mod, start_module,
+ ejabberd_cluster:call(Node, gen_mod, start_module,
[Host, Module, Opts]),
throw(submitted);
_ -> ok
@@ -2601,7 +2689,7 @@ node_update_parse_query(Node, Query) ->
proplists:get_all_values(<<"selected">>, Query),
ModulesToUpdate = [jlib:binary_to_atom(M)
|| M <- ModulesToUpdateStrings],
- case rpc:call(Node, ejabberd_update, update,
+ case ejabberd_cluster:call(Node, ejabberd_update, update,
[ModulesToUpdate])
of
{ok, _} -> ok;
@@ -2643,14 +2731,14 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
[{Attr, Val} | RestAttrs] ->
AttrPrefix = [Prefix,
str:copies(<<" ">>, byte_size(Name) + 2)],
- [$\s, Attr, $=, $', xml:crypt(Val) | [$',
+ [$\s, Attr, $=, $', fxml:crypt(Val) | [$',
lists:map(fun ({Attr1,
Val1}) ->
[$\n,
AttrPrefix,
Attr1, $=,
$',
- xml:crypt(Val1),
+ fxml:crypt(Val1),
$']
end,
RestAttrs)]]
@@ -2662,7 +2750,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
end,
Els),
if OnlyCData ->
- [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n];
+ [$>, fxml:get_cdata(Els), $<, $/, Name, $>, $\n];
true ->
[$>, $\n,
lists:map(fun (E) ->
@@ -2809,7 +2897,7 @@ make_server_menu(HostMenu, NodeMenu, Lang, JID) ->
Fixed2 = [Tuple
|| Tuple <- Fixed,
is_allowed_path(BasePath, Tuple, JID)],
- {Base, <<"ejabberd">>, Fixed2}.
+ {Base, <<"">>, Fixed2}.
get_menu_items_hook({hostnode, Host, Node}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host,
@@ -2876,4 +2964,8 @@ make_menu_item(item, 3, URI, Name, Lang) ->
%%%==================================
+
+opt_type(access) -> fun (V) -> V end;
+opt_type(_) -> [access].
+
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl
index 9d5f32c3..0cdd9bac 100644
--- a/src/ejabberd_websocket.erl
+++ b/src/ejabberd_websocket.erl
@@ -33,11 +33,13 @@
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%%% ==========================================================================================================
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%----------------------------------------------------------------------
-module(ejabberd_websocket).
+-protocol({rfc, 6455}).
+
-author('ecestari@process-one.net').
-export([check/2, socket_handoff/8]).
@@ -86,7 +88,8 @@ check(_Path, Headers) ->
end.
socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path,
- headers = Headers, host = Host, port = Port},
+ headers = Headers, host = Host, port = Port,
+ opts = HOpts},
Socket, SockMod, Buf, _Opts, HandlerModule, InfoMsgFun) ->
case check(LocalPath, Headers) of
true ->
@@ -99,7 +102,8 @@ socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path,
path = Path,
headers = Headers,
local_path = LocalPath,
- buf = Buf},
+ buf = Buf,
+ http_opts = HOpts},
connect(WS, HandlerModule);
_ ->
@@ -369,10 +373,10 @@ process_frame(#frame_info{unprocessed =
process_frame(FrameInfo#frame_info{unprocessed = <<>>},
<<UnprocessedPre/binary, Data/binary>>).
-handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, p1_tls) ->
- case p1_tls:recv_data(Socket, Data) of
+handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, fast_tls) ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, NewData} ->
- handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, p1_tls);
+ handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, fast_tls);
{error, Error} ->
{error, Error}
end;
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 904604fc..c7e72d66 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -2,8 +2,25 @@
%%% File : ejabberd_xmlrpc.erl
%%% Author : Badlop <badlop@process-one.net>
%%% Purpose : XML-RPC server that frontends ejabberd commands
-%%% Created : 21 Aug 2007 by Badlop <badlop@ono.com>
-%%% Id : $Id: ejabberd_xmlrpc.erl 595 2008-05-20 11:39:31Z badlop $
+%%% Created : 21 Aug 2007 by Badlop <badlop@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.
+%%%
%%%----------------------------------------------------------------------
%%% TODO: Implement a command in ejabberdctl 'help COMMAND LANGUAGE' that shows
@@ -181,41 +198,43 @@ socket_type() -> raw.
process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
AccessCommandsOpts = gen_mod:get_opt(access_commands, Opts,
fun(L) when is_list(L) -> L end,
- []),
- AccessCommands = lists:flatmap(
- fun({Ac, AcOpts}) ->
- Commands = gen_mod:get_opt(
- commands, AcOpts,
- fun(A) when is_atom(A) ->
- A;
- (L) when is_list(L) ->
- true = lists:all(
- fun is_atom/1,
- L),
- L
- end, all),
- CommOpts = gen_mod:get_opt(
- options, AcOpts,
- fun(L) when is_list(L) -> L end,
- []),
- [{Ac, Commands, CommOpts}];
- (Wrong) ->
- ?WARNING_MSG("wrong options format for ~p: ~p",
- [?MODULE, Wrong]),
- []
- end, AccessCommandsOpts),
- GetAuth = case [ACom || {Ac, _, _} = ACom <- AccessCommands, Ac /= all] of
- [] -> false;
- _ -> true
- end,
+ undefined),
+ AccessCommands =
+ case AccessCommandsOpts of
+ undefined -> undefined;
+ _ ->
+ lists:flatmap(
+ fun({Ac, AcOpts}) ->
+ Commands = gen_mod:get_opt(
+ commands, AcOpts,
+ fun(A) when is_atom(A) ->
+ A;
+ (L) when is_list(L) ->
+ true = lists:all(
+ fun is_atom/1,
+ L),
+ L
+ end, all),
+ CommOpts = gen_mod:get_opt(
+ options, AcOpts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ [{Ac, Commands, CommOpts}];
+ (Wrong) ->
+ ?WARNING_MSG("wrong options format for ~p: ~p",
+ [?MODULE, Wrong]),
+ []
+ end, AccessCommandsOpts)
+ end,
+ GetAuth = true,
State = #state{access_commands = AccessCommands, get_auth = GetAuth},
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
{error, _} ->
{400, [],
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"Malformed XML">>}]}};
El ->
- case p1_xmlrpc:decode(El) of
+ case fxmlrpc:decode(El) of
{error, _} = Err ->
?ERROR_MSG("XML-RPC request ~s failed with reason: ~p",
[Data, Err]),
@@ -225,7 +244,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
{ok, RPC} ->
?DEBUG("got XML-RPC request: ~p", [RPC]),
{false, Result} = handler(State, RPC),
- XML = xml:element_to_binary(p1_xmlrpc:encode(Result)),
+ XML = fxml:element_to_binary(fxmlrpc:encode(Result)),
{200, [{<<"Content-Type">>, <<"text/xml">>}],
<<"<?xml version=\"1.0\"?>", XML/binary>>}
end
@@ -240,17 +259,22 @@ process(_, _) ->
%% -----------------------------
get_auth(AuthList) ->
- [User, Server, Password] = try get_attrs([user, server,
- password],
- AuthList)
- of
- [U, S, P] -> [U, S, P]
- catch
- exit:{attribute_not_found, Attr, _} ->
- throw({error, missing_auth_arguments,
- Attr})
- end,
- {User, Server, Password}.
+ Admin =
+ case lists:keysearch(admin, 1, AuthList) of
+ {value, {admin, true}} -> true;
+ _ -> false
+ end,
+ try get_attrs([user, server, token], AuthList) of
+ [U, S, T] -> {U, S, {oauth, T}, Admin}
+ catch
+ exit:{attribute_not_found, _Attr, _} ->
+ try get_attrs([user, server, password], AuthList) of
+ [U, S, P] -> {U, S, P, Admin}
+ catch
+ exit:{attribute_not_found, Attr, _} ->
+ throw({error, missing_auth_arguments, Attr})
+ end
+ end.
%% -----------------------------
%% Handlers
@@ -280,9 +304,9 @@ handler(#state{get_auth = true, auth = noauth} = State,
{call, Method,
[{struct, AuthList} | Arguments] = AllArgs}) ->
try get_auth(AuthList) of
- Auth ->
- handler(State#state{get_auth = false, auth = Auth},
- {call, Method, Arguments})
+ Auth ->
+ handler(State#state{get_auth = false, auth = Auth},
+ {call, Method, Arguments})
catch
{error, missing_auth_arguments, _Attr} ->
handler(State#state{get_auth = false, auth = noauth},
@@ -311,7 +335,7 @@ handler(State, {call, Command, []}) ->
handler(State, {call, Command, [{struct, []}]});
handler(State,
{call, Command, [{struct, AttrL}]} = Payload) ->
- case ejabberd_commands:get_command_format(Command) of
+ case ejabberd_commands:get_command_format(Command, State#state.auth) of
{error, command_unknown} ->
build_fault_response(-112, "Unknown call: ~p",
[Payload]);
@@ -440,7 +464,7 @@ format_arg({array, Elements}, {list, ElementsDef})
[format_arg(Element, ElementsDef)
|| Element <- Elements];
format_arg(Arg, integer) when is_integer(Arg) -> Arg;
-format_arg(Arg, binary) when is_list(Arg) -> list_to_binary(Arg);
+format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg);
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
format_arg(Arg, string) when is_list(Arg) -> Arg;
format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg);
@@ -448,7 +472,12 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
- throw({error_formatting_argument, Arg, Format}).
+ error.
+
+process_unicode_codepoints(Str) ->
+ iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
+ (Y) -> Y
+ end, Str)).
%% -----------------------------
%% Result
@@ -462,10 +491,16 @@ format_result(Atom, {Name, atom}) ->
[{Name, iolist_to_binary(atom_to_list(Atom))}]};
format_result(Int, {Name, integer}) ->
{struct, [{Name, Int}]};
-format_result(String, {Name, string}) when is_list(String) ->
+format_result([A|_]=String, {Name, string}) when is_list(String) and is_integer(A) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, string}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]};
+format_result(Atom, {Name, string}) when is_atom(Atom) ->
+ {struct, [{Name, atom_to_list(Atom)}]};
+format_result(Integer, {Name, string}) when is_integer(Integer) ->
+ {struct, [{Name, integer_to_list(Integer)}]};
+format_result(Other, {Name, string}) ->
+ {struct, [{Name, io_lib:format("~p", [Other])}]};
format_result(String, {Name, binary}) when is_list(String) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, binary}) when is_binary(Binary) ->
diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl
index 90781481..1df88470 100644
--- a/src/ejd2odbc.erl
+++ b/src/ejd2odbc.erl
@@ -5,7 +5,7 @@
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -29,7 +29,8 @@
-include("logger.hrl").
--export([export/2, export/3, import_file/2, import/2, import/3]).
+-export([export/2, export/3, import_file/2, import/2,
+ import/3, delete/1]).
-define(MAX_RECORDS_PER_TRANSACTION, 100).
@@ -54,14 +55,14 @@ modules() ->
mod_offline,
mod_privacy,
mod_private,
- mod_pubsub,
+ %% mod_pubsub,
mod_roster,
mod_shared_roster,
mod_vcard,
mod_vcard_xupdate].
export(Server, Output) ->
- LServer = jlib:nameprep(iolist_to_binary(Server)),
+ LServer = jid:nameprep(iolist_to_binary(Server)),
Modules = modules(),
IO = prepare_output(Output),
lists:foreach(
@@ -71,7 +72,7 @@ export(Server, Output) ->
close_output(Output, IO).
export(Server, Output, Module) ->
- LServer = jlib:nameprep(iolist_to_binary(Server)),
+ LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output),
lists:foreach(
fun({Table, ConvertFun}) ->
@@ -79,6 +80,20 @@ export(Server, Output, Module) ->
end, Module:export(Server)),
close_output(Output, IO).
+delete(Server) ->
+ Modules = modules(),
+ lists:foreach(
+ fun(Module) ->
+ delete(Server, Module)
+ end, Modules).
+
+delete(Server, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ delete(LServer, Table, ConvertFun)
+ end, Module:export(Server)).
+
import_file(Server, FileName) when is_binary(FileName) ->
import(Server, binary_to_list(FileName));
import_file(Server, FileName) ->
@@ -86,7 +101,7 @@ import_file(Server, FileName) ->
{file, FileName},
{mode, read_only}]) of
{ok, Fd} ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
AuthMods = case lists:member(ejabberd_auth_internal,
@@ -105,7 +120,7 @@ import(Server, Output) ->
import(Server, Output, [{fast, true}]).
import(Server, Output, Opts) ->
- LServer = jlib:nameprep(iolist_to_binary(Server)),
+ LServer = jid:nameprep(iolist_to_binary(Server)),
Modules = modules(),
IO = prepare_output(Output, disk_log),
lists:foreach(
@@ -115,7 +130,7 @@ import(Server, Output, Opts) ->
close_output(Output, IO).
import(Server, Output, Opts, Module) ->
- LServer = jlib:nameprep(iolist_to_binary(Server)),
+ LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output, disk_log),
lists:foreach(
fun({SelectQuery, ConvertFun}) ->
@@ -159,6 +174,25 @@ output(_LServer, Table, Fd, SQLs) ->
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
"\n--\n", SQLs]).
+delete(LServer, Table, ConvertFun) ->
+ F = fun () ->
+ mnesia:write_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ _SQL ->
+ mnesia:delete_object(R),
+ Acc
+ end
+ end,
+ {0, []}, Table),
+ delete(LServer, Table, SQLs)
+ end,
+ mnesia:transaction(F).
+
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
F = case proplists:get_bool(fast, Opts) of
true ->
diff --git a/src/eldap.erl b/src/eldap.erl
index a9edebdf..f48b2d84 100644
--- a/src/eldap.erl
+++ b/src/eldap.erl
@@ -80,7 +80,6 @@
-export([get_status/1]).
-%% gen_fsm callbacks
-export([init/1, connecting/2, connecting/3,
wait_bind_response/3, active/3, active_bind/3,
handle_event/3, handle_sync_event/4, handle_info/3,
@@ -630,27 +629,10 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) ->
id = 0, dict = dict:new(), req_q = queue:new()},
0}.
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Called when gen_fsm:send_event/2,3 is invoked (async)
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
connecting(timeout, S) ->
{ok, NextState, NewS} = connect_bind(S),
{next_state, NextState, NewS}.
-%%----------------------------------------------------------------------
-%% Func: StateName/3
-%% Called when gen_fsm:sync_send_event/2,3 is invoked.
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
connecting(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
{next_state, connecting, S#eldap{req_q = Q}}.
@@ -679,34 +661,15 @@ handle_event(close, _StateName, S) ->
handle_event(_Event, StateName, S) ->
{next_state, StateName, S}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Called when gen_fsm:sync_send_all_state_event/2,3 is invoked
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, S) ->
{reply, {StateName, S}, StateName, S}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-
%%
%% Packets arriving in various states
%%
handle_info({Tag, _Socket, Data}, connecting, S)
when Tag == tcp; Tag == ssl ->
- ?DEBUG("tcp packet received when disconnected!~n~p",
- [Data]),
+ ?DEBUG("tcp packet received when disconnected!~n~p", [Data]),
{next_state, connecting, S};
handle_info({Tag, _Socket, Data}, wait_bind_response, S)
when Tag == tcp; Tag == ssl ->
@@ -725,8 +688,7 @@ handle_info({Tag, _Socket, Data}, wait_bind_response, S)
{next_state, connecting, close_and_retry(S)}
end;
handle_info({Tag, _Socket, Data}, StateName, S)
- when (StateName == active orelse
- StateName == active_bind)
+ when (StateName == active orelse StateName == active_bind)
andalso (Tag == tcp orelse Tag == ssl) ->
case catch recvd_packet(Data, S) of
{response, Response, RequestType} ->
@@ -767,8 +729,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
handle_info({timeout, retry_connect}, connecting, S) ->
{ok, NextState, NewS} = connect_bind(S),
{next_state, NextState, NewS};
-handle_info({timeout, _Timer, bind_timeout},
- wait_bind_response, S) ->
+handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) ->
{next_state, connecting, close_and_retry(S)};
%%
%% Make sure we don't fill the message queue with rubbish
@@ -825,17 +786,15 @@ send_command(Command, From, S) ->
{Name, Request} = gen_req(Command),
Message = #'LDAPMessage'{messageID = Id,
protocolOp = {Name, Request}},
- ?DEBUG("~p~n", [{Name, Request}]),
- {ok, Bytes} = 'ELDAPv3':encode('LDAPMessage',
- Message),
+ ?DEBUG("~p~n", [{Name, ejabberd_config:may_hide_data(Request)}]),
+ {ok, Bytes} = 'ELDAPv3':encode('LDAPMessage', Message),
case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of
ok ->
- Timer = erlang:start_timer(?CMD_TIMEOUT, self(),
- {cmd_timeout, Id}),
- New_dict = dict:store(Id,
- [{Timer, Command, From, Name}], S#eldap.dict),
- {ok, S#eldap{id = Id, dict = New_dict}};
- Error -> Error
+ Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
+ New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict),
+ {ok, S#eldap{id = Id, dict = New_dict}};
+ Error ->
+ Error
end.
gen_req({search, A}) ->
@@ -1148,9 +1107,8 @@ bind_request(Socket, S) ->
authentication = {simple, S#eldap.passwd}},
Message = #'LDAPMessage'{messageID = Id,
protocolOp = {bindRequest, Req}},
- ?DEBUG("Bind Request Message:~p~n", [Message]),
- {ok, Bytes} = 'ELDAPv3':encode('LDAPMessage',
- Message),
+ ?DEBUG("Bind Request Message:~p~n", [ejabberd_config:may_hide_data(Message)]),
+ {ok, Bytes} = 'ELDAPv3':encode('LDAPMessage', Message),
case (S#eldap.sockmod):send(Socket, Bytes) of
ok -> {ok, S#eldap{id = Id}};
Error -> Error
@@ -1163,24 +1121,6 @@ next_host(Host,
Hosts) -> % Find next in turn
next_host(Host, Hosts, Hosts).
-%%% --------------------------------------------------------------------
-%%% Verify the input data
-%%% --------------------------------------------------------------------
-%%% --------------------------------------------------------------------
-%%% Get and Validate the initial configuration
-%%% --------------------------------------------------------------------
-%% get_atom(Key, List) ->
-%% case lists:keysearch(Key, 1, List) of
-%% {value, {Key, Value}} when is_atom(Value) ->
-%% Value;
-%% {value, {Key, _Value}} ->
-%% throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
-%% false ->
-%% throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
-%% end.
-%%% --------------------------------------------------------------------
-%%% Other Stuff
-%%% --------------------------------------------------------------------
next_host(Host, [Host], Hosts) ->
hd(Hosts); % Wrap back to first
next_host(Host, [Host | Tail], _Hosts) ->
diff --git a/src/eldap_filter.erl b/src/eldap_filter.erl
index e8ce625c..d46e410a 100644
--- a/src/eldap_filter.erl
+++ b/src/eldap_filter.erl
@@ -6,7 +6,7 @@
%%% Author: Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/eldap_pool.erl b/src/eldap_pool.erl
index 51761c07..8e0096f8 100644
--- a/src/eldap_pool.erl
+++ b/src/eldap_pool.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Nov 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl
index eb660162..9b9e5fbc 100644
--- a/src/eldap_utils.erl
+++ b/src/eldap_utils.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -24,20 +24,14 @@
%%%----------------------------------------------------------------------
-module(eldap_utils).
+
+-behaviour(ejabberd_config).
-author('mremond@process-one.net').
--export([generate_subfilter/1,
- find_ldap_attrs/2,
- get_ldap_attr/2,
- get_user_part/2,
- make_filter/2,
- get_state/2,
- case_insensitive_match/2,
- get_opt/3,
- get_opt/4,
- get_config/2,
- decode_octet_string/3,
- uids_domain_subst/2]).
+-export([generate_subfilter/1, find_ldap_attrs/2,
+ get_ldap_attr/2, get_user_part/2, make_filter/2,
+ get_state/2, case_insensitive_match/2, get_config/2,
+ decode_octet_string/3, uids_domain_subst/2, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -171,64 +165,48 @@ uids_domain_subst(Host, UIDs) ->
end,
UIDs).
--spec get_opt({atom(), binary()}, list(), fun()) -> any().
-
-get_opt({Key, Host}, Opts, F) ->
- get_opt({Key, Host}, Opts, F, undefined).
-
--spec get_opt({atom(), binary()}, list(), fun(), any()) -> any().
-
-get_opt({Key, Host}, Opts, F, Default) ->
- case gen_mod:get_opt(Key, Opts, F, undefined) of
- undefined ->
- ejabberd_config:get_option(
- {Key, Host}, F, Default);
- Val ->
- Val
- end.
-
-spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) ->
- Servers = get_opt({ldap_servers, Host}, Opts,
+ Servers = gen_mod:get_opt({ldap_servers, Host}, Opts,
fun(L) ->
[iolist_to_binary(H) || H <- L]
end, [<<"localhost">>]),
- Backups = get_opt({ldap_backups, Host}, Opts,
+ Backups = gen_mod:get_opt({ldap_backups, Host}, Opts,
fun(L) ->
[iolist_to_binary(H) || H <- L]
end, []),
- Encrypt = get_opt({ldap_encrypt, Host}, Opts,
+ Encrypt = gen_mod:get_opt({ldap_encrypt, Host}, Opts,
fun(tls) -> tls;
(starttls) -> starttls;
(none) -> none
end, none),
- TLSVerify = get_opt({ldap_tls_verify, Host}, Opts,
+ TLSVerify = gen_mod:get_opt({ldap_tls_verify, Host}, Opts,
fun(hard) -> hard;
(soft) -> soft;
(false) -> false
end, false),
- TLSCAFile = get_opt({ldap_tls_cacertfile, Host}, Opts,
+ TLSCAFile = gen_mod:get_opt({ldap_tls_cacertfile, Host}, Opts,
fun iolist_to_binary/1),
- TLSDepth = get_opt({ldap_tls_depth, Host}, Opts,
+ TLSDepth = gen_mod:get_opt({ldap_tls_depth, Host}, Opts,
fun(I) when is_integer(I), I>=0 -> I end),
- Port = get_opt({ldap_port, Host}, Opts,
+ Port = gen_mod:get_opt({ldap_port, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end),
- RootDN = get_opt({ldap_rootdn, Host}, Opts,
+ RootDN = gen_mod:get_opt({ldap_rootdn, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
- Password = get_opt({ldap_password, Host}, Opts,
+ Password = gen_mod:get_opt({ldap_password, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
- Base = get_opt({ldap_base, Host}, Opts,
+ Base = gen_mod:get_opt({ldap_base, Host}, Opts,
fun iolist_to_binary/1,
<<"">>),
- OldDerefAliases = get_opt({deref_aliases, Host}, Opts,
+ OldDerefAliases = gen_mod:get_opt({deref_aliases, Host}, Opts,
fun(never) -> never;
(searching) -> searching;
(finding) -> finding;
@@ -236,7 +214,7 @@ get_config(Host, Opts) ->
end, unspecified),
DerefAliases =
if OldDerefAliases == unspecified ->
- get_opt({ldap_deref_aliases, Host}, Opts,
+ gen_mod:get_opt({ldap_deref_aliases, Host}, Opts,
fun(never) -> never;
(searching) -> searching;
(finding) -> finding;
@@ -367,3 +345,43 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]).
+
+opt_type(deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+opt_type(ldap_backups) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+opt_type(ldap_base) -> fun iolist_to_binary/1;
+opt_type(ldap_deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+opt_type(ldap_encrypt) ->
+ fun (tls) -> tls;
+ (starttls) -> starttls;
+ (none) -> none
+ end;
+opt_type(ldap_password) -> fun iolist_to_binary/1;
+opt_type(ldap_port) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
+opt_type(ldap_servers) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+opt_type(ldap_tls_cacertfile) -> fun iolist_to_binary/1;
+opt_type(ldap_tls_depth) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+opt_type(ldap_tls_verify) ->
+ fun (hard) -> hard;
+ (soft) -> soft;
+ (false) -> false
+ end;
+opt_type(_) ->
+ [deref_aliases, ldap_backups, ldap_base,
+ ldap_deref_aliases, ldap_encrypt, ldap_password,
+ ldap_port, ldap_rootdn, ldap_servers,
+ ldap_tls_cacertfile, ldap_tls_depth, ldap_tls_verify].
diff --git a/src/elixir_logger_backend.erl b/src/elixir_logger_backend.erl
new file mode 100644
index 00000000..c055853f
--- /dev/null
+++ b/src/elixir_logger_backend.erl
@@ -0,0 +1,122 @@
+%%%-------------------------------------------------------------------
+%%% @author Mickael Remond <mremond@process-one.net>
+%%% @doc
+%%% This module bridges lager logs to Elixir Logger.
+%%% @end
+%%% Created : 9 March 2016 by Mickael Remond <mremond@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(elixir_logger_backend).
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level = debug}).
+
+init(Opts) ->
+ Level = proplists:get_value(level, Opts, debug),
+ State = #state{level = Level},
+ {ok, State}.
+
+%% @private
+handle_event({log, LagerMsg}, State) ->
+ #{mode := Mode, truncate := Truncate, level := MinLevel, utc_log := UTCLog} = 'Elixir.Logger.Config':'__data__'(),
+ MsgLevel = severity_to_level(lager_msg:severity(LagerMsg)),
+ case {lager_util:is_loggable(LagerMsg, lager_util:level_to_num(State#state.level), ?MODULE),
+ 'Elixir.Logger':compare_levels(MsgLevel, MinLevel)} of
+ {_, lt}->
+ {ok, State};
+ {true, _} ->
+ Metadata = normalize_pid(lager_msg:metadata(LagerMsg)),
+ Message = 'Elixir.Logger.Utils':truncate(lager_msg:message(LagerMsg), Truncate),
+ Timestamp = timestamp(lager_msg:timestamp(LagerMsg), UTCLog),
+ GroupLeader = case proplists:get_value(pid, Metadata, self()) of
+ Pid when is_pid(Pid) ->
+ erlang:process_info(self(), group_leader);
+ _ -> {group_leader, self()}
+ end,
+ notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}),
+ {ok, State};
+ _ ->
+ {ok, State}
+ end;
+handle_event(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+%% TODO Handle loglevels
+handle_call(get_loglevel, State) ->
+ {ok, lager_util:config_to_mask(State#state.level), State};
+handle_call({set_loglevel, Config}, State) ->
+ {ok, ok, State#state{level = Config}}.
+
+%% @private
+handle_info(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+notify(sync, Msg) ->
+ gen_event:sync_notify('Elixir.Logger', Msg);
+notify(async, Msg) ->
+ gen_event:notify('Elixir.Logger', Msg).
+
+normalize_pid(Metadata) ->
+ case proplists:get_value(pid, Metadata) of
+ Pid when is_pid(Pid) -> Metadata;
+ Pid when is_list(Pid) ->
+ M1 = proplists:delete(pid, Metadata),
+ case catch erlang:list_to_pid(Pid) of
+ {'EXIT', _} ->
+ M1;
+ PidAsPid ->
+ [{pid, PidAsPid}|M1]
+ end;
+ _ ->
+ proplists:delete(pid, Metadata)
+ end.
+
+%% Return timestamp with milliseconds
+timestamp(Time, UTCLog) ->
+ {_, _, Micro} = p1_time_compat:timestamp(),
+ {Date, {Hours, Minutes, Seconds}} =
+ case UTCLog of
+ true -> calendar:now_to_universal_time(Time);
+ false -> calendar:now_to_local_time(Time)
+ end,
+ {Date, {Hours, Minutes, Seconds, Micro div 1000}}.
+
+
+severity_to_level(debug) -> debug;
+severity_to_level(info) -> info;
+severity_to_level(notice) -> info;
+severity_to_level(warning) -> warn;
+severity_to_level(error) -> error;
+severity_to_level(critical) -> error;
+severity_to_level(alert) -> error;
+severity_to_level(emergency) -> error.
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index f83fe2c6..91526e71 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -5,7 +5,7 @@
%%% Created : 19 Feb 2015 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2006-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2006-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
@@ -24,37 +24,34 @@
%%%----------------------------------------------------------------------
-module(ext_mod).
+
+-behaviour(ejabberd_config).
-author("Christophe Romain <christophe.romain@process-one.net>").
-%% Packaging service
-export([start/0, stop/0, update/0, check/1,
- available_command/0, available/0, available/1,
- installed_command/0, installed/0, installed/1,
- install/1, uninstall/1,
- upgrade/0, upgrade/1,
- add_sources/2, del_sources/1]).
+ available_command/0, available/0, available/1,
+ installed_command/0, installed/0, installed/1,
+ install/1, uninstall/1, upgrade/0, upgrade/1,
+ add_sources/2, del_sources/1, modules_dir/0,
+ config_dir/0, opt_type/1, get_commands_spec/0]).
-include("ejabberd_commands.hrl").
+-include("logger.hrl").
-define(REPOS, "https://github.com/processone/ejabberd-contrib").
%% -- ejabberd init and commands
start() ->
- case is_contrib_allowed() of
- true ->
- [code:add_patha(module_ebin_dir(Module))
- || {Module, _} <- installed()],
- application:start(inets),
- ejabberd_commands:register_commands(commands());
- false ->
- ok
- end.
+ [code:add_patha(module_ebin_dir(Module))
+ || {Module, _} <- installed()],
+ application:start(inets),
+ ejabberd_commands:register_commands(get_commands_spec()).
stop() ->
- ejabberd_commands:unregister_commands(commands()).
+ ejabberd_commands:unregister_commands(get_commands_spec()).
-commands() ->
+get_commands_spec() ->
[#ejabberd_commands{name = modules_update_specs,
tags = [admin,modules],
desc = "",
@@ -115,10 +112,19 @@ commands() ->
update() ->
add_sources(?REPOS),
- lists:foreach(fun({Package, Spec}) ->
+ Res = lists:foldl(fun({Package, Spec}, Acc) ->
Path = proplists:get_value(url, Spec, ""),
- add_sources(Package, Path)
- end, modules_spec(sources_dir(), "*")).
+ Update = add_sources(Package, Path),
+ ?INFO_MSG("Update package ~s: ~p", [Package, Update]),
+ case Update of
+ ok -> Acc;
+ Error -> [Error|Acc]
+ end
+ end, [], modules_spec(sources_dir(), "*")),
+ case Res of
+ [] -> ok;
+ [Error|_] -> Error
+ end.
available() ->
Jungle = modules_spec(sources_dir(), "*/*"),
@@ -163,6 +169,7 @@ install(Package) when is_binary(Package) ->
case compile_and_install(Module, Attrs) of
ok ->
code:add_patha(module_ebin_dir(Module)),
+ ejabberd_config:reload_file(),
ok;
Error ->
delete_path(module_lib_dir(Module)),
@@ -181,7 +188,8 @@ uninstall(Package) when is_binary(Package) ->
code:purge(Module),
code:delete(Module),
code:del_path(module_ebin_dir(Module)),
- delete_path(module_lib_dir(Module));
+ delete_path(module_lib_dir(Module)),
+ ejabberd_config:reload_file();
false ->
{error, not_installed}
end.
@@ -352,6 +360,10 @@ modules_dir() ->
sources_dir() ->
filename:join(modules_dir(), "sources").
+config_dir() ->
+ DefaultDir = filename:join(modules_dir(), "conf"),
+ getenv("CONTRIB_MODULES_CONF_DIR", DefaultDir).
+
module_lib_dir(Package) ->
filename:join(modules_dir(), Package).
@@ -444,9 +456,14 @@ compile_and_install(Module, Spec) ->
true ->
{ok, Dir} = file:get_cwd(),
file:set_cwd(SrcDir),
- Result = case compile(Module, Spec, LibDir) of
- ok -> install(Module, Spec, LibDir);
- Error -> Error
+ Result = case compile_deps(Module, Spec, LibDir) of
+ ok ->
+ case compile(Module, Spec, LibDir) of
+ ok -> install(Module, Spec, LibDir);
+ Error -> Error
+ end;
+ Error ->
+ Error
end,
file:set_cwd(Dir),
Result;
@@ -458,17 +475,46 @@ compile_and_install(Module, Spec) ->
end
end.
+compile_deps(_Module, _Spec, DestDir) ->
+ case filelib:is_dir("deps") of
+ true -> ok;
+ false -> fetch_rebar_deps()
+ end,
+ Ebin = filename:join(DestDir, "ebin"),
+ filelib:ensure_dir(filename:join(Ebin, ".")),
+ Result = lists:foldl(fun(Dep, Acc) ->
+ Inc = filename:join(Dep, "include"),
+ Src = filename:join(Dep, "src"),
+ Options = [{outdir, Ebin}, {i, Inc}],
+ [file:copy(App, Ebin) || App <- filelib:wildcard(Src++"/*.app")],
+ Acc++[case compile:file(File, Options) of
+ {ok, _} -> ok;
+ {ok, _, _} -> ok;
+ {ok, _, _, _} -> ok;
+ error -> {error, {compilation_failed, File}};
+ Error -> Error
+ end
+ || File <- filelib:wildcard(Src++"/*.erl")]
+ end, [], filelib:wildcard("deps/*")),
+ case lists:dropwhile(
+ fun(ok) -> true;
+ (_) -> false
+ end, Result) of
+ [] -> ok;
+ [Error|_] -> Error
+ end.
+
compile(_Module, _Spec, DestDir) ->
Ebin = filename:join(DestDir, "ebin"),
filelib:ensure_dir(filename:join(Ebin, ".")),
EjabBin = filename:dirname(code:which(ejabberd)),
EjabInc = filename:join(filename:dirname(EjabBin), "include"),
- XmlHrl = filename:join(EjabInc, "xml.hrl"),
- Logger = [{d, 'LAGER'} || code:is_loaded(lager)=/=false],
+ XmlHrl = filename:join(EjabInc, "fxml.hrl"),
ExtLib = [{d, 'NO_EXT_LIB'} || filelib:is_file(XmlHrl)],
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
verbose, report_errors, report_warnings]
- ++ Logger ++ ExtLib,
+ ++ ExtLib,
+ [file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
@@ -503,16 +549,59 @@ install(Module, Spec, DestDir) ->
Error -> Error
end.
+%% -- minimalist rebar spec parser, only support git
+
+fetch_rebar_deps() ->
+ case rebar_deps("rebar.config")++rebar_deps("rebar.config.script") of
+ [] ->
+ ok;
+ Deps ->
+ filelib:ensure_dir(filename:join("deps", ".")),
+ lists:foreach(fun({_App, Cmd}) ->
+ os:cmd("cd deps; "++Cmd++"; cd ..")
+ end, Deps)
+ end.
+rebar_deps(Script) ->
+ case file:script(Script) of
+ {ok, Config} when is_list(Config) ->
+ [rebar_dep(Dep) || Dep <- proplists:get_value(deps, Config, [])];
+ {ok, {deps, Deps}} ->
+ [rebar_dep(Dep) || Dep <- Deps];
+ _ ->
+ []
+ end.
+rebar_dep({App, _, {git, Url}}) ->
+ {App, "git clone "++Url++" "++filename:basename(App)};
+rebar_dep({App, _, {git, Url, {branch, Ref}}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q origin/"++Ref++")"};
+rebar_dep({App, _, {git, Url, {tag, Ref}}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q "++Ref++")"};
+rebar_dep({App, _, {git, Url, Ref}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q "++Ref++")"}.
+
%% -- YAML spec parser
consult(File) ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} -> {ok, []};
{ok, [Doc|_]} -> {ok, [format(Spec) || Spec <- Doc]};
- {error, Err} -> {error, p1_yaml:format_error(Err)}
+ {error, Err} -> {error, fast_yaml:format_error(Err)}
end.
format({Key, Val}) when is_binary(Val) ->
{Key, binary_to_list(Val)};
format({Key, Val}) -> % TODO: improve Yaml parsing
{Key, Val}.
+
+opt_type(allow_contrib_modules) ->
+ fun (false) -> false;
+ (no) -> false;
+ (_) -> true
+ end;
+opt_type(_) -> [allow_contrib_modules].
diff --git a/src/extauth.erl b/src/extauth.erl
index eb936ddf..50330b47 100644
--- a/src/extauth.erl
+++ b/src/extauth.erl
@@ -5,7 +5,7 @@
%%% Created : 30 Jul 2004 by Leif Johansson <leifj@it.su.se>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,13 @@
-module(extauth).
+-behaviour(ejabberd_config).
+
-author('leifj@it.su.se').
-export([start/2, stop/1, init/2, check_password/3,
set_password/3, try_register/3, remove_user/2,
- remove_user/3, is_user_exists/2]).
+ remove_user/3, is_user_exists/2, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -93,15 +95,14 @@ remove_user(User, Server, Password) ->
[<<"removeuser3">>, User, Server, Password]).
call_port(Server, Msg) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
ProcessName = get_process_name(LServer,
random_instance(get_instances(LServer))),
ProcessName ! {call, self(), Msg},
receive {eauth, Result} -> Result end.
random_instance(MaxNum) ->
- {A1, A2, A3} = now(),
- random:seed(A1, A2, A3),
+ random:seed(p1_time_compat:timestamp()),
random:uniform(MaxNum) - 1.
get_instances(Server) ->
@@ -157,3 +158,7 @@ encode(L) -> str:join(L, <<":">>).
decode([0, 0]) -> false;
decode([0, 1]) -> true.
+
+opt_type(extauth_instances) ->
+ fun (V) when is_integer(V), V > 0 -> V end;
+opt_type(_) -> [extauth_instances].
diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl
index bbad1eca..c2b4252c 100644
--- a/src/gen_iq_handler.erl
+++ b/src/gen_iq_handler.erl
@@ -5,7 +5,7 @@
%%% Created : 22 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -62,33 +62,37 @@ start_link(Host, Module, Function) ->
add_iq_handler(Component, Host, NS, Module, Function,
Type) ->
case Type of
- no_queue ->
- Component:register_iq_handler(Host, NS, Module,
- Function, no_queue);
- one_queue ->
- {ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
- [Host, Module, Function]),
- Component:register_iq_handler(Host, NS, Module,
- Function, {one_queue, Pid});
- N when is_integer(N) ->
- Pids = lists:map(fun (_) ->
- {ok, Pid} =
- supervisor:start_child(ejabberd_iq_sup,
- [Host, Module,
- Function]),
- Pid
- end,
- lists:seq(1, N)),
- Component:register_iq_handler(Host, NS, Module,
- Function, {queues, Pids});
- parallel ->
- Component:register_iq_handler(Host, NS, Module,
- Function, parallel)
+ no_queue ->
+ Component:register_iq_handler(Host, NS, Module,
+ Function, no_queue);
+ one_queue ->
+ {ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
+ [Host, Module, Function]),
+ Component:register_iq_handler(Host, NS, Module,
+ Function, {one_queue, Pid});
+ N when is_integer(N) ->
+ Pids = lists:map(fun (_) ->
+ {ok, Pid} =
+ supervisor:start_child(ejabberd_iq_sup,
+ [Host, Module,
+ Function]),
+ Pid
+ end,
+ lists:seq(1, N)),
+ Component:register_iq_handler(Host, NS, Module,
+ Function, {queues, Pids});
+ parallel ->
+ Component:register_iq_handler(Host, NS, Module,
+ Function, parallel)
end.
+-spec remove_iq_handler(component(), binary(), binary()) -> any().
+
remove_iq_handler(Component, Host, NS) ->
Component:unregister_iq_handler(Host, NS).
+-spec stop_iq_handler(atom(), atom(), [pid()]) -> any().
+
stop_iq_handler(_Module, _Function, Opts) ->
case Opts of
{one_queue, Pid} -> gen_server:call(Pid, stop);
@@ -100,21 +104,26 @@ stop_iq_handler(_Module, _Function, Opts) ->
_ -> ok
end.
+-spec handle(binary(), atom(), atom(), opts(), jid(), jid(), iq()) -> any().
+
handle(Host, Module, Function, Opts, From, To, IQ) ->
case Opts of
- no_queue ->
- process_iq(Host, Module, Function, From, To, IQ);
- {one_queue, Pid} -> Pid ! {process_iq, From, To, IQ};
- {queues, Pids} ->
- Pid = lists:nth(erlang:phash(now(), length(Pids)),
- Pids),
- Pid ! {process_iq, From, To, IQ};
- parallel ->
- spawn(?MODULE, process_iq,
+ no_queue ->
+ process_iq(Host, Module, Function, From, To, IQ);
+ {one_queue, Pid} ->
+ Pid ! {process_iq, From, To, IQ};
+ {queues, Pids} ->
+ Pid = lists:nth(erlang:phash(p1_time_compat:unique_integer(),
+ length(Pids)), Pids),
+ Pid ! {process_iq, From, To, IQ};
+ parallel ->
+ spawn(?MODULE, process_iq,
[Host, Module, Function, From, To, IQ]);
- _ -> todo
+ _ -> todo
end.
+-spec process_iq(binary(), atom(), atom(), jid(), jid(), iq()) -> any().
+
process_iq(_Host, Module, Function, From, To, IQ) ->
case catch Module:Function(From, To, IQ) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
@@ -146,44 +155,16 @@ transform_module_options(Opts) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Host, Module, Function]) ->
{ok,
#state{host = Host, module = Module,
function = Function}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
Reply = ok, {stop, normal, Reply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast(_Msg, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info({process_iq, From, To, IQ},
#state{host = Host, module = Module,
function = Function} =
@@ -192,22 +173,10 @@ handle_info({process_iq, From, To, IQ},
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, _State) -> ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index b8e155a0..c45642d4 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -1,12 +1,11 @@
%%%----------------------------------------------------------------------
%%% File : gen_mod.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose :
%%% Purpose :
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -26,14 +25,17 @@
-module(gen_mod).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
--export([start/0, start_module/2, start_module/3, stop_module/2,
- stop_module_keep_config/2, get_opt/3, get_opt/4,
- get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
- get_module_opt_host/3, loaded_modules/1,
- loaded_modules_with_opts/1, get_hosts/2,
- get_module_proc/2, is_loaded/2, default_db/1]).
+-export([start/0, start_module/2, start_module/3,
+ stop_module/2, stop_module_keep_config/2, get_opt/3,
+ get_opt/4, get_opt_host/3, db_type/1, db_type/2,
+ get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
+ loaded_modules/1, loaded_modules_with_opts/1,
+ get_hosts/2, get_module_proc/2, is_loaded/2,
+ start_modules/1, default_db/1, v_db/1, opt_type/1]).
%%-export([behaviour_info/1]).
@@ -45,11 +47,13 @@
opts = [] :: opts() | '_' | '$2'}).
-type opts() :: [{atom(), any()}].
+-type db_type() :: odbc | mnesia | riak.
-callback start(binary(), opts()) -> any().
-callback stop(binary()) -> any().
-export_type([opts/0]).
+-export_type([db_type/0]).
%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
%%behaviour_info(_Other) -> undefined.
@@ -60,6 +64,17 @@ start() ->
{keypos, #ejabberd_module.module_host}]),
ok.
+-spec start_modules(binary()) -> any().
+
+start_modules(Host) ->
+ Modules = ejabberd_config:get_option(
+ {modules, Host},
+ fun(L) when is_list(L) -> L end, []),
+ lists:foreach(
+ fun({Module, Opts}) ->
+ start_module(Host, Module, Opts)
+ end, Modules).
+
-spec start_module(binary(), atom()) -> any().
start_module(Host, Module) ->
@@ -75,7 +90,8 @@ start_module(Host, Module) ->
-spec start_module(binary(), atom(), opts()) -> any().
-start_module(Host, Module, Opts) ->
+start_module(Host, Module, Opts0) ->
+ Opts = validate_opts(Module, Opts0),
ets:insert(ejabberd_modules,
#ejabberd_module{module_host = {Module, Host},
opts = Opts}),
@@ -107,18 +123,12 @@ is_app_running(AppName) ->
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
-%% @doc Stop the module in a host, and forget its configuration.
stop_module(Host, Module) ->
case stop_module_keep_config(Host, Module) of
error -> error;
ok -> ok
end.
-%% @doc Stop the module in a host, but keep its configuration.
-%% As the module configuration is kept in the Mnesia local_config table,
-%% when ejabberd is restarted the module will be started again.
-%% This function is useful when ejabberd is being stopped
-%% and it stops all modules.
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
stop_module_keep_config(Host, Module) ->
@@ -155,13 +165,20 @@ wait_for_stop1(MonitorReference) ->
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
--spec get_opt(atom(), opts(), check_fun()) -> any().
+-spec get_opt(atom() | {atom(), binary()|global}, opts(), check_fun()) -> any().
get_opt(Opt, Opts, F) ->
get_opt(Opt, Opts, F, undefined).
--spec get_opt(atom(), opts(), check_fun(), any()) -> any().
+-spec get_opt(atom() | {atom(), binary()|global}, opts(), check_fun(), any()) -> any().
+get_opt({Opt, Host}, Opts, F, Default) ->
+ case lists:keysearch(Opt, 1, Opts) of
+ false ->
+ ejabberd_config:get_option({Opt, Host}, F, Default);
+ {value, {_, Val}} ->
+ ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
+ end;
get_opt(Opt, Opts, F, Default) ->
case lists:keysearch(Opt, 1, Opts) of
false ->
@@ -170,6 +187,11 @@ get_opt(Opt, Opts, F, Default) ->
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
end.
+-spec get_module_opt(global | binary(), atom(), atom(), check_fun()) -> any().
+
+get_module_opt(Host, Module, Opt, F) ->
+ get_module_opt(Host, Module, Opt, F, undefined).
+
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
get_module_opt(global, Module, Opt, F, Default) ->
@@ -209,26 +231,57 @@ get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
--spec v_db(odbc | mnesia | riak | internal) -> odbc | mnesia | riak.
+validate_opts(Module, Opts) ->
+ lists:filter(
+ fun({Opt, Val}) ->
+ case catch Module:mod_opt_type(Opt) of
+ VFun when is_function(VFun) ->
+ case catch VFun(Val) of
+ {'EXIT', _} ->
+ ?ERROR_MSG("ignoring invalid value '~p' for "
+ "option '~s' of module '~s'",
+ [Val, Opt, Module]),
+ false;
+ _ ->
+ true
+ end;
+ L when is_list(L) ->
+ SOpts = str:join([[$', atom_to_list(A), $'] || A <- L], <<", ">>),
+ ?ERROR_MSG("unknown option '~s' for module '~s' will be"
+ " likely ignored, available options are: ~s",
+ [Opt, Module, SOpts]),
+ true;
+ {'EXIT', {undef, _}} ->
+ ?WARNING_MSG("module '~s' doesn't export mod_opt_type/1",
+ [Module]),
+ true
+ end;
+ (Junk) ->
+ ?ERROR_MSG("failed to understand option ~p for module '~s'",
+ [Junk, Module]),
+ false
+ end, Opts).
+
+-spec v_db(db_type() | internal) -> db_type().
v_db(odbc) -> odbc;
v_db(internal) -> mnesia;
v_db(mnesia) -> mnesia;
v_db(riak) -> riak.
--spec db_type(opts()) -> odbc | mnesia | riak.
+-spec db_type(opts()) -> db_type().
db_type(Opts) ->
db_type(global, Opts).
--spec db_type(binary() | global, atom() | opts()) -> odbc | mnesia | riak.
+-spec db_type(binary() | global, atom() | opts()) -> db_type().
db_type(Host, Module) when is_atom(Module) ->
get_module_opt(Host, Module, db_type, fun v_db/1, default_db(Host));
db_type(Host, Opts) when is_list(Opts) ->
get_opt(db_type, Opts, fun v_db/1, default_db(Host)).
--spec default_db(binary() | global) -> odbc | mnesia | riak.
+-spec default_db(binary() | global) -> db_type().
default_db(Host) ->
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
@@ -278,3 +331,7 @@ get_module_proc(Host, Base) ->
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
+
+opt_type(default_db) -> fun v_db/1;
+opt_type(modules) -> fun (L) when is_list(L) -> L end;
+opt_type(_) -> [default_db, modules].
diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl
index c608dad8..0148da2e 100644
--- a/src/gen_pubsub_node.erl
+++ b/src/gen_pubsub_node.erl
@@ -1,32 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : gen_pubsub_node.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Define pubsub plugin behaviour
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
-
-%%% @private
-%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
-%%% plugin behaviour. This behaviour is used to check that a PubSub plugin
-%%% respects the current ejabberd PubSub plugin API.</p>
+%%% 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(gen_pubsub_node).
diff --git a/src/gen_pubsub_nodetree.erl b/src/gen_pubsub_nodetree.erl
index ce6750db..73583af0 100644
--- a/src/gen_pubsub_nodetree.erl
+++ b/src/gen_pubsub_nodetree.erl
@@ -1,32 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : gen_pubsub_nodetree.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Define the pubsub node tree plugin behaviour
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
-
-%%% @private
-%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
-%%% tree plugin behaviour. This behaviour is used to check that a PubSub
-%%% node tree plugin respects the current ejabberd PubSub plugin API.</p>
+%%% 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(gen_pubsub_nodetree).
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index bfa52bb2..099387c9 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -5,7 +5,7 @@
%%% Created : 2 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -42,13 +42,13 @@
import_file(File) ->
User = filename:rootname(filename:basename(File)),
Server = filename:basename(filename:dirname(File)),
- case jlib:nodeprep(User) /= error andalso
- jlib:nameprep(Server) /= error
+ case jid:nodeprep(User) /= error andalso
+ jid:nameprep(Server) /= error
of
true ->
case file:read_file(File) of
{ok, Text} ->
- case xml_stream:parse_element(Text) of
+ case fxml_stream:parse_element(Text) of
El when is_record(El, xmlel) ->
case catch process_xdb(User, Server, El) of
{'EXIT', Reason} ->
@@ -112,34 +112,34 @@ process_xdb(User, Server,
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
- From = jlib:make_jid(User, Server, <<"">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ From = jid:make(User, Server, <<"">>),
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_AUTH ->
- Password = xml:get_tag_cdata(El),
+ Password = fxml:get_tag_cdata(El),
ejabberd_auth:set_password(User, Server, Password),
ok;
?NS_ROSTER ->
catch mod_roster:set_items(User, Server, El), ok;
?NS_LAST ->
- TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
- Status = xml:get_tag_cdata(El),
+ TimeStamp = fxml:get_attr_s(<<"last">>, Attrs),
+ Status = fxml:get_tag_cdata(El),
catch mod_last:store_last_info(User, Server,
jlib:binary_to_integer(TimeStamp),
Status),
ok;
?NS_VCARD ->
catch mod_vcard:process_sm_iq(From,
- jlib:make_jid(<<"">>, Server, <<"">>),
+ jid:make(<<"">>, Server, <<"">>),
#iq{type = set, xmlns = ?NS_VCARD,
sub_el = El}),
ok;
<<"jabber:x:offline">> ->
process_offline(Server, From, El), ok;
XMLNS ->
- case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
+ case fxml:get_attr_s(<<"j_private_flag">>, Attrs) of
<<"1">> ->
catch mod_private:process_sm_iq(From,
- jlib:make_jid(<<"">>, Server,
+ jid:make(<<"">>, Server,
<<"">>),
#iq{type = set,
xmlns = ?NS_PRIVATE,
@@ -158,13 +158,13 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
end.
process_offline(Server, To, #xmlel{children = Els}) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
- FromS = xml:get_attr_s(<<"from">>, Attrs),
+ FromS = fxml:get_attr_s(<<"from">>, Attrs),
From = case FromS of
<<"">> ->
- jlib:make_jid(<<"">>, Server, <<"">>);
- _ -> jlib:string_to_jid(FromS)
+ jid:make(<<"">>, Server, <<"">>);
+ _ -> jid:from_string(FromS)
end,
case From of
error -> ok;
diff --git a/src/jid.erl b/src/jid.erl
new file mode 100644
index 00000000..7bdd652a
--- /dev/null
+++ b/src/jid.erl
@@ -0,0 +1,232 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @doc
+%%% JID processing library
+%%% @end
+%%% Created : 24 Nov 2015 by Evgeny Khramtsov <ekhramtsov@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(jid).
+
+%% API
+-export([start/0,
+ make/1,
+ make/3,
+ split/1,
+ from_string/1,
+ to_string/1,
+ is_nodename/1,
+ nodeprep/1,
+ nameprep/1,
+ resourceprep/1,
+ tolower/1,
+ remove_resource/1,
+ replace_resource/2]).
+
+-include("jlib.hrl").
+
+-export_type([jid/0]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec start() -> ok.
+
+start() ->
+ SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]),
+ catch ets:new(jlib, [named_table, protected, set, {keypos, 1}]),
+ ets:insert(jlib, {string_to_jid_pattern, SplitPattern}),
+ ok.
+
+-spec make(binary(), binary(), binary()) -> jid() | error.
+
+make(User, Server, Resource) ->
+ case nodeprep(User) of
+ error -> error;
+ LUser ->
+ case nameprep(Server) of
+ error -> error;
+ LServer ->
+ case resourceprep(Resource) of
+ error -> error;
+ LResource ->
+ #jid{user = User, server = Server, resource = Resource,
+ luser = LUser, lserver = LServer,
+ lresource = LResource}
+ end
+ end
+ end.
+
+-spec make({binary(), binary(), binary()}) -> jid() | error.
+
+make({User, Server, Resource}) ->
+ make(User, Server, Resource).
+
+%% This is the reverse of make_jid/1
+-spec split(jid()) -> {binary(), binary(), binary()} | error.
+
+split(#jid{user = U, server = S, resource = R}) ->
+ {U, S, R};
+split(_) ->
+ error.
+
+-spec from_string([binary()|string()]) -> jid() | error.
+from_string(S) when is_list(S) ->
+ %% We do not accept list because we want to enforce good practice of
+ %% using binaries for string. However, we do not let it crash to avoid
+ %% losing associated ets table.
+ {error, need_jid_as_binary};
+from_string(S) when is_binary(S) ->
+ SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2),
+ Size = size(S),
+ End = Size-1,
+ case binary:match(S, SplitPattern) of
+ {0, _} ->
+ error;
+ {End, _} ->
+ error;
+ {Pos1, _} ->
+ case binary:at(S, Pos1) of
+ $/ ->
+ make(<<>>,
+ binary:part(S, 0, Pos1),
+ binary:part(S, Pos1+1, Size-Pos1-1));
+ _ ->
+ Pos1N = Pos1+1,
+ case binary:match(S, SplitPattern, [{scope, {Pos1+1, Size-Pos1-1}}]) of
+ {End, _} ->
+ error;
+ {Pos1N, _} ->
+ error;
+ {Pos2, _} ->
+ case binary:at(S, Pos2) of
+ $/ ->
+ make(binary:part(S, 0, Pos1),
+ binary:part(S, Pos1+1, Pos2-Pos1-1),
+ binary:part(S, Pos2+1, Size-Pos2-1));
+ _ -> error
+ end;
+ _ ->
+ make(binary:part(S, 0, Pos1),
+ binary:part(S, Pos1+1, Size-Pos1-1),
+ <<>>)
+ end
+ end;
+ _ ->
+ make(<<>>, S, <<>>)
+ end.
+
+-spec to_string(jid() | ljid()) -> binary().
+
+to_string(#jid{user = User, server = Server,
+ resource = Resource}) ->
+ to_string({User, Server, Resource});
+to_string({N, S, R}) ->
+ Node = iolist_to_binary(N),
+ Server = iolist_to_binary(S),
+ Resource = iolist_to_binary(R),
+ S1 = case Node of
+ <<"">> -> <<"">>;
+ _ -> <<Node/binary, "@">>
+ end,
+ S2 = <<S1/binary, Server/binary>>,
+ S3 = case Resource of
+ <<"">> -> S2;
+ _ -> <<S2/binary, "/", Resource/binary>>
+ end,
+ S3.
+
+-spec is_nodename(binary()) -> boolean().
+
+is_nodename(Node) ->
+ N = nodeprep(Node),
+ (N /= error) and (N /= <<>>).
+
+-define(LOWER(Char),
+ if Char >= $A, Char =< $Z -> Char + 32;
+ true -> Char
+ end).
+
+-spec nodeprep(binary()) -> binary() | error.
+
+nodeprep("") -> <<>>;
+nodeprep(S) when byte_size(S) < 1024 ->
+ R = stringprep:nodeprep(S),
+ if byte_size(R) < 1024 -> R;
+ true -> error
+ end;
+nodeprep(_) -> error.
+
+-spec nameprep(binary()) -> binary() | error.
+
+nameprep(S) when byte_size(S) < 1024 ->
+ R = stringprep:nameprep(S),
+ if byte_size(R) < 1024 -> R;
+ true -> error
+ end;
+nameprep(_) -> error.
+
+-spec resourceprep(binary()) -> binary() | error.
+
+resourceprep(S) when byte_size(S) < 1024 ->
+ R = stringprep:resourceprep(S),
+ if byte_size(R) < 1024 -> R;
+ true -> error
+ end;
+resourceprep(_) -> error.
+
+-spec tolower(jid() | ljid()) -> error | ljid().
+
+tolower(#jid{luser = U, lserver = S,
+ lresource = R}) ->
+ {U, S, R};
+tolower({U, S, R}) ->
+ case nodeprep(U) of
+ error -> error;
+ LUser ->
+ case nameprep(S) of
+ error -> error;
+ LServer ->
+ case resourceprep(R) of
+ error -> error;
+ LResource -> {LUser, LServer, LResource}
+ end
+ end
+ end.
+
+-spec remove_resource(jid()) -> jid();
+ (ljid()) -> ljid().
+
+remove_resource(#jid{} = JID) ->
+ JID#jid{resource = <<"">>, lresource = <<"">>};
+remove_resource({U, S, _R}) -> {U, S, <<"">>}.
+
+-spec replace_resource(jid(), binary()) -> error | jid().
+
+replace_resource(JID, Resource) ->
+ case resourceprep(Resource) of
+ error -> error;
+ LResource ->
+ JID#jid{resource = Resource, lresource = LResource}
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/jlib.erl b/src/jlib.erl
index 76886a7d..8eaebbc8 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,10 @@
-author('alexey@process-one.net').
+-protocol({xep, 59, '1.0'}).
+-protocol({xep, 82, '1.1'}).
+-protocol({xep, 203, '2.0'}).
+
-compile({no_auto_import, [atom_to_binary/2,
binary_to_integer/1,
integer_to_binary/1]}).
@@ -35,15 +39,13 @@
make_error_reply/2, make_error_element/2,
make_correct_from_to_attrs/3, replace_from_to_attrs/3,
replace_from_to/3, replace_from_attrs/2, replace_from/2,
- remove_attr/2, make_jid/3, make_jid/1, string_to_jid/1,
- jid_to_string/1, is_nodename/1, tolower/1, nodeprep/1,
- nameprep/1, resourceprep/1, jid_tolower/1,
- jid_remove_resource/1, jid_replace_resource/2,
+ remove_attr/2, tolower/1,
get_iq_namespace/1, iq_query_info/1,
iq_query_or_response_info/1, is_iq_request_type/1,
iq_to_xml/1, parse_xdata_submit/1,
+ is_standalone_chat_state/1,
add_delay_info/3, add_delay_info/4,
- timestamp_to_iso/1, timestamp_to_iso/2,
+ timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2,
now_to_utc_string/1, now_to_local_string/1,
datetime_string_to_timestamp/1,
term_to_base64/1, base64_to_term/1,
@@ -54,14 +56,28 @@
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, queue_drop_while/2]).
-%% TODO: Remove once XEP-0091 is Obsolete
-%% TODO: Remove once XEP-0091 is Obsolete
+%% The following functions are deprecated and will be removed soon
+%% Use corresponding functions from jid.erl instead
+-export([make_jid/3, make_jid/1, split_jid/1, string_to_jid/1,
+ jid_to_string/1, is_nodename/1, nodeprep/1,
+ nameprep/1, resourceprep/1, jid_tolower/1,
+ jid_remove_resource/1, jid_replace_resource/2]).
+
+-deprecated([{make_jid, '_'},
+ {split_jid, 1},
+ {string_to_jid, 1},
+ {jid_to_string, 1},
+ {is_nodename, 1},
+ {nodeprep, 1},
+ {nameprep, 1},
+ {resourceprep, 1},
+ {jid_tolower, 1},
+ {jid_remove_resource, 1},
+ {jid_replace_resource, 2}]).
-include("ejabberd.hrl").
-include("jlib.hrl").
--export_type([jid/0]).
-
%send_iq(From, To, ID, SubTags) ->
% ok.
@@ -76,8 +92,8 @@ make_result_iq_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_result_iq_reply_attrs([attr()]) -> [attr()].
make_result_iq_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -117,8 +133,8 @@ make_error_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_error_reply_attrs([attr()]) -> [attr()].
make_error_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -143,7 +159,7 @@ make_error_element(Code, Desc) ->
make_correct_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
- Attrs2 = case xml:get_attr(<<"to">>, Attrs) of
+ Attrs2 = case fxml:get_attr(<<"to">>, Attrs) of
{value, _} -> Attrs1;
_ -> [{<<"to">>, To} | Attrs1]
end,
@@ -164,8 +180,8 @@ replace_from_to_attrs(From, To, Attrs) ->
replace_from_to(From, To,
#xmlel{name = Name, attrs = Attrs, children = Els}) ->
NewAttrs =
- replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To), Attrs),
+ replace_from_to_attrs(jid:to_string(From),
+ jid:to_string(To), Attrs),
#xmlel{name = Name, attrs = NewAttrs, children = Els}.
-spec replace_from_attrs(binary(), [attr()]) -> [attr()].
@@ -178,7 +194,7 @@ replace_from_attrs(From, Attrs) ->
replace_from(From,
#xmlel{name = Name, attrs = Attrs, children = Els}) ->
- NewAttrs = replace_from_attrs(jlib:jid_to_string(From),
+ NewAttrs = replace_from_attrs(jid:to_string(From),
Attrs),
#xmlel{name = Name, attrs = NewAttrs, children = Els}.
@@ -192,86 +208,32 @@ remove_attr(Attr,
-spec make_jid(binary(), binary(), binary()) -> jid() | error.
make_jid(User, Server, Resource) ->
- case nodeprep(User) of
- error -> error;
- LUser ->
- case nameprep(Server) of
- error -> error;
- LServer ->
- case resourceprep(Resource) of
- error -> error;
- LResource ->
- #jid{user = User, server = Server, resource = Resource,
- luser = LUser, lserver = LServer,
- lresource = LResource}
- end
- end
- end.
+ jid:make(User, Server, Resource).
-spec make_jid({binary(), binary(), binary()}) -> jid() | error.
make_jid({User, Server, Resource}) ->
- make_jid(User, Server, Resource).
+ jid:make({User, Server, Resource}).
+
+%% This is the reverse of make_jid/1
+-spec split_jid(jid()) -> {binary(), binary(), binary()} | error.
+split_jid(J) ->
+ jid:split(J).
-spec string_to_jid(binary()) -> jid() | error.
string_to_jid(S) ->
- string_to_jid1(binary_to_list(S), "").
-
-string_to_jid1([$@ | _J], "") -> error;
-string_to_jid1([$@ | J], N) ->
- string_to_jid2(J, lists:reverse(N), "");
-string_to_jid1([$/ | _J], "") -> error;
-string_to_jid1([$/ | J], N) ->
- string_to_jid3(J, "", lists:reverse(N), "");
-string_to_jid1([C | J], N) ->
- string_to_jid1(J, [C | N]);
-string_to_jid1([], "") -> error;
-string_to_jid1([], N) ->
- make_jid(<<"">>, list_to_binary(lists:reverse(N)), <<"">>).
-
-%% Only one "@" is admitted per JID
-string_to_jid2([$@ | _J], _N, _S) -> error;
-string_to_jid2([$/ | _J], _N, "") -> error;
-string_to_jid2([$/ | J], N, S) ->
- string_to_jid3(J, N, lists:reverse(S), "");
-string_to_jid2([C | J], N, S) ->
- string_to_jid2(J, N, [C | S]);
-string_to_jid2([], _N, "") -> error;
-string_to_jid2([], N, S) ->
- make_jid(list_to_binary(N), list_to_binary(lists:reverse(S)), <<"">>).
-
-string_to_jid3([C | J], N, S, R) ->
- string_to_jid3(J, N, S, [C | R]);
-string_to_jid3([], N, S, R) ->
- make_jid(list_to_binary(N), list_to_binary(S),
- list_to_binary(lists:reverse(R))).
+ jid:from_string(S).
-spec jid_to_string(jid() | ljid()) -> binary().
-jid_to_string(#jid{user = User, server = Server,
- resource = Resource}) ->
- jid_to_string({User, Server, Resource});
-jid_to_string({N, S, R}) ->
- Node = iolist_to_binary(N),
- Server = iolist_to_binary(S),
- Resource = iolist_to_binary(R),
- S1 = case Node of
- <<"">> -> <<"">>;
- _ -> <<Node/binary, "@">>
- end,
- S2 = <<S1/binary, Server/binary>>,
- S3 = case Resource of
- <<"">> -> S2;
- _ -> <<S2/binary, "/", Resource/binary>>
- end,
- S3.
+jid_to_string(J) ->
+ jid:to_string(J).
-spec is_nodename(binary()) -> boolean().
is_nodename(Node) ->
- N = nodeprep(Node),
- (N /= error) and (N /= <<>>).
+ jid:is_nodename(Node).
%tolower_c(C) when C >= $A, C =< $Z ->
% C + 32;
@@ -309,72 +271,36 @@ tolower_s([]) -> [].
-spec nodeprep(binary()) -> binary() | error.
-nodeprep("") -> <<>>;
-nodeprep(S) when byte_size(S) < 1024 ->
- R = stringprep:nodeprep(S),
- if byte_size(R) < 1024 -> R;
- true -> error
- end;
-nodeprep(_) -> error.
+nodeprep(S) -> jid:nodeprep(S).
-spec nameprep(binary()) -> binary() | error.
-nameprep(S) when byte_size(S) < 1024 ->
- R = stringprep:nameprep(S),
- if byte_size(R) < 1024 -> R;
- true -> error
- end;
-nameprep(_) -> error.
+nameprep(S) -> jid:nameprep(S).
-spec resourceprep(binary()) -> binary() | error.
-resourceprep(S) when byte_size(S) < 1024 ->
- R = stringprep:resourceprep(S),
- if byte_size(R) < 1024 -> R;
- true -> error
- end;
-resourceprep(_) -> error.
+resourceprep(S) -> jid:resourceprep(S).
-spec jid_tolower(jid() | ljid()) -> error | ljid().
-jid_tolower(#jid{luser = U, lserver = S,
- lresource = R}) ->
- {U, S, R};
-jid_tolower({U, S, R}) ->
- case nodeprep(U) of
- error -> error;
- LUser ->
- case nameprep(S) of
- error -> error;
- LServer ->
- case resourceprep(R) of
- error -> error;
- LResource -> {LUser, LServer, LResource}
- end
- end
- end.
+jid_tolower(J) ->
+ jid:tolower(J).
-spec jid_remove_resource(jid()) -> jid();
(ljid()) -> ljid().
-jid_remove_resource(#jid{} = JID) ->
- JID#jid{resource = <<"">>, lresource = <<"">>};
-jid_remove_resource({U, S, _R}) -> {U, S, <<"">>}.
+jid_remove_resource(J) -> jid:remove_resource(J).
-spec jid_replace_resource(jid(), binary()) -> error | jid().
jid_replace_resource(JID, Resource) ->
- case resourceprep(Resource) of
- error -> error;
- LResource ->
- JID#jid{resource = Resource, lresource = LResource}
- end.
+ jid:replace_resource(JID, Resource).
-spec get_iq_namespace(xmlel()) -> binary().
get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
- case xml:remove_cdata(Els) of
- [#xmlel{attrs = Attrs}] -> xml:get_attr_s(<<"xmlns">>, Attrs);
+ case fxml:remove_cdata(Els) of
+ [#xmlel{attrs = Attrs}] -> fxml:get_attr_s(<<"xmlns">>, Attrs);
_ -> <<"">>
end;
get_iq_namespace(_) -> <<"">>.
@@ -400,9 +326,9 @@ iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) ->
- ID = xml:get_attr_s(<<"id">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- {Type, Class} = case xml:get_attr_s(<<"type">>, Attrs) of
+ ID = fxml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ {Type, Class} = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"set">> -> {set, request};
<<"get">> -> {get, request};
<<"result">> -> {result, reply};
@@ -410,15 +336,15 @@ iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter)
_ -> {invalid, invalid}
end,
if Type == invalid -> invalid; Class == request; Filter == any ->
- FilteredEls = xml:remove_cdata(Els),
+ FilteredEls = fxml:remove_cdata(Els),
{XMLNS, SubEl} = case {Class, FilteredEls} of
{request, [#xmlel{attrs = Attrs2}]} ->
- {xml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
+ {fxml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
{reply, _} ->
NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls,
SubName /= <<"error">>],
{case NonErrorEls of
- [NonErrorEl] -> xml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
+ [NonErrorEl] -> fxml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
_ -> <<"">>
end,
FilteredEls};
@@ -473,7 +399,7 @@ iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
).
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"submit">> ->
lists:reverse(parse_xdata_fields(Els, []));
<<"form">> -> %% This is a workaround to accept Psi's wrong forms
@@ -492,7 +418,7 @@ parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
parse_xdata_fields([], Res) -> Res;
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
| Els], Res) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<>> ->
parse_xdata_fields(Els, Res);
Var ->
@@ -511,7 +437,7 @@ parse_xdata_fields([_ | Els], Res) ->
parse_xdata_values([], Res) -> Res;
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
- Val = xml:get_cdata(SubEls),
+ Val = fxml:get_cdata(SubEls),
parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
@@ -520,7 +446,7 @@ parse_xdata_values([_ | Els], Res) ->
rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl);
rsm_decode(#xmlel{} = SubEl) ->
- case xml:get_subtag(SubEl, <<"set">>) of
+ case fxml:get_subtag(SubEl, <<"set">>) of
false -> none;
#xmlel{name = <<"set">>, children = SubEls} ->
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
@@ -529,26 +455,26 @@ rsm_decode(#xmlel{} = SubEl) ->
rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} =
Elem,
RsmIn) ->
- CountStr = xml:get_tag_cdata(Elem),
+ CountStr = fxml:get_tag_cdata(Elem),
{Count, _} = str:to_integer(CountStr),
RsmIn#rsm_in{max = Count};
rsm_parse_element(#xmlel{name = <<"before">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = before, id = UID};
rsm_parse_element(#xmlel{name = <<"after">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = aft, id = UID};
rsm_parse_element(#xmlel{name = <<"index">>,
attrs = []} =
Elem,
RsmIn) ->
- IndexStr = xml:get_tag_cdata(Elem),
+ IndexStr = fxml:get_tag_cdata(Elem),
{Index, _} = str:to_integer(IndexStr),
RsmIn#rsm_in{index = Index};
rsm_parse_element(_, RsmIn) -> RsmIn.
@@ -602,6 +528,26 @@ rsm_encode_count(Count, Arr) ->
children = [{xmlcdata, i2l(Count)}]}
| Arr].
+-spec is_standalone_chat_state(xmlel()) -> boolean().
+
+is_standalone_chat_state(#xmlel{name = <<"message">>} = El) ->
+ ChatStates = [<<"active">>, <<"inactive">>, <<"gone">>, <<"composing">>,
+ <<"paused">>],
+ Stripped =
+ lists:foldl(fun(ChatState, AccEl) ->
+ fxml:remove_subtags(AccEl, ChatState,
+ {<<"xmlns">>, ?NS_CHATSTATES})
+ end, El, ChatStates),
+ case Stripped of
+ #xmlel{children = [#xmlel{name = <<"thread">>}]} ->
+ true;
+ #xmlel{children = []} ->
+ true;
+ _ ->
+ false
+ end;
+is_standalone_chat_state(_El) -> false.
+
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
-> xmlel().
@@ -612,24 +558,15 @@ add_delay_info(El, From, Time) ->
binary()) -> xmlel().
add_delay_info(El, From, Time, Desc) ->
- %% TODO: Remove support for <x/>, XEP-0091 is obsolete.
- El1 = add_delay_info(El, From, Time, Desc, <<"delay">>, ?NS_DELAY),
- El2 = add_delay_info(El1, From, Time, Desc, <<"x">>, ?NS_DELAY91),
- El2.
-
--spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp(),
- binary(), binary(), binary()) -> xmlel().
-
-add_delay_info(El, From, Time, Desc, Name, XMLNS) ->
- case xml:get_subtag_with_xmlns(El, Name, XMLNS) of
+ case fxml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
false ->
%% Add new tag
- DelayTag = create_delay_tag(Time, From, Desc, XMLNS),
- xml:append_subtags(El, [DelayTag]);
+ DelayTag = create_delay_tag(Time, From, Desc),
+ fxml:append_subtags(El, [DelayTag]);
DelayTag ->
%% Update existing tag
NewDelayTag =
- case {xml:get_tag_cdata(DelayTag), Desc} of
+ case {fxml:get_tag_cdata(DelayTag), Desc} of
{<<"">>, <<"">>} ->
DelayTag;
{OldDesc, <<"">>} ->
@@ -645,34 +582,28 @@ add_delay_info(El, From, Time, Desc, Name, XMLNS) ->
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
end
end,
- NewEl = xml:remove_subtags(El, Name, {<<"xmlns">>, XMLNS}),
- xml:append_subtags(NewEl, [NewDelayTag])
+ NewEl = fxml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
+ fxml:append_subtags(NewEl, [NewDelayTag])
end.
--spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary(),
- binary()) -> xmlel() | error.
-
-create_delay_tag(TimeStamp, FromJID, Desc, XMLNS) when is_tuple(FromJID) ->
- From = jlib:jid_to_string(FromJID),
- {Name, Stamp} = case XMLNS of
- ?NS_DELAY ->
- {<<"delay">>, now_to_utc_string(TimeStamp, 3)};
- ?NS_DELAY91 ->
- DateTime = calendar:now_to_universal_time(TimeStamp),
- {<<"x">>, timestamp_to_iso(DateTime)}
- end,
+-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
+ -> xmlel() | error.
+
+create_delay_tag(TimeStamp, FromJID, Desc) when is_tuple(FromJID) ->
+ From = jid:to_string(FromJID),
+ Stamp = now_to_utc_string(TimeStamp, 3),
Children = case Desc of
<<"">> -> [];
_ -> [{xmlcdata, Desc}]
end,
- #xmlel{name = Name,
+ #xmlel{name = <<"delay">>,
attrs =
- [{<<"xmlns">>, XMLNS}, {<<"from">>, From},
+ [{<<"xmlns">>, ?NS_DELAY}, {<<"from">>, From},
{<<"stamp">>, Stamp}],
children = Children};
-create_delay_tag(DateTime, Host, Desc, XMLNS) when is_binary(Host) ->
- FromJID = jlib:make_jid(<<"">>, Host, <<"">>),
- create_delay_tag(DateTime, FromJID, Desc, XMLNS).
+create_delay_tag(DateTime, Host, Desc) when is_binary(Host) ->
+ FromJID = jid:make(<<"">>, Host, <<"">>),
+ create_delay_tag(DateTime, FromJID, Desc).
-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc.
@@ -681,6 +612,9 @@ create_delay_tag(DateTime, Host, Desc, XMLNS) when is_binary(Host) ->
%% Minutes = integer()
-spec timestamp_to_iso(calendar:datetime(), tz()) -> {binary(), binary()}.
+%% This is the XEP-0082 date and time format
+%% http://xmpp.org/extensions/xep-0082.html
+
timestamp_to_iso({{Year, Month, Day},
{Hour, Minute, Second}},
Timezone) ->
@@ -701,13 +635,22 @@ timestamp_to_iso({{Year, Month, Day},
end,
{iolist_to_binary(Timestamp_string), iolist_to_binary(Timezone_string)}.
--spec timestamp_to_iso(calendar:datetime()) -> binary().
-timestamp_to_iso({{Year, Month, Day},
+-spec timestamp_to_legacy(calendar:datetime()) -> binary().
+%% This is the jabber legacy format
+%% http://xmpp.org/extensions/xep-0091.html#time
+timestamp_to_legacy({{Year, Month, Day},
{Hour, Minute, Second}}) ->
iolist_to_binary(io_lib:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
[Year, Month, Day, Hour, Minute, Second])).
+-spec timestamp_to_iso_basic(calendar:datetime()) -> binary().
+%% This is the ISO 8601 basic bormat
+timestamp_to_iso_basic({{Year, Month, Day},
+ {Hour, Minute, Second}}) ->
+ iolist_to_binary(io_lib:format("~4..0B~2..0B~2..0BT~2..0B~2..0B~2..0B",
+ [Year, Month, Day, Hour, Minute, Second])).
+
-spec now_to_utc_string(erlang:timestamp()) -> binary().
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
@@ -863,7 +806,7 @@ decode_base64(S) ->
decode_base64_bin(S, <<>>)
end.
-take_without_spaces(Bin, Count) ->
+take_without_spaces(Bin, Count) ->
take_without_spaces(Bin, Count, <<>>).
take_without_spaces(Bin, 0, Acc) ->
@@ -941,16 +884,16 @@ binary_to_atom(Bin) ->
erlang:binary_to_atom(Bin, utf8).
binary_to_integer(Bin) ->
- list_to_integer(binary_to_list(Bin)).
+ erlang:binary_to_integer(Bin).
binary_to_integer(Bin, Base) ->
- list_to_integer(binary_to_list(Bin), Base).
+ erlang:binary_to_integer(Bin, Base).
integer_to_binary(I) ->
- list_to_binary(integer_to_list(I)).
+ erlang:integer_to_binary(I).
integer_to_binary(I, Base) ->
- list_to_binary(erlang:integer_to_list(I, Base)).
+ erlang:integer_to_binary(I, Base).
tuple_to_binary(T) ->
iolist_to_binary(tuple_to_list(T)).
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index ec41e73f..9947c84a 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -5,7 +5,7 @@
%%% Created : 15 Nov 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,13 +27,15 @@
-author('henoch@dtek.chalmers.se').
+-protocol({xep, 50, '1.2'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3,
process_sm_iq/3, get_local_commands/5,
get_local_identity/5, get_local_features/5,
get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
- ping_item/4, ping_command/4]).
+ ping_item/4, ping_command/4, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -140,7 +142,7 @@ get_sm_commands(Acc, _From,
end,
Nodes = [#xmlel{name = <<"item">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(To)},
+ [{<<"jid">>, jid:to_string(To)},
{<<"node">>, ?NS_COMMANDS},
{<<"name">>,
translate:translate(Lang, <<"Commands">>)}],
@@ -278,3 +280,8 @@ ping_command(_Acc, _From, _To,
true -> {error, ?ERR_BAD_REQUEST}
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(report_commands_node) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) -> [iqdisc, report_commands_node].
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index 023c29dc..f0e56719 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -30,90 +30,48 @@
-include("logger.hrl").
--export([start/2, stop/1,
- %% Node
- compile/1,
- get_cookie/0,
- remove_node/1,
- export2odbc/2,
- %% Accounts
- set_password/3,
- check_password_hash/4,
- delete_old_users/1,
- delete_old_users_vhost/2,
- ban_account/3,
- num_active_users/2,
- %% Sessions
- num_resources/2,
- resource_num/3,
- kick_session/4,
- status_num/2, status_num/1,
- status_list/2, status_list/1,
- connected_users_info/0,
- connected_users_vhost/1,
- set_presence/7,
- user_sessions_info/2,
- %% Vcard
- set_nickname/3,
- get_vcard/3,
- get_vcard/4,
- get_vcard_multi/4,
- set_vcard/4,
- set_vcard/5,
- %% Roster
- add_rosteritem/7,
- delete_rosteritem/4,
- process_rosteritems/5,
- get_roster/2,
- push_roster/3,
- push_roster_all/1,
- push_alltoall/2,
- %% mod_last
- get_last/2,
- %% mod_private
- private_get/4,
- private_set/3,
- %% mod_shared_roster
- srg_create/5,
- srg_delete/2,
- srg_list/1,
- srg_get_info/2,
- srg_get_members/2,
- srg_user_add/4,
- srg_user_del/4,
- %% Stanza
- send_message/5,
- send_stanza_c2s/4,
- privacy_set/3,
- %% Stats
- stats/1, stats/2
- ]).
+-export([start/2, stop/1, compile/1, get_cookie/0,
+ remove_node/1, set_password/3,
+ check_password_hash/4, delete_old_users/1,
+ delete_old_users_vhost/2, ban_account/3,
+ num_active_users/2, num_resources/2, resource_num/3,
+ kick_session/4, status_num/2, status_num/1,
+ status_list/2, status_list/1, connected_users_info/0,
+ connected_users_vhost/1, set_presence/7,
+ user_sessions_info/2, set_nickname/3, get_vcard/3,
+ get_vcard/4, get_vcard_multi/4, set_vcard/4,
+ set_vcard/5, add_rosteritem/7, delete_rosteritem/4,
+ process_rosteritems/5, get_roster/2, push_roster/3,
+ push_roster_all/1, push_alltoall/2, get_last/2,
+ private_get/4, private_set/3, srg_create/5,
+ srg_delete/2, srg_list/1, srg_get_info/2,
+ srg_get_members/2, srg_user_add/4, srg_user_del/4,
+ send_message/5, send_stanza/3, send_stanza_c2s/4, privacy_set/3,
+ stats/1, stats/2, mod_opt_type/1, get_commands_spec/0]).
+
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
-include("mod_roster.hrl").
+-include("ejabberd_sm.hrl").
-include("jlib.hrl").
-%% Copied from ejabberd_sm.erl
--record(session, {sid, usr, us, priority, info}).
-
-
%%%
%%% gen_mod
%%%
start(_Host, _Opts) ->
- ejabberd_commands:register_commands(commands()).
+ ejabberd_commands:register_commands(get_commands_spec()).
stop(_Host) ->
- ejabberd_commands:unregister_commands(commands()).
+ ejabberd_commands:unregister_commands(get_commands_spec()).
%%%
%%% Register commands
%%%
-commands() ->
+get_commands_spec() ->
Vcard1FieldsString = "Some vcard field names in get/set_vcard are:\n"
" FN - Full Name\n"
" NICKNAME - Nickname\n"
@@ -143,91 +101,156 @@ commands() ->
desc = "Recompile and reload Erlang source code file",
module = ?MODULE, function = compile,
args = [{file, string}],
- result = {res, rescode}},
+ args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
+ args_desc = ["Filename of erlang source file to compile"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = get_cookie, tags = [erlang],
desc = "Get the Erlang cookie of this node",
module = ?MODULE, function = get_cookie,
args = [],
- result = {cookie, string}},
+ result = {cookie, string},
+ result_example = "MWTAVMODFELNLSMYXPPD",
+ result_desc = "Erlang cookie used for authentication by ejabberd"},
#ejabberd_commands{name = remove_node, tags = [erlang],
desc = "Remove an ejabberd node from Mnesia clustering config",
module = ?MODULE, function = remove_node,
args = [{node, string}],
- result = {res, rescode}},
- #ejabberd_commands{name = export2odbc, tags = [mnesia], %% Copied to ejabberd 2.1.x after 11
- desc = "Export Mnesia tables to files in directory",
- module = ?MODULE, function = export2odbc,
- args = [{host, string}, {path, string}],
- result = {res, rescode}},
-
+ args_example = ["ejabberd@server2"],
+ args_desc = ["Name of erlang node to remove"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_active_users, tags = [accounts, stats],
desc = "Get number of users active in the last days",
+ policy = admin,
module = ?MODULE, function = num_active_users,
args = [{host, binary}, {days, integer}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, 3],
+ args_desc = ["Name of host to check", "Number of days to calculate sum"],
+ result = {users, integer},
+ result_example = 123,
+ result_desc = "Number of users active on given server in last n days"},
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
desc = "Delete users that didn't log in last days, or that never logged",
module = ?MODULE, function = delete_old_users,
args = [{days, integer}],
- result = {res, restuple}},
+ args_example = [30],
+ args_desc = ["Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
desc = "Delete users that didn't log in last days in vhost, or that never logged",
module = ?MODULE, function = delete_old_users_vhost,
args = [{host, binary}, {days, integer}],
- result = {res, restuple}},
-
+ args_example = [<<"myserver.com">>, 30],
+ args_desc = ["Server name",
+ "Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = check_account, tags = [accounts],
desc = "Check if an account exists or not",
module = ejabberd_auth, function = is_user_exists,
args = [{user, binary}, {host, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name to check", "Server to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct",
module = ejabberd_auth, function = check_password,
args = [{user, binary}, {host, binary}, {password, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
+ args_desc = ["User name to check", "Server to check", "Password to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password_hash, tags = [accounts],
desc = "Check if the password hash is correct",
longdesc = "Allowed hash methods: md5, sha.",
module = ?MODULE, function = check_password_hash,
- args = [{user, binary}, {host, binary}, {passwordhash, binary}, {hashmethod, binary}],
- result = {res, rescode}},
+ args = [{user, binary}, {host, binary}, {passwordhash, string},
+ {hashmethod, string}],
+ args_example = [<<"peter">>, <<"myserver.com">>,
+ <<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>],
+ args_desc = ["User name to check", "Server to check",
+ "Password's hash value", "Name of hash method"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = change_password, tags = [accounts],
desc = "Change the password of an account",
module = ?MODULE, function = set_password,
args = [{user, binary}, {host, binary}, {newpass, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"blank">>],
+ args_desc = ["User name", "Server name",
+ "New password for user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account: kick sessions and set random password",
module = ?MODULE, function = ban_account,
args = [{user, binary}, {host, binary}, {reason, binary}],
- result = {res, rescode}},
-
+ args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
+ args_desc = ["User name to ban", "Server name",
+ "Reason for banning user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_resources, tags = [session],
desc = "Get the number of resources of a user",
module = ?MODULE, function = num_resources,
args = [{user, binary}, {host, binary}],
- result = {resources, integer}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name", "Server name"],
+ result = {resources, integer},
+ result_example = 5,
+ result_desc = "Number of active resources for a user"},
#ejabberd_commands{name = resource_num, tags = [session],
desc = "Resource string of a session number",
module = ?MODULE, function = resource_num,
args = [{user, binary}, {host, binary}, {num, integer}],
- result = {resource, string}},
+ args_example = [<<"peter">>, <<"myserver.com">>, 2],
+ args_desc = ["User name", "Server name", "ID of resource to return"],
+ result = {resource, string},
+ result_example = <<"Psi">>,
+ result_desc = "Name of user resource"},
#ejabberd_commands{name = kick_session, tags = [session],
desc = "Kick a user session",
module = ?MODULE, function = kick_session,
args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"Psi">>,
+ <<"Stuck connection">>],
+ args_desc = ["User name", "Server name", "User's resource",
+ "Reason for closing session"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = status_num_host, tags = [session, stats],
desc = "Number of logged users with this status in host",
+ policy = admin,
module = ?MODULE, function = status_num,
args = [{host, binary}, {status, binary}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, <<"dnd">>],
+ args_desc = ["Server name", "Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_num, tags = [session, stats],
desc = "Number of logged users with this status",
+ policy = admin,
module = ?MODULE, function = status_num,
args = [{status, binary}],
- result = {users, integer}},
+ args_example = [<<"dnd">>],
+ args_desc = ["Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_list_host, tags = [session],
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list,
@@ -275,7 +298,7 @@ commands() ->
tags = [session],
desc = "Get the list of established sessions in a vhost",
module = ?MODULE, function = connected_users_vhost,
- args = [{host, string}],
+ args = [{host, binary}],
result = {connected_users_vhost, {list, {sessions, string}}}},
#ejabberd_commands{name = user_sessions_info,
tags = [session],
@@ -347,7 +370,7 @@ commands() ->
desc = "Set multiple contents in a vCard subfield",
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
module = ?MODULE, function = set_vcard,
- args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {contents, {list, binary}}],
+ args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {contents, {list, {value, binary}}}],
result = {res, rescode}},
#ejabberd_commands{name = add_rosteritem, tags = [roster],
@@ -408,8 +431,9 @@ commands() ->
}}},
#ejabberd_commands{name = get_roster, tags = [roster],
desc = "Get roster of a local user",
+ policy = user,
module = ?MODULE, function = get_roster,
- args = [{user, binary}, {host, binary}],
+ args = [],
result = {contacts, {list, {contact, {tuple, [
{jid, string},
{nick, string},
@@ -420,17 +444,17 @@ commands() ->
#ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user",
module = ?MODULE, function = push_roster,
- args = [{file, string}, {user, string}, {host, string}],
+ args = [{file, binary}, {user, binary}, {host, binary}],
result = {res, rescode}},
#ejabberd_commands{name = push_roster_all, tags = [roster],
desc = "Push template roster from file to all those users",
module = ?MODULE, function = push_roster_all,
- args = [{file, string}],
+ args = [{file, binary}],
result = {res, rescode}},
#ejabberd_commands{name = push_alltoall, tags = [roster],
desc = "Add all the users to all the users of Host in Group",
module = ?MODULE, function = push_alltoall,
- args = [{host, string}, {group, string}],
+ args = [{host, binary}, {group, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_last, tags = [last],
@@ -503,6 +527,13 @@ commands() ->
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
result = {res, rescode}},
+ #ejabberd_commands{name = get_offline_count,
+ tags = [offline],
+ desc = "Get the number of unread offline messages",
+ policy = user,
+ module = mod_offline, function = count_offline_messages,
+ args = [],
+ result = {res, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
module = ?MODULE, function = send_message,
@@ -514,6 +545,11 @@ commands() ->
module = ?MODULE, function = send_stanza_c2s,
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
result = {res, rescode}},
+ #ejabberd_commands{name = send_stanza, tags = [stanza],
+ desc = "Send a stanza; provide From JID and valid To JID",
+ module = ?MODULE, function = send_stanza,
+ args = [{from, binary}, {to, binary}, {stanza, binary}],
+ result = {res, rescode}},
#ejabberd_commands{name = privacy_set, tags = [stanza],
desc = "Send a IQ set privacy stanza for a local account",
module = ?MODULE, function = privacy_set,
@@ -521,12 +557,14 @@ commands() ->
result = {res, rescode}},
#ejabberd_commands{name = stats, tags = [stats],
- desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds",
+ desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds processes",
+ policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}],
result = {stat, integer}},
#ejabberd_commands{name = stats_host, tags = [stats],
desc = "Get statistical value for this host: registeredusers onlineusers",
+ policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}, {host, binary}],
result = {stat, integer}}
@@ -547,25 +585,6 @@ remove_node(Node) ->
mnesia:del_table_copy(schema, list_to_atom(Node)),
ok.
-export2odbc(Host, Directory) ->
- Tables = [
- {export_last, last},
- {export_offline, offline},
- {export_passwd, passwd},
- {export_private_storage, private_storage},
- {export_roster, roster},
- {export_vcard, vcard},
- {export_vcard_search, vcard_search}],
- Export = fun({TableFun, Table}) ->
- Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
- io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
- Res = (catch ejd2odbc:TableFun(Host, Filename)),
- io:format(" Result: ~p~n", [Res])
- end,
- lists:foreach(Export, Tables),
- ok.
-
-
%%%
%%% Accounts
%%%
@@ -581,12 +600,16 @@ set_password(User, Host, Password) ->
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),
- AccountPassHash = case HashMethod of
- "md5" -> get_md5(AccountPass);
- "sha" -> get_sha(AccountPass);
+ AccountPassHash = case {AccountPass, HashMethod} of
+ {A, _} when is_tuple(A) -> scrammed;
+ {_, "md5"} -> get_md5(AccountPass);
+ {_, "sha"} -> get_sha(AccountPass);
_ -> undefined
end,
case AccountPassHash of
+ scrammed ->
+ ?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
+ throw(passwords_scrammed_command_cannot_work);
undefined -> error;
PasswordHash -> ok;
_ -> error
@@ -603,8 +626,7 @@ num_active_users(Host, Days) ->
%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
list_last_activity(Host, Integral, Days) ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp = MegaSecs * 1000000 + Secs,
+ TimeStamp = p1_time_compat:system_time(seconds),
TS = TimeStamp - Days * 86400,
case catch mnesia:dirty_select(
last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
@@ -669,8 +691,7 @@ delete_old_users(Days, Users) ->
SecOlder = Days*24*60*60,
%% Get current time
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp_now = MegaSecs * 1000000 + Secs,
+ TimeStamp_now = p1_time_compat:system_time(seconds),
%% For a user, remove if required and answer true
F = fun({LUser, LServer}) ->
@@ -727,28 +748,14 @@ kick_sessions(User, Server, Reason) ->
fun(Resource) ->
kick_this_session(User, Server, Resource, Reason)
end,
- get_resources(User, Server)).
-
-get_resources(User, Server) ->
- lists:map(
- fun(Session) ->
- element(3, Session#session.usr)
- end,
- get_sessions(User, Server)).
-
-get_sessions(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- Sessions = mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
- true = is_list(Sessions),
- Sessions.
+ ejabberd_sm:get_user_resources(User, Server)).
set_random_password(User, Server, Reason) ->
NewPass = build_random_password(Reason),
set_password_auth(User, Server, NewPass).
build_random_password(Reason) ->
- Date = jlib:timestamp_to_iso(calendar:universal_time()),
+ Date = jlib:timestamp_to_legacy(calendar:universal_time()),
RandomString = randoms:get_string(),
<<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
@@ -783,8 +790,8 @@ kick_session(User, Server, Resource, ReasonText) ->
ok.
kick_this_session(User, Server, Resource, Reason) ->
- ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
- jlib:make_jid(User, Server, Resource),
+ ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
+ jid:make(User, Server, Resource),
{broadcast, {exit, Reason}}).
status_num(Host, Status) ->
@@ -840,7 +847,8 @@ connected_users_info() ->
PI when is_integer(PI) -> PI;
_ -> nil
end,
- {[U, $@, S, $/, R], atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
+ {binary_to_list(<<U/binary, $@, S/binary, $/, R/binary>>),
+ atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
end,
USRIs).
@@ -863,8 +871,8 @@ stringize(String) ->
set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
Pid = ejabberd_sm:get_session_pid(User, Host, Resource),
- USR = jlib:jid_to_string(jlib:make_jid(User, Host, Resource)),
- US = jlib:jid_to_string(jlib:make_jid(User, Host, <<>>)),
+ USR = jid:to_string(jid:make(User, Host, Resource)),
+ US = jid:to_string(jid:make(User, Host, <<>>)),
Message = {route_xmlstreamelement,
{xmlel, <<"presence">>,
[{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
@@ -950,7 +958,7 @@ get_module_resource(Server) ->
get_vcard_content(User, Server, Data) ->
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
- JID = jlib:make_jid(User, Server, get_module_resource(Server)),
+ JID = jid:make(User, Server, get_module_resource(Server)),
IQ = #iq{type = get, xmlns = ?NS_VCARD},
IQr = Module:Function(JID, JID, IQ),
[A1] = IQr#iq.sub_el,
@@ -958,7 +966,7 @@ get_vcard_content(User, Server, Data) ->
[_|_] ->
case get_vcard(Data, A1) of
[false] -> throw(error_no_value_found_in_vcard);
- ElemList -> ?DEBUG("ELS ~p", [ElemList]), [xml:get_tag_cdata(Elem) || Elem <- ElemList]
+ ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList]
end;
[] ->
throw(error_no_vcard_found)
@@ -979,7 +987,7 @@ get_vcard([Data], A1) ->
get_subtag(A1, Data).
get_subtag(Xmlelement, Name) ->
- [xml:get_subtag(Xmlelement, Name)].
+ [fxml:get_subtag(Xmlelement, Name)].
set_vcard_content(User, Server, Data, SomeContent) ->
ContentList = case SomeContent of
@@ -987,7 +995,7 @@ set_vcard_content(User, Server, Data, SomeContent) ->
Bin when is_binary(Bin) -> [SomeContent]
end,
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
- JID = jlib:make_jid(User, Server, get_module_resource(Server)),
+ JID = jid:make(User, Server, get_module_resource(Server)),
IQ = #iq{type = get, xmlns = ?NS_VCARD},
IQr = Module:Function(JID, JID, IQ),
@@ -1009,7 +1017,7 @@ set_vcard_content(User, Server, Data, SomeContent) ->
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
{Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
- true -> {xml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
+ true -> {fxml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
false -> {Taken, [OldEl | NewEls]}
end,
take_vcard_tel(TelType, OldEls, NewEls2, Taken2);
@@ -1068,7 +1076,7 @@ subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) ->
mod_roster:set_items(
LU, LS,
{xmlel, <<"query">>,
- [{<<"xmlns">>, <<"jabber:iq:roster">>}],
+ [{<<"xmlns">>, ?NS_ROSTER}],
[ItemEl]}).
delete_rosteritem(LocalUser, LocalServer, User, Server) ->
@@ -1085,7 +1093,7 @@ unsubscribe(LU, LS, User, Server) ->
mod_roster:set_items(
LU, LS,
{xmlel, <<"query">>,
- [{<<"xmlns">>, <<"jabber:iq:roster">>}],
+ [{<<"xmlns">>, ?NS_ROSTER}],
[ItemEl]}).
%% -----------------------------
@@ -1101,7 +1109,7 @@ get_roster(User, Server) ->
make_roster_xmlrpc(Roster) ->
lists:foldl(
fun(Item, Res) ->
- JIDS = jlib:jid_to_string(Item#roster.jid),
+ JIDS = jid:to_string(Item#roster.jid),
Nick = Item#roster.name,
Subs = atom_to_list(Item#roster.subscription),
Ask = atom_to_list(Item#roster.ask),
@@ -1167,23 +1175,23 @@ push_roster_item(LU, LS, U, S, Action) ->
end, ejabberd_sm:get_user_resources(LU, LS)).
push_roster_item(LU, LS, R, U, S, Action) ->
- LJID = jlib:make_jid(LU, LS, R),
+ LJID = jid:make(LU, LS, R),
BroadcastEl = build_broadcast(U, S, Action),
ejabberd_sm:route(LJID, LJID, BroadcastEl),
Item = build_roster_item(U, S, Action),
ResIQ = build_iq_roster_push(Item),
- ejabberd_router:route(LJID, LJID, ResIQ).
+ ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ).
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
{xmlel, <<"item">>,
- [{<<"jid">>, jlib:jid_to_string(jlib:make_jid(U, S, <<>>))},
+ [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))},
{<<"name">>, Nick},
{<<"subscription">>, Subs}],
[{xmlel, <<"group">>, [], [{xmlcdata, Group}]}]
};
build_roster_item(U, S, remove) ->
{xmlel, <<"item">>,
- [{<<"jid">>, jlib:jid_to_string(jlib:make_jid(U, S, <<>>))},
+ [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))},
{<<"subscription">>, <<"remove">>}],
[]
}.
@@ -1242,20 +1250,20 @@ get_last(User, Server) ->
%% <aa xmlns='bb'>Cluth</aa>
private_get(Username, Host, Element, Ns) ->
- From = jlib:make_jid(Username, Host, <<>>),
- To = jlib:make_jid(Username, Host, <<>>),
+ From = jid:make(Username, Host, <<>>),
+ To = jid:make(Username, Host, <<>>),
IQ = {iq, <<>>, get, ?NS_PRIVATE, <<>>,
{xmlel, <<"query">>,
[{<<"xmlns">>,?NS_PRIVATE}],
[{xmlel, Element, [{<<"xmlns">>, Ns}], []}]}},
ResIq = mod_private:process_sm_iq(From, To, IQ),
[{xmlel, <<"query">>,
- [{<<"xmlns">>, <<"jabber:iq:private">>}],
+ [{<<"xmlns">>, ?NS_PRIVATE}],
[SubEl]}] = ResIq#iq.sub_el,
- binary_to_list(xml:element_to_binary(SubEl)).
+ binary_to_list(fxml:element_to_binary(SubEl)).
private_set(Username, Host, ElementString) ->
- case xml_stream:parse_element(ElementString) of
+ case fxml_stream:parse_element(ElementString) of
{error, Error} ->
io:format("Error found parsing the element:~n ~p~nError: ~p~n",
[ElementString, Error]),
@@ -1265,8 +1273,8 @@ private_set(Username, Host, ElementString) ->
end.
private_set2(Username, Host, Xml) ->
- From = jlib:make_jid(Username, Host, <<>>),
- To = jlib:make_jid(Username, Host, <<>>),
+ From = jid:make(Username, Host, <<>>),
+ To = jid:make(Username, Host, <<>>),
IQ = {iq, <<>>, set, ?NS_PRIVATE, <<>>,
{xmlel, <<"query">>,
[{<<"xmlns">>, ?NS_PRIVATE}],
@@ -1310,7 +1318,7 @@ btl(B) -> binary_to_list(B).
srg_get_members(Group, Host) ->
Members = mod_shared_roster:get_group_explicit_users(Host,Group),
- [jlib:jid_to_string(jlib:make_jid(MUser, MServer, <<>>))
+ [jid:to_string(jid:make(MUser, MServer, <<>>))
|| {MUser, MServer} <- Members].
srg_user_add(User, Host, Group, GroupHost) ->
@@ -1341,8 +1349,8 @@ send_message(Type, From, To, Subject, Body) ->
%% 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 = jlib:string_to_jid(FromJIDString),
- ToJID = jlib:string_to_jid(ToJIDString),
+ FromJID = jid:from_string(FromJIDString),
+ ToJID = jid:from_string(ToJIDString),
ToUser = ToJID#jid.user,
ToServer = ToJID#jid.server,
case ToJID#jid.resource of
@@ -1366,7 +1374,7 @@ send_packet_all_resources(FromJID, ToUser, ToServer, Packet) ->
end.
send_packet_all_resources(FromJID, ToU, ToS, ToR, Packet) ->
- ToJID = jlib:make_jid(ToU, ToS, ToR),
+ ToJID = jid:make(ToU, ToS, ToR),
ejabberd_router:route(FromJID, ToJID, Packet).
build_packet(Type, Subject, Body) ->
@@ -1378,15 +1386,33 @@ build_packet(Type, Subject, Body) ->
[{xmlel, <<"body">>, [], [{xmlcdata, Body}]} | Tail]
}.
+send_stanza(FromString, ToString, Stanza) ->
+ case fxml_stream:parse_element(Stanza) of
+ {error, Error} ->
+ {error, Error};
+ XmlEl ->
+ #xmlel{attrs = Attrs} = XmlEl,
+ From = jid:from_string(proplists:get_value(<<"from">>, Attrs, FromString)),
+ To = jid:from_string(proplists:get_value(<<"to">>, Attrs, ToString)),
+ ejabberd_router:route(From, To, XmlEl)
+ end.
+
send_stanza_c2s(Username, Host, Resource, Stanza) ->
- C2sPid = ejabberd_sm:get_session_pid(Username, Host, Resource),
- XmlEl = xml_stream:parse_element(Stanza),
- p1_fsm:send_event(C2sPid, {xmlstreamelement, XmlEl}).
+ case {fxml_stream:parse_element(Stanza),
+ ejabberd_sm:get_session_pid(Username, Host, Resource)}
+ of
+ {{error, Error}, _} ->
+ {error, Error};
+ {_, none} ->
+ {error, no_session};
+ {XmlEl, C2sPid} ->
+ p1_fsm:send_event(C2sPid, {xmlstreamelement, XmlEl})
+ end.
privacy_set(Username, Host, QueryS) ->
- From = jlib:make_jid(Username, Host, <<"">>),
- To = jlib:make_jid(<<"">>, Host, <<"">>),
- QueryEl = xml_stream:parse_element(QueryS),
+ From = jid:make(Username, Host, <<"">>),
+ To = jid:make(<<"">>, Host, <<"">>),
+ QueryEl = fxml_stream:parse_element(QueryS),
StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]},
IQ = jlib:iq_query_info(StanzaEl),
ejabberd_hooks:run_fold(
@@ -1404,6 +1430,7 @@ privacy_set(Username, Host, QueryS) ->
stats(Name) ->
case Name of
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
+ <<"processes">> -> length(erlang:processes());
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:get_vh_registered_users_number(Host) + Sum end, 0, ?MYHOSTS);
<<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
@@ -1526,7 +1553,7 @@ decide_rip_jid({UName, UServer, _UResource}, Match_list) ->
decide_rip_jid({UName, UServer}, Match_list) ->
lists:any(
fun(Match_string) ->
- MJID = jlib:string_to_jid(list_to_binary(Match_string)),
+ MJID = jid:from_string(list_to_binary(Match_string)),
MName = MJID#jid.luser,
MServer = MJID#jid.lserver,
Is_server = is_glob_match(UServer, MServer),
@@ -1559,3 +1586,6 @@ is_glob_match(String, <<"!", Glob/binary>>) ->
not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob));
is_glob_match(String, Glob) ->
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
+
+mod_opt_type(module_resource) -> fun (A) -> A end;
+mod_opt_type(_) -> [module_resource].
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 542417ff..d30cf57f 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Aug 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -31,20 +31,11 @@
-behaviour(gen_mod).
--export([start/2,
- init/0,
- stop/1,
- export/1,
- import/1,
- import/3,
- announce/3,
- send_motd/1,
- disco_identity/5,
- disco_features/5,
- disco_items/5,
- send_announcement_to_all/3,
- announce_commands/4,
- announce_items/4]).
+-export([start/2, init/0, stop/1, export/1, import/1,
+ import/3, announce/3, send_motd/1, disco_identity/5,
+ disco_features/5, disco_items/5,
+ send_announcement_to_all/3, announce_commands/4,
+ announce_items/4, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -700,10 +691,10 @@ announce_all(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
- Local = jlib:make_jid(<<>>, To#jid.server, <<>>),
+ Local = jid:make(<<>>, To#jid.server, <<>>),
lists:foreach(
fun({User, Server}) ->
- Dest = jlib:make_jid(User, Server, <<>>),
+ Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:get_vh_registered_users(Host))
end.
@@ -715,10 +706,10 @@ announce_all_hosts_all(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
- Local = jlib:make_jid(<<>>, To#jid.server, <<>>),
+ Local = jid:make(<<>>, To#jid.server, <<>>),
lists:foreach(
fun({User, Server}) ->
- Dest = jlib:make_jid(User, Server, <<>>),
+ Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:dirty_get_registered_users())
end.
@@ -749,10 +740,10 @@ announce_all_hosts_online(From, To, Packet) ->
end.
announce_online1(Sessions, Server, Packet) ->
- Local = jlib:make_jid(<<>>, Server, <<>>),
+ Local = jid:make(<<>>, Server, <<>>),
lists:foreach(
fun({U, S, R}) ->
- Dest = jlib:make_jid(U, S, R),
+ Dest = jid:make(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
end, Sessions).
@@ -779,7 +770,7 @@ announce_all_hosts_motd(From, To, Packet) ->
end.
announce_motd(Host, Packet) ->
- LServer = jlib:nameprep(Host),
+ LServer = jid:nameprep(Host),
announce_motd_update(LServer, Packet),
Sessions = ejabberd_sm:get_vh_session_list(LServer),
announce_online1(Sessions, LServer, Packet),
@@ -854,7 +845,7 @@ announce_motd_update(LServer, Packet) ->
packet = Packet},
motd_schema())};
odbc ->
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
+ XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)),
F = fun() ->
odbc_queries:update_t(
<<"motd">>,
@@ -931,7 +922,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
[#motd_users{}] ->
ok;
_ ->
- Local = jlib:make_jid(<<>>, LServer, <<>>),
+ Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{us = US})
@@ -949,7 +940,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
{ok, #motd_users{}} ->
ok;
_ ->
- Local = jlib:make_jid(<<>>, LServer, <<>>),
+ Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
{atomic, ejabberd_riak:put(
#motd_users{us = US}, motd_users_schema(),
@@ -962,7 +953,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>>
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
{selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
+ case fxml_stream:parse_element(XML) of
{error, _} ->
ok;
Packet ->
@@ -972,7 +963,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>>
[<<"select username from motd "
"where username='">>, Username, <<"';">>]) of
{selected, [<<"username">>], []} ->
- Local = jlib:make_jid(<<"">>, LServer, <<"">>),
+ Local = jid:make(<<"">>, LServer, <<"">>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
odbc_queries:update_t(
@@ -995,8 +986,8 @@ send_motd(_, odbc) ->
get_stored_motd(LServer) ->
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
{ok, Packet} ->
- {xml:get_subtag_cdata(Packet, <<"subject">>),
- xml:get_subtag_cdata(Packet, <<"body">>)};
+ {fxml:get_subtag_cdata(Packet, <<"subject">>),
+ fxml:get_subtag_cdata(Packet, <<"body">>)};
error ->
{<<>>, <<>>}
end.
@@ -1019,7 +1010,7 @@ get_stored_motd_packet(LServer, odbc) ->
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
{selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
+ case fxml_stream:parse_element(XML) of
{error, _} ->
error;
Packet ->
@@ -1047,10 +1038,10 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
children = SubjectEls ++ BodyEls
},
Sessions = ejabberd_sm:dirty_get_sessions_list(),
- Local = jlib:make_jid(<<>>, Host, <<>>),
+ Local = jid:make(<<>>, Host, <<>>),
lists:foreach(
fun({U, S, R}) ->
- Dest = jlib:make_jid(U, S, R),
+ Dest = jid:make(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
end, Sessions).
@@ -1076,7 +1067,7 @@ update_motd_table() ->
fun(#motd{server = S}) -> S end,
fun(#motd{server = S, packet = P} = R) ->
NewS = iolist_to_binary(S),
- NewP = xml:to_xmlel(P),
+ NewP = fxml:to_xmlel(P),
R#motd{server = NewS, packet = NewP}
end);
_ ->
@@ -1114,7 +1105,7 @@ export(_Server) ->
when LServer == Host ->
[[<<"delete from motd where username='';">>],
[<<"insert into motd(username, xml) values ('', '">>,
- ejabberd_odbc:escape(xml:element_to_binary(El)),
+ ejabberd_odbc:escape(fxml:element_to_binary(El)),
<<"');">>]];
(_Host, _R) ->
[]
@@ -1133,7 +1124,7 @@ export(_Server) ->
import(LServer) ->
[{<<"select xml from motd where username='';">>,
fun([XML]) ->
- El = xml_stream:parse_element(XML),
+ El = fxml_stream:parse_element(XML),
#motd{server = LServer, packet = El}
end},
{<<"select username from motd where xml='';">>,
@@ -1152,3 +1143,8 @@ import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
[{'2i', [{<<"server">>, S}]}]);
import(_, _, _) ->
pass.
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(_) -> [access, db_type].
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index 17278681..815884ff 100644
--- a/src/mod_blocking.erl
+++ b/src/mod_blocking.erl
@@ -5,7 +5,7 @@
%%% Created : 24 Aug 2008 by Stephan Maka <stephan@spaceboyz.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -17,10 +17,9 @@
%%% 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., 59 Temple Place, Suite 330, Boston, MA
-%%% 02111-1307 USA
+%%% 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.
%%%
%%%----------------------------------------------------------------------
@@ -28,8 +27,10 @@
-behaviour(gen_mod).
+-protocol({xep, 191, '1.2'}).
+
-export([start/2, stop/1, process_iq/3,
- process_iq_set/4, process_iq_get/5]).
+ process_iq_set/4, process_iq_get/5, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -75,7 +76,7 @@ process_iq_set(_, From, _To,
sub_el =
#xmlel{name = SubElName, children = SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From,
- Res = case {SubElName, xml:remove_cdata(SubEls)} of
+ Res = case {SubElName, fxml:remove_cdata(SubEls)} of
{<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
{<<"block">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
@@ -115,9 +116,9 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
attrs = Attrs}
| Els],
JIDs) ->
- case xml:get_attr(<<"jid">>, Attrs) of
+ case fxml:get_attr(<<"jid">>, Attrs) of
{value, JID1} ->
- JID = jlib:jid_tolower(jlib:string_to_jid(JID1)),
+ JID = jid:tolower(jid:from_string(JID1)),
parse_blocklist_items(Els, [JID | JIDs]);
false -> parse_blocklist_items(Els, JIDs)
end;
@@ -222,23 +223,18 @@ process_blocklist_block(LUser, LServer, Filter, odbc) ->
Default = case
mod_privacy:sql_get_default_privacy_list_t(LUser)
of
- {selected, [<<"name">>], []} ->
+ {selected, []} ->
Name = <<"Blocked contacts">>,
mod_privacy:sql_add_privacy_list(LUser, Name),
mod_privacy:sql_set_default_privacy_list(LUser,
Name),
Name;
- {selected, [<<"name">>], [[Name]]} -> Name
+ {selected, [{Name}]} -> Name
end,
- {selected, [<<"id">>], [[ID]]} =
+ {selected, [{ID}]} =
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
+ case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
_ -> List = []
end,
@@ -344,17 +340,12 @@ unblock_by_filter(LUser, LServer, Filter, odbc) ->
F = fun () ->
case mod_privacy:sql_get_default_privacy_list_t(LUser)
of
- {selected, [<<"name">>], []} -> ok;
- {selected, [<<"name">>], [[Default]]} ->
- {selected, [<<"id">>], [[ID]]} =
+ {selected, []} -> ok;
+ {selected, [{Default}]} ->
+ {selected, [{ID}]} =
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
+ case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy:raw_to_item/1,
RItems),
NewList = Filter(List),
@@ -374,13 +365,13 @@ make_userlist(Name, List) ->
#userlist{name = Name, list = List, needdb = NeedDb}.
broadcast_list_update(LUser, LServer, Name, UserList) ->
- ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>),
- jlib:make_jid(LUser, LServer, <<"">>),
+ jid:make(LUser, LServer, <<"">>),
{broadcast, {privacy_list, UserList, Name}}).
broadcast_blocklist_event(LUser, LServer, Event) ->
- JID = jlib:make_jid(LUser, LServer, <<"">>),
+ JID = jid:make(LUser, LServer, <<"">>),
ejabberd_sm:route(JID, JID,
{broadcast, {blocking, Event}}).
@@ -396,7 +387,7 @@ process_blocklist_get(LUser, LServer) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
- jlib:jid_to_string(JID)}],
+ jid:to_string(JID)}],
children = []}
end,
JIDs),
@@ -434,18 +425,17 @@ process_blocklist_get(LUser, LServer, odbc) ->
case catch
mod_privacy:sql_get_default_privacy_list(LUser, LServer)
of
- {selected, [<<"name">>], []} -> [];
- {selected, [<<"name">>], [[Default]]} ->
+ {selected, []} -> [];
+ {selected, [{Default}]} ->
case catch mod_privacy:sql_get_privacy_list_data(LUser,
LServer, Default)
of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
+ {selected, RItems} ->
lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
{'EXIT', _} -> error
end;
{'EXIT', _} -> error
end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 36c8c0ee..0646d381 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -5,7 +5,7 @@
%%% Created : 7 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -29,6 +29,8 @@
-author('henoch@dtek.chalmers.se').
+-protocol({xep, 115, '1.5'}).
+
-behaviour(gen_server).
-behaviour(gen_mod).
@@ -45,10 +47,9 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-%% hook handlers
--export([user_send_packet/3, user_receive_packet/4,
+-export([user_send_packet/4, user_receive_packet/5,
c2s_presence_in/2, c2s_filter_packet/6,
- c2s_broadcast_recipients/6]).
+ c2s_broadcast_recipients/6, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -119,12 +120,12 @@ read_caps(Els) -> read_caps(Els, nothing).
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CAPS ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
- Version = xml:get_attr_s(<<"ver">>, Attrs),
- Hash = xml:get_attr_s(<<"hash">>, Attrs),
- Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ Version = fxml:get_attr_s(<<"ver">>, Attrs),
+ Hash = fxml:get_attr_s(<<"hash">>, Attrs),
+ Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs),
<<" ">>),
read_caps(Tail,
#caps{node = Node, hash = Hash, version = Version,
@@ -134,7 +135,7 @@ read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER -> nothing;
_ -> read_caps(Tail, Result)
end;
@@ -142,12 +143,13 @@ read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
read_caps([], Result) -> Result.
-user_send_packet(#jid{luser = User, lserver = Server} = From,
+user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els} = Pkt,
+ _C2SState,
+ #jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server,
- lresource = <<"">>},
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = Els} = Pkt) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ lresource = <<"">>}) ->
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of
nothing -> ok;
@@ -157,14 +159,15 @@ user_send_packet(#jid{luser = User, lserver = Server} = From,
true -> ok
end,
Pkt;
-user_send_packet( _From, _To, Pkt) ->
+user_send_packet(Pkt, _C2SState, _From, _To) ->
Pkt.
-user_receive_packet(#jid{lserver = Server},
- From, _To,
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = Els} = Pkt) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els} = Pkt,
+ _C2SState,
+ #jid{lserver = Server},
+ From, _To) ->
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and
((Type == <<"">>) or (Type == <<"available">>)) ->
@@ -176,7 +179,7 @@ user_receive_packet(#jid{lserver = Server},
true -> ok
end,
Pkt;
-user_receive_packet( _JID, _From, _To, Pkt) ->
+user_receive_packet(Pkt, _C2SState, _JID, _From, _To) ->
Pkt.
-spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
@@ -224,7 +227,7 @@ disco_info(Acc, Host, Module, Node, Lang) ->
c2s_presence_in(C2SState,
{From, To, {_, _, Attrs, Els}}) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
Subscription = ejabberd_c2s:get_subscription(From,
C2SState),
Insert = ((Type == <<"">>) or (Type == <<"available">>))
@@ -232,7 +235,7 @@ c2s_presence_in(C2SState,
Delete = (Type == <<"unavailable">>) or
(Type == <<"error">>),
if Insert or Delete ->
- LFrom = jlib:jid_tolower(From),
+ LFrom = jid:tolower(From),
Rs = case ejabberd_c2s:get_aux_field(caps_resources,
C2SState)
of
@@ -240,27 +243,24 @@ c2s_presence_in(C2SState,
error -> gb_trees:empty()
end,
Caps = read_caps(Els),
- {CapsUpdated, NewRs} = case Caps of
- nothing when Insert == true -> {false, Rs};
- _ when Insert == true ->
- case gb_trees:lookup(LFrom, Rs) of
- {value, Caps} -> {false, Rs};
- none ->
- {true,
- gb_trees:insert(LFrom, Caps,
- Rs)};
- _ ->
- {true,
- gb_trees:update(LFrom, Caps, Rs)}
- end;
- _ -> {false, gb_trees:delete_any(LFrom, Rs)}
- end,
- if CapsUpdated ->
- ejabberd_hooks:run(caps_update, To#jid.lserver,
- [From, To,
- get_features(To#jid.lserver, Caps)]);
- true -> ok
- end,
+ NewRs = case Caps of
+ nothing when Insert == true -> Rs;
+ _ when Insert == true ->
+ case gb_trees:lookup(LFrom, Rs) of
+ {value, Caps} -> Rs;
+ none ->
+ ejabberd_hooks:run(caps_add, To#jid.lserver,
+ [From, To,
+ get_features(To#jid.lserver, Caps)]),
+ gb_trees:insert(LFrom, Caps, Rs);
+ _ ->
+ ejabberd_hooks:run(caps_update, To#jid.lserver,
+ [From, To,
+ get_features(To#jid.lserver, Caps)]),
+ gb_trees:update(LFrom, Caps, Rs)
+ end;
+ _ -> gb_trees:delete_any(LFrom, Rs)
+ end,
ejabberd_c2s:set_aux_field(caps_resources, NewRs,
C2SState);
true -> C2SState
@@ -269,7 +269,7 @@ c2s_presence_in(C2SState,
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
{ok, Rs} ->
- LTo = jlib:jid_tolower(To),
+ LTo = jid:tolower(To),
case gb_trees:lookup(LTo, Rs) of
{value, Caps} ->
Drop = not lists:member(Feature, get_features(Host, Caps)),
@@ -417,7 +417,7 @@ feature_request(Host, From, Caps,
feature_response(IQReply, Host, From, Caps,
SubNodes)
end,
- ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host,
+ ejabberd_local:route_iq(jid:make(<<"">>, Host,
<<"">>),
From, IQ, F);
true -> feature_request(Host, From, Caps, Tail)
@@ -434,7 +434,7 @@ feature_response(#iq{type = result,
Features = lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = FAttrs}) ->
- [xml:get_attr_s(<<"var">>, FAttrs)];
+ [fxml:get_attr_s(<<"var">>, FAttrs)];
(_) -> []
end,
Els),
@@ -449,7 +449,7 @@ feature_response(_IQResult, Host, From, Caps,
feature_request(Host, From, Caps, SubNodes).
caps_read_fun(Host, Node) ->
- LServer = jlib:nameprep(Host),
+ LServer = jid:nameprep(Host),
DBType = gen_mod:db_type(LServer, ?MODULE),
caps_read_fun(LServer, Node, DBType).
@@ -488,7 +488,7 @@ caps_read_fun(LServer, {Node, SubNode}, odbc) ->
end.
caps_write_fun(Host, Node, Features) ->
- LServer = jlib:nameprep(Host),
+ LServer = jid:nameprep(Host),
DBType = gen_mod:db_type(LServer, ?MODULE),
caps_write_fun(LServer, Node, Features, DBType).
@@ -511,7 +511,7 @@ caps_write_fun(LServer, NodePair, Features, odbc) ->
end.
make_my_disco_hash(Host) ->
- JID = jlib:make_jid(<<"">>, Host, <<"">>),
+ JID = jid:make(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features,
Host, empty, [JID, JID, <<"">>, <<"">>]),
ejabberd_hooks:run_fold(disco_local_identity, Host, [],
@@ -567,7 +567,7 @@ concat_features(Els) ->
lists:usort(lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"var">>, Attrs), $<]];
+ [[fxml:get_attr_s(<<"var">>, Attrs), $<]];
(_) -> []
end,
Els)).
@@ -576,11 +576,11 @@ concat_identities(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name =
<<"identity">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"category">>, Attrs),
- $/, xml:get_attr_s(<<"type">>, Attrs),
+ [[fxml:get_attr_s(<<"category">>, Attrs),
+ $/, fxml:get_attr_s(<<"type">>, Attrs),
$/,
- xml:get_attr_s(<<"xml:lang">>, Attrs),
- $/, xml:get_attr_s(<<"name">>, Attrs),
+ fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ $/, fxml:get_attr_s(<<"name">>, Attrs),
$<]];
(_) -> []
end,
@@ -589,8 +589,8 @@ concat_identities(Els) ->
concat_info(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
attrs = Attrs, children = Fields}) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)}
of
{?NS_XDATA, <<"result">>} ->
[concat_xdata_fields(Fields)];
@@ -606,10 +606,10 @@ concat_xdata_fields(Fields) ->
attrs = Attrs, children = Els} =
El,
[FormType, VarFields] = Acc) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<"">> -> Acc;
<<"FORM_TYPE">> ->
- [xml:get_subtag_cdata(El,
+ [fxml:get_subtag_cdata(El,
<<"value">>),
VarFields];
Var ->
@@ -622,7 +622,7 @@ concat_xdata_fields(Fields) ->
children
=
VEls}) ->
- [[xml:get_cdata(VEls),
+ [[fxml:get_cdata(VEls),
$<]];
(_) ->
[]
@@ -648,7 +648,7 @@ gb_trees_fold_iter(F, Acc, Iter) ->
end.
now_ts() ->
- {MegaSecs, Secs, _} = now(), MegaSecs * 1000000 + Secs.
+ p1_time_compat:system_time(seconds).
is_valid_node(Node) ->
case str:tokens(Node, <<"#">>) of
@@ -752,3 +752,11 @@ import_next(LServer, DBType, NodePair) ->
ok
end,
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
+
+mod_opt_type(cache_life_time) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(_) ->
+ [cache_life_time, cache_size, db_type].
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index 24c09bff..81cf78a9 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -7,7 +7,7 @@
%%% {mod_carboncopy, []}
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,7 +25,9 @@
%%%
%%%----------------------------------------------------------------------
-module (mod_carboncopy).
+
-author ('ecestari@process-one.net').
+-protocol({xep, 280, '0.8'}).
-behavior(gen_mod).
@@ -33,13 +35,9 @@
-export([start/2,
stop/1]).
-%% Hooks:
--export([user_send_packet/3,
- user_receive_packet/4,
- iq_handler2/3,
- iq_handler1/3,
- remove_connection/4,
- is_carbon_copy/1]).
+-export([user_send_packet/4, user_receive_packet/5,
+ iq_handler2/3, iq_handler1/3, remove_connection/4,
+ is_carbon_copy/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -57,9 +55,9 @@ is_carbon_copy(Packet) ->
is_carbon_copy(Packet, <<"received">>).
is_carbon_copy(Packet, Direction) ->
- case xml:get_subtag(Packet, Direction) of
+ case fxml:get_subtag(Packet, Direction) of
#xmlel{name = Direction, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CARBONS_2 -> true;
?NS_CARBONS_1 -> true;
_ -> false
@@ -106,7 +104,7 @@ iq_handler1(From, To, IQ) ->
iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
- {U, S, R} = jlib:jid_tolower(From),
+ {U, S, R} = jid:tolower(From),
Result = case Operation of
<<"enable">>->
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
@@ -115,7 +113,7 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
disable(S, U, R)
end,
- case Result of
+ case Result of
ok ->
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
@@ -127,43 +125,43 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
iq_handler(_From, _To, IQ, _CC)->
IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}.
-user_send_packet(From, To, Packet) ->
+user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent).
-user_receive_packet(JID, _From, To, Packet) ->
+user_receive_packet(Packet, _C2SState, JID, _From, To) ->
check_and_forward(JID, To, Packet, received).
-
-% verifier si le trafic est local
-% Modified from original version:
+
+% Modified from original version:
% - registered to the user_send_packet hook, to be called only once even for multicast
% - do not support "private" message mode, and do not modify the original packet in any way
% - we also replicate "read" notifications
check_and_forward(JID, To, Packet, Direction)->
- case is_chat_or_normal_message(Packet) andalso
- xml:get_subtag(Packet, <<"private">>) == false andalso
- xml:get_subtag(Packet, <<"no-copy">>) == false of
+ case is_chat_message(Packet) andalso
+ fxml:get_subtag(Packet, <<"private">>) == false andalso
+ fxml:get_subtag(Packet, <<"no-copy">>) == false of
true ->
case is_carbon_copy(Packet) of
false ->
- send_copies(JID, To, Packet, Direction);
+ send_copies(JID, To, Packet, Direction),
+ Packet;
true ->
- %% stop the hook chain, we don't want mod_logdb to register
- %% this message (duplicate)
- stop
+ %% stop the hook chain, we don't want logging modules to duplicates
+ %% this message
+ {stop, Packet}
end;
_ ->
- ok
+ Packet
end.
remove_connection(User, Server, Resource, _Status)->
disable(Server, User, Resource),
ok.
-
+
%%% Internal
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
send_copies(JID, To, Packet, Direction)->
- {U, S, R} = jlib:jid_tolower(JID),
+ {U, S, R} = jid:tolower(JID),
PrioRes = ejabberd_sm:get_user_present_resources(U, S),
{_, AvailRs} = lists:unzip(PrioRes),
{MaxPrio, MaxRes} = case catch lists:max(PrioRes) of
@@ -182,7 +180,7 @@ send_copies(JID, To, Packet, Direction)->
TargetJIDs = case {IsBareTo, R} of
{true, MaxRes} ->
OrigTo = fun(Res) -> lists:member({MaxPrio, Res}, PrioRes) end,
- [ {jlib:make_jid({U, S, CCRes}), CC_Version}
+ [ {jid:make({U, S, CCRes}), CC_Version}
|| {CCRes, CC_Version} <- list(U, S),
lists:member(CCRes, AvailRs), not OrigTo(CCRes) ];
{true, _} ->
@@ -193,16 +191,16 @@ send_copies(JID, To, Packet, Direction)->
%% MaxRes) in order to avoid duplicates.
[];
{false, _} ->
- [ {jlib:make_jid({U, S, CCRes}), CC_Version}
+ [ {jid:make({U, S, CCRes}), CC_Version}
|| {CCRes, CC_Version} <- list(U, S),
lists:member(CCRes, AvailRs), CCRes /= R ]
- %TargetJIDs = lists:delete(JID, [ jlib:make_jid({U, S, CCRes}) || CCRes <- list(U, S) ]),
+ %TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]),
end,
lists:map(fun({Dest,Version}) ->
- {_, _, Resource} = jlib:jid_tolower(Dest),
+ {_, _, Resource} = jid:tolower(Dest),
?DEBUG("Sending: ~p =/= ~p", [R, Resource]),
- Sender = jlib:make_jid({U, S, <<>>}),
+ Sender = jid:make({U, S, <<>>}),
%{xmlelement, N, A, C} = Packet,
New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version),
ejabberd_router:route(Sender, Dest, New)
@@ -210,31 +208,31 @@ send_copies(JID, To, Packet, Direction)->
ok.
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
- #xmlel{name = <<"message">>,
+ #xmlel{name = <<"message">>,
attrs = [{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, message_type(Packet)},
- {<<"from">>, jlib:jid_to_string(Sender)},
- {<<"to">>, jlib:jid_to_string(Dest)}],
- children = [
- #xmlel{name = list_to_binary(atom_to_list(Direction)),
+ {<<"from">>, jid:to_string(Sender)},
+ {<<"to">>, jid:to_string(Dest)}],
+ children = [
+ #xmlel{name = list_to_binary(atom_to_list(Direction)),
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}],
children = [
- #xmlel{name = <<"forwarded">>,
+ #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [
complete_packet(JID, Packet, Direction)]}
]}
]};
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
- #xmlel{name = <<"message">>,
+ #xmlel{name = <<"message">>,
attrs = [{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, message_type(Packet)},
- {<<"from">>, jlib:jid_to_string(Sender)},
- {<<"to">>, jlib:jid_to_string(Dest)}],
- children = [
- #xmlel{name = list_to_binary(atom_to_list(Direction)),
+ {<<"from">>, jid:to_string(Sender)},
+ {<<"to">>, jid:to_string(Dest)}],
+ children = [
+ #xmlel{name = list_to_binary(atom_to_list(Direction)),
attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]},
- #xmlel{name = <<"forwarded">>,
+ #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [complete_packet(JID, Packet, Direction)]}
]}.
@@ -261,7 +259,7 @@ complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet,
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
case proplists:get_value(<<"from">>, Attrs) of
undefined ->
- Packet#xmlel{attrs = [{<<"from">>, jlib:jid_to_string(From)}|Attrs]};
+ Packet#xmlel{attrs = [{<<"from">>, jid:to_string(From)}|Attrs]};
_ ->
Packet#xmlel{attrs = Attrs}
end;
@@ -270,20 +268,26 @@ complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, r
Packet#xmlel{attrs = Attrs}.
message_type(#xmlel{attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} -> Type;
false -> <<"normal">>
end.
-is_chat_or_normal_message(#xmlel{name = <<"message">>} = Packet) ->
+is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
case message_type(Packet) of
<<"chat">> -> true;
- <<"normal">> -> true;
+ <<"normal">> -> has_non_empty_body(Packet);
_ -> false
end;
-is_chat_or_normal_message(_Packet) -> false.
+is_chat_message(_Packet) -> false.
+
+has_non_empty_body(Packet) ->
+ fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
%% list {resource, cc_version} with carbons enabled for given user and host
-list(User, Server)->
+list(User, Server) ->
mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index fd72c02f..dfbfc028 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -1,11 +1,11 @@
%%%----------------------------------------------------------------------
%%% File : mod_client_state.erl
-%%% Author : Holger Weiss
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : Filter stanzas sent to inactive clients (XEP-0352)
-%%% Created : 11 Sep 2014 by Holger Weiss
+%%% Created : 11 Sep 2014 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
-%%% ejabberd, Copyright (C) 2014-2015 ProcessOne
+%%% ejabberd, Copyright (C) 2014-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
@@ -25,11 +25,14 @@
-module(mod_client_state).
-author('holger@zedat.fu-berlin.de').
+-protocol({xep, 85, '2.1'}).
+-protocol({xep, 352, '0.1'}).
-behavior(gen_mod).
--export([start/2, stop/1, add_stream_feature/2, filter_presence/2,
- filter_chat_states/2]).
+-export([start/2, stop/1, add_stream_feature/2,
+ filter_presence/2, filter_chat_states/2,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -39,11 +42,11 @@ start(Host, Opts) ->
QueuePresence = gen_mod:get_opt(queue_presence, Opts,
fun(true) -> true;
(false) -> false
- end, false),
+ end, true),
DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
fun(true) -> true;
(false) -> false
- end, false),
+ end, true),
if QueuePresence; DropChatStates ->
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
@@ -77,7 +80,7 @@ add_stream_feature(Features, _Host) ->
[Feature | Features].
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
?DEBUG("Got important presence stanza", []),
{stop, send};
@@ -88,22 +91,22 @@ filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
filter_presence(Action, _Stanza) -> Action.
filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
- %% All XEP-0085 chat states except for <gone/>:
- ChatStates = [<<"active">>, <<"inactive">>, <<"composing">>, <<"paused">>],
- Stripped =
- lists:foldl(fun(ChatState, AccStanza) ->
- xml:remove_subtags(AccStanza, ChatState,
- {<<"xmlns">>, ?NS_CHATSTATES})
- end, Stanza, ChatStates),
- case Stripped of
- #xmlel{children = [#xmlel{name = <<"thread">>}]} ->
+ case jlib:is_standalone_chat_state(Stanza) of
+ true ->
?DEBUG("Got standalone chat state notification", []),
{stop, drop};
- #xmlel{children = []} ->
- ?DEBUG("Got standalone chat state notification", []),
- {stop, drop};
- _ ->
- ?DEBUG("Got message with chat state notification", []),
+ false ->
+ ?DEBUG("Got message stanza", []),
{stop, send}
end;
filter_chat_states(Action, _Stanza) -> Action.
+
+mod_opt_type(drop_chat_states) ->
+ fun (true) -> true;
+ (false) -> false
+ end;
+mod_opt_type(queue_presence) ->
+ fun (true) -> true;
+ (false) -> false
+ end;
+mod_opt_type(_) -> [drop_chat_states, queue_presence].
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index 9e6e83e1..5e011704 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -5,7 +5,7 @@
%%% Created : 19 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -23,33 +23,29 @@
%%%
%%%----------------------------------------------------------------------
-%%% Implements most of XEP-0133: Service Administration Version 1.1
-%%% (2005-08-19)
-
-module(mod_configure).
-author('alexey@process-one.net').
+-protocol({xep, 133, '1.1'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, get_local_identity/5,
get_local_features/5, get_local_items/5,
adhoc_local_items/4, adhoc_local_commands/4,
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
- adhoc_sm_items/4, adhoc_sm_commands/4]).
+ adhoc_sm_items/4, adhoc_sm_commands/4, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-
+-include("ejabberd_sm.hrl").
-include("adhoc.hrl").
-define(T(Lang, Text), translate:translate(Lang, Text)).
-%% Copied from ejabberd_sm.erl
--record(session, {sid, usr, us, priority, info}).
-
start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
get_local_items, 50),
@@ -115,7 +111,7 @@ stop(Host) ->
-define(NODEJID(To, Name, Node),
#xmlel{name = <<"item">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(To)},
+ [{<<"jid">>, jid:to_string(To)},
{<<"name">>, ?T(Lang, Name)}, {<<"node">>, Node}],
children = []}).
@@ -293,7 +289,7 @@ adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
end,
Nodes = [#xmlel{name = <<"item">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(To)},
+ [{<<"jid">>, jid:to_string(To)},
{<<"name">>, ?T(Lang, <<"Configuration">>)},
{<<"node">>, <<"config">>}],
children = []}],
@@ -354,7 +350,7 @@ adhoc_local_items(Acc, From,
Nodes = recursively_get_local_items(PermLev, LServer,
<<"">>, Server, Lang),
Nodes1 = lists:filter(fun (N) ->
- Nd = xml:get_tag_attr_s(<<"node">>, N),
+ Nd = fxml:get_tag_attr_s(<<"node">>, N),
F = get_local_features([], From, To, Nd,
Lang),
case F of
@@ -383,9 +379,9 @@ recursively_get_local_items(PermLev, LServer, Node,
{error, _Error} -> []
end,
Nodes = lists:flatten(lists:map(fun (N) ->
- S = xml:get_tag_attr_s(<<"jid">>,
+ S = fxml:get_tag_attr_s(<<"jid">>,
N),
- Nd = xml:get_tag_attr_s(<<"node">>,
+ Nd = fxml:get_tag_attr_s(<<"node">>,
N),
if (S /= Server) or
(Nd == <<"">>) ->
@@ -416,7 +412,7 @@ get_permission_level(JID) ->
allow ->
PermLev = get_permission_level(From),
case get_local_items({PermLev, LServer}, LNode,
- jlib:jid_to_string(To), Lang)
+ jid:to_string(To), Lang)
of
{result, Res} -> {result, Res};
{error, Error} -> {error, Error}
@@ -438,7 +434,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
allow ->
PermLev = get_permission_level(From),
case get_local_items({PermLev, LServer}, [],
- jlib:jid_to_string(To), Lang)
+ jid:to_string(To), Lang)
of
{result, Res} -> {result, Items ++ Res};
{error, _Error} -> {result, Items}
@@ -986,7 +982,7 @@ get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
Node ->
- case rpc:call(Node, mnesia, system_info, [tables]) of
+ case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
Tables ->
@@ -1008,7 +1004,7 @@ get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
?T(Lang,
<<"Choose storage type of tables">>)}]}
| lists:map(fun (Table) ->
- case rpc:call(Node, mnesia,
+ case ejabberd_cluster:call(Node, mnesia,
table_info,
[Table,
storage_type])
@@ -1029,7 +1025,7 @@ get_form(Host,
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
Node ->
- case rpc:call(Node, gen_mod, loaded_modules, [Host]) of
+ case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
{badrpc, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
Modules ->
@@ -1608,7 +1604,7 @@ set_form(_From, Host,
case Vals of
[<<"1">>] ->
Module = jlib:binary_to_atom(Var),
- rpc:call(Node, gen_mod, stop_module,
+ ejabberd_cluster:call(Node, gen_mod, stop_module,
[Host, Module]);
_ -> ok
end
@@ -1635,7 +1631,7 @@ set_form(_From, Host,
case erl_parse:parse_term(Tokens) of
{ok, Modules} ->
lists:foreach(fun ({Module, Args}) ->
- rpc:call(Node, gen_mod,
+ ejabberd_cluster:call(Node, gen_mod,
start_module,
[Host, Module, Args])
end,
@@ -1657,7 +1653,7 @@ set_form(_From, _Host,
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
{value, {_, [String]}} ->
- case rpc:call(Node, mnesia, backup, [String]) of
+ case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
{badrpc, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
@@ -1676,7 +1672,7 @@ set_form(_From, _Host,
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
{value, {_, [String]}} ->
- case rpc:call(Node, ejabberd_admin, restore, [String])
+ case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String])
of
{badrpc, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
@@ -1696,7 +1692,7 @@ set_form(_From, _Host,
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
{value, {_, [String]}} ->
- case rpc:call(Node, ejabberd_admin, dump_to_textfile,
+ case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile,
[String])
of
{badrpc, _Reason} ->
@@ -1716,7 +1712,7 @@ set_form(_From, _Host,
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
{value, {_, [String]}} ->
- rpc:call(Node, jd2ejd, import_file, [String]),
+ ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
end
@@ -1730,7 +1726,7 @@ set_form(_From, _Host,
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
{value, {_, [String]}} ->
- rpc:call(Node, jd2ejd, import_dir, [String]),
+ ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
end
@@ -1818,7 +1814,7 @@ set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
AccountString = get_value(<<"accountjid">>, XData),
Password = get_value(<<"password">>, XData),
Password = get_value(<<"password-verify">>, XData),
- AccountJID = jlib:string_to_jid(AccountString),
+ AccountJID = jid:from_string(AccountString),
User = AccountJID#jid.luser,
Server = AccountJID#jid.lserver,
true = lists:member(Server, ?MYHOSTS),
@@ -1832,7 +1828,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
XData),
[_ | _] = AccountStringList,
ASL2 = lists:map(fun (AccountString) ->
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
@@ -1847,7 +1843,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
true = LServer == Host orelse
@@ -1873,7 +1869,7 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
set_form(From, Host,
?NS_ADMINL(<<"get-user-password">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
@@ -1893,7 +1889,7 @@ set_form(From, Host,
?NS_ADMINL(<<"change-user-password">>), _Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
Password = get_value(<<"password">>, XData),
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
@@ -1904,7 +1900,7 @@ set_form(From, Host,
set_form(From, Host,
?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
@@ -1913,7 +1909,6 @@ set_form(From, Host,
Server)
of
[] ->
- _US = {User, Server},
case get_last_info(User, Server) of
not_found -> ?T(Lang, <<"Never">>);
{ok, Timestamp, _Status} ->
@@ -1940,7 +1935,7 @@ set_form(From, Host,
set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
XData) ->
AccountString = get_value(<<"accountjid">>, XData),
- JID = jlib:string_to_jid(AccountString),
+ JID = jid:from_string(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
@@ -2023,17 +2018,17 @@ stop_node(From, Host, ENode, Action, XData) ->
#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
- <<"jabber:x:data">>},
+ ?NS_XDATA},
{<<"type">>, <<"submit">>}],
children = SubEls},
others =
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
- <<"jabber:x:data">>},
+ ?NS_XDATA},
{<<"type">>, <<"submit">>}],
children = SubEls}]},
- To = jlib:make_jid(<<"">>, Host, <<"">>),
+ To = jid:make(<<"">>, Host, <<"">>),
mod_announce:announce_commands(empty, From, To, Request)
end,
Time = timer:seconds(Delay),
@@ -2149,3 +2144,5 @@ set_sm_form(User, Server, <<"config">>,
end;
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
+
+mod_opt_type(_) -> [].
diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl
index b70f8fe5..0acd4a78 100644
--- a/src/mod_configure2.erl
+++ b/src/mod_configure2.erl
@@ -5,7 +5,7 @@
%%% Created : 26 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,11 +25,14 @@
-module(mod_configure2).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3,
+ mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -62,7 +65,7 @@ process_local_iq(From, To,
set ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
- %%case xml:get_tag_attr_s("type", SubEl) of
+ %%case fxml:get_tag_attr_s("type", SubEl) of
%% "cancel" ->
%% IQ#iq{type = result,
%% sub_el = [{xmlelement, "query",
@@ -76,7 +79,7 @@ process_local_iq(From, To,
%% _ ->
%% Node =
%% string:tokens(
- %% xml:get_tag_attr_s("node", SubEl),
+ %% fxml:get_tag_attr_s("node", SubEl),
%% "/"),
%% case set_form(Node, Lang, XData) of
%% {result, Res} ->
@@ -183,8 +186,7 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
Vals ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp = MegaSecs * 1000000 + Secs,
+ TimeStamp = p1_time_compat:system_time(seconds),
Str = list_to_binary(
[[jlib:integer_to_binary(TimeStamp - V),
<<" ">>] || V <- Vals]),
@@ -195,3 +197,21 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
process_get(_) -> {error, ?ERR_BAD_REQUEST}.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
+
+opt_type(registration_watchers) ->
+ fun (JIDs) when is_list(JIDs) ->
+ lists:map(fun (J) ->
+ #xmlel{name = <<"jid">>, attrs = [],
+ children =
+ [{xmlcdata, iolist_to_binary(J)}]}
+ end,
+ JIDs)
+ end;
+opt_type(welcome_message) ->
+ fun ({Subj, Body}) ->
+ {iolist_to_binary(Subj), iolist_to_binary(Body)}
+ end;
+opt_type(_) -> [registration_watchers, welcome_message].
diff --git a/src/mod_disco.erl b/src/mod_disco.erl
index 00b65d23..734e90d3 100644
--- a/src/mod_disco.erl
+++ b/src/mod_disco.erl
@@ -5,7 +5,7 @@
%%% Created : 1 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,9 @@
-author('alexey@process-one.net').
+-protocol({xep, 30, '2.4'}).
+-protocol({xep, 157, '1.0'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq_items/3,
@@ -36,7 +39,7 @@
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
get_info/5, register_feature/2, unregister_feature/2,
register_extra_domain/2, unregister_extra_domain/2,
- transform_module_options/1]).
+ transform_module_options/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -149,7 +152,7 @@ process_local_iq_items(From, To,
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items, Host,
empty, [From, To, Node, Lang])
@@ -177,7 +180,7 @@ process_local_iq_info(From, To,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host, [], [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
@@ -302,7 +305,7 @@ process_sm_iq_items(From, To,
case is_presence_subscribed(From, To) of
true ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case ejabberd_hooks:run_fold(disco_sm_items, Host,
empty, [From, To, Node, Lang])
of
@@ -375,10 +378,12 @@ process_sm_iq_info(From, To,
case is_presence_subscribed(From, To) of
true ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host, [],
[From, To, Node, Lang]),
+ Info = ejabberd_hooks:run_fold(disco_info, Host, [],
+ [From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features, Host,
empty, [From, To, Node, Lang])
of
@@ -394,7 +399,7 @@ process_sm_iq_info(From, To,
[{<<"xmlns">>, ?NS_DISCO_INFO}
| ANode],
children =
- Identity ++
+ Identity ++ Info ++
features_to_xml(Features)}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
@@ -516,3 +521,18 @@ values_to_xml(Values) ->
children = [{xmlcdata, Value}]}
end,
Values).
+
+mod_opt_type(extra_domains) ->
+ fun (Hs) -> [iolist_to_binary(H) || H <- Hs] end;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(server_info) ->
+ fun (L) ->
+ lists:map(fun (Opts) ->
+ Mods = proplists:get_value(modules, Opts, all),
+ Name = proplists:get_value(name, Opts, <<>>),
+ URLs = proplists:get_value(urls, Opts, []),
+ {Mods, Name, URLs}
+ end,
+ L)
+ end;
+mod_opt_type(_) -> [extra_domains, iqdisc, server_info].
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 63dc8c81..7184ee4e 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -5,7 +5,7 @@
%%% Created : 15 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -35,9 +35,9 @@
-export([start_link/2, start/2, stop/1,
do_client_version/3]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -86,7 +86,7 @@ stop(Host) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"echo.@HOST@">>),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok, #state{host = MyHost}}.
%%--------------------------------------------------------------------
@@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% Finally, the received response is printed in the ejabberd log file.
do_client_version(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) ->
- ToS = jlib:jid_to_string(To),
+ ToS = jid:to_string(To),
Random_resource =
iolist_to_binary(integer_to_list(random:uniform(100000))),
From2 = From#jid{resource = Random_resource,
@@ -182,7 +182,7 @@ do_client_version(enabled, From, To) ->
Els = receive
{route, To, From2, IQ} ->
#xmlel{name = <<"query">>, children = List} =
- xml:get_subtag(IQ, <<"query">>),
+ fxml:get_subtag(IQ, <<"query">>),
List
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
@@ -196,3 +196,6 @@ do_client_version(enabled, From, To) ->
Values_string2 = iolist_to_binary(Values_string1),
?INFO_MSG("Information of the client: ~s~s",
[ToS, Values_string2]).
+
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(_) -> [host].
diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl
index 63c09db2..fed10670 100644
--- a/src/mod_fail2ban.erl
+++ b/src/mod_fail2ban.erl
@@ -1,12 +1,12 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2014, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 15 Aug 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2014-2015 ProcessOne
+%%%
+%%% ejabberd, Copyright (C) 2014-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
@@ -31,9 +31,9 @@
%% API
-export([start_link/2, start/2, stop/1, c2s_auth_result/4, check_bl_c2s/3]).
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd.hrl").
@@ -65,7 +65,7 @@ c2s_auth_result(false, _User, LServer, {Addr, _Port}) ->
LServer, ?MODULE, c2s_max_auth_failures,
fun(I) when is_integer(I), I > 0 -> I end,
?C2S_MAX_AUTH_FAILURES),
- UnbanTS = unban_timestamp(BanLifetime),
+ UnbanTS = p1_time_compat:system_time(seconds) + BanLifetime,
case ets:lookup(failed_auth, Addr) of
[{Addr, N, _, _}] ->
ets:insert(failed_auth, {Addr, N+1, UnbanTS, MaxFailures});
@@ -79,11 +79,11 @@ c2s_auth_result(true, _User, _Server, _AddrPort) ->
check_bl_c2s(_Acc, Addr, Lang) ->
case ets:lookup(failed_auth, Addr) of
[{Addr, N, TS, MaxFailures}] when N >= MaxFailures ->
- case TS > now() of
+ case TS > p1_time_compat:system_time(seconds) of
true ->
IP = jlib:ip_to_list(Addr),
UnbanDate = format_date(
- calendar:now_to_universal_time(TS)),
+ calendar:now_to_universal_time(seconds_to_now(TS))),
LogReason = io_lib:fwrite(
"Too many (~p) failed authentications "
"from this IP address (~s). The address "
@@ -139,7 +139,7 @@ handle_cast(_Msg, State) ->
handle_info(clean, State) ->
?DEBUG("cleaning ~p ETS table", [failed_auth]),
- Now = now(),
+ Now = p1_time_compat:system_time(seconds),
ets:select_delete(
failed_auth,
ets:fun2ms(fun({_, _, UnbanTS, _}) -> UnbanTS =< Now end)),
@@ -171,11 +171,6 @@ is_whitelisted(Host, Addr) ->
none),
acl:match_rule(Host, Access, Addr) == allow.
-unban_timestamp(BanLifetime) ->
- {MegaSecs, MSecs, USecs} = now(),
- UnbanSecs = MegaSecs * 1000000 + MSecs + BanLifetime,
- {UnbanSecs div 1000000, UnbanSecs rem 1000000, USecs}.
-
is_loaded_at_other_hosts(Host) ->
lists:any(
fun(VHost) when VHost == Host ->
@@ -184,6 +179,18 @@ is_loaded_at_other_hosts(Host) ->
gen_mod:is_loaded(VHost, ?MODULE)
end, ?MYHOSTS).
+seconds_to_now(Secs) ->
+ {Secs div 1000000, Secs rem 1000000, 0}.
+
format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
io_lib:format("~2..0w:~2..0w:~2..0w ~2..0w.~2..0w.~4..0w",
[Hour, Minute, Second, Day, Month, Year]).
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(c2s_auth_ban_lifetime) ->
+ fun (T) when is_integer(T), T > 0 -> T end;
+mod_opt_type(c2s_max_auth_failures) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(_) ->
+ [access, c2s_auth_ban_lifetime, c2s_max_auth_failures].
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
new file mode 100644
index 00000000..3b7a09cb
--- /dev/null
+++ b/src/mod_http_api.erl
@@ -0,0 +1,392 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_http_api.erl
+%%% Author : Christophe romain <christophe.romain@process-one.net>
+%%% Purpose : Implements REST API for ejabberd using JSON data
+%%% Created : 15 Sep 2014 by Christophe Romain <christophe.romain@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.
+%%%
+%%%----------------------------------------------------------------------
+
+%% Example config:
+%%
+%% in ejabberd_http listener
+%% request_handlers:
+%% "/api": mod_http_api
+%%
+%% Access rights are defined with:
+%% commands_admin_access: configure
+%% commands:
+%% - add_commands: user
+%%
+%%
+%% add_commands allow exporting a class of commands, from
+%% open: methods is not risky and can be called by without any access check
+%% restricted (default): the same, but will appear only in ejabberdctl list.
+%% admin – auth is required with XMLRPC and HTTP API and checked for admin priviledges, works as usual in ejabberdctl.
+%% user - can be used through XMLRPC and HTTP API, even by user. Only admin can use the commands for other users.
+%%
+%% Then to perform an action, send a POST request to the following URL:
+%% http://localhost:5280/api/<call_name>
+
+-module(mod_http_api).
+
+-author('cromain@process-one.net').
+
+-behaviour(gen_mod).
+
+-export([start/2, stop/1, process/2, mod_opt_type/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("ejabberd_http.hrl").
+
+-define(CT_PLAIN,
+ {<<"Content-Type">>, <<"text/plain">>}).
+
+-define(CT_XML,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
+
+-define(CT_JSON,
+ {<<"Content-Type">>, <<"application/json">>}).
+
+-define(AC_ALLOW_ORIGIN,
+ {<<"Access-Control-Allow-Origin">>, <<"*">>}).
+
+-define(AC_ALLOW_METHODS,
+ {<<"Access-Control-Allow-Methods">>,
+ <<"GET, POST, OPTIONS">>}).
+
+-define(AC_ALLOW_HEADERS,
+ {<<"Access-Control-Allow-Headers">>,
+ <<"Content-Type">>}).
+
+-define(AC_MAX_AGE,
+ {<<"Access-Control-Max-Age">>, <<"86400">>}).
+
+-define(OPTIONS_HEADER,
+ [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
+ ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
+
+-define(HEADER(CType),
+ [CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
+
+%% -------------------
+%% Module control
+%% -------------------
+
+start(_Host, _Opts) ->
+ ok.
+
+stop(_Host) ->
+ ok.
+
+
+%% ----------
+%% basic auth
+%% ----------
+
+check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command)
+ when HTTPAuth /= undefined ->
+ case catch binary_to_existing_atom(Command, utf8) of
+ Call when is_atom(Call) ->
+ 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 ejabberd_oauth:check_token(Command, Token) of
+ {ok, User, Server} ->
+ {ok, {User, Server, {oauth, Token}, Admin}};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end,
+ case Auth of
+ {ok, A} -> {allowed, Call, A};
+ _ -> unauthorized_response()
+ end;
+ _ ->
+ unauthorized_response()
+ end;
+check_permissions(_, _Command) ->
+ unauthorized_response().
+
+%% ------------------
+%% command processing
+%% ------------------
+
+process(_, #request{method = 'POST', data = <<>>}) ->
+ ?DEBUG("Bad Request: no data", []),
+ badrequest_response();
+process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
+ try
+ Args = case jiffy:decode(Data) of
+ List when is_list(List) -> List;
+ {List} when is_list(List) -> List;
+ Other -> [Other]
+ end,
+ log(Call, Args, IP),
+ case check_permissions(Req, Call) of
+ {allowed, Cmd, Auth} ->
+ {Code, Result} = handle(Cmd, Auth, Args),
+ json_response(Code, jiffy:encode(Result));
+ ErrorResponse ->
+ ErrorResponse
+ end
+ catch _:Error ->
+ ?DEBUG("Bad Request: ~p", [Error]),
+ badrequest_response()
+ end;
+process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+ try
+ Args = case Data of
+ [{nokey, <<>>}] -> [];
+ _ -> Data
+ end,
+ log(Call, Args, IP),
+ case check_permissions(Req, Call) of
+ {allowed, Cmd, Auth} ->
+ {Code, Result} = handle(Cmd, Auth, Args),
+ json_response(Code, jiffy:encode(Result));
+ ErrorResponse ->
+ ErrorResponse
+ end
+ catch _:Error ->
+ ?DEBUG("Bad Request: ~p", [Error]),
+ badrequest_response()
+ end;
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, []};
+process(_Path, Request) ->
+ ?DEBUG("Bad Request: no handler ~p", [Request]),
+ badrequest_response().
+
+%% ----------------
+%% command handlers
+%% ----------------
+
+% generic ejabberd command handler
+handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
+ case ejabberd_commands:get_command_format(Call, Auth) of
+ {ArgsSpec, _} when is_list(ArgsSpec) ->
+ Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
+ Spec = lists:foldr(
+ fun ({Key, binary}, Acc) ->
+ [{Key, <<>>}|Acc];
+ ({Key, string}, Acc) ->
+ [{Key, <<>>}|Acc];
+ ({Key, integer}, Acc) ->
+ [{Key, 0}|Acc];
+ ({Key, {list, _}}, Acc) ->
+ [{Key, []}|Acc];
+ ({Key, atom}, Acc) ->
+ [{Key, undefined}|Acc]
+ end, [], ArgsSpec),
+ handle2(Call, Auth, match(Args2, Spec));
+ {error, Msg} ->
+ {400, Msg};
+ _Error ->
+ {400, <<"Error">>}
+ end.
+
+handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
+ {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
+ ArgsFormatted = format_args(Args, ArgsF),
+ case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
+ 0 -> {200, <<"OK">>};
+ 1 -> {500, <<"500 Internal server error">>};
+ 400 -> {400, <<"400 Bad Request">>};
+ 404 -> {404, <<"404 Not found">>};
+ Res -> format_command_result(Call, Auth, Res)
+ end.
+
+get_elem_delete(A, L) ->
+ case proplists:get_all_values(A, L) of
+ [Value] -> {Value, proplists:delete(A, L)};
+ [_, _ | _] ->
+ %% Crash reporting the error
+ exit({duplicated_attribute, A, L});
+ [] ->
+ %% Report the error and then force a crash
+ exit({attribute_not_found, A, L})
+ end.
+
+format_args(Args, ArgsFormat) ->
+ {ArgsRemaining, R} = lists:foldl(fun ({ArgName,
+ ArgFormat},
+ {Args1, Res}) ->
+ {ArgValue, Args2} =
+ get_elem_delete(ArgName,
+ Args1),
+ Formatted = format_arg(ArgValue,
+ ArgFormat),
+ {Args2, Res ++ [Formatted]}
+ end,
+ {Args, []}, ArgsFormat),
+ case ArgsRemaining of
+ [] -> R;
+ L when is_list(L) -> exit({additional_unused_args, L})
+ end.
+
+format_arg({array, Elements},
+ {list, {ElementDefName, ElementDefFormat}})
+ when is_list(Elements) ->
+ lists:map(fun ({struct, [{ElementName, ElementValue}]}) when
+ ElementDefName == ElementName ->
+ format_arg(ElementValue, ElementDefFormat)
+ end,
+ Elements);
+format_arg({array, [{struct, Elements}]},
+ {list, {ElementDefName, ElementDefFormat}})
+ when is_list(Elements) ->
+ lists:map(fun ({ElementName, ElementValue}) ->
+ true = ElementDefName == ElementName,
+ format_arg(ElementValue, ElementDefFormat)
+ end,
+ Elements);
+format_arg({array, [{struct, Elements}]},
+ {tuple, ElementsDef})
+ when is_list(Elements) ->
+ FormattedList = format_args(Elements, ElementsDef),
+ list_to_tuple(FormattedList);
+format_arg({array, Elements}, {list, ElementsDef})
+ when is_list(Elements) and is_atom(ElementsDef) ->
+ [format_arg(Element, ElementsDef)
+ || Element <- Elements];
+format_arg(Arg, integer) when is_integer(Arg) -> Arg;
+format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg);
+format_arg(Arg, binary) when is_binary(Arg) -> Arg;
+format_arg(Arg, string) when is_list(Arg) -> process_unicode_codepoints(Arg);
+format_arg(Arg, string) when is_binary(Arg) -> Arg;
+format_arg(undefined, binary) -> <<>>;
+format_arg(undefined, string) -> <<>>;
+format_arg(Arg, Format) ->
+ ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
+ error.
+
+process_unicode_codepoints(Str) ->
+ iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
+ (Y) -> Y
+ end, Str)).
+
+%% ----------------
+%% internal helpers
+%% ----------------
+
+match(Args, Spec) ->
+ [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
+
+ejabberd_command(Auth, Cmd, Args, Default) ->
+ case catch ejabberd_commands:execute_command(undefined, Auth, Cmd, Args) of
+ {'EXIT', _} -> Default;
+ {error, _} -> Default;
+ Result -> Result
+ end.
+
+format_command_result(Cmd, Auth, Result) ->
+ {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
+ case {ResultFormat, Result} of
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, <<"">>};
+ {{_, rescode}, _} ->
+ {500, <<"">>};
+ {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
+ {200, iolist_to_binary(Text1)};
+ {{_, restuple}, {_, Text2}} ->
+ {500, iolist_to_binary(Text2)};
+ {{_, {list, _}}, _V} ->
+ {_, L} = format_result(Result, ResultFormat),
+ {200, L};
+ {{_, {tuple, _}}, _V} ->
+ {_, T} = format_result(Result, ResultFormat),
+ {200, T};
+ _ ->
+ {200, {[format_result(Result, ResultFormat)]}}
+ end.
+
+format_result(Atom, {Name, atom}) ->
+ {jlib:atom_to_binary(Name), jlib:atom_to_binary(Atom)};
+
+format_result(Int, {Name, integer}) ->
+ {jlib:atom_to_binary(Name), Int};
+
+format_result(String, {Name, string}) ->
+ {jlib:atom_to_binary(Name), iolist_to_binary(String)};
+
+format_result(Code, {Name, rescode}) ->
+ {jlib:atom_to_binary(Name), Code == true orelse Code == ok};
+
+format_result({Code, Text}, {Name, restuple}) ->
+ {jlib:atom_to_binary(Name),
+ {[{<<"res">>, Code == true orelse Code == ok},
+ {<<"text">>, iolist_to_binary(Text)}]}};
+
+format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
+ {jlib:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+
+format_result(Els, {Name, {list, Def}}) ->
+ {jlib:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
+
+format_result(Tuple, {_Name, {tuple, [{_, atom}, ValFmt]}}) ->
+ {Name2, Val} = Tuple,
+ {_, Val2} = format_result(Val, ValFmt),
+ {jlib:atom_to_binary(Name2), Val2};
+
+format_result(Tuple, {Name, {tuple, Def}}) ->
+ Els = lists:zip(tuple_to_list(Tuple), Def),
+ {jlib:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
+
+format_result(404, {_Name, _}) ->
+ "not_found".
+
+unauthorized_response() ->
+ {401, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
+
+badrequest_response() ->
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+json_response(Code, Body) when is_integer(Code) ->
+ {Code, ?HEADER(?CT_JSON), Body}.
+
+log(Call, Args, {Addr, Port}) ->
+ AddrS = jlib:ip_to_list({Addr, Port}),
+ ?INFO_MSG("Admin call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
+
+mod_opt_type(access) ->
+ fun(Access) when is_atom(Access) -> Access end;
+mod_opt_type(_) -> [access].
diff --git a/src/mod_http_bind.erl b/src/mod_http_bind.erl
index 773ef241..1a07867e 100644
--- a/src/mod_http_bind.erl
+++ b/src/mod_http_bind.erl
@@ -5,7 +5,7 @@
%%% Created : Tue Feb 20 13:15:52 CET 2007
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -37,7 +37,7 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process/2]).
+-export([start/2, stop/1, process/2, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -64,9 +64,9 @@ process([], #request{method = 'POST', data = <<>>}) ->
{400, ?HEADER,
#xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}};
process([],
- #request{method = 'POST', data = Data, ip = IP}) ->
+ #request{method = 'POST', data = Data, ip = IP, opts = Opts}) ->
?DEBUG("Incoming data: ~s", [Data]),
- ejabberd_http_bind:process_request(Data, IP);
+ ejabberd_http_bind:process_request(Data, IP, Opts);
process([], #request{method = 'GET', data = <<>>}) ->
{200, ?HEADER, get_human_html_xmlel()};
process([], #request{method = 'OPTIONS', data = <<>>}) ->
@@ -78,54 +78,14 @@ process(_Path, _Request) ->
{400, ?HEADER,
#xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}.
-get_human_html_xmlel() ->
- Heading = <<"ejabberd ",
- (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
- #xmlel{name = <<"html">>,
- attrs =
- [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
- children =
- [#xmlel{name = <<"head">>,
- children =
- [#xmlel{name = <<"title">>,
- children = [{xmlcdata, Heading}]}]},
- #xmlel{name = <<"body">>,
- children =
- [#xmlel{name = <<"h1">>,
- children = [{xmlcdata, Heading}]},
- #xmlel{name = <<"p">>,
- children =
- [{xmlcdata, <<"An implementation of ">>},
- #xmlel{name = <<"a">>,
- attrs =
- [{<<"href">>,
- <<"http://xmpp.org/extensions/xep-0206.html">>}],
- children =
- [{xmlcdata,
- <<"XMPP over BOSH (XEP-0206)">>}]}]},
- #xmlel{name = <<"p">>,
- children =
- [{xmlcdata,
- <<"This web page is only informative. To "
- "use HTTP-Bind you need a Jabber/XMPP "
- "client that supports it.">>}]}]}]}.
-
%%%----------------------------------------------------------------------
%%% BEHAVIOUR CALLBACKS
%%%----------------------------------------------------------------------
-start(Host, _Opts) ->
- setup_database(),
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB),
- ChildSpec = {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, ejabberd_http_bind]},
- permanent, infinity, supervisor, [ejabberd_tmp_sup]},
- supervisor:start_child(ejabberd_sup, ChildSpec).
-
-stop(Host) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB),
- supervisor:terminate_child(ejabberd_sup, Proc),
- supervisor:delete_child(ejabberd_sup, Proc).
+start(_Host, _Opts) ->
+ setup_database().
+
+stop(_Host) ->
+ ok.
setup_database() ->
migrate_database(),
@@ -142,3 +102,136 @@ migrate_database() ->
%% of actually migrating data, let's just destroy the table
mnesia:delete_table(http_bind)
end.
+
+mod_opt_type(max_inactivity) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(max_pause) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(_) -> [max_inactivity, max_pause].
+
+
+%%%----------------------------------------------------------------------
+%%% Help Web Page
+%%%----------------------------------------------------------------------
+
+get_human_html_xmlel() ->
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>,
+ children =
+ [#xmlel{name = <<"title">>,
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"style">>,
+ children = [{xmlcdata, get_style_cdata()}]}]},
+ #xmlel{name = <<"body">>,
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"container">>}],
+ children = get_container_children(Heading)}]}]}.
+
+get_container_children(Heading) ->
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}],
+ children =
+ [#xmlel{name = <<"img">>,
+ attrs = [{<<"height">>, <<"32">>},
+ {<<"src">>, get_image_src()}]}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"white section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"h1">>, children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children = [{xmlcdata, <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>},
+ {<<"title">>, <<"ejabberd XMPP server">>}],
+ children = [{xmlcdata, <<"ejabberd">>}]},
+ {xmlcdata, <<" is maintained by ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.process-one.net">>},
+ {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
+ children = [{xmlcdata, <<"ProcessOne">>}]} ]}]}
+ ].
+
+get_style_cdata() ->
+ <<"
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+ color: #fff;
+ }
+ h1 {
+ font-size: 3em;
+ color: #444;
+ }
+ p {
+ line-height: 1.5em;
+ color: #888;
+ }
+ a {
+ color: #fff;
+ }
+ a:hover,
+ a:active {
+ text-decoration: underline;
+ }
+ .container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: #424A55;
+ background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ }
+ .section {
+ padding: 3em;
+ }
+ .white.section {
+ background: #fff;
+ border-bottom: 4px solid #41AFCA;
+ }
+ .white.section a {
+ text-decoration: none;
+ color: #41AFCA;
+ }
+ .white.section a:hover,
+ .white.section a:active {
+ text-decoration: underline;
+ }
+ .block {
+ margin: 0 auto;
+ max-width: 900px;
+ width: 100%;
+ }">>.
+
+get_image_src() ->
+ <<"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABACAYAAACgPErgAAAVtklEQVR42uydeXhU1d2ADzuyCcii4FZBZdEqVYtaUGqlikVwaRWsrWBRKIh8KgXcEAX9VECtoljcURAJICJWQLaKLAEjIHtBSNhC9oQkk2SW+36/52P+iIE5d5kzk4Fn3ud5H/7gIbnD3PPOveeeuVedygD9xLlQMh9yJ0D+GeokJAg1xcEWzOWYj0DBaQm0fXdaMCu8bROgpJVKksQj0+F84OXw/jRd7KVOdbbCFI6jdDNktlAnESVQNwSzOI6yhZBaV1UzAXiB4wj8CBtbqiRJXDIILtgL+zmOb8aoU5Xh0MMiEr5x6iSiAIZYROLo/aoayYMeocjbNlklSeKC9VAjBDM5IcEQfNZFnYq8AM8SkcJV6iSiCGYRkT1LVDWSA08Tkfy9MLaeOsUA2ot9xJHiW2KK+LW4Mvxnijg1/Pd9xQvFGiqJLUOhcT5kE5Etg0/VYI3TBOvbkyxYKURk2dpqDZb2gyEjC1SjUyBQdcQe4mtimujDHWXiD+Lr4g1iXZXkhISgBZBLZIaoU5GR0I2IVDxxkgVrNhH5z+pqDZb2g+FAJjQ4aYMFtBAfEzdils3iP8TkhYkqlMEZFuQQmcHqVGUBTOI40lLhz02TwUoGKxJAI/FxMYPYckB8Smyikvw/vlM/WHqAP4ifiHPF0fBsYyUkg5UM1okAbhV/JL5sE29TSZLBOhHJYCWDVRXgNPFVqpfXxQbJYCWDlQxWMlgRAc4VV5EYrBZ/kQxWMljJYFVLsBomdLCAS8RdJBZ7xMuSwTIcLKCO2EG8VrxdvEfsLV4F1vkJstq9BlidwN8hVleSLLgUuFzsEv6zs9g4EYIFNBAvFi87tm3WZWI7oHZ8glWnkWbb6ortCG/bsT+tDsBpcYrVpeIhzBHAHJni5XGOd5PwvnKJif0DaCh2/Pn7y/linbgFKx/qrjkWpffEHZo3qVhMBWuC2EVVA4ehay6sAYJgBeDocuh9iYqCL+F04DbxLTFNPMzxWGI6WCuBJ8Rfxi5YK34WLKC5BX3FVzj22n8S/fwcX/i9+1wcLraLVbAqr8OqgNOA31nwPLBc3HWCNU1+cbe4WBwjXhbD08A9eOcn8SNxmHij+EuxQ3iwdxP/Jr4pbsU7+2J5ehiCNuI9FkwN78sZol+0xDSYd6OH75R2BcZasALYKwZO0IWd4fd3LIS6g1XHeLBWQV1gsLgJ9wTBWgSBm1ScuBYuAvI4jrx9cOEZyiXzoUUIxhbDPm+fvFlL4PPe5oO14FslvAjnrYeXgYO4p0T8UrzJbLB2HlbCZMqbfwOjgrDD41HLCrE/LK1pKFYNxFS8sVDsK1Y5ctQeRf42HLcy3JMmNjIc6+ss+ETMRUuRH9Zd7SBS9QMwCFiDNzZBYAT4G5dTUcuC7KiClQ3dj8B6osYSAzOgoq2KMatgChF5e5RyQQXcVwL7MMNXsKOTuWAd2FBG9uhyyMMI6XPBd7GZYOUd8pP+UCn8hBEOr4KCbgYG7Ju4Z43YU0VB+LRoLu6ZpgyQAhflw2e4Y7lYS0XADzdZ8ANGsLaX88PgACHvwfLBY4Afo4T2Q9lNMZ68W0lE/J8oB5RDkxDMwDhF+XC4v6tgxZdcKLov+mBhYR4/FD8VRTRu93CE97RYTxkCuFfMxR13qSjIhoE+Tx9qPh9Mb6WqcBRqBuBli5hguQ4WUGsPvGsRKyw/hAaoGJEPy4lI4Uxlw3XHzu/XEVNKHk3QYAmWWDEecmraBqtaCE6HrCYeJpX34Jwc8eYYXp3cjHPSxWbKA9/Da3im+CeY1kBV4gNoUQILiB/6YD0JNYFP4jMoGFINwZqhNHwKLbfB5ji9/kdMBss8vk9MBcs8pasht7GLSIzHOQfFX6kYApyFuw/FFz38jveJitxBqhI3QMsS2IiQMMFaDFOJHyF45fZECdYOqA0sIX5YMPkOk8Eyz96xpoJlnvIZDgduazEfZ+SJVyoNhqO1DWcUim1dfJf2DTxTVgxFY1Ql+kOtivDYSJhgTYX7cIx1EJgmDhSvEbuK12XAiHyYCxThCKsQHmiXCMHa5+5T+BBY74qVX39vcUIhbLBwSkUO7DonBsEqBCtFfBjoHt6+34mjLfg34HO4feL8mw0HqwKsr8XRwA2E9x3xYY693jzHvWfZCAdheAZnWG6+1wecKd4oDhFHicPFvm6WI4SXRBTijOcdzi3+BefkifPAehLoI14Pqcdt/1x3YyNb/BisYUC38PvbU3wivOQhGHWw+sK5ASjAltBB8D8MQe05NXCBBZOBCmxJXwbTa1RnsDbCL4Pgx5ajeeJjYDVXEZgDNSugVwhW44j1sw0GKwjrX4El2kGzHDqVwzvO1x3d2dhMsPbMgjnaRZGroW0OjHcY1VIYeZHNMoZ0nPFP5YDwIJwp5miWinwt3u7w5z3g4lS1sc2c1Tl+Z0eTRXBkLKy1PWp7Ga4EAthSXAhHn4BQa5vX2zUIX1rRBKvU0fluYDaUt3F5xa5rNmzFlsfurs5grYP52JK7AVZcpBxSCrU3wvPOPtgzuxsI1gG3l9/90Cfg6Ihm/ONRBssn3udyceNVftiBLXNnaAZHbxcLNZs6uPXMFJdHCF84OeICluIIfQTzHI3jrI0Q7Kwc4myaxEqDtR2VC9bCCMDvOliZ0NGCCv1pwb7XlEdmQCtgOVp2bYU69aojWB/AVUEIoWcpfNlMeQD4q/0bs/GL6IK1JR3Wd/T4/lxZDJloKTgMrzXzFqyDJfBNT+WB2dD2kO1Eb9AP/zrhAAQ+xhl/d7A6fm0U977qbvPzr3YYwlkqArnQ2f4swb8SZjZXDtkG3SxsWSZ6HRt9RJ+rYP0H/omWdVNUlGyDxpm2b/i4P1VHsPbYnhrl7IQ/NlNRAPzDfqnH0o4eg1UEEy+N8mkl3e1P378e4CFYFnzRR0VBVzgP26BumqyqANQX92JPhm5FOXC2uJXoKBR7KA3AQuzZLzaMcJbwqv2pfXFL5YKV8BFaCvfCH1uqKDgKA0NOgzUAGuRp7664eaUyxD3Qpkz7u0oWxDtY/aBhFhzUD7hBN6goeRVq7IRVNkEY4y1YRx5VBiiHV9ByaK77YOW+pwxQCn9DS/4W2Fy7SgCuEEPYM1ETkXriSsxwRLxQe7RhjyV2VVW4E+ofhj1oSe2rXDAaGhTBAbQM72vo6VDzHQXrX9r7nxeXwqrLYWcNoFa0hj8F7rQiTuAV5cHIFvEM1jvQ3UJH+QJliN9Dj5B2APkWuw/Wrm3QrJ6hZyC20q/C9qfDsgbOg1WYDxe2NbRtdfTfZbXKIbW9x8ns30QISA1xEmbZLDbRzJEdwp6hqgqvwVWWdt9avVy5ZDL8Wj9VstbYwUwpdALKbIP1HxhJRAJlwPfhHWWzATeJafpz9bd/H89grYBRaHn+RmWQn2CdZtsyYWhjd8HaPVQZZKl2HV7AgvEXOw9W1pvKIDNhGFrevbVKACY6vPLWSHN0NUp8ThxnyJfECzRHWSlermaugfvRcuQu5ZLv4F60pN+lDLIZUmyDtRRmklAUPBzPYK2GT3Vf4IX+jZRBPoYniUioHA62dx6sQAVkt1MGSYXeFjoC3ZwFKyRm/14Z5Bu4OKidZwsOqjL4P8eeZSqBAMZgz0JVhQ3wOhEpLoLxZyqXHIAJRKS0CF48UxnkWehnG6wj8BUJxfK3VZQUuAiW/ntRwa+UYR6BP+gH+f6eLoK1CQprKoMchIv82itNGfc5DFY+FLUyvG0NS2E/Ecl8qcrg/w573kuwYP0Re1JVFd7RfvAWbYIU1/vJ2/AhETm6HlJqKIOshA4hKNMGK5RwwZrygYFHqC9zGqygNlgrP4vBjQW7hcCKHKy9tzh/kGrqd8ow+6CVX3tPovShKkw2PKuJx2G4tKEyyF6o4YPvicjhN1UlHD5TcGKCBetG7Nkq/ixA+doPNv9i5YFp2iuE1mJlmBxoEbR7kKqVcMGaOU1FyTpYq7tbg/NgbZ+tDLMLrrcJVi9ViTLtKfsq48E6CK392p0m/e8qTDE8RUQOHobmRoN1AGqWQZomWFMq33FE/C/2vJBgwboee/aKdVzMfXk6U3hLGyyMBysXWga1i5jTBqmchAvWY31VlGRqbyrmn64qka8NVsj4/MZg6IPgNFj7tV9iLfsRVtdSBsmATgEIaoJ/mwqzEwYTkWABbDhLGSQdGpdp12OVPF7lmQO7HU1gJxDAI9izs+oRVlAfrH8rDyzQXoAp3gxv1zJ8UaVTSDtHec9f1bqEmXQPiuUfwjdR/Se8AG0sOEpEUidVmax8n4iU58OKFoaPsJ5xE6x/wwT9pPuTRifdv4K79Nv3QZdK8yb90PLazcogH8HlIQgQkfl3ewjWpwn2yLH92JOmhFgHqwSGE5EyH7x/vjJICtyjX362s6f6Cf6hX9tSOB8Q+SJGfimmQPogZYCXoD9avh1WZYAORcvwfsogpbDeTbBmwW1oKR6mDJIGs4iIvxAeOLPSpGxn/dGYb6rhJRdPod2jn7nGQ7DWaQJSQzxLbC22iqFtxDvE7ThjRTyCNQ1uRkvqYGWQQzBH/yV3+R7zFOhhab8u8smt6iThN1AjB1brd+rnuqhKvAFX6hdz/rgBLqqpDJAHPS2w3ARrIpzth2IiEtgBM+spA2RBB/33unZ9qyrxDNTLg52a11MAS842dDrYRH/XhfxDMLyhh2AViK0jBKum+K5YKObE0CLc8WE8gvUStC7XbtuWrXBeHWWA7tDJp933dq+F8TXVHmgS1M4LFByE/q3VScB826OrwG7IqqsqcQvU3Q+70OJ7SEVJO6iVARsQHAcrTBosR8vrI5UBNsBctBQ+raowD6ag5ev3lQFmw3i0FM9SgiZYOiJ+KHMs4iUkFk+aC5aeLbAULYERygBB+BIt+8ZV3lHfQ8uRRaDqqASmF7Sxf8xV6SR1AjbBi+gphPIuUcb0aQQvwfoWBqDFKoEXuqooWAID0RLyw56OJ7j319WAhZZp/aOMVTegDC0ZfaII1sdKA9CPxOLWeAVrMgy0Hxu9ukS5NvEh+33v6Q6VT4uusCCElpnz4Ie6KgFZAWctgTS0lPjg2osiLDRtFwAfWoKHIfRrj/NCI0oAr8EqgAZ+2IOew7DkWuWBAPQvBT9ayuZo5uUWo8cH6z1FKwTXFkEOWio2Q1ndKIJVILa1XYGeGBSJbeIVrObQKBPS0VJ6ABZ7itY2eLAIO1JmqKqUwzzsWQyB9soFwGliQxUj/HCNs/tiH5hqM2hfxxarCPwPughBI80dEBwHK3wUOMDCjtKjbp5GlAn1vodxgIWeoO6hDGvhmqDtzygXA0/BN3VcPF/yAYfzO3crQRcsE+uxgIfEUqqXVUowGyw9I2EY9uRCxV0uls803AwTsacC3uisqjIEOgMV2GLlAeMgdK7NbW47Aq+Ke8UD4jsw4yyDj9ruIE6yoAxbyo/AjjOVhnehVQFk4ojQIgjeDNSNsGO3FP9mwVYEE8FqAbUOwwoccWAh+HuCVS/C9jUNwZ+BNByx9A0Hd4+ciiOOpELx3WBFumNB/RD0smARjti5FPrVNBCsQvECB9G6SvwaZ/xX/F/xf8S5YojoGRXvYM2C+j7nT8uZC9ZvI42NhdA8AAOALTgi5TkVic/gSRxjFQDzxKFiH7G7eJv4WAhWBqCc49i/Gya2US7hWPzuFf8iTrBgGVCOY4r/5HCupF8AV2wDazrwiDhEfEFcKGYRxkiwwlRAeyAb5+wQPxFHioPEseLn4gEcU7gdLm3q4PubTVw+ay9DTBGfCm/bKPFTcSfOyYe+FyrBQLAQPnfzyHdxSvg1F4hlYrGYHn5d94hNTnC/qxK8Uya2Mx8se0rhCsCHc7aCVXnfG2fB/Ao4hGPSV8O59VQkfgs1N8IiYkrBK8oFW+E5wIdndkx2eVXkeYxgPljh7bs1CCHigr8Ull7u4r26pBgKiBtb7lCCqWCFeVi5AKgtthE7iL8QGykNwGi8s0AJcQ9WmOnwIHEjlAFp5yg7noNmQCoxo2yNizmWoUG8Yon73oOzayiXAG8mZLDC/BkGlIBFTMkvg1v6eFhPc31mzKNVIf59hBJiECy/2FvFCOCcKObBesQjWNV/8aFiPxR0cbNRrcVVxITgauWAR6H+IdiNZxZPinJdyKSEDFaY5dAXyCY2HIJPeyqPvAlXANuJDT74/n4leA6WPUfFXjEKVjMxD/css/m5c7TBMsgmGBGI2VG+9T0UtvdyI7cme+EzjPPhROWACmiJp0/q8lzY/RdlgFUwDCjGGFZYXbDSb3Fx4aGzH77DKBmrIPdCFSUcWyU9B6Pk7ICMbkowFiz9fNFAZRjgGjGIOwLi1UrDBv0R1hJlmCLoFYIMs2Mj8D74m6poAIZbkGtmg/gIbmumHLID5uOK0GzIb294B7skCAssoiYLsh6FdWIk/D7Ycp7LZ7vV2Qejifo9CmYBo+Hb2oY/jQcC+4iKUAnwCmxvrgTjwdLzttjS0L7U3uOjwqYoG3ZqF19WvKVigA/aBGEaUEFUWFsh+CeTa50uCMAUC3LwRioEb1MueRw62a/DsXLE9yH4GxVD/HCjBfPEYlxxdDsUjgPrbCXMprTF/oiPnip9QnnEgnND8KwFu3DHdigfC4vaqhgBNA3CwxZsxB37xFdhUSelQRssM+wVh4iNPb7+RhwLyhHcs8PJM/+OHLv1ziaOw5cNmReoGAL8KgRvBWC/5e4IZh2EBomnqVgQgrYWDBUXApkRlhaUillgLRVfFm8AaiqPAN3ENWJ+2ExxJVjTIHQXBM9UcSQI51twLzBNXMSxQOSIeeEdchNYC469dq6HH+qd4DYpvwA+FPeIeeF/86Ch9+g0C34HTBC/FLeK2eHfkyWmiZ+LT4rXhddqxYUQ1LLgGmCMOF/8QcwKb1tOeFsXhNcu3QyWLhDmg2XPLvFZ8UpRO8iAVuL14nPiTrxRLnZVDjkC54TveJAfdjEcuUzFia/h9FLoDbwifhV+3XmVxsZ6cQ5Yj0LoChVPkI0TO4q9wLoDuFW8TrxAbGr4d9WwoAVwhni6SiCA+mIzsXn4/6S2i3/bIPzvasdw++pW2r6mYi2VIAC1wtvUPLyNdZUGz8EyTyj8s78KnzK+LL4kviPOE9eJeUTP/R5f+xlgnZEIY8OC5pXGRg2VJEkSTbBOXsaoJEmSJIN1EvC0SvJ/7dSxigEAHIDxf5FisAilbNbbWGSi5OZ7BgZPcS/hAZTNwFtYSVltit2iU/c9gxv8u75ffa/wSQ4ruR+ahySHldyZJiHJYSW3pnZIcliJHekrJDmsxMM60JQqIUkJh3WhJX1SKSTpzcN60J1udKQNfdOIaiFJLwxrTwua0Zj61PtjXfqgDtWpEJL04rCetKKBM5GUeVgnGoYkJR1Wka60o0ZIUuJhVWlLzZCk5MMqUyv0r/wCSDD/4sxS1q8AAAAASUVORK5CYII=">>.
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index 97738355..346dc41c 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -5,7 +5,7 @@
%%% Created :
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -43,8 +43,10 @@
%% request_handlers callbacks
-export([process/2]).
-%% ejabberd_hooks callbacks
--export([reopen_log/1]).
+%% utility for other http modules
+-export([content_type/3]).
+
+-export([reopen_log/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -86,8 +88,6 @@
{<<".xpi">>, <<"application/x-xpinstall">>},
{<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]).
--compile(export_all).
-
%%====================================================================
%% gen_mod callbacks
%%====================================================================
@@ -168,10 +168,17 @@ initialize(Host, Opts) ->
?DEFAULT_CONTENT_TYPE),
ContentTypes = build_list_content_types(
gen_mod:get_opt(content_types, Opts,
- fun(L) when is_list(L) -> L end,
- []),
+ fun(L) when is_list(L) ->
+ lists:map(
+ fun({K, V}) ->
+ {iolist_to_binary(K),
+ iolist_to_binary(V)}
+ end, L)
+ end, []),
?DEFAULT_CONTENT_TYPES),
- ?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
+ ?INFO_MSG("known content types: ~s",
+ [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes],
+ <<", ">>)]),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
CustomHeaders, DefaultContentType, ContentTypes}.
@@ -448,3 +455,17 @@ ip_to_string(Address) when size(Address) == 4 ->
ip_to_string(Address) when size(Address) == 8 ->
Parts = lists:map(fun (Int) -> io_lib:format("~.16B", [Int]) end, tuple_to_list(Address)),
string:to_lower(lists:flatten(join(Parts, ":"))).
+
+mod_opt_type(accesslog) -> fun iolist_to_binary/1;
+mod_opt_type(content_types) ->
+ fun (L) when is_list(L) -> L end;
+mod_opt_type(custom_headers) ->
+ fun (L) when is_list(L) -> L end;
+mod_opt_type(default_content_type) ->
+ fun iolist_to_binary/1;
+mod_opt_type(directory_indices) ->
+ fun (L) when is_list(L) -> L end;
+mod_opt_type(docroot) -> fun (A) -> A end;
+mod_opt_type(_) ->
+ [accesslog, content_types, custom_headers,
+ default_content_type, directory_indices, docroot].
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
new file mode 100644
index 00000000..6c029c43
--- /dev/null
+++ b/src/mod_http_upload.erl
@@ -0,0 +1,1043 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_http_upload.erl
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Purpose : HTTP File Upload (XEP-0363)
+%%% Created : 20 Aug 2015 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2015-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(mod_http_upload).
+-author('holger@zedat.fu-berlin.de').
+
+-protocol({xep, 363, '0.1'}).
+
+-define(GEN_SERVER, gen_server).
+-define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds.
+-define(SLOT_TIMEOUT, 18000000). % 5 hours.
+-define(PROCNAME, ?MODULE).
+-define(FORMAT(Error), file:format_error(Error)).
+-define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))).
+-define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(jlib:ip_to_list(IP))).
+-define(STR_TO_INT(Str, B), jlib:binary_to_integer(iolist_to_binary(Str), B)).
+-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
+-define(CONTENT_TYPES,
+ [{<<".avi">>, <<"video/avi">>},
+ {<<".bmp">>, <<"image/bmp">>},
+ {<<".bz2">>, <<"application/x-bzip2">>},
+ {<<".gif">>, <<"image/gif">>},
+ {<<".gz">>, <<"application/x-gzip">>},
+ {<<".jpeg">>, <<"image/jpeg">>},
+ {<<".jpg">>, <<"image/jpeg">>},
+ {<<".mp3">>, <<"audio/mpeg">>},
+ {<<".mp4">>, <<"video/mp4">>},
+ {<<".mpeg">>, <<"video/mpeg">>},
+ {<<".mpg">>, <<"video/mpeg">>},
+ {<<".ogg">>, <<"application/ogg">>},
+ {<<".pdf">>, <<"application/pdf">>},
+ {<<".png">>, <<"image/png">>},
+ {<<".rtf">>, <<"application/rtf">>},
+ {<<".svg">>, <<"image/svg+xml">>},
+ {<<".tiff">>, <<"image/tiff">>},
+ {<<".txt">>, <<"text/plain">>},
+ {<<".wav">>, <<"audio/wav">>},
+ {<<".webp">>, <<"image/webp">>},
+ {<<".xz">>, <<"application/x-xz">>},
+ {<<".zip">>, <<"application/zip">>}]).
+
+-behaviour(?GEN_SERVER).
+-behaviour(gen_mod).
+
+%% gen_mod/supervisor callbacks.
+-export([start_link/3,
+ start/2,
+ stop/1,
+ mod_opt_type/1]).
+
+%% gen_server callbacks.
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% ejabberd_http callback.
+-export([process/2]).
+
+%% ejabberd_hooks callback.
+-export([remove_user/2]).
+
+%% Utility functions.
+-export([get_proc_name/2,
+ expand_home/1,
+ expand_host/2]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_http.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+
+-record(state,
+ {server_host :: binary(),
+ host :: binary(),
+ name :: binary(),
+ access :: atom(),
+ max_size :: pos_integer() | infinity,
+ secret_length :: pos_integer(),
+ jid_in_url :: sha1 | node,
+ file_mode :: integer() | undefined,
+ dir_mode :: integer() | undefined,
+ docroot :: binary(),
+ put_url :: binary(),
+ get_url :: binary(),
+ service_url :: binary() | undefined,
+ thumbnail :: boolean(),
+ slots = #{} :: map()}).
+
+-record(media_info,
+ {type :: binary(),
+ height :: integer(),
+ width :: integer()}).
+
+-type state() :: #state{}.
+-type slot() :: [binary(), ...].
+-type media_info() :: #media_info{}.
+
+%%--------------------------------------------------------------------
+%% gen_mod/supervisor callbacks.
+%%--------------------------------------------------------------------
+
+-spec start_link(binary(), atom(), gen_mod:opts())
+ -> {ok, pid()} | ignore | {error, _}.
+
+start_link(ServerHost, Proc, Opts) ->
+ ?GEN_SERVER:start_link({local, Proc}, ?MODULE, {ServerHost, Opts}, []).
+
+-spec start(binary(), gen_mod:opts()) -> {ok, _} | {ok, _, _} | {error, _}.
+
+start(ServerHost, Opts) ->
+ case gen_mod:get_opt(rm_on_unregister, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true) of
+ true ->
+ ejabberd_hooks:add(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE,
+ remove_user, 50);
+ false ->
+ ok
+ end,
+ Proc = get_proc_name(ServerHost, ?PROCNAME),
+ Spec = {Proc,
+ {?MODULE, start_link, [ServerHost, Proc, Opts]},
+ permanent,
+ 3000,
+ worker,
+ [?MODULE]},
+ supervisor:start_child(ejabberd_sup, Spec).
+
+-spec stop(binary()) -> ok.
+
+stop(ServerHost) ->
+ case gen_mod:get_module_opt(ServerHost, ?MODULE, rm_on_unregister,
+ fun(B) when is_boolean(B) -> B end,
+ true) of
+ true ->
+ ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE,
+ remove_user, 50);
+ false ->
+ ok
+ end,
+ Proc = get_proc_name(ServerHost, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
+
+mod_opt_type(host) ->
+ fun iolist_to_binary/1;
+mod_opt_type(name) ->
+ fun iolist_to_binary/1;
+mod_opt_type(access) ->
+ fun(A) when is_atom(A) -> A end;
+mod_opt_type(max_size) ->
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(secret_length) ->
+ fun(I) when is_integer(I), I >= 8 -> I end;
+mod_opt_type(jid_in_url) ->
+ fun(sha1) -> sha1;
+ (node) -> node
+ end;
+mod_opt_type(file_mode) ->
+ fun(Mode) -> ?STR_TO_INT(Mode, 8) end;
+mod_opt_type(dir_mode) ->
+ fun(Mode) -> ?STR_TO_INT(Mode, 8) end;
+mod_opt_type(docroot) ->
+ fun iolist_to_binary/1;
+mod_opt_type(put_url) ->
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end;
+mod_opt_type(get_url) ->
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end;
+mod_opt_type(service_url) ->
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end;
+mod_opt_type(custom_headers) ->
+ fun(Headers) ->
+ lists:map(fun({K, V}) ->
+ {iolist_to_binary(K), iolist_to_binary(V)}
+ end, Headers)
+ end;
+mod_opt_type(rm_on_unregister) ->
+ fun(B) when is_boolean(B) -> B end;
+mod_opt_type(thumbnail) ->
+ fun(B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [host, name, access, max_size, secret_length, jid_in_url, file_mode,
+ dir_mode, docroot, put_url, get_url, service_url, custom_headers,
+ rm_on_unregister, thumbnail].
+
+%%--------------------------------------------------------------------
+%% gen_server callbacks.
+%%--------------------------------------------------------------------
+
+-spec init({binary(), gen_mod:opts()}) -> {ok, state()}.
+
+init({ServerHost, Opts}) ->
+ process_flag(trap_exit, true),
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>),
+ Name = gen_mod:get_opt(name, Opts,
+ fun iolist_to_binary/1,
+ <<"HTTP File Upload">>),
+ Access = gen_mod:get_opt(access, Opts,
+ fun(A) when is_atom(A) -> A end,
+ local),
+ MaxSize = gen_mod:get_opt(max_size, Opts,
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end,
+ 104857600),
+ SecretLength = gen_mod:get_opt(secret_length, Opts,
+ fun(I) when is_integer(I), I >= 8 -> I end,
+ 40),
+ JIDinURL = gen_mod:get_opt(jid_in_url, Opts,
+ fun(sha1) -> sha1;
+ (node) -> node
+ end,
+ sha1),
+ DocRoot = gen_mod:get_opt(docroot, Opts,
+ fun iolist_to_binary/1,
+ <<"@HOME@/upload">>),
+ FileMode = gen_mod:get_opt(file_mode, Opts,
+ fun(Mode) -> ?STR_TO_INT(Mode, 8) end),
+ DirMode = gen_mod:get_opt(dir_mode, Opts,
+ fun(Mode) -> ?STR_TO_INT(Mode, 8) end),
+ PutURL = gen_mod:get_opt(put_url, Opts,
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end,
+ <<"http://@HOST@:5444">>),
+ GetURL = gen_mod:get_opt(get_url, Opts,
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end,
+ PutURL),
+ ServiceURL = gen_mod:get_opt(service_url, Opts,
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL
+ end),
+ Thumbnail = gen_mod:get_opt(thumbnail, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true),
+ DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
+ DocRoot2 = expand_host(DocRoot1, ServerHost),
+ case ServiceURL of
+ undefined ->
+ ok;
+ <<"http://", _/binary>> ->
+ application:start(inets);
+ <<"https://", _/binary>> ->
+ application:start(inets),
+ application:start(crypto),
+ application:start(asn1),
+ application:start(public_key),
+ application:start(ssl)
+ end,
+ case DirMode of
+ undefined ->
+ ok;
+ Mode ->
+ file:change_mode(DocRoot2, Mode)
+ end,
+ case Thumbnail of
+ true ->
+ case string:str(os:cmd("identify"), "Magick") of
+ 0 ->
+ ?ERROR_MSG("Cannot find 'identify' command, please install "
+ "ImageMagick or disable thumbnail creation", []);
+ _ ->
+ ok
+ end;
+ false ->
+ ok
+ end,
+ ejabberd_router:register_route(Host, ServerHost),
+ {ok, #state{server_host = ServerHost, host = Host, name = Name,
+ access = Access, max_size = MaxSize,
+ secret_length = SecretLength, jid_in_url = JIDinURL,
+ file_mode = FileMode, dir_mode = DirMode,
+ thumbnail = Thumbnail,
+ docroot = DocRoot2,
+ put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
+ get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
+ service_url = ServiceURL}}.
+
+-spec handle_call(_, {pid(), _}, state())
+ -> {reply, {ok, pos_integer(), binary(),
+ pos_integer() | undefined,
+ pos_integer() | undefined}, state()} |
+ {reply, {error, binary()}, state()} | {noreply, state()}.
+
+handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode,
+ dir_mode = DirMode,
+ get_url = GetPrefix,
+ thumbnail = Thumbnail,
+ docroot = DocRoot} = State) ->
+ case get_slot(Slot, State) of
+ {ok, {Size, Timer}} ->
+ timer:cancel(Timer),
+ NewState = del_slot(Slot, State),
+ Path = str:join([DocRoot | Slot], <<$/>>),
+ {reply, {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail},
+ NewState};
+ error ->
+ {reply, {error, <<"Invalid slot">>}, State}
+ end;
+handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
+ {reply, {ok, DocRoot}, State};
+handle_call(Request, From, State) ->
+ ?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
+ {noreply, State}.
+
+-spec handle_cast(_, state()) -> {noreply, state()}.
+
+handle_cast(Request, State) ->
+ ?ERROR_MSG("Got unexpected request: ~p", [Request]),
+ {noreply, State}.
+
+-spec handle_info(timeout | _, state()) -> {noreply, state()}.
+
+handle_info({route, From, To, #xmlel{name = <<"iq">>} = Stanza}, State) ->
+ Request = jlib:iq_query_info(Stanza),
+ {Reply, NewState} = case process_iq(From, Request, State) of
+ R when is_record(R, iq) ->
+ {R, State};
+ {R, S} ->
+ {R, S};
+ not_request ->
+ {none, State}
+ end,
+ if Reply /= none ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Reply));
+ true ->
+ ok
+ end,
+ {noreply, NewState};
+handle_info({slot_timed_out, Slot}, State) ->
+ NewState = del_slot(Slot, State),
+ {noreply, NewState};
+handle_info(Info, State) ->
+ ?ERROR_MSG("Got unexpected info: ~p", [Info]),
+ {noreply, State}.
+
+-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+
+terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
+ ?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
+ ejabberd_router:unregister_route(Host),
+ ok.
+
+-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
+
+code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
+ ?DEBUG("Updating HTTP upload process for ~s", [ServerHost]),
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%% ejabberd_http callback.
+%%--------------------------------------------------------------------
+
+-spec process([binary()], #request{})
+ -> {pos_integer(), [{binary(), binary()}], binary()}.
+
+process(LocalPath, #request{method = Method, host = Host, ip = IP})
+ when length(LocalPath) < 3,
+ Method == 'PUT' orelse
+ Method == 'GET' orelse
+ Method == 'HEAD' ->
+ ?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
+ [Method, ?ADDR_TO_STR(IP), Host]),
+ http_response(Host, 404);
+process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
+ data = Data} = Request) ->
+ {Proc, Slot} = parse_http_request(Request),
+ case catch gen_server:call(Proc, {use_slot, Slot}) of
+ {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail}
+ when byte_size(Data) == Size ->
+ ?DEBUG("Storing file from ~s for ~s: ~s",
+ [?ADDR_TO_STR(IP), Host, Path]),
+ case store_file(Path, Data, FileMode, DirMode,
+ GetPrefix, Slot, Thumbnail) of
+ ok ->
+ http_response(Host, 201);
+ {ok, Headers, OutData} ->
+ http_response(Host, 201, Headers, OutData);
+ {error, Error} ->
+ ?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p",
+ [Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
+ http_response(Host, 500)
+ end;
+ {ok, Size, Path, _FileMode, _DirMode, _GetPrefix, _Thumbnail} ->
+ ?INFO_MSG("Rejecting file ~s from ~s for ~s: Size is ~B, not ~B",
+ [Path, ?ADDR_TO_STR(IP), Host, byte_size(Data), Size]),
+ http_response(Host, 413);
+ {error, Error} ->
+ ?INFO_MSG("Rejecting file from ~s for ~s: ~p",
+ [?ADDR_TO_STR(IP), Host, Error]),
+ http_response(Host, 403);
+ Error ->
+ ?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
+ [?ADDR_TO_STR(IP), Host, Error]),
+ http_response(Host, 500)
+ end;
+process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
+ when Method == 'GET';
+ Method == 'HEAD' ->
+ {Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
+ case catch gen_server:call(Proc, get_docroot) of
+ {ok, DocRoot} ->
+ Path = str:join([DocRoot | Slot], <<$/>>),
+ case file:read_file(Path) of
+ {ok, Data} ->
+ ?INFO_MSG("Serving ~s to ~s", [Path, ?ADDR_TO_STR(IP)]),
+ ContentType = guess_content_type(FileName),
+ Headers1 = case ContentType of
+ <<"image/", _SubType/binary>> -> [];
+ <<"text/", _SubType/binary>> -> [];
+ _ ->
+ [{<<"Content-Disposition">>,
+ <<"attachment; filename=",
+ $", FileName/binary, $">>}]
+ end,
+ Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
+ http_response(Host, 200, Headers2, Data);
+ {error, eacces} ->
+ ?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
+ [Path, ?ADDR_TO_STR(IP)]),
+ http_response(Host, 403);
+ {error, enoent} ->
+ ?INFO_MSG("Cannot serve ~s to ~s: No such file",
+ [Path, ?ADDR_TO_STR(IP)]),
+ http_response(Host, 404);
+ {error, eisdir} ->
+ ?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
+ [Path, ?ADDR_TO_STR(IP)]),
+ http_response(Host, 404);
+ {error, Error} ->
+ ?INFO_MSG("Cannot serve ~s to ~s: ~s",
+ [Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]),
+ http_response(Host, 500)
+ end;
+ Error ->
+ ?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
+ [Method, ?ADDR_TO_STR(IP), Host, Error]),
+ http_response(Host, 500)
+ end;
+process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) ->
+ ?DEBUG("Responding to OPTIONS request from ~s for ~s",
+ [?ADDR_TO_STR(IP), Host]),
+ http_response(Host, 200);
+process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
+ ?DEBUG("Rejecting ~s request from ~s for ~s",
+ [Method, ?ADDR_TO_STR(IP), Host]),
+ http_response(Host, 405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
+
+%%--------------------------------------------------------------------
+%% Exported utility functions.
+%%--------------------------------------------------------------------
+
+-spec get_proc_name(binary(), atom()) -> atom().
+
+get_proc_name(ServerHost, ModuleName) ->
+ PutURL = gen_mod:get_module_opt(ServerHost, ?MODULE, put_url,
+ fun(<<"http://", _/binary>> = URL) -> URL;
+ (<<"https://", _/binary>> = URL) -> URL;
+ (_) -> <<"http://@HOST@">>
+ end,
+ <<"http://@HOST@">>),
+ {ok, {_Scheme, _UserInfo, Host, _Port, Path, _Query}} =
+ http_uri:parse(binary_to_list(expand_host(PutURL, ServerHost))),
+ ProcPrefix = list_to_binary(string:strip(Host ++ Path, right, $/)),
+ gen_mod:get_module_proc(ProcPrefix, ModuleName).
+
+-spec expand_home(binary()) -> binary().
+
+expand_home(Subject) ->
+ {ok, [[Home]]} = init:get_argument(home),
+ Parts = binary:split(Subject, <<"@HOME@">>, [global]),
+ str:join(Parts, list_to_binary(Home)).
+
+-spec expand_host(binary(), binary()) -> binary().
+
+expand_host(Subject, Host) ->
+ Parts = binary:split(Subject, <<"@HOST@">>, [global]),
+ str:join(Parts, Host).
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+
+%% XMPP request handling.
+
+-spec process_iq(jid(), iq_request() | reply | invalid, state())
+ -> {iq_reply(), state()} | iq_reply() | not_request.
+
+process_iq(_From,
+ #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
+ #state{server_host = ServerHost, name = Name}) ->
+ AddInfo = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
+ [ServerHost, ?MODULE, <<"">>, <<"">>]),
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
+ children = iq_disco_info(ServerHost, Lang, Name)
+ ++ AddInfo}]};
+process_iq(From,
+ #iq{type = get, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ,
+ #state{server_host = ServerHost, access = Access} = State)
+ when XMLNS == ?NS_HTTP_UPLOAD;
+ XMLNS == ?NS_HTTP_UPLOAD_OLD ->
+ case acl:match_rule(ServerHost, Access, From) of
+ allow ->
+ case parse_request(SubEl, Lang) of
+ {ok, File, Size, ContentType} ->
+ case create_slot(State, From, File, Size, ContentType,
+ Lang) of
+ {ok, Slot} ->
+ {ok, Timer} = timer:send_after(?SLOT_TIMEOUT,
+ {slot_timed_out,
+ Slot}),
+ NewState = add_slot(Slot, Size, Timer, State),
+ SlotEl = slot_el(Slot, State, XMLNS),
+ {IQ#iq{type = result, sub_el = [SlotEl]}, NewState};
+ {ok, PutURL, GetURL} ->
+ SlotEl = slot_el(PutURL, GetURL, XMLNS),
+ IQ#iq{type = result, sub_el = [SlotEl]};
+ {error, Error} ->
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end;
+ {error, Error} ->
+ ?DEBUG("Cannot parse request from ~s",
+ [jid:to_string(From)]),
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end;
+ deny ->
+ ?DEBUG("Denying HTTP upload slot request from ~s",
+ [jid:to_string(From)]),
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ end;
+process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+process_iq(_From, reply, _State) ->
+ not_request;
+process_iq(_From, invalid, _State) ->
+ not_request.
+
+-spec parse_request(xmlel(), binary())
+ -> {ok, binary(), pos_integer(), binary()} | {error, xmlel()}.
+
+parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
+ {value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD;
+ XMLNS == ?NS_HTTP_UPLOAD_OLD ->
+ case {fxml:get_subtag_cdata(Request, <<"filename">>),
+ fxml:get_subtag_cdata(Request, <<"size">>),
+ fxml:get_subtag_cdata(Request, <<"content-type">>)} of
+ {File, SizeStr, ContentType} when byte_size(File) > 0 ->
+ case catch jlib:binary_to_integer(SizeStr) of
+ Size when is_integer(Size), Size > 0 ->
+ {ok, File, Size, yield_content_type(ContentType)};
+ _ ->
+ Text = <<"Please specify file size.">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Text)}
+ end;
+ _ ->
+ Text = <<"Please specify file name.">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Text)}
+ end;
+ _ ->
+ {error, ?ERR_BAD_REQUEST}
+ end;
+parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
+
+-spec create_slot(state(), jid(), binary(), pos_integer(), binary(), binary())
+ -> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}.
+
+create_slot(#state{service_url = undefined, max_size = MaxSize},
+ JID, File, Size, _ContentType, Lang) when MaxSize /= infinity,
+ Size > MaxSize ->
+ Text = <<"File larger than ", (jlib:integer_to_binary(MaxSize))/binary,
+ " Bytes.">>,
+ ?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
+ [File, jid:to_string(JID), Size]),
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, Text)};
+create_slot(#state{service_url = undefined,
+ jid_in_url = JIDinURL,
+ secret_length = SecretLength,
+ server_host = ServerHost,
+ docroot = DocRoot},
+ JID, File, Size, _ContentType, Lang) ->
+ UserStr = make_user_string(JID, JIDinURL),
+ UserDir = <<DocRoot/binary, $/, UserStr/binary>>,
+ case ejabberd_hooks:run_fold(http_upload_slot_request, ServerHost, allow,
+ [JID, UserDir, Size, Lang]) of
+ allow ->
+ RandStr = make_rand_string(SecretLength),
+ FileStr = make_file_string(File),
+ ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
+ [jid:to_string(JID), File]),
+ {ok, [UserStr, RandStr, FileStr]};
+ deny ->
+ {error, ?ERR_SERVICE_UNAVAILABLE};
+ #xmlel{} = Error ->
+ {error, Error}
+ end;
+create_slot(#state{service_url = ServiceURL},
+ #jid{luser = U, lserver = S} = JID, File, Size, ContentType,
+ _Lang) ->
+ Options = [{body_format, binary}, {full_result, false}],
+ HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
+ SizeStr = jlib:integer_to_binary(Size),
+ GetRequest = binary_to_list(ServiceURL) ++
+ "?jid=" ++ ?URL_ENC(jid:to_string({U, S, <<"">>})) ++
+ "&name=" ++ ?URL_ENC(File) ++
+ "&size=" ++ ?URL_ENC(SizeStr) ++
+ "&content_type=" ++ ?URL_ENC(ContentType),
+ case httpc:request(get, {GetRequest, []}, HttpOptions, Options) of
+ {ok, {Code, Body}} when Code >= 200, Code =< 299 ->
+ case binary:split(Body, <<$\n>>, [global, trim]) of
+ [<<"http", _/binary>> = PutURL,
+ <<"http", _/binary>> = GetURL] ->
+ ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
+ [jid:to_string(JID), File]),
+ {ok, PutURL, GetURL};
+ Lines ->
+ ?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
+ [jid:to_string(JID), ServiceURL, Lines]),
+ {error, ?ERR_SERVICE_UNAVAILABLE}
+ end;
+ {ok, {402, _Body}} ->
+ ?INFO_MSG("Got status code 402 for ~s from <~s>",
+ [jid:to_string(JID), ServiceURL]),
+ {error, ?ERR_RESOURCE_CONSTRAINT};
+ {ok, {403, _Body}} ->
+ ?INFO_MSG("Got status code 403 for ~s from <~s>",
+ [jid:to_string(JID), ServiceURL]),
+ {error, ?ERR_NOT_ALLOWED};
+ {ok, {413, _Body}} ->
+ ?INFO_MSG("Got status code 413 for ~s from <~s>",
+ [jid:to_string(JID), ServiceURL]),
+ {error, ?ERR_NOT_ACCEPTABLE};
+ {ok, {Code, _Body}} ->
+ ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B",
+ [jid:to_string(JID), ServiceURL, Code]),
+ {error, ?ERR_SERVICE_UNAVAILABLE};
+ {error, Reason} ->
+ ?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p",
+ [jid:to_string(JID), ServiceURL, Reason]),
+ {error, ?ERR_SERVICE_UNAVAILABLE}
+ end.
+
+-spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state().
+
+add_slot(Slot, Size, Timer, #state{slots = Slots} = State) ->
+ NewSlots = maps:put(Slot, {Size, Timer}, Slots),
+ State#state{slots = NewSlots}.
+
+-spec get_slot(slot(), state()) -> {ok, {pos_integer(), timer:tref()}} | error.
+
+get_slot(Slot, #state{slots = Slots}) ->
+ maps:find(Slot, Slots).
+
+-spec del_slot(slot(), state()) -> state().
+
+del_slot(Slot, #state{slots = Slots} = State) ->
+ NewSlots = maps:remove(Slot, Slots),
+ State#state{slots = NewSlots}.
+
+-spec slot_el(slot() | binary(), state() | binary(), binary()) -> xmlel().
+
+slot_el(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) ->
+ PutURL = str:join([PutPrefix | Slot], <<$/>>),
+ GetURL = str:join([GetPrefix | Slot], <<$/>>),
+ slot_el(PutURL, GetURL, XMLNS);
+slot_el(PutURL, GetURL, XMLNS) ->
+ #xmlel{name = <<"slot">>,
+ attrs = [{<<"xmlns">>, XMLNS}],
+ children = [#xmlel{name = <<"put">>,
+ children = [{xmlcdata, PutURL}]},
+ #xmlel{name = <<"get">>,
+ children = [{xmlcdata, GetURL}]}]}.
+
+-spec make_user_string(jid(), sha1 | node) -> binary().
+
+make_user_string(#jid{luser = U, lserver = S}, sha1) ->
+ p1_sha:sha(<<U/binary, $@, S/binary>>);
+make_user_string(#jid{luser = U}, node) ->
+ re:replace(U, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
+
+-spec make_file_string(binary()) -> binary().
+
+make_file_string(File) ->
+ re:replace(File, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
+
+-spec make_rand_string(non_neg_integer()) -> binary().
+
+make_rand_string(Length) ->
+ list_to_binary(make_rand_string([], Length)).
+
+-spec make_rand_string(string(), non_neg_integer()) -> string().
+
+make_rand_string(S, 0) -> S;
+make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1).
+
+-spec make_rand_char() -> char().
+
+make_rand_char() ->
+ map_int_to_char(crypto:rand_uniform(0, 62)).
+
+-spec map_int_to_char(0..61) -> char().
+
+map_int_to_char(N) when N =< 9 -> N + 48; % Digit.
+map_int_to_char(N) when N =< 35 -> N + 55; % Upper-case character.
+map_int_to_char(N) when N =< 61 -> N + 61. % Lower-case character.
+
+-spec yield_content_type(binary()) -> binary().
+
+yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
+yield_content_type(Type) -> Type.
+
+-spec iq_disco_info(binary(), binary(), binary()) -> [xmlel()].
+
+iq_disco_info(Host, Lang, Name) ->
+ Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size,
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end,
+ 104857600) of
+ infinity ->
+ [];
+ MaxSize ->
+ MaxSizeStr = jlib:integer_to_binary(MaxSize),
+ Fields = [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"FORM_TYPE">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ ?NS_HTTP_UPLOAD}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"max-file-size">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ MaxSizeStr}]}]}],
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = Fields}]
+ end,
+ [#xmlel{name = <<"identity">>,
+ attrs = [{<<"category">>, <<"store">>},
+ {<<"type">>, <<"file">>},
+ {<<"name">>, translate:translate(Lang, Name)}]},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_HTTP_UPLOAD}]},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]} | Form].
+
+%% HTTP request handling.
+
+-spec parse_http_request(#request{}) -> {atom(), slot()}.
+
+parse_http_request(#request{host = Host, path = Path}) ->
+ PrefixLength = length(Path) - 3,
+ {ProcURL, Slot} = if PrefixLength > 0 ->
+ Prefix = lists:sublist(Path, PrefixLength),
+ {str:join([Host | Prefix], $/),
+ lists:nthtail(PrefixLength, Path)};
+ true ->
+ {Host, Path}
+ end,
+ {gen_mod:get_module_proc(ProcURL, ?PROCNAME), Slot}.
+
+-spec store_file(binary(), binary(),
+ integer() | undefined,
+ integer() | undefined,
+ binary(), slot(), boolean())
+ -> ok | {ok, [{binary(), binary()}], binary()} | {error, term()}.
+
+store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
+ case do_store_file(Path, Data, FileMode, DirMode) of
+ ok when Thumbnail ->
+ case identify(Path) of
+ {ok, MediaInfo} ->
+ case convert(Path, MediaInfo) of
+ {ok, OutPath} ->
+ [UserDir, RandDir | _] = Slot,
+ FileName = filename:basename(OutPath),
+ URL = str:join([GetPrefix, UserDir,
+ RandDir, FileName], <<$/>>),
+ ThumbEl = thumb_el(OutPath, URL),
+ {ok,
+ [{<<"Content-Type">>,
+ <<"text/xml; charset=utf-8">>}],
+ fxml:element_to_binary(ThumbEl)};
+ pass ->
+ ok
+ end;
+ pass ->
+ ok
+ end;
+ ok ->
+ ok;
+ Err ->
+ Err
+ end.
+
+-spec do_store_file(file:filename_all(), binary(),
+ integer() | undefined,
+ integer() | undefined)
+ -> ok | {error, term()}.
+
+do_store_file(Path, Data, FileMode, DirMode) ->
+ try
+ ok = filelib:ensure_dir(Path),
+ {ok, Io} = file:open(Path, [write, exclusive, raw]),
+ Ok = file:write(Io, Data),
+ ok = file:close(Io),
+ if is_integer(FileMode) ->
+ ok = file:change_mode(Path, FileMode);
+ FileMode == undefined ->
+ ok
+ end,
+ if is_integer(DirMode) ->
+ RandDir = filename:dirname(Path),
+ UserDir = filename:dirname(RandDir),
+ ok = file:change_mode(RandDir, DirMode),
+ ok = file:change_mode(UserDir, DirMode);
+ DirMode == undefined ->
+ ok
+ end,
+ ok = Ok % Raise an exception if file:write/2 failed.
+ catch
+ _:{badmatch, {error, Error}} ->
+ {error, Error};
+ _:Error ->
+ {error, Error}
+ end.
+
+-spec guess_content_type(binary()) -> binary().
+
+guess_content_type(FileName) ->
+ mod_http_fileserver:content_type(FileName,
+ ?DEFAULT_CONTENT_TYPE,
+ ?CONTENT_TYPES).
+
+-spec http_response(binary(), 100..599)
+ -> {pos_integer(), [{binary(), binary()}], binary()}.
+
+http_response(Host, Code) ->
+ http_response(Host, Code, []).
+
+-spec http_response(binary(), 100..599, [{binary(), binary()}])
+ -> {pos_integer(), [{binary(), binary()}], binary()}.
+
+http_response(Host, Code, ExtraHeaders) ->
+ Message = <<(code_to_message(Code))/binary, $\n>>,
+ http_response(Host, Code, ExtraHeaders, Message).
+
+-spec http_response(binary(), 100..599, [{binary(), binary()}], binary())
+ -> {pos_integer(), [{binary(), binary()}], binary()}.
+
+http_response(Host, Code, ExtraHeaders, Body) ->
+ ServerHeader = {<<"Server">>, <<"ejabberd ", (?VERSION)/binary>>},
+ CustomHeaders =
+ gen_mod:get_module_opt(Host, ?MODULE, custom_headers,
+ fun(Headers) ->
+ lists:map(fun({K, V}) ->
+ {iolist_to_binary(K),
+ iolist_to_binary(V)}
+ end, Headers)
+ end,
+ []),
+ Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
+ true ->
+ [ServerHeader | ExtraHeaders];
+ false ->
+ [ServerHeader, {<<"Content-Type">>, <<"text/plain">>} |
+ ExtraHeaders]
+ end ++ CustomHeaders,
+ {Code, Headers, Body}.
+
+-spec code_to_message(100..599) -> binary().
+
+code_to_message(201) -> <<"Upload successful.">>;
+code_to_message(403) -> <<"Forbidden.">>;
+code_to_message(404) -> <<"Not found.">>;
+code_to_message(405) -> <<"Method not allowed.">>;
+code_to_message(413) -> <<"File size doesn't match requested size.">>;
+code_to_message(500) -> <<"Internal server error.">>;
+code_to_message(_Code) -> <<"">>.
+
+%%--------------------------------------------------------------------
+%% Image manipulation stuff.
+%%--------------------------------------------------------------------
+
+-spec identify(binary()) -> {ok, media_info()} | pass.
+
+identify(Path) ->
+ Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]),
+ Res = string:strip(os:cmd(Cmd), right, $\n),
+ case string:tokens(Res, " ") of
+ ["ok", T, H, W] ->
+ {ok, #media_info{type = list_to_binary(string:to_lower(T)),
+ height = list_to_integer(H),
+ width = list_to_integer(W)}};
+ _ ->
+ ?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]),
+ pass
+ end.
+
+-spec convert(binary(), media_info()) -> {ok, binary()} | pass.
+
+convert(Path, #media_info{type = T, width = W, height = H}) ->
+ if W * H >= 25000000 ->
+ ?DEBUG("The image ~s is more than 25 Mpix", [Path]),
+ pass;
+ W =< 300, H =< 300 ->
+ {ok, Path};
+ T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> ->
+ Dir = filename:dirname(Path),
+ FileName = <<(randoms:get_string())/binary, $., T/binary>>,
+ OutPath = filename:join(Dir, FileName),
+ Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]),
+ case os:cmd(Cmd) of
+ "" ->
+ {ok, OutPath};
+ Err ->
+ ?ERROR_MSG("Failed to convert ~s to ~s: ~s",
+ [Path, OutPath, string:strip(Err, right, $\n)]),
+ pass
+ end;
+ true ->
+ ?DEBUG("Won't call 'convert' for unknown type ~s", [T]),
+ pass
+ end.
+
+-spec thumb_el(binary(), binary()) -> xmlel().
+
+thumb_el(Path, URI) ->
+ ContentType = guess_content_type(Path),
+ case identify(Path) of
+ {ok, #media_info{height = H, width = W}} ->
+ #xmlel{name = <<"thumbnail">>,
+ attrs = [{<<"xmlns">>, ?NS_THUMBS_1},
+ {<<"media-type">>, ContentType},
+ {<<"uri">>, URI},
+ {<<"height">>, jlib:integer_to_binary(H)},
+ {<<"width">>, jlib:integer_to_binary(W)}]};
+ pass ->
+ #xmlel{name = <<"thumbnail">>,
+ attrs = [{<<"xmlns">>, ?NS_THUMBS_1},
+ {<<"uri">>, URI},
+ {<<"media-type">>, ContentType}]}
+ end.
+
+%%--------------------------------------------------------------------
+%% Remove user.
+%%--------------------------------------------------------------------
+
+-spec remove_user(binary(), binary()) -> ok.
+
+remove_user(User, Server) ->
+ ServerHost = jid:nameprep(Server),
+ DocRoot = gen_mod:get_module_opt(ServerHost, ?MODULE, docroot,
+ fun iolist_to_binary/1,
+ <<"@HOME@/upload">>),
+ JIDinURL = gen_mod:get_module_opt(ServerHost, ?MODULE, jid_in_url,
+ fun(sha1) -> sha1;
+ (node) -> node
+ end,
+ sha1),
+ DocRoot1 = expand_host(expand_home(DocRoot), ServerHost),
+ UserStr = make_user_string(jid:make(User, Server, <<"">>), JIDinURL),
+ UserDir = str:join([DocRoot1, UserStr], <<$/>>),
+ case del_tree(UserDir) of
+ ok ->
+ ?INFO_MSG("Removed HTTP upload directory of ~s@~s", [User, Server]);
+ {error, enoent} ->
+ ?DEBUG("Found no HTTP upload directory of ~s@~s", [User, Server]);
+ {error, Error} ->
+ ?ERROR_MSG("Cannot remove HTTP upload directory of ~s@~s: ~p",
+ [User, Server, ?FORMAT(Error)])
+ end,
+ ok.
+
+-spec del_tree(file:filename_all()) -> ok | {error, term()}.
+
+del_tree(Dir) when is_binary(Dir) ->
+ del_tree(binary_to_list(Dir));
+del_tree(Dir) ->
+ try
+ {ok, Entries} = file:list_dir(Dir),
+ lists:foreach(fun(Path) ->
+ case filelib:is_dir(Path) of
+ true ->
+ ok = del_tree(Path);
+ false ->
+ ok = file:delete(Path)
+ end
+ end, [Dir ++ "/" ++ Entry || Entry <- Entries]),
+ ok = file:del_dir(Dir)
+ catch
+ _:{badmatch, {error, Error}} ->
+ {error, Error};
+ _:Error ->
+ {error, Error}
+ end.
diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl
new file mode 100644
index 00000000..a5ae0c3c
--- /dev/null
+++ b/src/mod_http_upload_quota.erl
@@ -0,0 +1,372 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_http_upload_quota.erl
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Purpose : Quota management for HTTP File Upload (XEP-0363)
+%%% Created : 15 Oct 2015 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2015-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(mod_http_upload_quota).
+-author('holger@zedat.fu-berlin.de').
+
+-define(GEN_SERVER, gen_server).
+-define(PROCNAME, ?MODULE).
+-define(TIMEOUT, timer:hours(24)).
+-define(INITIAL_TIMEOUT, timer:minutes(10)).
+-define(FORMAT(Error), file:format_error(Error)).
+
+-behaviour(?GEN_SERVER).
+-behaviour(gen_mod).
+
+%% gen_mod/supervisor callbacks.
+-export([start_link/3,
+ start/2,
+ stop/1,
+ mod_opt_type/1]).
+
+%% gen_server callbacks.
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% ejabberd_hooks callback.
+-export([handle_slot_request/5]).
+
+-include("jlib.hrl").
+-include("logger.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-record(state,
+ {server_host :: binary(),
+ access_soft_quota :: atom(),
+ access_hard_quota :: atom(),
+ max_days :: pos_integer() | infinity,
+ docroot :: binary(),
+ disk_usage = #{} :: map(),
+ timers :: [timer:tref()]}).
+
+-type state() :: #state{}.
+
+%%--------------------------------------------------------------------
+%% gen_mod/supervisor callbacks.
+%%--------------------------------------------------------------------
+
+-spec start_link(binary(), atom(), gen_mod:opts())
+ -> {ok, pid()} | ignore | {error, _}.
+
+start_link(ServerHost, Proc, Opts) ->
+ ?GEN_SERVER:start_link({local, Proc}, ?MODULE, {ServerHost, Opts}, []).
+
+-spec start(binary(), gen_mod:opts()) -> {ok, _} | {ok, _, _} | {error, _}.
+
+start(ServerHost, Opts) ->
+ Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
+ Spec = {Proc,
+ {?MODULE, start_link, [ServerHost, Proc, Opts]},
+ permanent,
+ 3000,
+ worker,
+ [?MODULE]},
+ supervisor:start_child(ejabberd_sup, Spec).
+
+-spec stop(binary()) -> ok.
+
+stop(ServerHost) ->
+ Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
+
+mod_opt_type(access_soft_quota) ->
+ fun(A) when is_atom(A) -> A end;
+mod_opt_type(access_hard_quota) ->
+ fun(A) when is_atom(A) -> A end;
+mod_opt_type(max_days) ->
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(_) ->
+ [access_soft_quota, access_hard_quota, max_days].
+
+%%--------------------------------------------------------------------
+%% gen_server callbacks.
+%%--------------------------------------------------------------------
+
+-spec init({binary(), gen_mod:opts()}) -> {ok, state()}.
+
+init({ServerHost, Opts}) ->
+ process_flag(trap_exit, true),
+ AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts,
+ fun(A) when is_atom(A) -> A end,
+ soft_upload_quota),
+ AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts,
+ fun(A) when is_atom(A) -> A end,
+ hard_upload_quota),
+ MaxDays = gen_mod:get_opt(max_days, Opts,
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end,
+ infinity),
+ DocRoot1 = gen_mod:get_module_opt(ServerHost, mod_http_upload, docroot,
+ fun iolist_to_binary/1,
+ <<"@HOME@/upload">>),
+ DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
+ DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
+ Timers = if MaxDays == infinity -> [];
+ true ->
+ {ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
+ {ok, T2} = timer:send_interval(?TIMEOUT, sweep),
+ [T1, T2]
+ end,
+ ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
+ handle_slot_request, 50),
+ {ok, #state{server_host = ServerHost,
+ access_soft_quota = AccessSoftQuota,
+ access_hard_quota = AccessHardQuota,
+ max_days = MaxDays,
+ docroot = DocRoot3,
+ timers = Timers}}.
+
+-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
+
+handle_call(Request, From, State) ->
+ ?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
+ {noreply, State}.
+
+-spec handle_cast(_, state()) -> {noreply, state()}.
+
+handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size},
+ #state{server_host = ServerHost,
+ access_soft_quota = AccessSoftQuota,
+ access_hard_quota = AccessHardQuota,
+ disk_usage = DiskUsage} = State) ->
+ HardQuota = case acl:match_rule(ServerHost, AccessHardQuota, JID) of
+ Hard when is_integer(Hard), Hard > 0 ->
+ Hard * 1024 * 1024;
+ _ ->
+ 0
+ end,
+ SoftQuota = case acl:match_rule(ServerHost, AccessSoftQuota, JID) of
+ Soft when is_integer(Soft), Soft > 0 ->
+ Soft * 1024 * 1024;
+ _ ->
+ 0
+ end,
+ OldSize = case maps:find({U, S}, DiskUsage) of
+ {ok, Value} ->
+ Value;
+ error ->
+ undefined
+ end,
+ NewSize = case {HardQuota, SoftQuota} of
+ {0, 0} ->
+ ?DEBUG("No quota specified for ~s",
+ [jid:to_string(JID)]),
+ undefined;
+ {0, _} ->
+ ?WARNING_MSG("No hard quota specified for ~s",
+ [jid:to_string(JID)]),
+ enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota);
+ {_, 0} ->
+ ?WARNING_MSG("No soft quota specified for ~s",
+ [jid:to_string(JID)]),
+ enforce_quota(Path, Size, OldSize, HardQuota, HardQuota);
+ _ when SoftQuota > HardQuota ->
+ ?WARNING_MSG("Bad quota for ~s (soft: ~p, hard: ~p)",
+ [jid:to_string(JID),
+ SoftQuota, HardQuota]),
+ enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota);
+ _ ->
+ ?DEBUG("Enforcing quota for ~s",
+ [jid:to_string(JID)]),
+ enforce_quota(Path, Size, OldSize, SoftQuota, HardQuota)
+ end,
+ NewDiskUsage = if is_integer(NewSize) ->
+ maps:put({U, S}, NewSize, DiskUsage);
+ true ->
+ DiskUsage
+ end,
+ {noreply, State#state{disk_usage = NewDiskUsage}};
+handle_cast(Request, State) ->
+ ?ERROR_MSG("Got unexpected request: ~p", [Request]),
+ {noreply, State}.
+
+-spec handle_info(_, state()) -> {noreply, state()}.
+
+handle_info(sweep, #state{server_host = ServerHost,
+ docroot = DocRoot,
+ max_days = MaxDays} = State)
+ when is_integer(MaxDays), MaxDays > 0 ->
+ ?DEBUG("Got 'sweep' message for ~s", [ServerHost]),
+ case file:list_dir(DocRoot) of
+ {ok, Entries} ->
+ BackThen = secs_since_epoch() - (MaxDays * 86400),
+ DocRootS = binary_to_list(DocRoot),
+ PathNames = lists:map(fun(Entry) ->
+ DocRootS ++ "/" ++ Entry
+ end, Entries),
+ UserDirs = lists:filter(fun filelib:is_dir/1, PathNames),
+ lists:foreach(fun(UserDir) ->
+ delete_old_files(UserDir, BackThen)
+ end, UserDirs);
+ {error, Error} ->
+ ?ERROR_MSG("Cannot open document root ~s: ~s",
+ [DocRoot, ?FORMAT(Error)])
+ end,
+ {noreply, State};
+handle_info(Info, State) ->
+ ?ERROR_MSG("Got unexpected info: ~p", [Info]),
+ {noreply, State}.
+
+-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+
+terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
+ ?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
+ ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
+ handle_slot_request, 50),
+ lists:foreach(fun(Timer) -> timer:cancel(Timer) end, Timers).
+
+-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
+
+code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
+ ?DEBUG("Updating upload quota process for ~s", [ServerHost]),
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%% ejabberd_hooks callback.
+%%--------------------------------------------------------------------
+
+-spec handle_slot_request(term(), jid(), binary(), non_neg_integer(), binary())
+ -> term().
+
+handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size,
+ _Lang) ->
+ Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
+ ?GEN_SERVER:cast(Proc, {handle_slot_request, JID, Path, Size}),
+ allow;
+handle_slot_request(Acc, _JID, _Path, _Size, _Lang) -> Acc.
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+
+-spec enforce_quota(file:filename_all(), non_neg_integer(),
+ non_neg_integer() | undefined, non_neg_integer(),
+ non_neg_integer())
+ -> non_neg_integer().
+
+enforce_quota(_UserDir, SlotSize, OldSize, _MinSize, MaxSize)
+ when is_integer(OldSize), OldSize + SlotSize =< MaxSize ->
+ OldSize + SlotSize;
+enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) ->
+ Files = lists:sort(fun({_PathA, _SizeA, TimeA}, {_PathB, _SizeB, TimeB}) ->
+ TimeA > TimeB
+ end, gather_file_info(UserDir)),
+ {DelFiles, OldSize, NewSize} =
+ lists:foldl(fun({_Path, Size, _Time}, {[], AccSize, AccSize})
+ when AccSize + Size + SlotSize =< MinSize ->
+ {[], AccSize + Size, AccSize + Size};
+ ({Path, Size, _Time}, {[], AccSize, AccSize}) ->
+ {[Path], AccSize + Size, AccSize};
+ ({Path, Size, _Time}, {AccFiles, AccSize, NewSize}) ->
+ {[Path | AccFiles], AccSize + Size, NewSize}
+ end, {[], 0, 0}, Files),
+ if OldSize + SlotSize > MaxSize ->
+ lists:foreach(fun(File) -> del_file_and_dir(File) end, DelFiles),
+ file:del_dir(UserDir), % In case it's empty, now.
+ NewSize + SlotSize;
+ true ->
+ OldSize + SlotSize
+ end.
+
+-spec delete_old_files(file:filename_all(), integer()) -> ok.
+
+delete_old_files(UserDir, CutOff) ->
+ FileInfo = gather_file_info(UserDir),
+ case [Path || {Path, _Size, Time} <- FileInfo, Time < CutOff] of
+ [] ->
+ ok;
+ OldFiles ->
+ lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles),
+ file:del_dir(UserDir) % In case it's empty, now.
+ end.
+
+-spec gather_file_info(file:filename_all())
+ -> [{binary(), non_neg_integer(), non_neg_integer()}].
+
+gather_file_info(Dir) when is_binary(Dir) ->
+ gather_file_info(binary_to_list(Dir));
+gather_file_info(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, Entries} ->
+ lists:foldl(fun(Entry, Acc) ->
+ Path = Dir ++ "/" ++ Entry,
+ case file:read_file_info(Path,
+ [{time, posix}]) of
+ {ok, #file_info{type = directory}} ->
+ gather_file_info(Path) ++ Acc;
+ {ok, #file_info{type = regular,
+ mtime = Time,
+ size = Size}} ->
+ [{Path, Size, Time} | Acc];
+ {ok, _Info} ->
+ ?DEBUG("Won't stat(2) non-regular file ~s",
+ [Path]),
+ Acc;
+ {error, Error} ->
+ ?ERROR_MSG("Cannot stat(2) ~s: ~s",
+ [Path, ?FORMAT(Error)]),
+ Acc
+ end
+ end, [], Entries);
+ {error, enoent} ->
+ ?DEBUG("Directory ~s doesn't exist", [Dir]),
+ [];
+ {error, Error} ->
+ ?ERROR_MSG("Cannot open directory ~s: ~s", [Dir, ?FORMAT(Error)]),
+ []
+ end.
+
+-spec del_file_and_dir(file:name_all()) -> ok.
+
+del_file_and_dir(File) ->
+ case file:delete(File) of
+ ok ->
+ ?INFO_MSG("Removed ~s", [File]),
+ Dir = filename:dirname(File),
+ case file:del_dir(Dir) of
+ ok ->
+ ?DEBUG("Removed ~s", [Dir]);
+ {error, Error} ->
+ ?DEBUG("Cannot remove ~s: ~s", [Dir, ?FORMAT(Error)])
+ end;
+ {error, Error} ->
+ ?WARNING_MSG("Cannot remove ~s: ~s", [File, ?FORMAT(Error)])
+ end.
+
+-spec secs_since_epoch() -> non_neg_integer().
+
+secs_since_epoch() ->
+ {MegaSecs, Secs, _MicroSecs} = os:timestamp(),
+ MegaSecs * 1000000 + Secs.
diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl
index 6225343c..a6f9f619 100644
--- a/src/mod_ip_blacklist.erl
+++ b/src/mod_ip_blacklist.erl
@@ -7,7 +7,7 @@
%%% {mod_ip_blacklist, []}
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -36,8 +36,7 @@
-export([update_bl_c2s/0]).
-%% Hooks:
--export([is_ip_in_c2s_blacklist/3]).
+-export([is_ip_in_c2s_blacklist/3, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -129,3 +128,5 @@ is_ip_in_c2s_blacklist(_Val, _IP, _Lang) -> false.
%% - For now, we do not kick user already logged on a given IP after
%% we update the blacklist.
+
+mod_opt_type(_) -> [].
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index e96fc4dc..d5cd0135 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -5,7 +5,7 @@
%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -35,9 +35,9 @@
-export([start_link/2, start/2, stop/1, export/1, import/1,
import/3, closed_connection/3, get_connection_params/3]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -46,12 +46,16 @@
-include("adhoc.hrl").
--define(DEFAULT_IRC_ENCODING, <<"iso8859-1">>).
+-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
-define(DEFAULT_IRC_PORT, 6667).
+-define(DEFAULT_REALNAME, <<"WebIRC-User">>).
+
+-define(DEFAULT_WEBIRC_PASSWORD, <<"">>).
+
-define(POSSIBLE_ENCODINGS,
- [<<"koi8-r">>, <<"iso8859-1">>, <<"iso8859-2">>,
+ [<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
@@ -112,7 +116,7 @@ stop(Host) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- ejabberd:start_app(p1_iconv),
+ ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
case gen_mod:db_type(Host, Opts) of
@@ -129,7 +133,7 @@ init([Host, Opts]) ->
catch ets:new(irc_connection,
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok,
#state{host = MyHost, server_host = Host,
access = Access}}.
@@ -212,7 +216,7 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
allow -> do_route1(Host, ServerHost, From, To, Packet);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
@@ -230,7 +234,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE,
@@ -258,7 +262,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case Node of
<<>> ->
ResIQ = IQ#iq{type = result,
@@ -379,11 +383,15 @@ do_route1(Host, ServerHost, From, To, Packet) ->
%% username part of the JID).
_ -> Username
end,
+ Ident = extract_ident(Packet),
+ RemoteAddr = extract_ip_address(Packet),
+ RealName = get_realname(ServerHost),
+ WebircPassword = get_webirc_password(ServerHost),
{ok, Pid} = mod_irc_connection:start(From, Host,
ServerHost, Server,
ConnectionUsername,
Encoding, Port,
- Password, ?MODULE),
+ Password, Ident, RemoteAddr, RealName, WebircPassword, ?MODULE),
ets:insert(irc_connection,
#irc_connection{jid_server_host =
{From, Server, Host},
@@ -466,7 +474,7 @@ iq_get_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd IRC module">>))/binary,
- "\nCopyright (c) 2003-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
command_items(ServerHost, Host, Lang) ->
lists:map(fun ({Node, Name, _Function}) ->
@@ -508,7 +516,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -527,7 +535,7 @@ process_irc_register(ServerHost, Host, From, _To,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
#xmlel{attrs = Attrs} ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"cancel">> ->
IQ#iq{type = result,
sub_el =
@@ -541,7 +549,7 @@ process_irc_register(ServerHost, Host, From, _To,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_BAD_REQUEST]};
_ ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>,
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
SubEl),
<<"/">>),
case set_form(ServerHost, Host, From, Node, Lang,
@@ -563,7 +571,7 @@ process_irc_register(ServerHost, Host, From, _To,
end
end;
get ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
case get_form(ServerHost, Host, From, Node, Lang) of
{result, Res} ->
@@ -578,7 +586,7 @@ process_irc_register(ServerHost, Host, From, _To,
end.
get_data(ServerHost, Host, From) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
get_data(LServer, Host, From,
gen_mod:db_type(LServer, ?MODULE)).
@@ -604,7 +612,7 @@ get_data(LServer, Host, From, riak) ->
end;
get_data(LServer, Host, From, odbc) ->
SJID =
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select data from irc_custom where jid='">>,
@@ -722,12 +730,12 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_data(ServerHost, Host, From, Data) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
set_data(LServer, Host, From, data_to_binary(From, Data),
gen_mod:db_type(LServer, ?MODULE)).
set_data(_LServer, Host, From, Data, mnesia) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
F = fun () ->
mnesia:write(#irc_custom{us_host = {US, Host},
@@ -735,14 +743,14 @@ set_data(_LServer, Host, From, Data, mnesia) ->
end,
mnesia:transaction(F);
set_data(LServer, Host, From, Data, riak) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
{atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
data = Data},
irc_custom_schema())};
set_data(LServer, Host, From, Data, odbc) ->
SJID =
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
SData = ejabberd_odbc:encode_term(Data),
F = fun () ->
@@ -784,8 +792,6 @@ set_form(ServerHost, Host, From, [], _Lang, XData) ->
set_form(_ServerHost, _Host, _, _, _Lang, _XData) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
-%% Host = "irc.example.com"
-%% ServerHost = "example.com"
get_connection_params(Host, From, IRCServer) ->
[_ | HostTail] = str:tokens(Host, <<".">>),
ServerHost = str:join(HostTail, <<".">>),
@@ -801,6 +807,12 @@ get_default_encoding(ServerHost) ->
[ServerHost, Result]),
Result.
+get_realname(ServerHost) ->
+ gen_mod:get_module_opt(ServerHost, ?MODULE, realname, fun iolist_to_binary/1, ?DEFAULT_REALNAME).
+
+get_webirc_password(ServerHost) ->
+ gen_mod:get_module_opt(ServerHost, ?MODULE, webirc_password, fun iolist_to_binary/1, ?DEFAULT_WEBIRC_PASSWORD).
+
get_connection_params(Host, ServerHost, From,
IRCServer) ->
#jid{user = User, server = _Server} = From,
@@ -923,7 +935,7 @@ adhoc_join(From, To,
<<"invite">>,
attrs =
[{<<"from">>,
- jlib:jid_to_string(From)}],
+ jid:to_string(From)}],
children =
[#xmlel{name
=
@@ -954,7 +966,7 @@ adhoc_join(From, To,
Lang,
<<"Join the IRC channel in this Jabber ID: ~s">>),
[RoomJID]))}]}]},
- ejabberd_router:route(jlib:string_to_jid(RoomJID), From,
+ ejabberd_router:route(jid:from_string(RoomJID), From,
Invite),
adhoc:produce_response(Request,
#adhoc_response{status =
@@ -1250,7 +1262,7 @@ data_to_binary(JID, Data) ->
?ERROR_MSG("failed to convert "
"parameter ~p for user ~s",
[Param,
- jlib:jid_to_string(JID)]);
+ jid:to_string(JID)]);
true ->
?ERROR_MSG("failed to convert "
"parameter ~p",
@@ -1297,7 +1309,7 @@ update_table() ->
fun(#irc_custom{us_host = {_, H}}) -> H end,
fun(#irc_custom{us_host = {{U, S}, H},
data = Data} = R) ->
- JID = jlib:make_jid(U, S, <<"">>),
+ JID = jid:make(U, S, <<"">>),
R#irc_custom{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
@@ -1315,8 +1327,8 @@ export(_Server) ->
case str:suffix(Host, IRCHost) of
true ->
SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:make_jid(U, S, <<"">>))),
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
SIRCHost = ejabberd_odbc:escape(IRCHost),
SData = ejabberd_odbc:encode_term(Data),
[[<<"delete from irc_custom where jid='">>, SJID,
@@ -1333,7 +1345,7 @@ export(_Server) ->
import(_LServer) ->
[{<<"select jid, host, data from irc_custom;">>,
fun([SJID, IRCHost, SData]) ->
- #jid{luser = U, lserver = S} = jlib:string_to_jid(SJID),
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
Data = ejabberd_odbc:decode_term(SData),
#irc_custom{us_host = {{U, S}, IRCHost},
data = Data}
@@ -1345,3 +1357,38 @@ import(_LServer, riak, #irc_custom{} = R) ->
ejabberd_riak:put(R, irc_custom_schema());
import(_, _, _) ->
pass.
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(default_encoding) ->
+ fun iolist_to_binary/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(_) ->
+ [access, db_type, default_encoding, host].
+
+extract_ident(Packet) ->
+ case fxml:get_subtag(Packet, <<"headers">>) of
+ {xmlel, _Name, _Attrs, Headers} ->
+ extract_header(<<"X-Irc-Ident">>, Headers);
+ _ ->
+ "chatmovil"
+ end.
+
+extract_ip_address(Packet) ->
+ case fxml:get_subtag(Packet, <<"headers">>) of
+ {xmlel, _Name, _Attrs, Headers} ->
+ extract_header(<<"X-Forwarded-For">>, Headers);
+ _ ->
+ "127.0.0.1"
+ end.
+
+extract_header(HeaderName, [{xmlel, _Name, _Attrs, [{xmlcdata, Value}]} | Tail]) ->
+ case fxml:get_attr(<<"name">>, _Attrs) of
+ {value, HeaderName} ->
+ binary_to_list(Value);
+ _ ->
+ extract_header(HeaderName, Tail)
+ end;
+extract_header(_HeaderName, _Headers) ->
+ false.
diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl
index cc21b0f1..098c8c28 100644
--- a/src/mod_irc_connection.erl
+++ b/src/mod_irc_connection.erl
@@ -5,7 +5,7 @@
%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -30,7 +30,7 @@
-behaviour(gen_fsm).
%% External exports
--export([start_link/8, start/9, route_chan/4,
+-export([start_link/12, start/13, route_chan/4,
route_nick/3]).
%% gen_fsm callbacks
@@ -55,9 +55,13 @@
user = #jid{} :: jid(),
host = <<"">> :: binary(),
server = <<"">> :: binary(),
+ remoteAddr = <<"">> :: binary(),
+ ident = <<"">> :: binary(),
+ realname = <<"">> :: binary(),
nick = <<"">> :: binary(),
channels = dict:new() :: ?TDICT,
nickchannel :: binary(),
+ webirc_password :: binary(),
mod = mod_irc :: atom(),
inbuf = <<"">> :: binary(),
outbuf = <<"">> :: binary()}).
@@ -78,18 +82,18 @@
-endif.
start(From, Host, ServerHost, Server, Username,
- Encoding, Port, Password, Mod) ->
+ Encoding, Port, Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
Supervisor = gen_mod:get_module_proc(ServerHost,
ejabberd_mod_irc_sup),
supervisor:start_child(Supervisor,
[From, Host, Server, Username, Encoding, Port,
- Password, Mod]).
+ Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]).
start_link(From, Host, Server, Username, Encoding, Port,
- Password, Mod) ->
+ Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
gen_fsm:start_link(?MODULE,
[From, Host, Server, Username, Encoding, Port, Password,
- Mod],
+ Ident, RemoteAddr, RealName, WebircPassword, Mod],
?FSMOPTS).
%%%----------------------------------------------------------------------
@@ -104,13 +108,14 @@ start_link(From, Host, Server, Username, Encoding, Port,
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([From, Host, Server, Username, Encoding, Port,
- Password, Mod]) ->
+ Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
gen_fsm:send_event(self(), init),
{ok, open_socket,
#state{queue = queue:new(), mod = Mod,
encoding = Encoding, port = Port, password = Password,
user = From, nick = Username, host = Host,
- server = Server}}.
+ server = Server, ident = Ident, realname = RealName,
+ remoteAddr = RemoteAddr, webirc_password = WebircPassword }}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -122,6 +127,10 @@ open_socket(init, StateData) ->
Addr = StateData#state.server,
Port = StateData#state.port,
?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]),
+ From = StateData#state.user,
+ #jid{user = JidUser, server = JidServer, resource = JidResource} = From,
+ UserIP = ejabberd_sm:get_user_ip(JidUser, JidServer, JidResource),
+ UserIPStr = inet_parse:ntoa(element(1, UserIP)),
Connect6 = gen_tcp:connect(binary_to_list(Addr), Port,
[inet6, binary, {packet, 0}]),
Connect = case Connect6 of
@@ -136,6 +145,8 @@ open_socket(init, StateData) ->
case Connect of
{ok, Socket} ->
NewStateData = StateData#state{socket = Socket},
+ send_text(NewStateData,
+ io_lib:format("WEBIRC ~s ~s ~s ~s\r\n", [StateData#state.webirc_password, JidResource, UserIPStr, UserIPStr])),
if StateData#state.password /= <<"">> ->
send_text(NewStateData,
io_lib:format("PASS ~s\r\n",
@@ -146,9 +157,10 @@ open_socket(init, StateData) ->
io_lib:format("NICK ~s\r\n", [StateData#state.nick])),
send_text(NewStateData,
io_lib:format("USER ~s ~s ~s :~s\r\n",
- [StateData#state.nick, StateData#state.nick,
+ [StateData#state.ident,
+ StateData#state.nick,
StateData#state.host,
- StateData#state.nick])),
+ StateData#state.realname])),
{next_state, wait_for_registration, NewStateData};
{error, Reason} ->
?DEBUG("connect return ~p~n", [Reason]),
@@ -221,7 +233,7 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
case lists:filter(fun (El) ->
case El of
#xmlel{name = <<"x">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC -> true;
_ -> false
end;
@@ -231,9 +243,9 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
Els)
of
[ElXMUC | _] ->
- case xml:get_subtag(ElXMUC, <<"password">>) of
+ case fxml:get_subtag(ElXMUC, <<"password">>) of
#xmlel{name = <<"password">>} = PasswordTag ->
- {true, xml:get_tag_cdata(PasswordTag)};
+ {true, fxml:get_tag_cdata(PasswordTag)};
_ -> false
end;
_ -> false
@@ -249,7 +261,7 @@ handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"presence">>, attrs = Attrs} =
Presence},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
send_stanza_unavailable(Channel, StateData),
S1 = (?SEND((io_lib:format("PART #~s\r\n",
@@ -300,20 +312,20 @@ handle_info({route_chan, Channel, Resource,
handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
- case xml:get_path_s(El, [{elem, <<"subject">>}, cdata])
+ case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata])
of
<<"">> ->
ejabberd_router:route(
- jlib:make_jid(
+ jid:make(
iolist_to_binary([Channel,
<<"%">>,
StateData#state.server]),
StateData#state.host,
StateData#state.nick),
StateData#state.user, El),
- Body = xml:get_path_s(El,
+ Body = fxml:get_path_s(El,
[{elem, <<"body">>},
cdata]),
case Body of
@@ -374,7 +386,7 @@ handle_info({route_chan, Channel, Resource,
when Type == <<"chat">>;
Type == <<"">>;
Type == <<"normal">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -430,7 +442,7 @@ handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"iq">>} = El},
StateName, StateData) ->
From = StateData#state.user,
- To = jlib:make_jid(iolist_to_binary([Channel, <<"%">>,
+ To = jid:make(iolist_to_binary([Channel, <<"%">>,
StateData#state.server]),
StateData#state.host, StateData#state.nick),
_ = case jlib:iq_query_info(El) of
@@ -469,9 +481,9 @@ handle_info({route_chan, _Channel, _Resource, _Packet},
handle_info({route_nick, Nick,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -716,7 +728,7 @@ terminate(_Reason, _StateName, FullStateData) ->
send_stanza(Chan, StateData, Stanza) ->
ejabberd_router:route(
- jlib:make_jid(
+ jid:make(
iolist_to_binary([Chan,
<<"%">>,
StateData#state.server]),
@@ -766,13 +778,13 @@ bounce_messages(Reason) ->
receive
{send_element, El} ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(El, <<"502">>, Reason),
- From = jlib:string_to_jid(xml:get_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_attr_s(<<"from">>,
Attrs)),
- To = jlib:string_to_jid(xml:get_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_attr_s(<<"to">>,
Attrs)),
ejabberd_router:route(To, From, Err)
end,
@@ -830,7 +842,7 @@ process_channel_list_user(StateData, Chan, User) ->
{U2, <<"admin">>, <<"moderator">>};
_ -> {User1, <<"member">>, <<"participant">>}
end,
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ ejabberd_router:route(jid:make(iolist_to_binary([Chan,
<<"%">>,
StateData#state.server]),
StateData#state.host, User2),
@@ -860,7 +872,7 @@ process_channel_topic(StateData, Chan, String) ->
Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>,
<<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ ejabberd_router:route(jid:make(iolist_to_binary([Chan,
<<"%">>,
StateData#state.server]),
StateData#state.host, <<"">>),
@@ -889,7 +901,7 @@ process_channel_topic_who(StateData, Chan, String) ->
_ -> String
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ ejabberd_router:route(jid:make(iolist_to_binary([Chan,
<<"%">>,
StateData#state.server]),
StateData#state.host, <<"">>),
@@ -921,7 +933,7 @@ process_nick_in_use(StateData, String) ->
% Shouldn't happen with a well behaved server
StateData;
Chan ->
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan,
+ ejabberd_router:route(jid:make(iolist_to_binary([Chan,
<<"%">>,
StateData#state.server]),
StateData#state.host,
@@ -938,7 +950,7 @@ process_num_error(StateData, String) ->
<<"continue">>),
lists:foreach(fun (Chan) ->
ejabberd_router:route(
- jlib:make_jid(
+ jid:make(
iolist_to_binary(
[Chan,
<<"%">>,
@@ -956,7 +968,7 @@ process_num_error(StateData, String) ->
StateData.
process_endofwhois(StateData, _String, Nick) ->
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ ejabberd_router:route(jid:make(iolist_to_binary([Nick,
<<"!">>,
StateData#state.server]),
StateData#state.host, <<"">>),
@@ -973,7 +985,7 @@ process_whois311(StateData, String, Nick, Ident,
Irchost) ->
Fullname = ejabberd_regexp:replace(String,
<<".*311[^:]*:">>, <<"">>),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ ejabberd_router:route(jid:make(iolist_to_binary([Nick,
<<"!">>,
StateData#state.server]),
StateData#state.host, <<"">>),
@@ -997,7 +1009,7 @@ process_whois311(StateData, String, Nick, Ident,
process_whois312(StateData, String, Nick, Ircserver) ->
Ircserverdesc = ejabberd_regexp:replace(String,
<<".*312[^:]*:">>, <<"">>),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick,
+ ejabberd_router:route(jid:make(iolist_to_binary([Nick,
<<"!">>,
StateData#state.server]),
StateData#state.host, <<"">>),
@@ -1019,7 +1031,7 @@ process_whois312(StateData, String, Nick, Ircserver) ->
process_whois319(StateData, String, Nick) ->
Chanlist = ejabberd_regexp:replace(String,
<<".*319[^:]*:">>, <<"">>),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Nick,
<<"!">>,
StateData#state.server]),
@@ -1047,7 +1059,7 @@ process_chanprivmsg(StateData, Chan, From, String) ->
_ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1069,7 +1081,7 @@ process_channotice(StateData, Chan, From, String) ->
_ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1091,7 +1103,7 @@ process_privmsg(StateData, _Nick, From, String) ->
_ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[FromUser,
<<"!">>,
StateData#state.server]),
@@ -1113,7 +1125,7 @@ process_notice(StateData, _Nick, From, String) ->
_ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[FromUser,
<<"!">>,
StateData#state.server]),
@@ -1141,14 +1153,14 @@ process_userinfo(StateData, _Nick, From) ->
send_text(StateData,
io_lib:format("NOTICE ~s :\001USERINFO xmpp:~s\001\r\n",
[FromUser,
- jlib:jid_to_string(StateData#state.user)])).
+ jid:to_string(StateData#state.user)])).
process_topic(StateData, Chan, From, String) ->
[FromUser | _] = str:tokens(From, <<"!">>),
Msg = ejabberd_regexp:replace(String,
<<".*TOPIC[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1170,7 +1182,7 @@ process_part(StateData, Chan, From, String) ->
Msg = ejabberd_regexp:replace(String,
<<".*PART[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1212,7 +1224,7 @@ process_quit(StateData, From, String) ->
dict:map(fun (Chan, Ps) ->
case (?SETS):is_member(FromUser, Ps) of
true ->
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1261,7 +1273,7 @@ process_quit(StateData, From, String) ->
process_join(StateData, Channel, From, _String) ->
[FromUser | FromIdent] = str:tokens(From, <<"!">>),
[Chan | _] = binary:split(Channel, <<":#">>),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1294,7 +1306,7 @@ process_join(StateData, Channel, From, _String) ->
process_mode_o(StateData, Chan, _From, Nick,
Affiliation, Role) ->
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1318,7 +1330,7 @@ process_kick(StateData, Chan, From, Nick, String) ->
Msg = lists:last(str:tokens(String, <<":">>)),
Msg2 = <<Nick/binary, " kicked by ", From/binary, " (",
(filter_message(Msg))/binary, ")">>,
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1329,7 +1341,7 @@ process_kick(StateData, Chan, From, Nick, String) ->
children =
[#xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, Msg2}]}]}),
- ejabberd_router:route(jlib:make_jid(iolist_to_binary(
+ ejabberd_router:route(jid:make(iolist_to_binary(
[Chan,
<<"%">>,
StateData#state.server]),
@@ -1361,7 +1373,7 @@ process_nick(StateData, From, NewNick) ->
NewChans = dict:map(fun (Chan, Ps) ->
case (?SETS):is_member(FromUser, Ps) of
true ->
- ejabberd_router:route(jlib:make_jid(
+ ejabberd_router:route(jid:make(
iolist_to_binary(
[Chan,
<<"%">>,
@@ -1408,7 +1420,7 @@ process_nick(StateData, From, NewNick) ->
children
=
[]}]}]}),
- ejabberd_router:route(jlib:make_jid(
+ ejabberd_router:route(jid:make(
iolist_to_binary(
[Chan,
<<"%">>,
@@ -1456,7 +1468,7 @@ process_nick(StateData, From, NewNick) ->
process_error(StateData, String) ->
lists:foreach(fun (Chan) ->
- ejabberd_router:route(jlib:make_jid(
+ ejabberd_router:route(jid:make(
iolist_to_binary(
[Chan,
<<"%">>,
@@ -1523,14 +1535,14 @@ iq_admin(StateData, Channel, From, To,
end.
process_iq_admin(StateData, Channel, set, SubEl) ->
- case xml:get_subtag(SubEl, <<"item">>) of
+ case fxml:get_subtag(SubEl, <<"item">>) of
false -> {error, ?ERR_BAD_REQUEST};
ItemEl ->
- Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl),
- Affiliation = xml:get_tag_attr_s(<<"affiliation">>,
+ Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl),
+ Affiliation = fxml:get_tag_attr_s(<<"affiliation">>,
ItemEl),
- Role = xml:get_tag_attr_s(<<"role">>, ItemEl),
- Reason = xml:get_path_s(ItemEl,
+ Role = fxml:get_tag_attr_s(<<"role">>, ItemEl),
+ Reason = fxml:get_path_s(ItemEl,
[{elem, <<"reason">>}, cdata]),
process_admin(StateData, Channel, Nick, Affiliation,
Role, Reason)
diff --git a/src/mod_last.erl b/src/mod_last.erl
index e079a2d3..33f88e02 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -5,7 +5,7 @@
%%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,14 +25,19 @@
-module(mod_last).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
+-protocol({xep, 12, '2.0'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3, export/1,
- process_sm_iq/3, on_presence_update/4, import/1, import/3,
- store_last_info/4, get_last_info/2, remove_user/2,
- transform_options/1]).
+ process_sm_iq/3, on_presence_update/4, import/1,
+ import/3, store_last_info/4, get_last_info/2,
+ remove_user/2, transform_options/1, mod_opt_type/1,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -107,7 +112,7 @@ get_node_uptime() ->
undefined ->
trunc(element(1, erlang:statistics(wall_clock)) / 1000);
Now ->
- now_to_seconds(now()) - Now
+ p1_time_compat:system_time(seconds) - Now
end.
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
@@ -180,18 +185,12 @@ get_last(LUser, LServer, riak) ->
Err
end;
get_last(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_last(LServer, Username) of
- {selected, [<<"seconds">>, <<"state">>], []} ->
- not_found;
- {selected, [<<"seconds">>, <<"state">>],
- [[STimeStamp, Status]]} ->
- case catch jlib:binary_to_integer(STimeStamp) of
- TimeStamp when is_integer(TimeStamp) ->
- {ok, TimeStamp, Status};
- Reason -> {error, {invalid_timestamp, Reason}}
- end;
- Reason -> {error, {invalid_result, Reason}}
+ case catch odbc_queries:get_last(LServer, LUser) of
+ {selected, []} ->
+ not_found;
+ {selected, [{TimeStamp, Status}]} ->
+ {ok, TimeStamp, Status};
+ Reason -> {error, {invalid_result, Reason}}
end.
get_last_iq(IQ, SubEl, LUser, LServer) ->
@@ -205,7 +204,7 @@ get_last_iq(IQ, SubEl, LUser, LServer) ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
{ok, TimeStamp, Status} ->
- TimeStamp2 = now_to_seconds(now()),
+ TimeStamp2 = p1_time_compat:system_time(seconds),
Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result,
sub_el =
@@ -227,12 +226,12 @@ get_last_iq(IQ, SubEl, LUser, LServer) ->
end.
on_presence_update(User, Server, _Resource, Status) ->
- TimeStamp = now_to_seconds(now()),
+ TimeStamp = p1_time_compat:system_time(seconds),
store_last_info(User, Server, TimeStamp, Status).
store_last_info(User, Server, TimeStamp, Status) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
store_last_info(LUser, LServer, TimeStamp, Status,
DBType).
@@ -255,12 +254,7 @@ store_last_info(LUser, LServer, TimeStamp, Status,
last_activity_schema())};
store_last_info(LUser, LServer, TimeStamp, Status,
odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds =
- ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))),
- State = ejabberd_odbc:escape(Status),
- odbc_queries:set_last_t(LServer, Username, Seconds,
- State).
+ odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status).
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found
@@ -271,8 +265,8 @@ get_last_info(LUser, LServer) ->
end.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
remove_user(LUser, LServer, DBType).
@@ -281,8 +275,7 @@ remove_user(LUser, LServer, mnesia) ->
F = fun () -> mnesia:delete({last_activity, US}) end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_last(LServer, Username);
+ odbc_queries:del_last(LServer, LUser);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
@@ -349,3 +342,11 @@ transform_options({node_start, {_, _, _} = Now}, Opts) ->
[{node_start, now_to_seconds(Now)}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
+
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [db_type, iqdisc].
+
+opt_type(node_start) ->
+ fun (S) when is_integer(S), S >= 0 -> S end;
+opt_type(_) -> [node_start].
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
new file mode 100644
index 00000000..38642c0c
--- /dev/null
+++ b/src/mod_mam.erl
@@ -0,0 +1,1421 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% @doc
+%%% Message Archive Management (XEP-0313)
+%%% @end
+%%% Created : 4 Jul 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2013-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(mod_mam).
+
+-protocol({xep, 313, '0.4'}).
+-protocol({xep, 334, '0.2'}).
+
+-behaviour(gen_mod).
+
+%% API
+-export([start/2, stop/1]).
+
+-export([user_send_packet/4, user_receive_packet/5,
+ process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
+ remove_user/2, remove_user/3, mod_opt_type/1, muc_process_iq/4,
+ muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
+ get_commands_spec/0]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("mod_muc_room.hrl").
+-include("ejabberd_commands.hrl").
+
+-define(DEF_PAGE_SIZE, 50).
+-define(MAX_PAGE_SIZE, 250).
+
+-define(BIN_GREATER_THAN(A, B),
+ ((A > B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) > byte_size(B))).
+-define(BIN_LESS_THAN(A, B),
+ ((A < B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) < byte_size(B))).
+
+-record(archive_msg,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
+ id = <<>> :: binary() | '_',
+ timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
+ peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
+ bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
+ packet = #xmlel{} :: xmlel() | '_',
+ nick = <<"">> :: binary(),
+ type = chat :: chat | groupchat}).
+
+-record(archive_prefs,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ default = never :: never | always | roster,
+ always = [] :: [ljid()],
+ never = [] :: [ljid()]}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start(Host, Opts) ->
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ DBType = gen_mod:db_type(Host, Opts),
+ init_db(DBType, Host),
+ init_cache(DBType, Opts),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
+ user_receive_packet, 500),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send_packet, 500),
+ ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
+ muc_filter_message, 50),
+ ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
+ muc_process_iq, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
+ remove_user, 50),
+ case gen_mod:get_opt(assume_mam_usage, Opts,
+ fun(if_enabled) -> if_enabled;
+ (on_request) -> on_request;
+ (never) -> never
+ end, never) of
+ never ->
+ ok;
+ _ ->
+ ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
+ message_is_archived, 50)
+ end,
+ ejabberd_commands:register_commands(get_commands_spec()),
+ ok.
+
+init_db(mnesia, _Host) ->
+ mnesia:create_table(archive_msg,
+ [{disc_only_copies, [node()]},
+ {type, bag},
+ {attributes, record_info(fields, archive_msg)}]),
+ mnesia:create_table(archive_prefs,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, archive_prefs)}]);
+init_db(_, _) ->
+ ok.
+
+init_cache(_DBType, Opts) ->
+ MaxSize = gen_mod:get_opt(cache_size, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 1000),
+ LifeTime = gen_mod:get_opt(cache_life_time, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ timer:hours(1) div 1000),
+ cache_tab:new(archive_prefs, [{max_size, MaxSize},
+ {life_time, LifeTime}]).
+
+stop(Host) ->
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ user_send_packet, 500),
+ ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
+ user_receive_packet, 500),
+ ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
+ muc_filter_message, 50),
+ ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
+ muc_process_iq, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_TMP),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_0),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_0),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_1),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_1),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:delete(anonymous_purge_hook, Host,
+ ?MODULE, remove_user, 50),
+ case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
+ fun(if_enabled) -> if_enabled;
+ (on_request) -> on_request;
+ (never) -> never
+ end, never) of
+ never ->
+ ok;
+ _ ->
+ ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
+ message_is_archived, 50)
+ end,
+ ejabberd_commands:unregister_commands(get_commands_spec()),
+ ok.
+
+remove_user(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ remove_user(LUser, LServer,
+ gen_mod:db_type(LServer, ?MODULE)).
+
+remove_user(LUser, LServer, mnesia) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:delete({archive_msg, US}),
+ mnesia:delete({archive_prefs, US})
+ end,
+ mnesia:transaction(F);
+remove_user(LUser, LServer, odbc) ->
+ SUser = ejabberd_odbc:escape(LUser),
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"delete from archive where username='">>, SUser, <<"';">>]),
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
+
+user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
+ LUser = JID#jid.luser,
+ LServer = JID#jid.lserver,
+ IsBareCopy = is_bare_copy(JID, To),
+ case should_archive(Pkt, LServer) of
+ true when not IsBareCopy ->
+ NewPkt = strip_my_archived_tag(Pkt, LServer),
+ case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
+ {ok, ID} ->
+ Archived = #xmlel{name = <<"archived">>,
+ attrs = [{<<"by">>, LServer},
+ {<<"xmlns">>, ?NS_MAM_TMP},
+ {<<"id">>, ID}]},
+ StanzaID = #xmlel{name = <<"stanza-id">>,
+ attrs = [{<<"by">>, LServer},
+ {<<"xmlns">>, ?NS_SID_0},
+ {<<"id">>, ID}]},
+ NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
+ NewPkt#xmlel{children = NewEls};
+ _ ->
+ NewPkt
+ end;
+ _ ->
+ Pkt
+ end.
+
+user_send_packet(Pkt, C2SState, JID, Peer) ->
+ LUser = JID#jid.luser,
+ LServer = JID#jid.lserver,
+ case should_archive(Pkt, LServer) of
+ true ->
+ NewPkt = strip_my_archived_tag(Pkt, LServer),
+ store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
+ LUser, LServer, Peer, send),
+ NewPkt;
+ false ->
+ Pkt
+ end.
+
+muc_filter_message(Pkt, #state{config = Config} = MUCState,
+ RoomJID, From, FromNick) ->
+ if Config#config.mam ->
+ LServer = RoomJID#jid.lserver,
+ NewPkt = strip_my_archived_tag(Pkt, LServer),
+ StorePkt = strip_x_jid_tags(NewPkt),
+ case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
+ {ok, ID} ->
+ Archived = #xmlel{name = <<"archived">>,
+ attrs = [{<<"by">>, LServer},
+ {<<"xmlns">>, ?NS_MAM_TMP},
+ {<<"id">>, ID}]},
+ StanzaID = #xmlel{name = <<"stanza-id">>,
+ attrs = [{<<"by">>, LServer},
+ {<<"xmlns">>, ?NS_SID_0},
+ {<<"id">>, ID}]},
+ NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
+ NewPkt#xmlel{children = NewEls};
+ _ ->
+ NewPkt
+ end;
+ true ->
+ Pkt
+ end.
+
+% Query archive v0.2
+process_iq_v0_2(#jid{lserver = LServer} = From,
+ #jid{lserver = LServer} = To,
+ #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
+ Fs = parse_query_v0_2(SubEl),
+ process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
+process_iq_v0_2(From, To, IQ) ->
+ process_iq(From, To, IQ).
+
+% Query archive v0.3
+process_iq_v0_3(#jid{lserver = LServer} = From,
+ #jid{lserver = LServer} = To,
+ #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
+ process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
+process_iq_v0_3(#jid{lserver = LServer},
+ #jid{lserver = LServer},
+ #iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) ->
+ process_iq(LServer, IQ);
+process_iq_v0_3(From, To, IQ) ->
+ process_iq(From, To, IQ).
+
+muc_process_iq(#iq{type = set,
+ sub_el = #xmlel{name = <<"query">>,
+ attrs = Attrs} = SubEl} = IQ,
+ MUCState, From, To) ->
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
+ NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+ muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
+ _ ->
+ IQ
+ end;
+muc_process_iq(#iq{type = get,
+ sub_el = #xmlel{name = <<"query">>,
+ attrs = Attrs} = SubEl} = IQ,
+ MUCState, From, To) ->
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MAM_TMP ->
+ muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
+ NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+ LServer = MUCState#state.server_host,
+ process_iq(LServer, IQ);
+ _ ->
+ IQ
+ end;
+muc_process_iq(IQ, _MUCState, _From, _To) ->
+ IQ.
+
+get_xdata_fields(SubEl) ->
+ case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
+ fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
+ {#xmlel{} = XData, false} ->
+ jlib:parse_xdata_submit(XData);
+ {#xmlel{} = XData, #xmlel{}} ->
+ [{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)];
+ {false, #xmlel{}} ->
+ [{<<"set">>, SubEl}];
+ {false, false} ->
+ []
+ end.
+
+disco_sm_features(empty, From, To, Node, Lang) ->
+ disco_sm_features({result, []}, From, To, Node, Lang);
+disco_sm_features({result, OtherFeatures},
+ #jid{luser = U, lserver = S},
+ #jid{luser = U, lserver = S}, <<>>, _Lang) ->
+ {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
+disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
+ true;
+message_is_archived(false, C2SState, Peer,
+ #jid{luser = LUser, lserver = LServer}, Pkt) ->
+ Res = case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
+ fun(if_enabled) -> if_enabled;
+ (on_request) -> on_request;
+ (never) -> never
+ end, never) of
+ if_enabled ->
+ get_prefs(LUser, LServer);
+ on_request ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ cache_tab:lookup(archive_prefs, {LUser, LServer},
+ fun() ->
+ get_prefs(LUser, LServer, DBType)
+ end);
+ never ->
+ error
+ end,
+ case Res of
+ {ok, Prefs} ->
+ should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
+ andalso should_archive_peer(C2SState, Prefs, Peer);
+ error ->
+ false
+ end.
+
+delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
+ TypeBin == <<"groupchat">>;
+ TypeBin == <<"all">> ->
+ Diff = Days * 24 * 60 * 60 * 1000000,
+ TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
+ Type = jlib:binary_to_atom(TypeBin),
+ {Results, _} =
+ lists:foldl(fun(Host, {Results, MnesiaDone}) ->
+ case {gen_mod:db_type(Host, ?MODULE), MnesiaDone} of
+ {mnesia, true} ->
+ {Results, true};
+ {mnesia, false} ->
+ Res = delete_old_messages(TimeStamp, Type,
+ global, mnesia),
+ {[Res|Results], true};
+ {DBType, _} ->
+ Res = delete_old_messages(TimeStamp, Type,
+ Host, DBType),
+ {[Res|Results], MnesiaDone}
+ end
+ end, {[], false}, ?MYHOSTS),
+ case lists:filter(fun(Res) -> Res /= ok end, Results) of
+ [] -> ok;
+ [NotOk|_] -> NotOk
+ end;
+delete_old_messages(_TypeBin, _Days) ->
+ unsupported_type.
+
+delete_old_messages(TimeStamp, Type, global, mnesia) ->
+ MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
+ type = MsgType} = Msg)
+ when MsgTS < TimeStamp,
+ MsgType == Type orelse Type == all ->
+ Msg
+ end),
+ OldMsgs = mnesia:dirty_select(archive_msg, MS),
+ lists:foreach(fun(Rec) ->
+ ok = mnesia:dirty_delete_object(Rec)
+ end, OldMsgs);
+delete_old_messages(_TimeStamp, _Type, _Host, _DBType) ->
+ %% TODO
+ not_implemented.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
+ NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MAM_0 ->
+ ?NS_MAM_0;
+ _ ->
+ ?NS_MAM_1
+ end,
+ CommonFields = [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"FORM_TYPE">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata, NS}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"jid-single">>},
+ {<<"var">>, <<"with">>}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"text-single">>},
+ {<<"var">>, <<"start">>}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"text-single">>},
+ {<<"var">>, <<"end">>}]}],
+ Fields = case gen_mod:db_type(LServer, ?MODULE) of
+ odbc ->
+ WithText = #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"text-single">>},
+ {<<"var">>, <<"withtext">>}]},
+ [WithText|CommonFields];
+ _ ->
+ CommonFields
+ end,
+ Form = #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
+ children = Fields},
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, NS}],
+ children = [Form]}]}.
+
+% Preference setting (both v0.2 & v0.3)
+process_iq(#jid{luser = LUser, lserver = LServer},
+ #jid{lserver = LServer},
+ #iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
+ try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
+ <<"always">> -> always;
+ <<"never">> -> never;
+ <<"roster">> -> roster
+ end,
+ lists:foldl(
+ fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) ->
+ {get_jids(Els) ++ A, N};
+ (#xmlel{name = <<"never">>, children = Els}, {A, N}) ->
+ {A, get_jids(Els) ++ N};
+ (_, {A, N}) ->
+ {A, N}
+ end, {[], []}, SubEl#xmlel.children)} of
+ {Default, {Always0, Never0}} ->
+ Always = lists:usort(Always0),
+ Never = lists:usort(Never0),
+ case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
+ ok ->
+ NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
+ IQ#iq{type = result, sub_el = [NewPrefs]};
+ _Err ->
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ end
+ catch _:_ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ end;
+process_iq(#jid{luser = LUser, lserver = LServer},
+ #jid{lserver = LServer},
+ #iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
+ Prefs = get_prefs(LUser, LServer),
+ PrefsEl = prefs_el(Prefs#archive_prefs.default,
+ Prefs#archive_prefs.always,
+ Prefs#archive_prefs.never,
+ IQ#iq.xmlns),
+ IQ#iq{type = result, sub_el = [PrefsEl]};
+process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+
+process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
+ case MsgType of
+ chat ->
+ maybe_activate_mam(LUser, LServer);
+ {groupchat, _Role, _MUCState} ->
+ ok
+ end,
+ case catch lists:foldl(
+ fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
+ {{_, _, _} = jlib:datetime_string_to_timestamp(Data),
+ End, With, RSM};
+ ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
+ {Start,
+ {_, _, _} = jlib:datetime_string_to_timestamp(Data),
+ With, RSM};
+ ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
+ {Start, End, jid:tolower(jid:from_string(Data)), RSM};
+ ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
+ {Start, End, {text, Data}, RSM};
+ ({<<"set">>, El}, {Start, End, With, _}) ->
+ {Start, End, With, jlib:rsm_decode(El)};
+ (_, Acc) ->
+ Acc
+ end, {none, [], none, none}, Fs) of
+ {'EXIT', _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ {_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
+ {Start, End, With, RSM} ->
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ select_and_send(LServer, From, To, Start, End,
+ With, limit_max(RSM, NS), IQ, MsgType)
+ end.
+
+muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
+ case may_enter_room(From, MUCState) of
+ true ->
+ LServer = MUCState#state.server_host,
+ Role = mod_muc_room:get_role(From, MUCState),
+ process_iq(LServer, From, To, IQ, SubEl, Fs,
+ {groupchat, Role, MUCState});
+ false ->
+ Text = <<"Only members may query archives of this room">>,
+ Error = ?ERRT_FORBIDDEN(Lang, Text),
+ IQ#iq{type = error, sub_el = [SubEl, Error]}
+ end.
+
+parse_query_v0_2(Query) ->
+ lists:flatmap(
+ fun (#xmlel{name = <<"start">>} = El) ->
+ [{<<"start">>, [fxml:get_tag_cdata(El)]}];
+ (#xmlel{name = <<"end">>} = El) ->
+ [{<<"end">>, [fxml:get_tag_cdata(El)]}];
+ (#xmlel{name = <<"with">>} = El) ->
+ [{<<"with">>, [fxml:get_tag_cdata(El)]}];
+ (#xmlel{name = <<"withtext">>} = El) ->
+ [{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
+ (#xmlel{name = <<"set">>}) ->
+ [{<<"set">>, Query}];
+ (_) ->
+ []
+ end, Query#xmlel.children).
+
+should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ <<"error">> ->
+ false;
+ <<"groupchat">> ->
+ false;
+ _ ->
+ case is_resent(Pkt, LServer) of
+ true ->
+ false;
+ false ->
+ case check_store_hint(Pkt) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
+ <<>> ->
+ %% Empty body
+ false;
+ _ ->
+ true
+ end
+ end
+ end
+ end;
+should_archive(#xmlel{}, _LServer) ->
+ false.
+
+strip_my_archived_tag(Pkt, LServer) ->
+ NewEls = lists:filter(
+ fun(#xmlel{name = Tag, attrs = Attrs})
+ when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
+ case catch jid:nameprep(
+ fxml:get_attr_s(
+ <<"by">>, Attrs)) of
+ LServer ->
+ false;
+ _ ->
+ true
+ end;
+ (_) ->
+ true
+ end, Pkt#xmlel.children),
+ Pkt#xmlel{children = NewEls}.
+
+strip_x_jid_tags(Pkt) ->
+ NewEls = lists:filter(
+ fun(#xmlel{name = <<"x">>} = XEl) ->
+ not lists:any(fun(ItemEl) ->
+ fxml:get_tag_attr(<<"jid">>, ItemEl)
+ /= false
+ end, fxml:get_subtags(XEl, <<"item">>));
+ (_) ->
+ true
+ end, Pkt#xmlel.children),
+ Pkt#xmlel{children = NewEls}.
+
+should_archive_peer(C2SState,
+ #archive_prefs{default = Default,
+ always = Always,
+ never = Never},
+ Peer) ->
+ LPeer = jid:tolower(Peer),
+ case lists:member(LPeer, Always) of
+ true ->
+ true;
+ false ->
+ case lists:member(LPeer, Never) of
+ true ->
+ false;
+ false ->
+ case Default of
+ always -> true;
+ never -> false;
+ roster ->
+ case ejabberd_c2s:get_subscription(
+ LPeer, C2SState) of
+ both -> true;
+ from -> true;
+ to -> true;
+ _ -> false
+ end
+ end
+ end
+ end.
+
+should_archive_muc(Pkt) ->
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ <<"groupchat">> ->
+ case check_store_hint(Pkt) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
+ <<>> ->
+ case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
+ <<>> ->
+ false;
+ _ ->
+ true
+ end;
+ _ ->
+ true
+ end
+ end;
+ _ ->
+ false
+ end.
+
+check_store_hint(Pkt) ->
+ case has_store_hint(Pkt) of
+ true ->
+ store;
+ false ->
+ case has_no_store_hint(Pkt) of
+ true ->
+ no_store;
+ false ->
+ none
+ end
+ end.
+
+has_store_hint(Message) ->
+ fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
+ /= false.
+
+has_no_store_hint(Message) ->
+ fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
+ /= false orelse
+ fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
+ /= false orelse
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
+ /= false orelse
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
+ /= false.
+
+is_resent(Pkt, LServer) ->
+ case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
+ #xmlel{attrs = Attrs} ->
+ case fxml:get_attr(<<"by">>, Attrs) of
+ {value, LServer} ->
+ true;
+ _ ->
+ false
+ end;
+ false ->
+ false
+ end.
+
+may_enter_room(From,
+ #state{config = #config{members_only = false}} = MUCState) ->
+ mod_muc_room:get_affiliation(From, MUCState) /= outcast;
+may_enter_room(From, MUCState) ->
+ mod_muc_room:is_occupant_or_admin(From, MUCState).
+
+store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
+ Prefs = get_prefs(LUser, LServer),
+ case should_archive_peer(C2SState, Prefs, Peer) of
+ true ->
+ US = {LUser, LServer},
+ store(Pkt, LServer, US, chat, Peer, <<"">>, Dir,
+ gen_mod:db_type(LServer, ?MODULE));
+ false ->
+ pass
+ end.
+
+store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
+ case should_archive_muc(Pkt) of
+ true ->
+ LServer = MUCState#state.server_host,
+ {U, S, _} = jid:tolower(RoomJID),
+ store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv,
+ gen_mod:db_type(LServer, ?MODULE));
+ false ->
+ pass
+ end.
+
+store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) ->
+ LPeer = {PUser, PServer, _} = jid:tolower(Peer),
+ TS = p1_time_compat:timestamp(),
+ ID = jlib:integer_to_binary(now_to_usec(TS)),
+ case mnesia:dirty_write(
+ #archive_msg{us = {LUser, LServer},
+ id = ID,
+ timestamp = TS,
+ peer = LPeer,
+ bare_peer = {PUser, PServer, <<>>},
+ type = Type,
+ nick = Nick,
+ packet = Pkt}) of
+ ok ->
+ {ok, ID};
+ Err ->
+ Err
+ end;
+store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) ->
+ TSinteger = p1_time_compat:system_time(micro_seconds),
+ ID = TS = jlib:integer_to_binary(TSinteger),
+ SUser = case Type of
+ chat -> LUser;
+ groupchat -> jid:to_string({LUser, LHost, <<>>})
+ end,
+ BarePeer = jid:to_string(
+ jid:tolower(
+ jid:remove_resource(Peer))),
+ LPeer = jid:to_string(
+ jid:tolower(Peer)),
+ XML = fxml:element_to_binary(Pkt),
+ Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
+ case ejabberd_odbc:sql_query(
+ LServer,
+ [<<"insert into archive (username, timestamp, "
+ "peer, bare_peer, xml, txt, kind, nick) values (">>,
+ <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>,
+ <<"'">>, TS, <<"', ">>,
+ <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>,
+ <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>,
+ <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>,
+ <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>,
+ <<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
+ <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of
+ {updated, _} ->
+ {ok, ID};
+ Err ->
+ Err
+ end.
+
+write_prefs(LUser, LServer, Host, Default, Always, Never) ->
+ DBType = case gen_mod:db_type(Host, ?MODULE) of
+ odbc -> {odbc, Host};
+ DB -> DB
+ end,
+ Prefs = #archive_prefs{us = {LUser, LServer},
+ default = Default,
+ always = Always,
+ never = Never},
+ cache_tab:dirty_insert(
+ archive_prefs, {LUser, LServer}, Prefs,
+ fun() -> write_prefs(LUser, LServer, Prefs, DBType) end).
+
+write_prefs(_LUser, _LServer, Prefs, mnesia) ->
+ mnesia:dirty_write(Prefs);
+write_prefs(LUser, _LServer, #archive_prefs{default = Default,
+ never = Never,
+ always = Always},
+ {odbc, Host}) ->
+ SUser = ejabberd_odbc:escape(LUser),
+ SDefault = erlang:atom_to_binary(Default, utf8),
+ SAlways = ejabberd_odbc:encode_term(Always),
+ SNever = ejabberd_odbc:encode_term(Never),
+ case update(Host, <<"archive_prefs">>,
+ [<<"username">>, <<"def">>, <<"always">>, <<"never">>],
+ [SUser, SDefault, SAlways, SNever],
+ [<<"username='">>, SUser, <<"'">>]) of
+ {updated, _} ->
+ ok;
+ Err ->
+ Err
+ end.
+
+get_prefs(LUser, LServer) ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
+ fun() -> get_prefs(LUser, LServer,
+ DBType)
+ end),
+ case Res of
+ {ok, Prefs} ->
+ Prefs;
+ error ->
+ ActivateOpt = gen_mod:get_module_opt(
+ LServer, ?MODULE, request_activates_archiving,
+ fun(B) when is_boolean(B) -> B end, false),
+ case ActivateOpt of
+ true ->
+ #archive_prefs{us = {LUser, LServer}, default = never};
+ false ->
+ Default = gen_mod:get_module_opt(
+ LServer, ?MODULE, default,
+ fun(always) -> always;
+ (never) -> never;
+ (roster) -> roster
+ end, never),
+ #archive_prefs{us = {LUser, LServer}, default = Default}
+ end
+ end.
+
+get_prefs(LUser, LServer, mnesia) ->
+ case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of
+ [Prefs] ->
+ {ok, Prefs};
+ _ ->
+ error
+ end;
+get_prefs(LUser, LServer, odbc) ->
+ case ejabberd_odbc:sql_query(
+ LServer,
+ [<<"select def, always, never from archive_prefs ">>,
+ <<"where username='">>,
+ ejabberd_odbc:escape(LUser), <<"';">>]) of
+ {selected, _, [[SDefault, SAlways, SNever]]} ->
+ Default = erlang:binary_to_existing_atom(SDefault, utf8),
+ Always = ejabberd_odbc:decode_term(SAlways),
+ Never = ejabberd_odbc:decode_term(SNever),
+ {ok, #archive_prefs{us = {LUser, LServer},
+ default = Default,
+ always = Always,
+ never = Never}};
+ _ ->
+ error
+ end.
+
+prefs_el(Default, Always, Never, NS) ->
+ Default1 = jlib:atom_to_binary(Default),
+ JFun = fun(L) ->
+ [#xmlel{name = <<"jid">>,
+ children = [{xmlcdata, jid:to_string(J)}]}
+ || J <- L]
+ end,
+ Always1 = #xmlel{name = <<"always">>,
+ children = JFun(Always)},
+ Never1 = #xmlel{name = <<"never">>,
+ children = JFun(Never)},
+ #xmlel{name = <<"prefs">>,
+ attrs = [{<<"xmlns">>, NS},
+ {<<"default">>, Default1}],
+ children = [Always1, Never1]}.
+
+maybe_activate_mam(LUser, LServer) ->
+ ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
+ request_activates_archiving,
+ fun(B) when is_boolean(B) -> B end,
+ false),
+ case ActivateOpt of
+ true ->
+ Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
+ fun() ->
+ get_prefs(LUser, LServer,
+ gen_mod:db_type(LServer,
+ ?MODULE))
+ end),
+ case Res of
+ {ok, _Prefs} ->
+ ok;
+ error ->
+ Default = gen_mod:get_module_opt(LServer, ?MODULE, default,
+ fun(always) -> always;
+ (never) -> never;
+ (roster) -> roster
+ end, never),
+ write_prefs(LUser, LServer, LServer, Default, [], [])
+ end;
+ false ->
+ ok
+ end.
+
+select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
+ DBType = case gen_mod:db_type(LServer, ?MODULE) of
+ odbc -> {odbc, LServer};
+ DB -> DB
+ end,
+ select_and_send(LServer, From, To, Start, End, With, RSM, IQ,
+ MsgType, DBType).
+
+select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) ->
+ {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
+ With, RSM, MsgType, DBType),
+ SortedMsgs = lists:keysort(2, Msgs),
+ send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
+
+select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) ->
+ case MsgType of
+ chat ->
+ select(LServer, From, From, Start, End, With, RSM, MsgType, DBType);
+ {groupchat, _Role, _MUCState} ->
+ select(LServer, From, To, Start, End, With, RSM, MsgType, DBType)
+ end.
+
+select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
+ {groupchat, _Role, #state{config = #config{mam = false},
+ history = History}} = MsgType,
+ _DBType) ->
+ #lqueue{len = L, queue = Q} = History,
+ {Msgs0, _} =
+ lists:mapfoldl(
+ fun({Nick, Pkt, _HaveSubject, UTCDateTime, _Size}, I) ->
+ Now = datetime_to_now(UTCDateTime, I),
+ TS = now_to_usec(Now),
+ case match_interval(Now, Start, End) and
+ match_rsm(Now, RSM) of
+ true ->
+ {[{jlib:integer_to_binary(TS), TS,
+ msg_to_el(#archive_msg{
+ type = groupchat,
+ timestamp = Now,
+ peer = undefined,
+ nick = Nick,
+ packet = Pkt},
+ MsgType, JidRequestor, JidArchive)}],
+ I+1};
+ false ->
+ {[], I+1}
+ end
+ end, 0, queue:to_list(Q)),
+ Msgs = lists:flatten(Msgs0),
+ case RSM of
+ #rsm_in{max = Max, direction = before} ->
+ {NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
+ {NewMsgs, IsComplete, L};
+ #rsm_in{max = Max} ->
+ {NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
+ {NewMsgs, IsComplete, L};
+ _ ->
+ {Msgs, true, L}
+ end;
+select(_LServer, JidRequestor,
+ #jid{luser = LUser, lserver = LServer} = JidArchive,
+ Start, End, With, RSM, MsgType, mnesia) ->
+ MS = make_matchspec(LUser, LServer, Start, End, With),
+ Msgs = mnesia:dirty_select(archive_msg, MS),
+ SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
+ {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
+ Count = length(Msgs),
+ {lists:map(
+ fun(Msg) ->
+ {Msg#archive_msg.id,
+ jlib:binary_to_integer(Msg#archive_msg.id),
+ msg_to_el(Msg, MsgType, JidRequestor, JidArchive)}
+ end, FilteredMsgs), IsComplete, Count};
+select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
+ Start, End, With, RSM, MsgType, {odbc, Host}) ->
+ User = case MsgType of
+ chat -> LUser;
+ {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
+ end,
+ {Query, CountQuery} = make_sql_query(User, LServer,
+ Start, End, With, RSM),
+ % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
+ % reasonable limit on how many stanzas may be pushed to a client in one
+ % request. If a query returns a number of stanzas greater than this limit
+ % and the client did not specify a limit using RSM then the server should
+ % return a policy-violation error to the client." We currently don't do this
+ % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
+ case {ejabberd_odbc:sql_query(Host, Query),
+ ejabberd_odbc:sql_query(Host, CountQuery)} of
+ {{selected, _, Res}, {selected, _, [[Count]]}} ->
+ {Max, Direction} = case RSM of
+ #rsm_in{max = M, direction = D} -> {M, D};
+ _ -> {undefined, undefined}
+ end,
+ {Res1, IsComplete} =
+ if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
+ if Direction == before ->
+ {lists:nthtail(1, Res), false};
+ true ->
+ {lists:sublist(Res, Max), false}
+ end;
+ true ->
+ {Res, true}
+ end,
+ {lists:flatmap(
+ fun([TS, XML, PeerBin, Kind, Nick]) ->
+ try
+ #xmlel{} = El = fxml_stream:parse_element(XML),
+ Now = usec_to_now(jlib:binary_to_integer(TS)),
+ PeerJid = jid:tolower(jid:from_string(PeerBin)),
+ T = case Kind of
+ <<"">> -> chat;
+ null -> chat;
+ _ -> jlib:binary_to_atom(Kind)
+ end,
+ [{TS, jlib:binary_to_integer(TS),
+ msg_to_el(#archive_msg{timestamp = Now,
+ packet = El,
+ type = T,
+ nick = Nick,
+ peer = PeerJid},
+ MsgType, JidRequestor, JidArchive)}]
+ catch _:Err ->
+ ?ERROR_MSG("failed to parse data from SQL: ~p. "
+ "The data was: "
+ "timestamp = ~s, xml = ~s, "
+ "peer = ~s, kind = ~s, nick = ~s",
+ [Err, TS, XML, PeerBin, Kind, Nick]),
+ []
+ end
+ end, Res1), IsComplete, jlib:binary_to_integer(Count)};
+ _ ->
+ {[], false, 0}
+ end.
+
+msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
+ MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
+ Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
+ Nick),
+ Pkt3 = #xmlel{name = <<"forwarded">>,
+ attrs = [{<<"xmlns">>, ?NS_FORWARD}],
+ children = [fxml:replace_tag_attr(
+ <<"xmlns">>, <<"jabber:client">>, Pkt2)]},
+ jlib:add_delay_info(Pkt3, LServer, TS).
+
+maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
+ Peer, {groupchat, Role,
+ #state{config = #config{anonymous = Anon}}},
+ Nick) ->
+ ExposeJID = case {Peer, JidRequestor} of
+ {undefined, _JidRequestor} ->
+ false;
+ {{U, S, _R}, #jid{luser = U, lserver = S}} ->
+ true;
+ {_Peer, _JidRequestor} when not Anon; Role == moderator ->
+ true;
+ {_Peer, _JidRequestor} ->
+ false
+ end,
+ Items = case ExposeJID of
+ true ->
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
+ children =
+ [#xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>,
+ jid:to_string(Peer)}]}]}];
+ false ->
+ []
+ end,
+ Pkt1 = Pkt#xmlel{children = Items ++ Els},
+ Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
+ jlib:remove_attr(<<"to">>, Pkt2);
+maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
+ Pkt.
+
+is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
+ PrioRes = ejabberd_sm:get_user_present_resources(U, S),
+ MaxRes = case catch lists:max(PrioRes) of
+ {_Prio, Res} when is_binary(Res) ->
+ Res;
+ _ ->
+ undefined
+ end,
+ IsBareTo = case To of
+ #jid{lresource = <<"">>} ->
+ true;
+ #jid{lresource = LRes} ->
+ %% Unavailable resources are handled like bare JIDs.
+ lists:keyfind(LRes, 2, PrioRes) =:= false
+ end,
+ case {IsBareTo, R} of
+ {true, MaxRes} ->
+ ?DEBUG("Recipient of message to bare JID has top priority: ~s@~s/~s",
+ [U, S, R]),
+ false;
+ {true, _R} ->
+ %% The message was sent to our bare JID, and we currently have
+ %% multiple resources with the same highest priority, so the session
+ %% manager routes the message to each of them. We store the message
+ %% only from the resource where R equals MaxRes.
+ ?DEBUG("Additional recipient of message to bare JID: ~s@~s/~s",
+ [U, S, R]),
+ true;
+ {false, _R} ->
+ false
+ end.
+
+send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
+ QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ QIDAttr = if QID /= <<>> ->
+ [{<<"queryid">>, QID}];
+ true ->
+ []
+ end,
+ CompleteAttr = if NS == ?NS_MAM_TMP ->
+ [];
+ NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+ [{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
+ end,
+ Els = lists:map(
+ fun({ID, _IDInt, El}) ->
+ #xmlel{name = <<"message">>,
+ children = [#xmlel{name = <<"result">>,
+ attrs = [{<<"xmlns">>, NS},
+ {<<"id">>, ID}|QIDAttr],
+ children = [El]}]}
+ end, Msgs),
+ RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
+ if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
+ lists:foreach(
+ fun(El) ->
+ ejabberd_router:route(To, From, El)
+ end, Els),
+ IQ#iq{type = result, sub_el = RSMOut};
+ NS == ?NS_MAM_0 ->
+ ejabberd_router:route(
+ To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
+ lists:foreach(
+ fun(El) ->
+ ejabberd_router:route(To, From, El)
+ end, Els),
+ ejabberd_router:route(
+ To, From, #xmlel{name = <<"message">>,
+ children = RSMOut}),
+ ignore
+ end.
+
+
+make_rsm_out([], _, Count, Attrs, NS) ->
+ Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
+ true -> <<"fin">>
+ end,
+ [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
+ children = jlib:rsm_encode(#rsm_out{count = Count})}];
+make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
+ {LastID, _, _} = lists:last(Msgs),
+ Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
+ true -> <<"fin">>
+ end,
+ [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
+ children = jlib:rsm_encode(
+ #rsm_out{first = FirstID, count = Count,
+ last = LastID})}].
+
+filter_by_rsm(Msgs, none) ->
+ {Msgs, true};
+filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+ {[], true};
+filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
+ NewMsgs = case Direction of
+ aft when ID /= <<"">> ->
+ lists:filter(
+ fun(#archive_msg{id = I}) ->
+ ?BIN_GREATER_THAN(I, ID)
+ end, Msgs);
+ before when ID /= <<"">> ->
+ lists:foldl(
+ fun(#archive_msg{id = I} = Msg, Acc)
+ when ?BIN_LESS_THAN(I, ID) ->
+ [Msg|Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Msgs);
+ before when ID == <<"">> ->
+ lists:reverse(Msgs);
+ _ ->
+ Msgs
+ end,
+ filter_by_max(NewMsgs, Max).
+
+filter_by_max(Msgs, undefined) ->
+ {Msgs, true};
+filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
+ {lists:sublist(Msgs, Len), length(Msgs) =< Len};
+filter_by_max(_Msgs, _Junk) ->
+ {[], true}.
+
+limit_max(RSM, ?NS_MAM_TMP) ->
+ RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
+limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
+ RSM#rsm_in{max = ?DEF_PAGE_SIZE};
+limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
+ RSM#rsm_in{max = ?MAX_PAGE_SIZE};
+limit_max(RSM, _NS) ->
+ RSM.
+
+match_interval(Now, Start, End) ->
+ (Now >= Start) and (Now =< End).
+
+match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> ->
+ Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
+ Now > Now1;
+match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
+ Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
+ Now < Now1;
+match_rsm(_Now, _) ->
+ true.
+
+make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ bare_peer = BPeer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ BPeer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ Peer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, none) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer} ->
+ Msg
+ end).
+
+make_sql_query(User, _LServer, Start, End, With, RSM) ->
+ {Max, Direction, ID} = case RSM of
+ #rsm_in{} ->
+ {RSM#rsm_in.max,
+ RSM#rsm_in.direction,
+ RSM#rsm_in.id};
+ none ->
+ {none, none, <<>>}
+ end,
+ LimitClause = if is_integer(Max), Max >= 0 ->
+ [<<" limit ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
+ WithClause = case With of
+ {text, <<>>} ->
+ [];
+ {text, Txt} ->
+ [<<" and match (txt) against ('">>,
+ ejabberd_odbc:escape(Txt), <<"')">>];
+ {_, _, <<>>} ->
+ [<<" and bare_peer='">>,
+ ejabberd_odbc:escape(jid:to_string(With)),
+ <<"'">>];
+ {_, _, _} ->
+ [<<" and peer='">>,
+ ejabberd_odbc:escape(jid:to_string(With)),
+ <<"'">>];
+ none ->
+ []
+ end,
+ PageClause = case catch jlib:binary_to_integer(ID) of
+ I when is_integer(I), I >= 0 ->
+ case Direction of
+ before ->
+ [<<" AND timestamp < ">>, ID];
+ aft ->
+ [<<" AND timestamp > ">>, ID];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ StartClause = case Start of
+ {_, _, _} ->
+ [<<" and timestamp >= ">>,
+ jlib:integer_to_binary(now_to_usec(Start))];
+ _ ->
+ []
+ end,
+ EndClause = case End of
+ {_, _, _} ->
+ [<<" and timestamp <= ">>,
+ jlib:integer_to_binary(now_to_usec(End))];
+ _ ->
+ []
+ end,
+ SUser = ejabberd_odbc:escape(User),
+
+ Query = [<<"SELECT timestamp, xml, peer, kind, nick"
+ " FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause,
+ PageClause],
+
+ QueryPage =
+ case Direction of
+ before ->
+ % ID can be empty because of
+ % XEP-0059: Result Set Management
+ % 2.5 Requesting the Last Page in a Result Set
+ [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
+ <<" ORDER BY timestamp DESC ">>,
+ LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
+ _ ->
+ [Query, <<" ORDER BY timestamp ASC ">>,
+ LimitClause, <<";">>]
+ end,
+ {QueryPage,
+ [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
+
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+usec_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
+datetime_to_now(DateTime, USecs) ->
+ Seconds = calendar:datetime_to_gregorian_seconds(DateTime) -
+ calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+ {Seconds div 1000000, Seconds rem 1000000, USecs}.
+
+get_jids(Els) ->
+ lists:flatmap(
+ fun(#xmlel{name = <<"jid">>} = El) ->
+ J = jid:from_string(fxml:get_tag_cdata(El)),
+ [jid:tolower(jid:remove_resource(J)),
+ jid:tolower(J)];
+ (_) ->
+ []
+ end, Els).
+
+update(LServer, Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_odbc:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> {updated, 1};
+ _ ->
+ ejabberd_odbc:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>])
+ end.
+
+%% Almost a copy of string:join/2.
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
+
+get_commands_spec() ->
+ [#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
+ desc = "Delete MAM messages older than DAYS",
+ longdesc = "Valid message TYPEs: "
+ "\"chat\", \"groupchat\", \"all\".",
+ module = ?MODULE, function = delete_old_messages,
+ args = [{type, binary}, {days, integer}],
+ result = {res, rescode}}].
+
+mod_opt_type(assume_mam_usage) ->
+ fun(if_enabled) -> if_enabled;
+ (on_request) -> on_request;
+ (never) -> never
+ end;
+mod_opt_type(cache_life_time) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(default) ->
+ fun (always) -> always;
+ (never) -> never;
+ (roster) -> roster
+ end;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(request_activates_archiving) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [assume_mam_usage, cache_life_time, cache_size, db_type, default, iqdisc,
+ request_activates_archiving].
diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl
new file mode 100644
index 00000000..c175fcb8
--- /dev/null
+++ b/src/mod_metrics.erl
@@ -0,0 +1,128 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_metrics.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Simple metrics handler for runtime statistics
+%%% Created : 22 Oct 2015 by Christophe Romain <christophe.romain@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(mod_metrics).
+
+-behaviour(ejabberd_config).
+-author('christophe.romain@process-one.net').
+-behaviour(gen_mod).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("jlib.hrl").
+
+-define(HOOKS, [offline_message_hook,
+ sm_register_connection_hook, sm_remove_connection_hook,
+ user_send_packet, user_receive_packet,
+ s2s_send_packet, s2s_receive_packet,
+ remove_user, register_user]).
+
+-export([start/2, stop/1, send_metrics/4, opt_type/1]).
+
+-export([offline_message_hook/3,
+ sm_register_connection_hook/3, sm_remove_connection_hook/3,
+ user_send_packet/4, user_receive_packet/5,
+ s2s_send_packet/3, s2s_receive_packet/3,
+ remove_user/2, register_user/2]).
+
+%%====================================================================
+%% API
+%%====================================================================
+
+start(Host, _Opts) ->
+ [ejabberd_hooks:add(Hook, Host, ?MODULE, Hook, 20)
+ || Hook <- ?HOOKS].
+
+stop(Host) ->
+ [ejabberd_hooks:delete(Hook, Host, ?MODULE, Hook, 20)
+ || Hook <- ?HOOKS].
+
+%%====================================================================
+%% Hooks handlers
+%%====================================================================
+
+offline_message_hook(_From, #jid{lserver=LServer}, _Packet) ->
+ push(LServer, offline_message).
+
+sm_register_connection_hook(_SID, #jid{lserver=LServer}, _Info) ->
+ push(LServer, sm_register_connection).
+sm_remove_connection_hook(_SID, #jid{lserver=LServer}, _Info) ->
+ push(LServer, sm_remove_connection).
+
+user_send_packet(Packet, _C2SState, #jid{lserver=LServer}, _To) ->
+ push(LServer, user_send_packet),
+ Packet.
+user_receive_packet(Packet, _C2SState, _JID, _From, #jid{lserver=LServer}) ->
+ push(LServer, user_receive_packet),
+ Packet.
+
+s2s_send_packet(#jid{lserver=LServer}, _To, _Packet) ->
+ push(LServer, s2s_send_packet).
+s2s_receive_packet(_From, #jid{lserver=LServer}, _Packet) ->
+ push(LServer, s2s_receive_packet).
+
+remove_user(_User, Server) ->
+ push(jid:nameprep(Server), remove_user).
+register_user(_User, Server) ->
+ push(jid:nameprep(Server), register_user).
+
+%%====================================================================
+%% metrics push handler
+%%====================================================================
+
+push(Host, Probe) ->
+ spawn(?MODULE, send_metrics, [Host, Probe, {127,0,0,1}, 11111]).
+
+send_metrics(Host, Probe, Peer, Port) ->
+ % our default metrics handler is https://github.com/processone/grapherl
+ % grapherl metrics are named first with service domain, then nodename
+ % and name of the data itself, followed by type timestamp and value
+ % example => process-one.net/xmpp-1.user_receive_packet:c/1441784958:1
+ [_, NodeId] = str:tokens(jlib:atom_to_binary(node()), <<"@">>),
+ [Node | _] = str:tokens(NodeId, <<".">>),
+ BaseId = <<Host/binary, "/", Node/binary, ".">>,
+ DateTime = erlang:universaltime(),
+ UnixTime = calendar:datetime_to_gregorian_seconds(DateTime) - 62167219200,
+ TS = integer_to_binary(UnixTime),
+ case gen_udp:open(0) of
+ {ok, Socket} ->
+ case Probe of
+ {Key, Val} ->
+ BVal = integer_to_binary(Val),
+ Data = <<BaseId/binary, (jlib:atom_to_binary(Key))/binary,
+ ":g/", TS/binary, ":", BVal/binary>>,
+ gen_udp:send(Socket, Peer, Port, Data);
+ Key ->
+ Data = <<BaseId/binary, (jlib:atom_to_binary(Key))/binary,
+ ":c/", TS/binary, ":1">>,
+ gen_udp:send(Socket, Peer, Port, Data)
+ end,
+ gen_udp:close(Socket);
+ Error ->
+ ?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error])
+ end.
+
+opt_type(_) ->
+ [].
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
new file mode 100644
index 00000000..d8cf94ac
--- /dev/null
+++ b/src/mod_mix.erl
@@ -0,0 +1,347 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mix).
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% API
+-export([start_link/2, start/2, stop/1, process_iq/3,
+ disco_items/5, disco_identity/5, disco_info/5,
+ disco_features/5, mod_opt_type/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include("logger.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+
+-define(PROCNAME, ejabberd_mod_mix).
+-define(NODES, [?NS_MIX_NODES_MESSAGES,
+ ?NS_MIX_NODES_PRESENCE,
+ ?NS_MIX_NODES_PARTICIPANTS,
+ ?NS_MIX_NODES_SUBJECT,
+ ?NS_MIX_NODES_CONFIG]).
+
+-record(state, {server_host :: binary(),
+ host :: binary()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 5000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc),
+ ok.
+
+disco_features(_Acc, _From, _To, _Node, _Lang) ->
+ {result, [?NS_MIX_0]}.
+
+disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
+ To_s = jid:to_string(jid:remove_resource(To)),
+ {result, [#xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, To_s},
+ {<<"node">>, Node}]} || Node <- ?NODES]};
+disco_items(_Acc, _From, _To, _Node, _Lang) ->
+ {result, []}.
+
+disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"name">>, <<"MIX service">>},
+ {<<"type">>, <<"text">>}]}];
+disco_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"mix">>}]}].
+
+disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_MIX_SERVICEINFO_0}]}]}]}];
+disco_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) ->
+ Nodes = lists:flatmap(
+ fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) ->
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ case lists:member(Node, ?NODES) of
+ true -> [Node];
+ false -> []
+ end;
+ (_) ->
+ []
+ end, SubEl#xmlel.children),
+ case subscribe_nodes(From, To, Nodes) of
+ {result, _} ->
+ case publish_participant(From, To) of
+ {result, _} ->
+ LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ Subscribe = [#xmlel{name = <<"subscribe">>,
+ attrs = [{<<"node">>, Node}]} || Node <- Nodes],
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"join">>,
+ attrs = [{<<"jid">>, LFrom_s},
+ {<<"xmlns">>, ?NS_MIX_0}],
+ children = Subscribe}]};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) ->
+ case delete_participant(From, To) of
+ {result, _} ->
+ case unsubscribe_nodes(From, To, ?NODES) of
+ {result, _} ->
+ IQ#iq{type = result, sub_el = []};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(_From, _To, #iq{sub_el = SubEl} = IQ) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([ServerHost, Opts]) ->
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ ConfigTab = gen_mod:get_module_proc(Host, config),
+ ets:new(ConfigTab, [named_table]),
+ ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MIX_0, ?MODULE, process_iq, IQDisc),
+ ejabberd_router:register_route(Host, ServerHost),
+ {ok, #state{server_host = ServerHost, host = Host}}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(State, From, To, Packet) of
+ {'EXIT', _} = Err ->
+ try
+ ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p",
+ [Packet, jid:to_string(From), jid:to_string(To), Err]),
+ ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route_error(To, From, ErrPkt, Packet)
+ catch _:_ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{host = Host}) ->
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
+ ejabberd_router:unregister_route(Host),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) ->
+ if To#jid.luser == <<"">> ->
+ ejabberd_local:process_iq(From, To, Packet);
+ true ->
+ ejabberd_sm:process_iq(From, To, Packet)
+ end;
+do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet)
+ when To#jid.luser /= <<"">> ->
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"unavailable">> ->
+ delete_presence(From, To);
+ _ ->
+ ok
+ end;
+do_route(_State, _From, _To, _Packet) ->
+ ok.
+
+subscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _}) ->
+ case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) of
+ {error, _} = Err ->
+ case is_item_not_found(Err) of
+ true ->
+ case mod_pubsub:create_node(
+ LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
+ {result, _} ->
+ mod_pubsub:subscribe_node(LTo, Node, From, From_s, []);
+ Error ->
+ Error
+ end;
+ false ->
+ Err
+ end;
+ {result, _} = Result ->
+ Result
+ end
+ end, {result, []}, Nodes).
+
+unsubscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _} = Result) ->
+ case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of
+ {error, _} = Err ->
+ case is_not_subscribed(Err) of
+ true -> Result;
+ _ -> Err
+ end;
+ {result, _} = Res ->
+ Res
+ end
+ end, {result, []}, Nodes).
+
+publish_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ Participant = #xmlel{name = <<"participant">>,
+ attrs = [{<<"xmlns">>, ?NS_MIX_0},
+ {<<"jid">>, jid:to_string(LFrom)}]},
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ mod_pubsub:publish_item(
+ LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
+ From, ItemID, [Participant]).
+
+delete_presence(From, To) ->
+ LFrom = jid:tolower(From),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
+ Items when is_list(Items) ->
+ lists:foreach(
+ fun(#pubsub_item{modification = {_, LJID},
+ itemid = {ItemID, _}}) when LJID == LFrom ->
+ delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
+ (_) ->
+ ok
+ end, Items);
+ _ ->
+ ok
+ end.
+
+delete_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ delete_presence(From, To),
+ delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
+
+delete_item(From, To, Node, ItemID) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ case mod_pubsub:delete_item(
+ LTo, Node, From, ItemID, true) of
+ {result, _} = Res ->
+ Res;
+ {error, _} = Err ->
+ case is_item_not_found(Err) of
+ true -> {result, []};
+ false -> Err
+ end
+ end.
+
+is_item_not_found({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+is_not_subscribed({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(_) -> [host, iqdisc].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index a3a8a933..0d37a236 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -5,7 +5,7 @@
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,8 @@
-author('alexey@process-one.net').
+-protocol({xep, 45, '1.25'}).
+
-behaviour(gen_server).
-behaviour(gen_mod).
@@ -40,35 +42,23 @@
restore_room/3,
forget_room/3,
create_room/5,
- shutdown_rooms/1,
+ shutdown_rooms/1,
process_iq_disco_items/4,
broadcast_service_message/2,
- export/1,
- import/1,
- import/3,
+ export/1,
+ import/1,
+ import/3,
can_use_nick/4]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-
--record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} |
- {'_', binary()},
- opts = [] :: list() | '_'}).
-
--record(muc_online_room,
- {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1' |
- {'_', binary()} | '_',
- pid = self() :: pid() | '$2' | '_' | '$1'}).
-
--record(muc_registered,
- {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
- nick = <<"">> :: binary()}).
+-include("mod_muc.hrl").
-record(state,
{host = <<"">> :: binary(),
@@ -80,20 +70,17 @@
-define(PROCNAME, ejabberd_mod_muc).
+-define(MAX_ROOMS_DISCOITEMS, 100).
+
%%====================================================================
%% API
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
start(Host, Opts) ->
- start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 1000, worker, [?MODULE]},
@@ -101,7 +88,6 @@ start(Host, Opts) ->
stop(Host) ->
Rooms = shutdown_rooms(Host),
- stop_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc),
@@ -113,7 +99,8 @@ shutdown_rooms(Host) ->
Rooms = mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1',
pid = '$2'},
- [{'==', {element, 2, '$1'}, MyHost}],
+ [{'==', {element, 2, '$1'}, MyHost},
+ {'==', {node, '$2'}, node()}],
['$2']}]),
[Pid ! shutdown || Pid <- Rooms],
Rooms.
@@ -137,7 +124,7 @@ create_room(Host, Name, From, Nick, Opts) ->
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
store_room(LServer, Host, Name, Opts,
gen_mod:db_type(LServer, ?MODULE)).
@@ -165,7 +152,7 @@ store_room(LServer, Host, Name, Opts, odbc) ->
ejabberd_odbc:sql_transaction(LServer, F).
restore_room(ServerHost, Host, Name) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
restore_room(LServer, Host, Name,
gen_mod:db_type(LServer, ?MODULE)).
@@ -193,17 +180,20 @@ restore_room(LServer, Host, Name, odbc) ->
end.
forget_room(ServerHost, Host, Name) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
forget_room(LServer, Host, Name,
gen_mod:db_type(LServer, ?MODULE)).
-forget_room(_LServer, Host, Name, mnesia) ->
+forget_room(LServer, Host, Name, mnesia) ->
+ remove_room_mam(LServer, Host, Name),
F = fun () -> mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F);
-forget_room(_LServer, Host, Name, riak) ->
+forget_room(LServer, Host, Name, riak) ->
+ remove_room_mam(LServer, Host, Name),
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
forget_room(LServer, Host, Name, odbc) ->
+ remove_room_mam(LServer, Host, Name),
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
F = fun () ->
@@ -213,24 +203,41 @@ forget_room(LServer, Host, Name, odbc) ->
end,
ejabberd_odbc:sql_transaction(LServer, F).
+remove_room_mam(LServer, Host, Name) ->
+ case gen_mod:is_loaded(LServer, mod_mam) of
+ true ->
+ U = jid:nodeprep(Name),
+ S = jid:nameprep(Host),
+ DBType = gen_mod:db_type(LServer, mod_mam),
+ if DBType == odbc ->
+ mod_mam:remove_user(jid:to_string({U, S, <<>>}),
+ LServer, DBType);
+ true ->
+ mod_mam:remove_user(U, S, DBType)
+ end;
+ false ->
+ ok
+ end.
+
process_iq_disco_items(Host, From, To,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
+ DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
Res = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
- children = iq_disco_items(Host, From, Lang, Rsm)}]},
+ children = iq_disco_items(Host, From, Lang, DiscoNode, Rsm)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
can_use_nick(LServer, Host, JID, Nick,
gen_mod:db_type(LServer, ?MODULE)).
can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
- {LUser, LServer, _} = jlib:jid_tolower(JID),
+ {LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer},
case catch mnesia:dirty_select(muc_registered,
[{#muc_registered{us_host = '$1',
@@ -243,7 +250,7 @@ can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
[#muc_registered{us_host = {U, _Host}}] -> U == LUS
end;
can_use_nick(LServer, Host, JID, Nick, riak) ->
- {LUser, LServer, _} = jlib:jid_tolower(JID),
+ {LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered,
muc_registered_schema(),
@@ -257,7 +264,7 @@ can_use_nick(LServer, Host, JID, Nick, riak) ->
end;
can_use_nick(LServer, Host, JID, Nick, odbc) ->
SJID =
- jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
+ jid:to_string(jid:tolower(jid:remove_resource(JID))),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
@@ -273,13 +280,6 @@ can_use_nick(LServer, Host, JID, Nick, odbc) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
@@ -305,18 +305,78 @@ init([Host, Opts]) ->
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system),
- Access = gen_mod:get_opt(access, Opts, fun(A) -> A end, all),
- AccessCreate = gen_mod:get_opt(access_create, Opts, fun(A) -> A end, all),
- AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun(A) -> A end, none),
- AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun(A) -> A end, all),
- HistorySize = gen_mod:get_opt(history_size, Opts, fun(A) -> A end, 20),
- DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, fun(A) -> A end, []),
- RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) -> A end, none),
- ejabberd_router:register_route(MyHost),
+ Access = gen_mod:get_opt(access, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ AccessCreate = gen_mod:get_opt(access_create, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ AccessAdmin = gen_mod:get_opt(access_admin, Opts,
+ fun(A) when is_atom(A) -> A end,
+ none),
+ AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ HistorySize = gen_mod:get_opt(history_size, Opts,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 20),
+ DefRoomOpts1 = gen_mod:get_opt(default_room_options, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ DefRoomOpts =
+ lists:flatmap(
+ fun({Opt, Val}) ->
+ Bool = fun(B) when is_boolean(B) -> B end,
+ VFun = case Opt of
+ allow_change_subj -> Bool;
+ allow_private_messages -> Bool;
+ allow_query_users -> Bool;
+ allow_user_invites -> Bool;
+ allow_visitor_nickchange -> Bool;
+ allow_visitor_status -> Bool;
+ anonymous -> Bool;
+ captcha_protected -> Bool;
+ logging -> Bool;
+ members_by_default -> Bool;
+ members_only -> Bool;
+ moderated -> Bool;
+ password_protected -> Bool;
+ persistent -> Bool;
+ public -> Bool;
+ public_list -> Bool;
+ mam -> Bool;
+ password -> fun iolist_to_binary/1;
+ title -> fun iolist_to_binary/1;
+ allow_private_messages_from_visitors ->
+ fun(anyone) -> anyone;
+ (moderators) -> moderators;
+ (nobody) -> nobody
+ end;
+ max_users ->
+ fun(I) when is_integer(I), I > 0 -> I end;
+ presence_broadcast ->
+ fun(L) ->
+ lists:map(
+ fun(moderator) -> moderator;
+ (participant) -> participant;
+ (visitor) -> visitor
+ end, L)
+ end;
+ _ ->
+ ?ERROR_MSG("unknown option ~p with value ~p",
+ [Opt, Val]),
+ fun(_) -> undefined end
+ end,
+ case gen_mod:get_opt(Opt, [{Opt, Val}], VFun) of
+ undefined -> [];
+ NewVal -> [{Opt, NewVal}]
+ end
+ end, DefRoomOpts1),
+ RoomShaper = gen_mod:get_opt(room_shaper, Opts,
+ fun(A) when is_atom(A) -> A end,
+ none),
+ ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
- HistorySize,
- RoomShaper),
+ HistorySize, RoomShaper),
{ok, #state{host = MyHost,
server_host = Host,
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
@@ -324,15 +384,6 @@ init([Host, Opts]) ->
history_size = HistorySize,
room_shaper = RoomShaper}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call({create, Room, From, Nick, Opts}, _From,
@@ -353,20 +404,8 @@ handle_call({create, Room, From, Nick, Opts}, _From,
register_room(Host, Room, Pid),
{reply, ok, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast(_Msg, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
#state{host = Host, server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts,
@@ -393,39 +432,15 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-start_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host,
- ejabberd_mod_muc_sup),
- ChildSpec = {Proc,
- {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
- permanent, infinity, supervisor, [ejabberd_tmp_sup]},
- supervisor:start_child(ejabberd_sup, ChildSpec).
-
-stop_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host,
- ejabberd_mod_muc_sup),
- supervisor:terminate_child(ejabberd_sup, Proc),
- supervisor:delete_child(ejabberd_sup, Proc).
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
@@ -433,13 +448,13 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
case acl:match_rule(ServerHost, AccessRoute, From) of
allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts);
+ From, To, Packet, DefRoomOpts);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
+ ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route_error(To, From, Err, Packet)
end.
@@ -447,7 +462,7 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
- {Room, _, Nick} = jlib:jid_tolower(To),
+ {Room, _, Nick} = jid:tolower(To),
#xmlel{name = Name, attrs = Attrs} = Packet,
case Room of
<<"">> ->
@@ -469,7 +484,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
attrs =
[{<<"xmlns">>, XMLNS}],
children =
- iq_disco_info(Lang) ++
+ iq_disco_info(
+ ServerHost, Lang) ++
Info}]},
ejabberd_router:route(To, From,
jlib:iq_to_xml(Res));
@@ -541,18 +557,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
case acl:match_rule(ServerHost, AccessAdmin, From)
of
allow ->
- Msg = xml:get_path_s(Packet,
+ Msg = fxml:get_path_s(Packet,
[{elem, <<"body">>},
cdata]),
broadcast_service_message(Host, Msg);
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText =
<<"Only service administrators are allowed "
"to send service messages">>,
@@ -565,70 +581,72 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
<<"presence">> -> ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
- end
- end;
- _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end
+ end;
+ _ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case check_user_can_create_room(ServerHost,
- AccessCreate, From,
- Room) of
+ AccessCreate, From, Room) and
+ check_create_roomid(ServerHost, Room) of
true ->
- {ok, Pid} = start_new_room(
- Host, ServerHost, Access,
- Room, HistorySize,
- RoomShaper, From,
- Nick, DefRoomOpts),
+ {ok, Pid} = start_new_room(Host, ServerHost, Access,
+ Room, HistorySize,
+ RoomShaper, From, Nick, DefRoomOpts),
register_room(Host, Room, Pid),
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Room creation is denied by service policy">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- ErrText = <<"Conference room does not exist">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang,
- ErrText)),
- ejabberd_router:route(To, From, Err)
- end;
- [R] ->
- Pid = R#muc_online_room.pid,
- ?DEBUG("MUC: send to process ~p~n", [Pid]),
- mod_muc_room:route(Pid, From, Nick, Packet),
- ok
- end
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Conference room does not exist">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end;
+ [R] ->
+ Pid = R#muc_online_room.pid,
+ ?DEBUG("MUC: send to process ~p~n", [Pid]),
+ mod_muc_room:route(Pid, From, Nick, Packet),
+ ok
+ end
end.
check_user_can_create_room(ServerHost, AccessCreate,
- From, RoomID) ->
+ From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow ->
- byte_size(RoomID) =<
- gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
- fun(infinity) -> infinity;
- (I) when is_integer(I), I>0 -> I
- end, infinity);
+ allow -> true;
_ -> false
end.
+check_create_roomid(ServerHost, RoomID) ->
+ Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I), I>0 -> I
+ end, infinity),
+ Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id,
+ fun iolist_to_binary/1, ""),
+ (byte_size(RoomID) =< Max) and
+ (re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
+
get_rooms(ServerHost, Host) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
get_rooms(LServer, Host,
gen_mod:db_type(LServer, ?MODULE)).
@@ -667,52 +685,47 @@ get_rooms(LServer, Host, odbc) ->
Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), []
end.
-load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
+load_permanent_rooms(Host, ServerHost, Access,
+ HistorySize, RoomShaper) ->
lists:foreach(
fun(R) ->
- {Room, Host} = R#muc_room.name_host,
- case mnesia:dirty_read(muc_online_room, {Room, Host}) of
- [] ->
- {ok, Pid} = mod_muc_room:start(
- Host,
- ServerHost,
- Access,
- Room,
- HistorySize,
- RoomShaper,
- R#muc_room.opts),
- register_room(Host, Room, Pid);
- _ ->
- ok
- end
- end, get_rooms(ServerHost, Host)).
+ {Room, Host} = R#muc_room.name_host,
+ case mnesia:dirty_read(muc_online_room, {Room, Host}) of
+ [] ->
+ {ok, Pid} = mod_muc_room:start(Host,
+ ServerHost, Access, Room,
+ HistorySize, RoomShaper,
+ R#muc_room.opts),
+ register_room(Host, Room, Pid);
+ _ -> ok
+ end
+ end,
+ get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
- HistorySize, RoomShaper, From,
- Nick, DefRoomOpts) ->
+ HistorySize, RoomShaper, From,
+ Nick, DefRoomOpts) ->
case restore_room(ServerHost, Host, Room) of
- error ->
+ error ->
?DEBUG("MUC: open new room '~s'~n", [Room]),
- mod_muc_room:start(Host, ServerHost, Access,
- Room, HistorySize,
- RoomShaper, From,
- Nick, DefRoomOpts);
- Opts ->
+ mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper,
+ From, Nick, DefRoomOpts);
+ Opts ->
?DEBUG("MUC: restore room '~s'~n", [Room]),
- mod_muc_room:start(Host, ServerHost, Access,
- Room, HistorySize,
- RoomShaper, Opts)
+ mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper, Opts)
end.
register_room(Host, Room, Pid) ->
F = fun() ->
- mnesia:write(#muc_online_room{name_host = {Room, Host},
- pid = Pid})
- end,
+ mnesia:write(#muc_online_room{name_host = {Room, Host},
+ pid = Pid})
+ end,
mnesia:transaction(F).
-iq_disco_info(Lang) ->
+iq_disco_info(ServerHost, Lang) ->
[#xmlel{name = <<"identity">>,
attrs =
[{<<"category">>, <<"conference">>},
@@ -733,40 +746,49 @@ iq_disco_info(Lang) ->
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_RSM}], children = []},
#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}], children = []}].
-
-iq_disco_items(Host, From, Lang, none) ->
- lists:zf(fun (#muc_online_room{name_host =
- {Name, _Host},
- pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(Pid,
- {get_disco_item,
- From, Lang},
- 100)
- of
- {item, Desc} ->
- flush(),
- {true,
- #xmlel{name = <<"item">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
+ case gen_mod:is_loaded(ServerHost, mod_mam) of
+ true ->
+ [#xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MAM_TMP}]},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MAM_0}]},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MAM_1}]}];
+ false ->
+ []
+ end.
+
+iq_disco_items(Host, From, Lang, <<>>, none) ->
+ Rooms = get_vh_rooms(Host),
+ case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
+ true ->
+ iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
+ false ->
+ iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
+ end;
+iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
+ XmlEmpty = #xmlel{name = <<"item">>,
attrs =
- [{<<"jid">>,
- jlib:jid_to_string({Name, Host,
- <<"">>})},
- {<<"name">>, Desc}],
- children = []}};
- _ -> false
- end
- end, get_vh_rooms(Host));
-
-iq_disco_items(Host, From, Lang, Rsm) ->
+ [{<<"jid">>, <<"conference.localhost">>},
+ {<<"node">>, <<"emptyrooms">>},
+ {<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
+ 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_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
+iq_disco_items(Host, From, Lang, _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.
+
+iq_disco_items_list(Host, Rooms, Query) ->
lists:zf(fun (#muc_online_room{name_host =
{Name, _Host},
pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(Pid,
- {get_disco_item,
- From, Lang},
+ Query,
100)
of
{item, Desc} ->
@@ -775,15 +797,13 @@ iq_disco_items(Host, From, Lang, Rsm) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
- jlib:jid_to_string({Name, Host,
+ jid:to_string({Name, Host,
<<"">>})},
{<<"name">>, Desc}],
children = []}};
_ -> false
end
- end,
- Rooms)
- ++ RsmOut.
+ end, Rooms).
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
AllRooms = lists:sort(get_vh_rooms(Host)),
@@ -837,13 +857,6 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
flush() -> receive _ -> flush() after 0 -> ok end.
-define(XFIELD(Type, Label, Var, Val),
-%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
-%% the requester JID, the local time and a random salt.
-%%
-%% "pseudo" because we don't verify that there is not a room
-%% with the returned Name already created, nor mark the generated Name
-%% as "already used". But in practice, it is unique enough. See
-%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, Type},
@@ -855,16 +868,16 @@ flush() -> receive _ -> flush() after 0 -> ok end.
iq_get_unique(From) ->
{xmlcdata,
- p1_sha:sha(term_to_binary([From, now(),
+ p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
randoms:get_string()]))}.
get_nick(ServerHost, Host, From) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
get_nick(LServer, Host, From,
gen_mod:db_type(LServer, ?MODULE)).
get_nick(_LServer, Host, From, mnesia) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
LUS = {LUser, LServer},
case catch mnesia:dirty_read(muc_registered,
{LUS, Host})
@@ -874,7 +887,7 @@ get_nick(_LServer, Host, From, mnesia) ->
[#muc_registered{nick = Nick}] -> Nick
end;
get_nick(LServer, Host, From, riak) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
case ejabberd_riak:get(muc_registered,
muc_registered_schema(),
@@ -884,7 +897,7 @@ get_nick(LServer, Host, From, riak) ->
end;
get_nick(LServer, Host, From, odbc) ->
SJID =
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select nick from muc_registered where "
@@ -932,12 +945,12 @@ iq_get_register_info(ServerHost, Host, From, Lang) ->
Nick)]}].
set_nick(ServerHost, Host, From, Nick) ->
- LServer = jlib:nameprep(ServerHost),
+ LServer = jid:nameprep(ServerHost),
set_nick(LServer, Host, From, Nick,
gen_mod:db_type(LServer, ?MODULE)).
set_nick(_LServer, Host, From, Nick, mnesia) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
LUS = {LUser, LServer},
F = fun () ->
case Nick of
@@ -967,7 +980,7 @@ set_nick(_LServer, Host, From, Nick, mnesia) ->
end,
mnesia:transaction(F);
set_nick(LServer, Host, From, Nick, riak) ->
- {LUser, LServer, _} = jlib:jid_tolower(From),
+ {LUser, LServer, _} = jid:tolower(From),
LUS = {LUser, LServer},
{atomic,
case Nick of
@@ -997,7 +1010,7 @@ set_nick(LServer, Host, From, Nick, riak) ->
end};
set_nick(LServer, Host, From, Nick, odbc) ->
JID =
- jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
+ jid:to_string(jid:tolower(jid:remove_resource(From))),
SJID = ejabberd_odbc:escape(JID),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
@@ -1049,12 +1062,12 @@ iq_set_register_info(ServerHost, Host, From, Nick,
process_iq_register_set(ServerHost, Host, From, SubEl,
Lang) ->
#xmlel{children = Els} = SubEl,
- case xml:get_subtag(SubEl, <<"remove">>) of
+ case fxml:get_subtag(SubEl, <<"remove">>) of
false ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, []};
{?NS_XDATA, <<"submit">>} ->
@@ -1092,15 +1105,14 @@ iq_get_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd MUC module">>))/binary,
- "\nCopyright (c) 2003-2015 ProcessOne">>}]}].
-
+ "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
broadcast_service_message(Host, Msg) ->
lists:foreach(
- fun(#muc_online_room{pid = Pid}) ->
- gen_fsm:send_all_state_event(
- Pid, {service_message, Msg})
- end, get_vh_rooms(Host)).
+ fun(#muc_online_room{pid = Pid}) ->
+ gen_fsm:send_all_state_event(
+ Pid, {service_message, Msg})
+ end, get_vh_rooms(Host)).
get_vh_rooms(Host) ->
@@ -1247,8 +1259,8 @@ export(_Server) ->
case str:suffix(Host, RoomHost) of
true ->
SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:make_jid(U, S, <<"">>))),
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
SNick = ejabberd_odbc:escape(Nick),
SRoomHost = ejabberd_odbc:escape(RoomHost),
[[<<"delete from muc_registered where jid='">>,
@@ -1266,13 +1278,12 @@ import(_LServer) ->
[{<<"select name, host, opts from muc_room;">>,
fun([Name, RoomHost, SOpts]) ->
Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
- #muc_room{name_host = {Name, RoomHost},
- opts = Opts}
+ #muc_room{name_host = {Name, RoomHost}, opts = Opts}
end},
{<<"select jid, host, nick from muc_registered;">>,
fun([J, RoomHost, Nick]) ->
#jid{user = U, server = S} =
- jlib:string_to_jid(J),
+ jid:from_string(J),
#muc_registered{us_host = {{U, S}, RoomHost},
nick = Nick}
end}].
@@ -1289,3 +1300,58 @@ import(_LServer, riak,
[{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
import(_, _, _) ->
pass.
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(access_admin) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(access_create) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(access_persistent) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(default_room_options) ->
+ fun (L) when is_list(L) -> L end;
+mod_opt_type(history_size) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(max_room_desc) ->
+ fun (infinity) -> infinity;
+ (I) when is_integer(I), I > 0 -> I
+ end;
+mod_opt_type(max_room_id) ->
+ fun (infinity) -> infinity;
+ (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) ->
+ fun (infinity) -> infinity;
+ (I) when is_integer(I), I > 0 -> I
+ end;
+mod_opt_type(max_user_conferences) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(max_users) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(max_users_admin_threshold) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(max_users_presence) ->
+ fun (MUP) when is_integer(MUP) -> MUP end;
+mod_opt_type(min_message_interval) ->
+ fun (MMI) when is_number(MMI) -> MMI end;
+mod_opt_type(min_presence_interval) ->
+ fun (I) when is_number(I), I >= 0 -> I end;
+mod_opt_type(room_shaper) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(user_message_shaper) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(user_presence_shaper) ->
+ fun (A) when is_atom(A) -> A end;
+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_users_admin_threshold, max_users_presence,
+ min_message_interval, min_presence_interval,
+ room_shaper, user_message_shaper, user_presence_shaper].
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 11f96b9b..7c6e84c4 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -11,48 +11,39 @@
-behaviour(gen_mod).
--export([
- start/2, stop/1, % gen_mod API
- muc_online_rooms/1,
- muc_unregister_nick/1,
- create_room/3, destroy_room/3,
+-export([start/2, stop/1, muc_online_rooms/1,
+ muc_unregister_nick/1, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
- get_user_rooms/2,
- get_room_occupants/2,
- get_room_occupants_number/2,
- send_direct_invitation/4,
- change_room_option/4,
- set_room_affiliation/4,
- get_room_affiliations/2,
- web_menu_main/2, web_page_main/2, % Web Admin API
- web_menu_host/3, web_page_host/3
- ]).
+ get_user_rooms/2, get_room_occupants/2,
+ get_room_occupants_number/2, send_direct_invitation/5,
+ change_room_option/4, get_room_options/2,
+ set_room_affiliation/4, get_room_affiliations/2,
+ web_menu_main/2, web_page_main/2, web_menu_host/3,
+ web_page_host/3, mod_opt_type/1, get_commands_spec/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_muc_room.hrl").
+-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
-%% Copied from mod_muc/mod_muc.erl
--record(muc_online_room, {name_host, pid}).
-
%%----------------------------
%% gen_mod
%%----------------------------
start(Host, _Opts) ->
- ejabberd_commands:register_commands(commands()),
+ ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
stop(Host) ->
- ejabberd_commands:unregister_commands(commands()),
+ ejabberd_commands:unregister_commands(get_commands_spec()),
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
@@ -62,10 +53,11 @@ stop(Host) ->
%%% Register commands
%%%
-commands() ->
+get_commands_spec() ->
[
#ejabberd_commands{name = muc_online_rooms, tags = [muc],
desc = "List existing rooms ('global' to get all vhosts)",
+ policy = admin,
module = ?MODULE, function = muc_online_rooms,
args = [{host, binary}],
result = {rooms, {list, {room, string}}}},
@@ -84,16 +76,17 @@ commands() ->
#ejabberd_commands{name = destroy_room, tags = [muc_room],
desc = "Destroy a MUC room",
module = ?MODULE, function = destroy_room,
- args = [{name, binary}, {service, binary},
- {host, binary}],
+ args = [{name, binary}, {service, binary}],
result = {res, rescode}},
#ejabberd_commands{name = create_rooms_file, tags = [muc],
desc = "Create the rooms indicated in file",
+ longdesc = "Provide one room JID per line. Rooms will be created after restart.",
module = ?MODULE, function = create_rooms_file,
args = [{file, string}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_rooms_file, tags = [muc],
desc = "Destroy the rooms indicated in file",
+ longdesc = "Provide one room JID per line.",
module = ?MODULE, function = destroy_rooms_file,
args = [{file, string}],
result = {res, rescode}},
@@ -136,7 +129,7 @@ commands() ->
desc = "Send a direct invitation to several destinations",
longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
module = ?MODULE, function = send_direct_invitation,
- args = [{room, binary}, {password, binary}, {reason, binary}, {users, binary}],
+ args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}],
result = {res, rescode}},
#ejabberd_commands{name = change_room_option, tags = [muc_room],
@@ -145,6 +138,16 @@ commands() ->
args = [{name, binary}, {service, binary},
{option, binary}, {value, binary}],
result = {res, rescode}},
+ #ejabberd_commands{name = get_room_options, tags = [muc_room],
+ desc = "Get options from a MUC room",
+ module = ?MODULE, function = get_room_options,
+ args = [{name, binary}, {service, binary}],
+ result = {options, {list,
+ {option, {tuple,
+ [{name, string},
+ {value, string}
+ ]}}
+ }}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
@@ -175,7 +178,8 @@ muc_online_rooms(ServerHost) ->
MUCHost = find_host(ServerHost),
Rooms = ets:tab2list(muc_online_room),
lists:foldl(
- fun({_, {Roomname, Host}, _}, Results) ->
+ fun(Room, Results) ->
+ {Roomname, Host} = Room#muc_online_room.name_host,
case MUCHost of
global ->
[<<Roomname/binary, "@", Host/binary>> | Results];
@@ -238,8 +242,8 @@ web_menu_host(Acc, _Host, Lang) ->
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
- Res = [?XC(<<"h1">>, <<"Multi-User Chat">>),
- ?XC(<<"h3">>, <<"Statistics">>),
+ Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
+ ?XCT(<<"h3">>, <<"Statistics">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
@@ -276,7 +280,7 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
- Integer = list_to_integer(binary_to_list(String)),
+ Integer = jlib:binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
@@ -298,22 +302,22 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
<<"Persistent">>,
<<"Logging">>,
<<"Just created">>,
- <<"Title">>],
+ <<"Room title">>],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = jlib:integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
- ?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
+ ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
- ?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
+ ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column+1}
end,
1,
Titles),
- [?XC(<<"h1">>, <<"Multi-User Chat">>),
- ?XC(<<"h2">>, <<"Rooms">>),
+ [?XCT(<<"h1">>, <<"Multi-User Chat">>),
+ ?XCT(<<"h2">>, <<"Chatrooms">>),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>, Titles_TR)]
@@ -352,7 +356,7 @@ build_info_room({Name, Host, Pid}) ->
false ->
Last_message1 = queue:last(History),
{_, _, _, Ts_last, _} = Last_message1,
- jlib:timestamp_to_iso(Ts_last)
+ jlib:timestamp_to_legacy(Ts_last)
end,
{<<Name/binary, "@", Host/binary>>,
@@ -392,7 +396,9 @@ prepare_room_info(Room_info) ->
%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
%% ok | error
%% @doc Create a room immediately with the default options.
-create_room(Name, Host, ServerHost) ->
+create_room(Name1, Host1, ServerHost) ->
+ Name = jid:nodeprep(Name1),
+ Host = jid:nodeprep(Host1),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
@@ -406,7 +412,6 @@ create_room(Name, Host, ServerHost) ->
AcCreate = gen_mod:get_module_opt(ServerHost, mod_muc, access_create, fun(X) -> X end, all),
AcAdmin = gen_mod:get_module_opt(ServerHost, mod_muc, access_admin, fun(X) -> X end, none),
AcPer = gen_mod:get_module_opt(ServerHost, mod_muc, access_persistent, fun(X) -> X end, all),
- _PersistHistory = gen_mod:get_module_opt(ServerHost, mod_muc, persist_history, fun(X) -> X end, false),
HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, fun(X) -> X end, 20),
RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper, fun(X) -> X end, none),
@@ -441,12 +446,12 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
io:format("Creating room ~s@~s~n", [Name, Host]),
mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
-%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
+%% @spec (Name::binary(), Host::binary()) ->
%% ok | {error, room_not_exists}
%% @doc Destroy the room immediately.
%% If the room has participants, they are not notified that the room was destroyed;
%% they will notice when they try to chat and receive an error that the room doesn't exist.
-destroy_room(Name, Service, _Server) ->
+destroy_room(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[R] ->
Pid = R#muc_online_room.pid,
@@ -458,7 +463,7 @@ destroy_room(Name, Service, _Server) ->
destroy_room({N, H, SH}) ->
io:format("Destroying room: ~s@~s - vhost: ~s~n", [N, H, SH]),
- destroy_room(N, H, SH).
+ destroy_room(N, H).
%%----------------------------
@@ -469,7 +474,7 @@ destroy_room({N, H, SH}) ->
%% The file encoding must be UTF-8
destroy_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -497,23 +502,16 @@ read_room(F) ->
%% This function is quite rudimentary
%% and may not be accurate
split_roomjid(RoomJID) ->
- [Name, Host] = string:tokens(RoomJID, "@"),
- [_MUC_service_name | ServerHostList] = string:tokens(Host, "."),
- ServerHost = join(ServerHostList, "."),
+ [Name, Host] = binary:split(RoomJID, <<"@">>),
+ [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
{Name, Host, ServerHost}.
-%% This function is copied from string:join/2 in Erlang/OTP R12B-1
-%% Note that string:join/2 is not implemented in Erlang/OTP R11B
-join([H|T], Sep) ->
- H ++ lists:concat([Sep ++ X || X <- T]).
-
-
%%----------------------------
%% Create Rooms in File
%%----------------------------
create_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -604,7 +602,7 @@ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
Num_users = length(?DICT:to_list(Room_users)),
History = (S#state.history)#lqueue.queue,
- Ts_now = calendar:now_to_universal_time(now()),
+ Ts_now = calendar:universal_time(),
Ts_uptime = uptime_seconds(),
{Has_hist, Last} = case queue:is_empty(History) of
true ->
@@ -672,7 +670,7 @@ get_room_occupants(Pid) ->
S = get_room_state(Pid),
lists:map(
fun({_LJID, Info}) ->
- {jlib:jid_to_string(Info#user.jid),
+ {jid:to_string(Info#user.jid),
Info#user.nick,
atom_to_list(Info#user.role)}
end,
@@ -686,32 +684,36 @@ get_room_occupants_number(Room, Host) ->
%%----------------------------
%% http://xmpp.org/extensions/xep-0249.html
-send_direct_invitation(RoomString, Password, Reason, UsersString) ->
- RoomJid = jlib:string_to_jid(RoomString),
+send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) ->
+ RoomJid = jid:make(RoomName, RoomService, <<"">>),
+ RoomString = jid:to_string(RoomJid),
XmlEl = build_invitation(Password, Reason, RoomString),
- UsersStrings = get_users_to_invite(RoomJid, binary_to_list(UsersString)),
- [send_direct_invitation(RoomJid, jlib:string_to_jid(list_to_binary(UserStrings)), XmlEl)
+ UsersStrings = get_users_to_invite(RoomJid, UsersString),
+ [send_direct_invitation(RoomJid, UserStrings, XmlEl)
|| UserStrings <- UsersStrings],
timer:sleep(1000),
ok.
get_users_to_invite(RoomJid, UsersString) ->
- UsersStrings = string:tokens(UsersString, ":"),
+ UsersStrings = binary:split(UsersString, <<":">>, [global]),
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
RoomJid#jid.lserver),
- OccupantsJids = [jlib:string_to_jid(JidString)
+ OccupantsJids = [jid:from_string(JidString)
|| {JidString, _Nick, _} <- OccupantsTuples],
- lists:filter(
- fun(UserString) ->
- UserJid = jlib:string_to_jid(list_to_binary(UserString)),
- %% [{"badlop@localhost/work","badlop","moderator"}]
- lists:all(fun(OccupantJid) ->
- UserJid#jid.luser /= OccupantJid#jid.luser
- orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
- end,
- OccupantsJids)
- end,
- UsersStrings).
+ lists:filtermap(
+ fun(UserString) ->
+ UserJid = jid:from_string(UserString),
+ Val = lists:all(fun(OccupantJid) ->
+ UserJid#jid.luser /= OccupantJid#jid.luser
+ orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
+ end,
+ OccupantsJids),
+ case Val of
+ true -> {true, UserJid};
+ _ -> false
+ end
+ end,
+ UsersStrings).
build_invitation(Password, Reason, RoomString) ->
PasswordAttrList = case Password of
@@ -782,10 +784,17 @@ change_option(Option, Value, Config) ->
case Option of
allow_change_subj -> Config#config{allow_change_subj = Value};
allow_private_messages -> Config#config{allow_private_messages = Value};
+ allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
allow_query_users -> Config#config{allow_query_users = Value};
allow_user_invites -> Config#config{allow_user_invites = Value};
+ allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
+ allow_visitor_status -> Config#config{allow_visitor_status = Value};
+ allow_voice_requests -> Config#config{allow_voice_requests = Value};
anonymous -> Config#config{anonymous = Value};
+ captcha_protected -> Config#config{captcha_protected = Value};
+ description -> Config#config{description = Value};
logging -> Config#config{logging = Value};
+ mam -> Config#config{mam = Value};
max_users -> Config#config{max_users = Value};
members_by_default -> Config#config{members_by_default = Value};
members_only -> Config#config{members_only = Value};
@@ -795,9 +804,29 @@ change_option(Option, Value, Config) ->
persistent -> Config#config{persistent = Value};
public -> Config#config{public = Value};
public_list -> Config#config{public_list = Value};
- title -> Config#config{title = Value}
+ title -> Config#config{title = Value};
+ vcard -> Config#config{vcard = Value};
+ voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
+ end.
+
+%%----------------------------
+%% Get Room Options
+%%----------------------------
+
+get_room_options(Name, Service) ->
+ case get_room_pid(Name, Service) of
+ room_not_found -> [];
+ Pid -> get_room_options(Pid)
end.
+get_room_options(Pid) ->
+ Config = get_room_config(Pid),
+ get_options(Config).
+
+get_options(Config) ->
+ Fields = record_info(fields, config),
+ [config | Values] = tuple_to_list(Config),
+ lists:zip(Fields, Values).
%%----------------------------
%% Get Room Affiliations
@@ -841,34 +870,32 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
[R] ->
%% Get the PID for the online room so we can get the state of the room
Pid = R#muc_online_room.pid,
- {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jlib:string_to_jid(JID), affiliation, Affiliation, <<"">>}, <<"">>}),
+ {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:from_string(JID), affiliation, Affiliation, <<"">>}, <<"">>}),
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok;
[] ->
error
end.
--define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
-
make_opts(StateData) ->
Config = StateData#state.config,
[
- ?MAKE_CONFIG_OPT(title),
- ?MAKE_CONFIG_OPT(allow_change_subj),
- ?MAKE_CONFIG_OPT(allow_query_users),
- ?MAKE_CONFIG_OPT(allow_private_messages),
- ?MAKE_CONFIG_OPT(public),
- ?MAKE_CONFIG_OPT(public_list),
- ?MAKE_CONFIG_OPT(persistent),
- ?MAKE_CONFIG_OPT(moderated),
- ?MAKE_CONFIG_OPT(members_by_default),
- ?MAKE_CONFIG_OPT(members_only),
- ?MAKE_CONFIG_OPT(allow_user_invites),
- ?MAKE_CONFIG_OPT(password_protected),
- ?MAKE_CONFIG_OPT(password),
- ?MAKE_CONFIG_OPT(anonymous),
- ?MAKE_CONFIG_OPT(logging),
- ?MAKE_CONFIG_OPT(max_users),
+ {title, Config#config.title},
+ {allow_change_subj, Config#config.allow_change_subj},
+ {allow_query_users, Config#config.allow_query_users},
+ {allow_private_messages, Config#config.allow_private_messages},
+ {public, Config#config.public},
+ {public_list, Config#config.public_list},
+ {persistent, Config#config.persistent},
+ {moderated, Config#config.moderated},
+ {members_by_default, Config#config.members_by_default},
+ {members_only, Config#config.members_only},
+ {allow_user_invites, Config#config.allow_user_invites},
+ {password_protected, Config#config.password_protected},
+ {password, Config#config.password},
+ {anonymous, Config#config.anonymous},
+ {logging, Config#config.logging},
+ {max_users, Config#config.max_users},
{affiliations, ?DICT:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{subject_author, StateData#state.subject_author}
@@ -892,3 +919,5 @@ find_host(ServerHost) when is_list(ServerHost) ->
find_host(list_to_binary(ServerHost));
find_host(ServerHost) ->
gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>).
+
+mod_opt_type(_) -> [].
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index d9415141..1f32f6f9 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Mar 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,6 +25,10 @@
-module(mod_muc_log).
+-protocol({xep, 334, '0.2'}).
+
+-behaviour(ejabberd_config).
+
-author('badlop@process-one.net').
-behaviour(gen_server).
@@ -35,21 +39,17 @@
-export([start_link/2, start/2, stop/1, transform_module_options/1,
check_access_log/2, add_to_log/5]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-
+-include("mod_muc.hrl").
-include("mod_muc_room.hrl").
-%% Copied from mod_muc/mod_muc.erl
--record(muc_online_room, {name_host = {<<>>, <<>>} :: {binary(), binary()},
- pid = self() :: pid()}).
-
-define(T(Text), translate:translate(Lang, Text)).
-define(PROCNAME, ejabberd_mod_muc_log).
-record(room, {jid, title, subject, subject_author, config}).
@@ -74,23 +74,14 @@
%%====================================================================
%% API
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -121,19 +112,11 @@ transform_module_options(Opts) ->
%%====================================================================
%% gen_server callbacks
%%====================================================================
-
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Host, Opts]) ->
OutDir = gen_mod:get_opt(outdir, Opts,
fun iolist_to_binary/1,
<<"www/muc">>),
- DirType = gen_mod:get_opt(dirtype, Opts,
+ DirType = gen_mod:get_opt(dirtype, Opts,
fun(subdirs) -> subdirs;
(plain) -> plain
end, subdirs),
@@ -179,31 +162,17 @@ init([Host, Opts]) ->
{ok,
#logstate{host = Host, out_dir = OutDir,
dir_type = DirType, dir_name = DirName,
- file_format = FileFormat, file_permissions = FilePermissions, css_file = CSSFile,
+ file_format = FileFormat, css_file = CSSFile,
+ file_permissions = FilePermissions,
access = AccessLog, lang = Lang, timezone = Timezone,
spam_prevention = NoFollow, top_link = Top_link}}.
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
handle_call({check_access_log, ServerHost, FromJID}, _From, State) ->
Reply = acl:match_rule(ServerHost, State#logstate.access, FromJID),
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast({add_to_log, Type, Data, Room, Opts}, State) ->
case catch add_to_log2(Type, Data, Room, Opts, State) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
@@ -212,49 +181,30 @@ handle_cast({add_to_log, Type, Data, Room, Opts}, State) ->
{noreply, State};
handle_cast(_Msg, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: 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.
-%%--------------------------------------------------------------------
terminate(_Reason, _State) -> ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
- case {xml:get_subtag(Packet, <<"no-store">>),
- xml:get_subtag(Packet, <<"no-permanent-store">>)}
- of
- {false, false} ->
- case {xml:get_subtag(Packet, <<"subject">>),
- xml:get_subtag(Packet, <<"body">>)}
- of
- {false, false} -> ok;
- {false, SubEl} ->
- Message = {body, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State);
- {SubEl, _} ->
- Message = {subject, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State)
- end;
- {_, _} -> ok
+ case has_no_permanent_store_hint(Packet) of
+ false ->
+ case {fxml:get_subtag(Packet, <<"subject">>),
+ fxml:get_subtag(Packet, <<"body">>)}
+ of
+ {false, false} -> ok;
+ {false, SubEl} ->
+ Message = {body, fxml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State);
+ {SubEl, _} ->
+ Message = {subject, fxml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State)
+ end;
+ true -> ok
end;
add_to_log2(roomconfig_change, _Occupants, Room, Opts,
State) ->
@@ -327,7 +277,7 @@ build_filename_string(TimeStamp, OutDir, RoomJID,
{Fd, Fn, Fnrel}.
get_room_name(RoomJID) ->
- JID = jlib:string_to_jid(RoomJID), JID#jid.user.
+ JID = jid:from_string(RoomJID), JID#jid.user.
%% calculate day before
get_timestamp_daydiff(TimeStamp, Daydiff) ->
@@ -349,12 +299,11 @@ close_previous_log(Fn, Images_dir, FileFormat) ->
write_last_lines(_, _, plaintext) -> ok;
write_last_lines(F, Images_dir, _FileFormat) ->
-%% list_to_integer/2 was introduced in OTP R14
fw(F, <<"<div class=\"legend\">">>),
fw(F,
<<" <a href=\"http://www.ejabberd.im\"><img "
"style=\"border:0\" src=\"~s/powered-by-ejabbe"
- "rd.png\" alt=\"Powered by ejabberd\"/></a>">>,
+ "rd.png\" alt=\"Powered by ejabberd - robust, scalable and extensible XMPP server\"/></a>">>,
[Images_dir]),
fw(F,
<<" <a href=\"http://www.erlang.org/\"><img "
@@ -378,7 +327,7 @@ write_last_lines(F, Images_dir, _FileFormat) ->
fw(F, <<"</span></div></body></html>">>).
set_filemode(Fn, {FileMode, FileGroup}) ->
- ok = file:change_mode(Fn, list_to_integer(integer_to_list(FileMode), 8)),
+ ok = file:change_mode(Fn, list_to_integer(integer_to_list(FileMode), 8)),
ok = file:change_group(Fn, FileGroup).
htmlize_nick(Nick1, html) ->
@@ -397,7 +346,7 @@ add_message_to_log(Nick1, Message, RoomJID, Opts,
Room = get_room_info(RoomJID, Opts),
Nick = htmlize(Nick1, FileFormat),
Nick2 = htmlize_nick(Nick1, FileFormat),
- Now = now(),
+ Now = p1_time_compat:timestamp(),
TimeStamp = case Timezone of
local -> calendar:now_to_local_time(Now);
universal -> calendar:now_to_universal_time(Now)
@@ -951,7 +900,7 @@ put_header_script(F) ->
put_room_config(_F, _RoomConfig, _Lang, plaintext) ->
ok;
put_room_config(F, RoomConfig, Lang, _FileFormat) ->
- {_, Now2, _} = now(),
+ {_, Now2, _} = p1_time_compat:timestamp(),
fw(F, <<"<div class=\"rc\">">>),
fw(F,
<<"<div class=\"rct\" onclick=\"sh('a~p');return "
@@ -968,7 +917,7 @@ put_room_occupants(_F, _RoomOccupants, _Lang,
ok;
put_room_occupants(F, RoomOccupants, Lang,
_FileFormat) ->
- {_, Now2, _} = now(),
+ {_, Now2, _} = p1_time_compat:timestamp(),
%% htmlize
%% The default behaviour is to ignore the nofollow spam prevention on links
%% (NoFollow=false)
@@ -1048,7 +997,7 @@ get_room_info(RoomJID, Opts) ->
{value, {_, SA}} -> SA;
false -> <<"">>
end,
- #room{jid = jlib:jid_to_string(RoomJID), title = Title,
+ #room{jid = jid:to_string(RoomJID), title = Title,
subject = Subject, subject_author = SubjectAuthor,
config = Opts}.
@@ -1163,10 +1112,7 @@ roomoccupants_to_string(Users, _FileFormat) ->
Users1 /= []],
iolist_to_binary([<<"<div class=\"rcot\">">>, Res, <<"</div>">>]).
-%% Users = [{JID, Nick, Role}]
group_by_role(Users) ->
-%% Role = atom()
-%% Users = [{JID, Nick}]
{Ms, Ps, Vs, Ns} = lists:foldl(fun ({JID, Nick,
moderator},
{Mod, Par, Vis, Non}) ->
@@ -1212,7 +1158,7 @@ role_users_to_string(RoleS, Users) ->
<<RoleS/binary, ": ", UsersString/binary>>.
get_room_occupants(RoomJIDString) ->
- RoomJID = jlib:string_to_jid(RoomJIDString),
+ RoomJID = jid:from_string(RoomJIDString),
RoomName = RoomJID#jid.luser,
MucService = RoomJID#jid.lserver,
StateData = get_room_state(RoomName, MucService),
@@ -1238,10 +1184,11 @@ get_room_state(RoomPid) ->
get_state),
R.
-get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
+get_proc_name(Host) ->
+ gen_mod:get_module_proc(Host, ?PROCNAME).
calc_hour_offset(TimeHere) ->
- TimeZero = calendar:now_to_universal_time(now()),
+ TimeZero = calendar:universal_time(),
TimeHereHour =
calendar:datetime_to_gregorian_seconds(TimeHere) div
3600,
@@ -1252,3 +1199,54 @@ calc_hour_offset(TimeHere) ->
fjoin(FileList) ->
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
+
+has_no_permanent_store_hint(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
+ =/= false orelse
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
+ =/= false orelse
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
+ =/= false orelse
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
+ =/= false.
+
+mod_opt_type(access_log) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(cssfile) -> fun iolist_to_binary/1;
+mod_opt_type(dirname) ->
+ fun (room_jid) -> room_jid;
+ (room_name) -> room_name
+ end;
+mod_opt_type(dirtype) ->
+ fun (subdirs) -> subdirs;
+ (plain) -> plain
+ end;
+mod_opt_type(file_format) ->
+ fun (html) -> html;
+ (plaintext) -> plaintext
+ end;
+mod_opt_type(file_permissions) ->
+ fun (SubOpts) ->
+ F = fun ({mode, Mode}, {_M, G}) -> {Mode, G};
+ ({group, Group}, {M, _G}) -> {M, Group}
+ end,
+ lists:foldl(F, {644, 33}, SubOpts)
+ end;
+mod_opt_type(outdir) -> fun iolist_to_binary/1;
+mod_opt_type(spam_prevention) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(timezone) ->
+ fun (local) -> local;
+ (universal) -> universal
+ end;
+mod_opt_type(top_link) ->
+ fun ([{S1, S2}]) ->
+ {iolist_to_binary(S1), iolist_to_binary(S2)}
+ end;
+mod_opt_type(_) ->
+ [access_log, cssfile, dirname, dirtype, file_format,
+ file_permissions, outdir, spam_prevention, timezone,
+ top_link].
+
+opt_type(language) -> fun iolist_to_binary/1;
+opt_type(_) -> [language].
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 8d0b36b6..06fdf325 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -5,7 +5,7 @@
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -34,6 +34,9 @@
start_link/7,
start/9,
start/7,
+ get_role/2,
+ get_affiliation/2,
+ is_occupant_or_admin/2,
route/4]).
%% gen_fsm callbacks
@@ -55,6 +58,8 @@
-define(MAX_USERS_DEFAULT_LIST,
[5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]).
+-define(DEFAULT_MAX_USERS_PRESENCE,1000).
+
%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -67,32 +72,19 @@
-endif.
-%% Module start with or without supervisor:
--ifdef(NO_TRANSIENT_SUPERVISORS).
--define(SUPERVISOR_START,
- gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
- RoomShaper, Creator, Nick, DefRoomOpts],
- ?FSMOPTS)).
--else.
--define(SUPERVISOR_START,
- Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
- supervisor:start_child(
- Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
- Creator, Nick, DefRoomOpts])).
--endif.
-
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts) ->
- ?SUPERVISOR_START.
+ gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ RoomShaper, Creator, Nick, DefRoomOpts],
+ ?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
- Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
- supervisor:start_child(
- Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
- Opts]).
+ gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ RoomShaper, Opts],
+ ?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts) ->
@@ -109,23 +101,17 @@ start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
-init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, DefRoomOpts]) ->
+init([Host, ServerHost, Access, Room, HistorySize,
+ RoomShaper, Creator, _Nick, DefRoomOpts]) ->
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
State = set_affiliation(Creator, owner,
- #state{host = Host, server_host = ServerHost,
- access = Access, room = Room,
- history = lqueue_new(HistorySize),
- jid = jlib:make_jid(Room, Host, <<"">>),
- just_created = true,
- room_shaper = Shaper}),
+ #state{host = Host, server_host = ServerHost,
+ access = Access, room = Room,
+ history = lqueue_new(HistorySize),
+ jid = jid:make(Room, Host, <<"">>),
+ just_created = true,
+ room_shaper = Shaper}),
State1 = set_opts(DefRoomOpts, State),
if (State1#state.config)#config.persistent ->
mod_muc:store_room(State1#state.server_host,
@@ -134,8 +120,8 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D
make_opts(State1));
true -> ok
end,
- ?INFO_MSG("Created MUC room ~s@~s by ~s",
- [Room, Host, jlib:jid_to_string(Creator)]),
+ ?INFO_MSG("Created MUC room ~s@~s by ~s",
+ [Room, Host, jid:to_string(Creator)]),
add_to_log(room_existence, created, State1),
add_to_log(room_existence, started, State1),
{ok, normal_state, State1};
@@ -147,31 +133,25 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
access = Access,
room = Room,
history = lqueue_new(HistorySize),
- jid = jlib:make_jid(Room, Host, <<"">>),
+ jid = jid:make(Room, Host, <<"">>),
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
{ok, normal_state, State}.
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
normal_state({route, From, <<"">>,
#xmlel{name = <<"message">>, attrs = Attrs,
children = Els} =
Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
true ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
Activity = get_user_activity(From, StateData),
- Now = now_to_usec(now()),
+ Now = p1_time_compat:system_time(micro_seconds),
MinMessageInterval =
trunc(gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0)
@@ -247,8 +227,9 @@ normal_state({route, From, <<"">>,
<<"error">> ->
case is_user_online(From, StateData) of
true ->
- ErrorText = <<"This participant is kicked from the "
- "room because he sent an error message">>,
+ ErrorText = <<"It is not allowed to send error messages to the"
+ " room. The participant (~s) has sent an error "
+ "message (~s) and got kicked from the room">>,
NewState = expulse_participant(Packet, From, StateData,
translate:translate(Lang,
ErrorText)),
@@ -309,8 +290,8 @@ normal_state({route, From, <<"">>,
MinInterval =
(StateData#state.config)#config.voice_request_min_interval,
BareFrom =
- jlib:jid_remove_resource(jlib:jid_tolower(From)),
- NowPriority = -now_to_usec(now()),
+ jid:remove_resource(jid:tolower(From)),
+ NowPriority = -p1_time_compat:system_time(micro_seconds),
CleanPriority = NowPriority +
MinInterval *
1000000,
@@ -385,7 +366,8 @@ normal_state({route, From, <<"">>,
catch
send_new_presence(TargetJid,
Reason,
- NSD),
+ NSD,
+ StateData),
NSD;
_ -> StateData
end
@@ -412,7 +394,7 @@ normal_state({route, From, <<"">>,
{next_state, normal_state, StateData}
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
handle_roommessage_from_nonparticipant(Packet, Lang,
@@ -424,62 +406,76 @@ normal_state({route, From, <<"">>,
#xmlel{name = <<"iq">>} = Packet},
StateData) ->
case jlib:iq_query_info(Packet) of
- #iq{type = Type, xmlns = XMLNS, lang = Lang,
- sub_el = #xmlel{name = SubElName} = SubEl} =
- IQ
- when (XMLNS == (?NS_MUC_ADMIN)) or
- (XMLNS == (?NS_MUC_OWNER))
- or (XMLNS == (?NS_DISCO_INFO))
- or (XMLNS == (?NS_DISCO_ITEMS))
- or (XMLNS == (?NS_VCARD))
- or (XMLNS == (?NS_CAPTCHA)) ->
- Res1 = case XMLNS of
- ?NS_MUC_ADMIN ->
- process_iq_admin(From, Type, Lang, SubEl, StateData);
- ?NS_MUC_OWNER ->
- process_iq_owner(From, Type, Lang, SubEl, StateData);
- ?NS_DISCO_INFO ->
- process_iq_disco_info(From, Type, Lang, StateData);
- ?NS_DISCO_ITEMS ->
- process_iq_disco_items(From, Type, Lang, StateData);
- ?NS_VCARD ->
- process_iq_vcard(From, Type, Lang, SubEl, StateData);
- ?NS_CAPTCHA ->
- process_iq_captcha(From, Type, Lang, SubEl, StateData)
- end,
- {IQRes, NewStateData} = case Res1 of
- {result, Res, SD} ->
- {IQ#iq{type = result,
- sub_el =
- [#xmlel{name = SubElName,
- attrs =
- [{<<"xmlns">>,
- XMLNS}],
- children = Res}]},
- SD};
- {error, Error} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- StateData}
- end,
- ejabberd_router:route(StateData#state.jid, From,
- jlib:iq_to_xml(IQRes)),
- case NewStateData of
- stop -> {stop, normal, StateData};
- _ -> {next_state, normal_state, NewStateData}
- end;
- reply -> {next_state, normal_state, StateData};
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
+ reply ->
+ {next_state, normal_state, StateData};
+ IQ0 ->
+ case ejabberd_hooks:run_fold(
+ muc_process_iq,
+ StateData#state.server_host,
+ IQ0, [StateData, From, StateData#state.jid]) of
+ ignore ->
+ {next_state, normal_state, StateData};
+ #iq{type = T} = IQRes when T == error; T == result ->
+ ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)),
+ {next_state, normal_state, StateData};
+ #iq{type = Type, xmlns = XMLNS, lang = Lang,
+ sub_el = #xmlel{name = SubElName, attrs = Attrs} = SubEl} = IQ
+ when (XMLNS == (?NS_MUC_ADMIN)) or
+ (XMLNS == (?NS_MUC_OWNER))
+ or (XMLNS == (?NS_DISCO_INFO))
+ or (XMLNS == (?NS_DISCO_ITEMS))
+ or (XMLNS == (?NS_VCARD))
+ or (XMLNS == (?NS_CAPTCHA)) ->
+ Res1 = case XMLNS of
+ ?NS_MUC_ADMIN ->
+ process_iq_admin(From, Type, Lang, SubEl, StateData);
+ ?NS_MUC_OWNER ->
+ process_iq_owner(From, Type, Lang, SubEl, StateData);
+ ?NS_DISCO_INFO ->
+ case fxml:get_attr(<<"node">>, Attrs) of
+ false -> process_iq_disco_info(From, Type, Lang, StateData);
+ {value, _} -> {error, ?ERR_SERVICE_UNAVAILABLE}
+ end;
+ ?NS_DISCO_ITEMS ->
+ process_iq_disco_items(From, Type, Lang, StateData);
+ ?NS_VCARD ->
+ process_iq_vcard(From, Type, Lang, SubEl, StateData);
+ ?NS_CAPTCHA ->
+ process_iq_captcha(From, Type, Lang, SubEl, StateData)
+ end,
+ {IQRes, NewStateData} =
+ case Res1 of
+ {result, Res, SD} ->
+ {IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = SubElName,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = Res}]},
+ SD};
+ {error, Error} ->
+ {IQ#iq{type = error,
+ sub_el = [SubEl, Error]},
+ StateData}
+ end,
+ ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)),
+ case NewStateData of
+ stop -> {stop, normal, StateData};
+ _ -> {next_state, normal_state, NewStateData}
+ end;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
+ end
end;
normal_state({route, From, Nick,
#xmlel{name = <<"presence">>} = Packet},
StateData) ->
Activity = get_user_activity(From, StateData),
- Now = now_to_usec(now()),
+ Now = p1_time_compat:system_time(micro_seconds),
MinPresenceInterval =
trunc(gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, min_presence_interval,
@@ -513,15 +509,15 @@ normal_state({route, From, Nick,
normal_state({route, From, ToNick,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case decide_fate_message(Type, Packet, From, StateData)
of
{expulse_sender, Reason} ->
?DEBUG(Reason, []),
- ErrorText = <<"This participant is kicked from the "
- "room because he sent an error message "
- "to another participant">>,
+ ErrorText = <<"It is not allowed to send error messages to the"
+ " room. The participant (~s) has sent an error "
+ "message (~s) and got kicked from the room">>,
NewState = expulse_participant(Packet, From, StateData,
translate:translate(Lang, ErrorText)),
{next_state, normal_state, NewState};
@@ -540,7 +536,7 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_BAD_REQUEST(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err);
_ ->
@@ -551,7 +547,7 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_ITEM_NOT_FOUND(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err);
ToJIDs ->
@@ -565,14 +561,14 @@ normal_state({route, From, ToNick,
(PmFromVisitors == moderators) and
DstIsModerator ->
{ok, #user{nick = FromNick}} =
- (?DICT):find(jlib:jid_tolower(From),
+ (?DICT):find(jid:tolower(From),
StateData#state.users),
FromNickJID =
- jlib:jid_replace_resource(StateData#state.jid,
+ jid:replace_resource(StateData#state.jid,
FromNick),
X = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
- PrivMsg = xml:append_subtags(Packet, [X]),
+ PrivMsg = fxml:append_subtags(Packet, [X]),
[ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
|| ToJID <- ToJIDs];
true ->
@@ -581,7 +577,7 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err)
end
@@ -594,7 +590,7 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_NOT_ACCEPTABLE(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err);
{false, _} ->
@@ -602,7 +598,7 @@ normal_state({route, From, ToNick,
<<"It is not allowed to send private messages">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err)
end,
@@ -611,8 +607,8 @@ normal_state({route, From, ToNick,
normal_state({route, From, ToNick,
#xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- StanzaId = xml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ StanzaId = fxml:get_attr_s(<<"id">>, Attrs),
case {(StateData#state.config)#config.allow_query_users,
is_user_online_iq(StanzaId, From, StateData)}
of
@@ -626,17 +622,17 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_ITEM_NOT_FOUND(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err)
end;
ToJID ->
{ok, #user{nick = FromNick}} =
- (?DICT):find(jlib:jid_tolower(FromFull),
+ (?DICT):find(jid:tolower(FromFull),
StateData#state.users),
{ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
StanzaId, NewId, Packet),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
FromNick),
ToJID2, Packet2)
end;
@@ -650,7 +646,7 @@ normal_state({route, From, ToNick,
Err = jlib:make_error_reply(Packet,
?ERRT_NOT_ACCEPTABLE(Lang,
ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err)
end;
@@ -662,7 +658,7 @@ normal_state({route, From, ToNick,
"not allowed in this room">>,
Err = jlib:make_error_reply(Packet,
?ERRT_NOT_ALLOWED(Lang, ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
ToNick),
From, Err)
end
@@ -671,12 +667,6 @@ normal_state({route, From, ToNick,
normal_state(_Event, StateData) ->
{next_state, normal_state, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_event({service_message, Msg}, _StateName,
StateData) ->
MessagePkt = #xmlel{name = <<"message">>,
@@ -711,12 +701,12 @@ handle_event({destroy, Reason}, _StateName,
end},
StateData),
?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
- [jlib:jid_to_string(StateData#state.jid), Reason]),
+ [jid:to_string(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
{stop, shutdown, StateData};
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
- [jlib:jid_to_string(StateData#state.jid)]),
+ [jid:to_string(StateData#state.jid)]),
handle_event({destroy, none}, StateName, StateData);
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
@@ -725,19 +715,20 @@ handle_event({set_affiliations, Affiliations},
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
-handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) ->
- Reply = get_roomdesc_reply(JID, StateData,
- get_roomdesc_tail(StateData, Lang)),
+handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) ->
+ Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
+ StateData#state.users),
+ Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
+ true ->
+ get_roomdesc_reply(JID, StateData,
+ get_roomdesc_tail(StateData, Lang));
+ false ->
+ false
+ end,
{reply, Reply, StateName, StateData};
+%% This clause is only for backwards compatibility
+handle_sync_event({get_disco_item, JID, Lang}, From, StateName, StateData) ->
+ handle_sync_event({get_disco_item, any, JID, Lang}, From, StateName, StateData);
handle_sync_event(get_config, _From, StateName,
StateData) ->
{reply, {ok, StateData#state.config}, StateName,
@@ -762,12 +753,6 @@ handle_sync_event(_Event, _From, StateName,
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
handle_info({process_user_presence, From}, normal_state = _StateName, StateData) ->
RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
RoomQueue = queue:in({presence, From}, StateData#state.room_queue),
@@ -835,7 +820,7 @@ handle_info({captcha_failed, From}, normal_state,
Err = jlib:make_error_reply(Packet,
?ERR_NOT_AUTHORIZED),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid,
+ (jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData#state{robots = Robots};
@@ -847,11 +832,6 @@ handle_info(shutdown, _StateName, StateData) ->
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
@@ -881,7 +861,7 @@ terminate(Reason, _StateName, StateData) ->
Nick = Info#user.nick,
case Reason of
shutdown ->
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet);
_ -> ok
@@ -904,7 +884,7 @@ route(Pid, From, ToNick, Packet) ->
process_groupchat_message(From,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
@@ -945,20 +925,33 @@ process_groupchat_message(From,
end,
case IsAllowed of
true ->
- send_multiple(
- jlib:jid_replace_resource(StateData#state.jid, FromNick),
- StateData#state.server_host,
- StateData#state.users,
- Packet),
- NewStateData2 = case has_body_or_subject(Packet) of
- true ->
- add_message_to_history(FromNick, From,
- Packet,
- NewStateData1);
- false ->
- NewStateData1
- end,
- {next_state, normal_state, NewStateData2};
+ case
+ ejabberd_hooks:run_fold(muc_filter_message,
+ StateData#state.server_host,
+ Packet,
+ [StateData,
+ StateData#state.jid,
+ From, FromNick])
+ of
+ drop ->
+ {next_state, normal_state, StateData};
+ NewPacket1 ->
+ NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+ send_multiple(jid:replace_resource(StateData#state.jid,
+ FromNick),
+ StateData#state.server_host,
+ StateData#state.users,
+ NewPacket),
+ NewStateData2 = case has_body_or_subject(NewPacket) of
+ true ->
+ add_message_to_history(FromNick, From,
+ NewPacket,
+ NewStateData1);
+ false ->
+ NewStateData1
+ end,
+ {next_state, normal_state, NewStateData2}
+ end;
_ ->
Err = case
(StateData#state.config)#config.allow_change_subj
@@ -1012,7 +1005,7 @@ is_user_allowed_message_nonparticipant(JID,
%% @doc Get information of this participant, or default values.
%% If the JID is not a participant, return values for a service message.
get_participant_data(From, StateData) ->
- case (?DICT):find(jlib:jid_tolower(From),
+ case (?DICT):find(jid:tolower(From),
StateData#state.users)
of
{ok, #user{nick = FromNick, role = Role}} ->
@@ -1021,117 +1014,126 @@ get_participant_data(From, StateData) ->
end.
process_presence(From, Nick,
- #xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
+ #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
StateData) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- StateData1 = case Type of
- <<"unavailable">> ->
- case is_user_online(From, StateData) of
- true ->
- NewPacket = case
- {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)}
- of
- {false, true} ->
- strip_status(Packet);
- _ -> Packet
- end,
- NewState = add_user_presence_un(From, NewPacket,
- StateData),
- case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} -> ok;
- _ -> send_new_presence(From, NewState)
- end,
- Reason = case xml:get_subtag(NewPacket,
- <<"status">>)
- of
- false -> <<"">>;
- Status_el ->
- xml:get_tag_cdata(Status_el)
- end,
- remove_online_user(From, NewState, Reason);
- _ -> StateData
- end;
- <<"error">> ->
- case is_user_online(From, StateData) of
- true ->
- ErrorText =
- <<"This participant is kicked from the "
- "room because he sent an error presence">>,
- expulse_participant(Packet, From, StateData,
- translate:translate(Lang,
- ErrorText));
- _ -> StateData
- end;
- <<"">> ->
- case is_user_online(From, StateData) of
- true ->
- case is_nick_change(From, Nick, StateData) of
- true ->
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(StateData#state.server_host,
- StateData#state.host,
- From, Nick),
- {(StateData#state.config)#config.allow_visitor_nickchange,
- is_visitor(From, StateData)}}
- of
- {_, _, {false, true}} ->
- ErrText =
- <<"Visitors are not allowed to change their "
- "nicknames in this room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ALLOWED(Lang,
- ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- {true, _, _} ->
- Lang = xml:get_attr_s(<<"xml:lang">>,
- Attrs),
- ErrText =
- <<"That nickname is already in use by another "
- "occupant">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_CONFLICT(Lang,
- ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
- Nick), % TODO: s/Nick/""/
- From, Err),
- StateData;
- {_, false, _} ->
- ErrText =
- <<"That nickname is registered by another "
- "person">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_CONFLICT(Lang,
- ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- _ -> change_nick(From, Nick, StateData)
- end;
- _NotNickChange ->
- Stanza = case
- {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)}
- of
- {false, true} ->
- strip_status(Packet);
- _Allowed -> Packet
- end,
- NewState = add_user_presence(From, Stanza,
- StateData),
- send_new_presence(From, NewState),
- NewState
- end;
- _ -> add_new_user(From, Nick, Packet, StateData)
- end;
- _ -> StateData
- end,
- close_room_if_temporary_and_empty(StateData1).
+ Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
+ IsOnline = is_user_online(From, StateData),
+ if Type0 == <<"">>;
+ IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
+ case ejabberd_hooks:run_fold(muc_filter_presence,
+ StateData#state.server_host,
+ Packet0,
+ [StateData,
+ StateData#state.jid,
+ From, Nick]) of
+ drop ->
+ {next_state, normal_state, StateData};
+ #xmlel{attrs = Attrs} = Packet ->
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ StateData1 = case Type of
+ <<"unavailable">> ->
+ NewPacket = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _ -> Packet
+ end,
+ NewState = add_user_presence_un(From, NewPacket,
+ StateData),
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [_, _ | _]} -> ok;
+ _ -> send_new_presence(From, NewState, StateData)
+ end,
+ Reason = case fxml:get_subtag(NewPacket,
+ <<"status">>)
+ of
+ false -> <<"">>;
+ Status_el ->
+ fxml:get_tag_cdata(Status_el)
+ end,
+ remove_online_user(From, NewState, Reason);
+ <<"error">> ->
+ ErrorText = <<"It is not allowed to send error messages to the"
+ " room. The participant (~s) has sent an error "
+ "message (~s) and got kicked from the room">>,
+ expulse_participant(Packet, From, StateData,
+ translate:translate(Lang,
+ ErrorText));
+ <<"">> ->
+ if not IsOnline ->
+ add_new_user(From, Nick, Packet, StateData);
+ true ->
+ case is_nick_change(From, Nick, StateData) of
+ true ->
+ case {nick_collision(From, Nick, StateData),
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick),
+ {(StateData#state.config)#config.allow_visitor_nickchange,
+ is_visitor(From, StateData)}}
+ of
+ {_, _, {false, true}} ->
+ ErrText =
+ <<"Visitors are not allowed to change their "
+ "nicknames in this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang,
+ ErrText)),
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {true, _, _} ->
+ Lang = fxml:get_attr_s(<<"xml:lang">>,
+ Attrs),
+ ErrText =
+ <<"That nickname is already in use by another "
+ "occupant">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
+ Nick), % TODO: s/Nick/""/
+ From, Err),
+ StateData;
+ {_, false, _} ->
+ ErrText =
+ <<"That nickname is registered by another "
+ "person">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ _ -> change_nick(From, Nick, StateData)
+ end;
+ _NotNickChange ->
+ Stanza = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _Allowed -> Packet
+ end,
+ NewState = add_user_presence(From, Stanza,
+ StateData),
+ send_new_presence(
+ From, NewState, StateData),
+ NewState
+ end
+ end
+ end,
+ close_room_if_temporary_and_empty(StateData1)
+ end;
+ true ->
+ {next_state, normal_state, StateData}
+ end.
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
@@ -1140,14 +1142,14 @@ close_room_if_temporary_and_empty(StateData1) ->
true ->
?INFO_MSG("Destroyed MUC room ~s because it's temporary "
"and empty",
- [jlib:jid_to_string(StateData1#state.jid)]),
+ [jid:to_string(StateData1#state.jid)]),
add_to_log(room_existence, destroyed, StateData1),
{stop, normal, StateData1};
_ -> {next_state, normal_state, StateData1}
end.
is_user_online(JID, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
(?DICT):is_key(LJID, StateData#state.users).
%% Check if the user is occupant of the room, or at least is an admin or owner.
@@ -1172,7 +1174,7 @@ is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
{OriginalId, Resource} ->
- JIDWithResource = jlib:jid_replace_resource(JID,
+ JIDWithResource = jid:replace_resource(JID,
Resource),
{is_user_online(JIDWithResource, StateData), OriginalId,
JIDWithResource}
@@ -1182,7 +1184,7 @@ is_user_online_iq(StanzaId, JID, StateData)
handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
Packet) ->
- ToBareJID = jlib:jid_remove_resource(ToJID),
+ ToBareJID = jid:remove_resource(ToJID),
IQ = jlib:iq_query_info(Packet),
handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId,
NewId, IQ, Packet).
@@ -1264,7 +1266,7 @@ decide_fate_message(<<"error">>, Packet, From,
Reason =
io_lib:format("This participant is considered a ghost "
"and is expulsed: ~s",
- [jlib:jid_to_string(From)]),
+ [jid:to_string(From)]),
{expulse_sender, Reason};
false -> continue_delivery
end,
@@ -1302,7 +1304,7 @@ get_error_condition(Packet) ->
end.
get_error_condition2(Packet) ->
- #xmlel{children = EEls} = xml:get_subtag(Packet,
+ #xmlel{children = EEls} = fxml:get_subtag(Packet,
<<"error">>),
[Condition] = [Name
|| #xmlel{name = Name,
@@ -1311,11 +1313,13 @@ get_error_condition2(Packet) ->
<- EEls],
{condition, Condition}.
+make_reason(Packet, From, StateData, Reason1) ->
+ {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users),
+ Condition = get_error_condition(Packet),
+ iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])).
+
expulse_participant(Packet, From, StateData, Reason1) ->
- ErrorCondition = get_error_condition(Packet),
- Reason2 = iolist_to_binary(
- io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s",
- [ErrorCondition])),
+ Reason2 = make_reason(Packet, From, StateData, Reason1),
NewState = add_user_presence_un(From,
#xmlel{name = <<"presence">>,
attrs =
@@ -1328,14 +1332,14 @@ expulse_participant(Packet, From, StateData, Reason1) ->
[{xmlcdata,
Reason2}]}]},
StateData),
- send_new_presence(From, NewState),
+ send_new_presence(From, NewState, StateData),
remove_online_user(From, NewState).
set_affiliation(JID, Affiliation, StateData) ->
set_affiliation(JID, Affiliation, StateData, <<"">>).
set_affiliation(JID, Affiliation, StateData, Reason) ->
- LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
+ LJID = jid:remove_resource(jid:tolower(JID)),
Affiliations = case Affiliation of
none ->
(?DICT):erase(LJID, StateData#state.affiliations);
@@ -1354,11 +1358,11 @@ get_affiliation(JID, StateData) ->
of
allow -> owner;
_ ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.affiliations) of
{ok, Affiliation} -> Affiliation;
_ ->
- LJID1 = jlib:jid_remove_resource(LJID),
+ LJID1 = jid:remove_resource(LJID),
case (?DICT):find(LJID1, StateData#state.affiliations)
of
{ok, Affiliation} -> Affiliation;
@@ -1369,7 +1373,7 @@ get_affiliation(JID, StateData) ->
of
{ok, Affiliation} -> Affiliation;
_ ->
- LJID3 = jlib:jid_remove_resource(LJID2),
+ LJID3 = jid:remove_resource(LJID2),
case (?DICT):find(LJID3,
StateData#state.affiliations)
of
@@ -1397,7 +1401,7 @@ get_service_affiliation(JID, StateData) ->
end.
set_role(JID, Role, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
LJIDs = case LJID of
{U, S, <<"">>} ->
(?DICT):fold(fun (J, _, Js) ->
@@ -1444,7 +1448,7 @@ set_role(JID, Role, StateData) ->
StateData#state{users = Users, nicks = Nicks}.
get_role(JID, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{role = Role}} -> Role;
_ -> none
@@ -1494,7 +1498,7 @@ get_max_users_admin_threshold(StateData) ->
5).
get_user_activity(JID, StateData) ->
- case treap:lookup(jlib:jid_tolower(JID),
+ case treap:lookup(jid:tolower(JID),
StateData#state.activity)
of
{ok, _P, A} -> A;
@@ -1526,8 +1530,8 @@ store_user_activity(JID, UserActivity, StateData) ->
fun(I) when is_number(I), I>=0 -> I end,
0)
* 1000),
- Key = jlib:jid_tolower(JID),
- Now = now_to_usec(now()),
+ Key = jid:tolower(JID),
+ Now = p1_time_compat:system_time(micro_seconds),
Activity1 = clean_treap(StateData#state.activity,
{1, -Now}),
Activity = case treap:lookup(Key, Activity1) of
@@ -1609,7 +1613,7 @@ prepare_room_queue(StateData) ->
end.
add_online_user(JID, Nick, Role, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
Users = (?DICT):store(LJID,
#user{jid = JID, nick = Nick, role = Role},
StateData#state.users),
@@ -1629,7 +1633,7 @@ remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, <<"">>).
remove_online_user(JID, StateData, Reason) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
{ok, #user{nick = Nick}} = (?DICT):find(LJID,
StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
@@ -1651,7 +1655,7 @@ filter_presence(#xmlel{name = <<"presence">>,
case El of
{xmlcdata, _} -> false;
#xmlel{attrs = Attrs1} ->
- XMLNS = xml:get_attr_s(<<"xmlns">>,
+ XMLNS = fxml:get_attr_s(<<"xmlns">>,
Attrs1),
NS_MUC = ?NS_MUC,
Size = byte_size(NS_MUC),
@@ -1678,7 +1682,7 @@ strip_status(#xmlel{name = <<"presence">>,
children = FEls}.
add_user_presence(JID, Presence, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
Users = (?DICT):update(LJID,
fun (#user{} = User) ->
@@ -1688,7 +1692,7 @@ add_user_presence(JID, Presence, StateData) ->
StateData#state{users = Users}.
add_user_presence_un(JID, Presence, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
Users = (?DICT):update(LJID,
fun (#user{} = User) ->
@@ -1702,8 +1706,8 @@ add_user_presence_un(JID, Presence, StateData) ->
%% Return jid record.
find_jids_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [User]} -> [jlib:make_jid(User)];
- {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users];
+ {ok, [User]} -> [jid:make(User)];
+ {ok, Users} -> [jid:make(LJID) || LJID <- Users];
error -> false
end.
@@ -1711,7 +1715,7 @@ find_jids_by_nick(Nick, StateData) ->
%% highest-priority presence. Return jid record.
find_jid_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [User]} -> jlib:make_jid(User);
+ {ok, [User]} -> jid:make(User);
{ok, [FirstUser | Users]} ->
#user{last_presence = FirstPresence} =
(?DICT):fetch(FirstUser, StateData#state.users),
@@ -1729,7 +1733,7 @@ find_jid_by_nick(Nick, StateData) ->
end
end,
{FirstUser, FirstPresence}, Users),
- jlib:make_jid(LJID);
+ jid:make(LJID);
error -> false
end.
@@ -1739,11 +1743,11 @@ higher_presence(Pres1, Pres2) ->
Pri1 > Pri2.
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -1759,7 +1763,7 @@ find_nick_by_jid(Jid, StateData) ->
Nick.
is_nick_change(JID, Nick, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
case Nick of
<<"">> -> false;
_ ->
@@ -1770,14 +1774,14 @@ is_nick_change(JID, Nick, StateData) ->
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
- UserOfNick /= false andalso
- jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick))
- /= jlib:jid_remove_resource(jlib:jid_tolower(User)).
+ (UserOfNick /= false andalso
+ jid:remove_resource(jid:tolower(UserOfNick))
+ /= jid:remove_resource(jid:tolower(User))).
add_new_user(From, Nick,
#xmlel{attrs = Attrs, children = Els} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
get_max_users_admin_threshold(StateData),
@@ -1807,7 +1811,7 @@ add_new_user(From, Nick,
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ (jid:replace_resource(StateData#state.jid, Nick),
From, Err),
StateData;
{_, _, _, none} ->
@@ -1824,14 +1828,14 @@ add_new_user(From, Nick,
ErrText)
end),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ (jid:replace_resource(StateData#state.jid, Nick),
From, Err),
StateData;
{_, true, _, _} ->
ErrText = <<"That nickname is already in use by another occupant">>,
Err = jlib:make_error_reply(Packet,
?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData;
@@ -1839,7 +1843,7 @@ add_new_user(From, Nick,
ErrText = <<"That nickname is registered by another person">>,
Err = jlib:make_error_reply(Packet,
?ERRT_CONFLICT(Lang, ErrText)),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData;
@@ -1851,37 +1855,12 @@ add_new_user(From, Nick,
NewState = add_user_presence(From, Packet,
add_online_user(From, Nick, Role,
StateData)),
- if not (NewState#state.config)#config.anonymous ->
- WPacket = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>,
- attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"This room is not anonymous">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"status">>,
- attrs =
- [{<<"code">>,
- <<"100">>}],
- children =
- []}]}]},
- ejabberd_router:route(StateData#state.jid, From, WPacket);
- true -> ok
- end,
send_existing_presences(From, NewState),
- send_new_presence(From, NewState),
+ send_initial_presence(From, NewState, StateData),
Shift = count_stanza_shift(Nick, Els, NewState),
case send_history(From, Shift, NewState) of
true -> ok;
- _ -> send_subject(From, Lang, StateData)
+ _ -> send_subject(From, StateData)
end,
case NewState#state.just_created of
true -> NewState#state{just_created = false};
@@ -1895,14 +1874,14 @@ add_new_user(From, Nick,
?ERRT_NOT_AUTHORIZED(Lang,
ErrText)),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid,
+ (jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData;
captcha_required ->
- SID = xml:get_attr_s(<<"id">>, Attrs),
+ SID = fxml:get_attr_s(<<"id">>, Attrs),
RoomJID = StateData#state.jid,
- To = jlib:jid_replace_resource(RoomJID, Nick),
+ To = jid:replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
case ejabberd_captcha:create_captcha(SID, RoomJID, To,
Lang, Limiter, From)
@@ -1921,7 +1900,7 @@ add_new_user(From, Nick,
?ERRT_RESOURCE_CONSTRAINT(Lang,
ErrText)),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid,
+ (jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData;
@@ -1931,7 +1910,7 @@ add_new_user(From, Nick,
?ERRT_INTERNAL_SERVER_ERROR(Lang,
ErrText)),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid,
+ (jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData
@@ -1942,7 +1921,7 @@ add_new_user(From, Nick,
?ERRT_NOT_AUTHORIZED(Lang,
ErrText)),
ejabberd_router:route % TODO: s/Nick/""/
- (jlib:jid_replace_resource(StateData#state.jid,
+ (jid:replace_resource(StateData#state.jid,
Nick),
From, Err),
StateData
@@ -2000,11 +1979,11 @@ check_captcha(Affiliation, From, StateData) ->
extract_password([]) -> false;
extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- case xml:get_subtag(El, <<"password">>) of
+ case fxml:get_subtag(El, <<"password">>) of
false -> false;
- SubEl -> xml:get_tag_cdata(SubEl)
+ SubEl -> fxml:get_tag_cdata(SubEl)
end;
_ -> extract_password(Els)
end;
@@ -2023,9 +2002,8 @@ count_stanza_shift(Nick, Els, StateData) ->
Shift1 = case Seconds of
false -> 0;
_ ->
- Sec =
- calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now()))
- - Seconds,
+ Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
+ - Seconds,
count_seconds_shift(Sec, HL)
end,
MaxStanzas = extract_history(Els, <<"maxstanzas">>),
@@ -2079,9 +2057,9 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
extract_history([], _Type) -> false;
extract_history([#xmlel{attrs = Attrs} = El | Els],
Type) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- AttrVal = xml:get_path_s(El,
+ AttrVal = fxml:get_path_s(El,
[{elem, <<"history">>}, {attr, Type}]),
case Type of
<<"since">> ->
@@ -2101,11 +2079,31 @@ extract_history([#xmlel{attrs = Attrs} = El | Els],
extract_history([_ | Els], Type) ->
extract_history(Els, Type).
-send_update_presence(JID, StateData) ->
- send_update_presence(JID, <<"">>, StateData).
+is_room_overcrowded(StateData) ->
+ MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, max_users_presence,
+ fun(MUP) when is_integer(MUP) -> MUP end,
+ ?DEFAULT_MAX_USERS_PRESENCE),
+ (?DICT):size(StateData#state.users) > MaxUsersPresence.
+
+presence_broadcast_allowed(JID, StateData) ->
+ Role = get_role(JID, StateData),
+ lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+
+send_initial_presence(NJID, StateData, OldStateData) ->
+ send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
-send_update_presence(JID, Reason, StateData) ->
- LJID = jlib:jid_tolower(JID),
+send_update_presence(JID, StateData, OldStateData) ->
+ send_update_presence(JID, <<"">>, StateData, OldStateData).
+
+send_update_presence(JID, Reason, StateData, OldStateData) ->
+ case is_room_overcrowded(StateData) of
+ true -> ok;
+ false -> send_update_presence1(JID, Reason, StateData, OldStateData)
+ end.
+
+send_update_presence1(JID, Reason, StateData, OldStateData) ->
+ LJID = jid:tolower(JID),
LJIDs = case LJID of
{U, S, <<"">>} ->
(?DICT):fold(fun (J, _, Js) ->
@@ -2122,34 +2120,67 @@ send_update_presence(JID, Reason, StateData) ->
end
end,
lists:foreach(fun (J) ->
- send_new_presence(J, Reason, StateData)
+ send_new_presence1(J, Reason, false, StateData,
+ OldStateData)
end,
LJIDs).
-send_new_presence(NJID, StateData) ->
- send_new_presence(NJID, <<"">>, StateData).
+send_new_presence(NJID, StateData, OldStateData) ->
+ send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
-send_new_presence(NJID, Reason, StateData) ->
- #user{nick = Nick} =
- (?DICT):fetch(jlib:jid_tolower(NJID),
- StateData#state.users),
+send_new_presence(NJID, Reason, StateData, OldStateData) ->
+ send_new_presence(NJID, Reason, false, StateData, OldStateData).
+
+send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
+ case is_room_overcrowded(StateData) of
+ true -> ok;
+ false -> send_new_presence1(NJID, Reason, IsInitialPresence, StateData,
+ OldStateData)
+ end.
+
+send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
+ LNJID = jid:tolower(NJID),
+ #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
{ok,
- #user{jid = RealJID, role = Role,
- last_presence = Presence}} =
- (?DICT):find(jlib:jid_tolower(LJID),
+ #user{jid = RealJID, role = Role0,
+ last_presence = Presence0} = UserInfo} =
+ (?DICT):find(jid:tolower(LJID),
StateData#state.users),
+ {Role1, Presence1} =
+ case presence_broadcast_allowed(NJID, StateData) of
+ true -> {Role0, Presence0};
+ false ->
+ {none,
+ #xmlel{name = <<"presence">>,
+ attrs = [{<<"type">>, <<"unavailable">>}],
+ children = []}
+ }
+ end,
Affiliation = get_affiliation(LJID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
- SRole = role_to_list(Role),
- lists:foreach(fun ({_LJID, Info}) ->
+ UserList =
+ case not (presence_broadcast_allowed(NJID, StateData) orelse
+ presence_broadcast_allowed(NJID, OldStateData)) of
+ true ->
+ [{LNJID, UserInfo}];
+ false ->
+ (?DICT):to_list(StateData#state.users)
+ end,
+ lists:foreach(fun ({LUJID, Info}) ->
+ {Role, Presence} =
+ if
+ LNJID == LUJID -> {Role0, Presence0};
+ true -> {Role1, Presence1}
+ end,
+ SRole = role_to_list(Role),
ItemAttrs = case Info#user.role == moderator orelse
(StateData#state.config)#config.anonymous
== false
of
true ->
[{<<"jid">>,
- jlib:jid_to_string(RealJID)},
+ jid:to_string(RealJID)},
{<<"affiliation">>, SAffiliation},
{<<"role">>, SRole}];
_ ->
@@ -2164,37 +2195,9 @@ send_new_presence(NJID, Reason, StateData) ->
children =
[{xmlcdata, Reason}]}]
end,
- Status = case StateData#state.just_created of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"201">>}],
- children = []}];
- false -> []
- end,
- Status2 = case
- (StateData#state.config)#config.anonymous
- == false
- andalso NJID == Info#user.jid
- of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"100">>}],
- children = []}
- | Status];
- false -> Status
- end,
- Status3 = case NJID == Info#user.jid of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"110">>}],
- children = []}
- | Status2];
- false -> Status2
- end,
- Packet = xml:append_subtags(Presence,
+ StatusEls = status_els(IsInitialPresence, NJID, Info,
+ StateData),
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2208,25 +2211,35 @@ send_new_presence(NJID, Reason, StateData) ->
children
=
ItemEls}
- | Status3]}]),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ | StatusEls]}]),
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
end,
- (?DICT):to_list(StateData#state.users)).
+ UserList).
send_existing_presences(ToJID, StateData) ->
- LToJID = jlib:jid_tolower(ToJID),
+ case is_room_overcrowded(StateData) of
+ true -> ok;
+ false -> send_existing_presences1(ToJID, StateData)
+ end.
+
+send_existing_presences1(ToJID, StateData) ->
+ LToJID = jid:tolower(ToJID),
{ok, #user{jid = RealToJID, role = Role}} =
(?DICT):find(LToJID, StateData#state.users),
lists:foreach(fun ({FromNick, _Users}) ->
LJID = find_jid_by_nick(FromNick, StateData),
#user{jid = FromJID, role = FromRole,
last_presence = Presence} =
- (?DICT):fetch(jlib:jid_tolower(LJID),
+ (?DICT):fetch(jid:tolower(LJID),
StateData#state.users),
- case RealToJID of
- FromJID -> ok;
+ PresenceBroadcast =
+ lists:member(
+ FromRole, (StateData#state.config)#config.presence_broadcast),
+ case {RealToJID, PresenceBroadcast} of
+ {FromJID, _} -> ok;
+ {_, false} -> ok;
_ ->
FromAffiliation = get_affiliation(LJID,
StateData),
@@ -2236,7 +2249,7 @@ send_existing_presences(ToJID, StateData) ->
of
true ->
[{<<"jid">>,
- jlib:jid_to_string(FromJID)},
+ jid:to_string(FromJID)},
{<<"affiliation">>,
affiliation_to_list(FromAffiliation)},
{<<"role">>,
@@ -2247,7 +2260,7 @@ send_existing_presences(ToJID, StateData) ->
{<<"role">>,
role_to_list(FromRole)}]
end,
- Packet = xml:append_subtags(Presence,
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name =
<<"x">>,
attrs =
@@ -2263,18 +2276,15 @@ send_existing_presences(ToJID, StateData) ->
children
=
[]}]}]),
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
FromNick),
RealToJID, Packet)
end
end,
(?DICT):to_list(StateData#state.nicks)).
-now_to_usec({MSec, Sec, USec}) ->
- (MSec * 1000000 + Sec) * 1000000 + USec.
-
change_nick(JID, Nick, StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
{ok, #user{nick = OldNick}} = (?DICT):find(LJID,
StateData#state.users),
Users = (?DICT):update(LJID,
@@ -2302,8 +2312,12 @@ change_nick(JID, Nick, StateData) ->
end,
NewStateData = StateData#state{users = Users,
nicks = Nicks},
- send_nick_changing(JID, OldNick, NewStateData,
- SendOldUnavailable, SendNewAvailable),
+ case presence_broadcast_allowed(JID, NewStateData) of
+ true ->
+ send_nick_changing(JID, OldNick, NewStateData,
+ SendOldUnavailable, SendNewAvailable);
+ false -> ok
+ end,
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
@@ -2312,7 +2326,7 @@ send_nick_changing(JID, OldNick, StateData,
{ok,
#user{jid = RealJID, nick = Nick, role = Role,
last_presence = Presence}} =
- (?DICT):find(jlib:jid_tolower(JID),
+ (?DICT):find(jid:tolower(JID),
StateData#state.users),
Affiliation = get_affiliation(JID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
@@ -2324,7 +2338,7 @@ send_nick_changing(JID, OldNick, StateData,
of
true ->
[{<<"jid">>,
- jlib:jid_to_string(RealJID)},
+ jid:to_string(RealJID)},
{<<"affiliation">>, SAffiliation},
{<<"role">>, SRole},
{<<"nick">>, Nick}];
@@ -2339,7 +2353,7 @@ send_nick_changing(JID, OldNick, StateData,
of
true ->
[{<<"jid">>,
- jlib:jid_to_string(RealJID)},
+ jid:to_string(RealJID)},
{<<"affiliation">>, SAffiliation},
{<<"role">>, SRole}];
_ ->
@@ -2377,7 +2391,7 @@ send_nick_changing(JID, OldNick, StateData,
<<"303">>}],
children =
[]}|Status110]}]},
- Packet2 = xml:append_subtags(Presence,
+ Packet2 = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2393,13 +2407,13 @@ send_nick_changing(JID, OldNick, StateData,
=
[]}|Status110]}]),
if SendOldUnavailable ->
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
OldNick),
Info#user.jid, Packet1);
true -> ok
end,
if SendNewAvailable ->
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet2);
true -> ok
@@ -2407,6 +2421,40 @@ send_nick_changing(JID, OldNick, StateData,
end,
(?DICT):to_list(StateData#state.users)).
+status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
+ Status = case IsInitialPresence of
+ true ->
+ S1 = case StateData#state.just_created of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"201">>}],
+ children = []}];
+ false -> []
+ end,
+ S2 = case (StateData#state.config)#config.anonymous of
+ true -> S1;
+ false ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"100">>}],
+ children = []} | S1]
+ end,
+ S3 = case (StateData#state.config)#config.logging of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"170">>}],
+ children = []} | S2];
+ false -> S2
+ end,
+ S3;
+ false -> []
+ end,
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"110">>}],
+ children = []} | Status];
+status_els(_IsInitialPresence, _JID, _Info, _StateData) -> [].
+
lqueue_new(Max) ->
#lqueue{queue = queue:new(), len = 0, max = Max}.
@@ -2431,28 +2479,28 @@ lqueue_to_list(#lqueue{queue = Q1}) ->
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case xml:get_subtag(Packet, <<"subject">>)
+ HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
of
false -> false;
_ -> true
end,
- TimeStamp = now(),
+ TimeStamp = p1_time_compat:timestamp(),
AddrPacket = case (StateData#state.config)#config.anonymous of
true -> Packet;
false ->
Address = #xmlel{name = <<"address">>,
attrs = [{<<"type">>, <<"ofrom">>},
{<<"jid">>,
- jlib:jid_to_string(FromJID)}],
+ jid:to_string(FromJID)}],
children = []},
Addresses = #xmlel{name = <<"addresses">>,
attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
children = [Address]},
- xml:append_subtags(Packet, [Addresses])
+ fxml:append_subtags(Packet, [Addresses])
end,
TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
SPacket =
- jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid,
+ jlib:replace_from_to(jid:replace_resource(StateData#state.jid,
FromNick),
StateData#state.jid, TSPacket),
Size = element_size(SPacket),
@@ -2466,7 +2514,7 @@ send_history(JID, Shift, StateData) ->
lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
_Size},
B) ->
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
JID, Packet),
B or HaveSubject
@@ -2475,30 +2523,21 @@ send_history(JID, Shift, StateData) ->
lists:nthtail(Shift,
lqueue_to_list(StateData#state.history))).
-send_subject(JID, Lang, StateData) ->
- case StateData#state.subject_author of
- <<"">> -> ok;
- Nick ->
- Subject = StateData#state.subject,
- Packet = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"subject">>, attrs = [],
- children = [{xmlcdata, Subject}]},
- #xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- <<Nick/binary,
- (translate:translate(Lang,
- <<" has set the subject to: ">>))/binary,
- Subject/binary>>}]}]},
- ejabberd_router:route(StateData#state.jid, JID, Packet)
- end.
+send_subject(_JID, #state{subject_author = <<"">>}) -> ok;
+send_subject(JID, #state{subject_author = Nick} = StateData) ->
+ Subject = StateData#state.subject,
+ Packet = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Subject}]}]},
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID,
+ Packet).
check_subject(Packet) ->
- case xml:get_subtag(Packet, <<"subject">>) of
+ case fxml:get_subtag(Packet, <<"subject">>) of
false -> false;
- SubjEl -> xml:get_tag_cdata(SubjEl)
+ SubjEl -> fxml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
@@ -2515,14 +2554,14 @@ process_iq_admin(From, set, Lang, SubEl, StateData) ->
#xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case xml:get_subtag(SubEl, <<"item">>) of
+ case fxml:get_subtag(SubEl, <<"item">>) of
false -> {error, ?ERR_BAD_REQUEST};
Item ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
- case xml:get_tag_attr(<<"role">>, Item) of
+ case fxml:get_tag_attr(<<"role">>, Item) of
false ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
+ case fxml:get_tag_attr(<<"affiliation">>, Item) of
false -> {error, ?ERR_BAD_REQUEST};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
@@ -2530,7 +2569,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) ->
SAffiliation ->
if (FAffiliation == owner) or
(FAffiliation == admin) or
- ((FAffiliation == member) and (SAffiliation == member)) ->
+ ((FAffiliation == member) and not
+ (StateData#state.config)#config.anonymous) ->
Items = items_with_affiliation(SAffiliation,
StateData),
{result, Items, StateData};
@@ -2567,7 +2607,7 @@ items_with_affiliation(SAffiliation, StateData) ->
attrs =
[{<<"affiliation">>,
affiliation_to_list(Affiliation)},
- {<<"jid">>, jlib:jid_to_string(JID)}],
+ {<<"jid">>, jid:to_string(JID)}],
children =
[#xmlel{name = <<"reason">>, attrs = [],
children = [{xmlcdata, Reason}]}]};
@@ -2576,7 +2616,7 @@ items_with_affiliation(SAffiliation, StateData) ->
attrs =
[{<<"affiliation">>,
affiliation_to_list(Affiliation)},
- {<<"jid">>, jlib:jid_to_string(JID)}],
+ {<<"jid">>, jid:to_string(JID)}],
children = []}
end,
search_affiliation(SAffiliation, StateData)).
@@ -2589,7 +2629,7 @@ user_to_item(#user{role = Role, nick = Nick, jid = JID},
[{<<"role">>, role_to_list(Role)},
{<<"affiliation">>, affiliation_to_list(Affiliation)},
{<<"nick">>, Nick},
- {<<"jid">>, jlib:jid_to_string(JID)}],
+ {<<"jid">>, jid:to_string(JID)}],
children = []}.
search_role(Role, StateData) ->
@@ -2615,8 +2655,8 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
{result, Res} ->
?INFO_MSG("Processing MUC admin query from ~s in "
"room ~s:~n ~p",
- [jlib:jid_to_string(UJID),
- jlib:jid_to_string(StateData#state.jid), Res]),
+ [jid:to_string(UJID),
+ jid:to_string(StateData#state.jid), Res]),
NSD = lists:foldl(process_item_change(UJID),
StateData, lists:flatten(Res)),
case (NSD#state.config)#config.persistent of
@@ -2661,7 +2701,7 @@ process_item_change(E, SD, UJID) ->
set_role(JID, none, SD1);
_ ->
SD1 = set_affiliation(JID, none, SD),
- send_update_presence(JID, SD1),
+ send_update_presence(JID, SD1, SD),
SD1
end;
{JID, affiliation, outcast, Reason} ->
@@ -2679,21 +2719,21 @@ process_item_change(E, SD, UJID) ->
when (A == admin) or (A == owner) ->
SD1 = set_affiliation(JID, A, SD, Reason),
SD2 = set_role(JID, moderator, SD1),
- send_update_presence(JID, Reason, SD2),
+ send_update_presence(JID, Reason, SD2, SD),
SD2;
{JID, affiliation, member, Reason} ->
SD1 = set_affiliation(JID, member, SD, Reason),
SD2 = set_role(JID, participant, SD1),
- send_update_presence(JID, Reason, SD2),
+ send_update_presence(JID, Reason, SD2, SD),
SD2;
{JID, role, Role, Reason} ->
SD1 = set_role(JID, Role, SD),
catch
- send_new_presence(JID, Reason, SD1),
+ send_new_presence(JID, Reason, SD1, SD),
SD1;
{JID, affiliation, A, _Reason} ->
SD1 = set_affiliation(JID, A, SD),
- send_update_presence(JID, SD1),
+ send_update_presence(JID, SD1, SD),
SD1
end
of
@@ -2714,9 +2754,9 @@ find_changed_items(UJID, UAffiliation, URole,
[#xmlel{name = <<"item">>, attrs = Attrs} = Item
| Items],
Lang, StateData, Res) ->
- TJID = case xml:get_attr(<<"jid">>, Attrs) of
+ TJID = case fxml:get_attr(<<"jid">>, Attrs) of
{value, S} ->
- case jlib:string_to_jid(S) of
+ case jid:from_string(S) of
error ->
ErrText = iolist_to_binary(
io_lib:format(translate:translate(
@@ -2727,7 +2767,7 @@ find_changed_items(UJID, UAffiliation, URole,
J -> {value, [J]}
end;
_ ->
- case xml:get_attr(<<"nick">>, Attrs) of
+ case fxml:get_attr(<<"nick">>, Attrs) of
{value, N} ->
case find_jids_by_nick(N, StateData) of
false ->
@@ -2747,9 +2787,9 @@ find_changed_items(UJID, UAffiliation, URole,
{value, [JID | _] = JIDs} ->
TAffiliation = get_affiliation(JID, StateData),
TRole = get_role(JID, StateData),
- case xml:get_attr(<<"role">>, Attrs) of
+ case fxml:get_attr(<<"role">>, Attrs) of
false ->
- case xml:get_attr(<<"affiliation">>, Attrs) of
+ case fxml:get_attr(<<"affiliation">>, Attrs) of
false -> {error, ?ERR_BAD_REQUEST};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
@@ -2777,9 +2817,9 @@ find_changed_items(UJID, UAffiliation, URole,
StateData)
of
[{OJID, _}] ->
- jlib:jid_remove_resource(OJID)
+ jid:remove_resource(OJID)
/=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ jid:tolower(jid:remove_resource(UJID));
_ -> true
end;
_ -> false
@@ -2790,10 +2830,10 @@ find_changed_items(UJID, UAffiliation, URole,
Items, Lang, StateData,
Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
- MoreRes = [{jlib:jid_remove_resource(Jidx),
+ MoreRes = [{jid:remove_resource(Jidx),
affiliation, SAffiliation, Reason}
|| Jidx <- JIDs],
find_changed_items(UJID, UAffiliation, URole,
@@ -2825,9 +2865,9 @@ find_changed_items(UJID, UAffiliation, URole,
StateData)
of
[{OJID, _}] ->
- jlib:jid_remove_resource(OJID)
+ jid:remove_resource(OJID)
/=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ jid:tolower(jid:remove_resource(UJID));
_ -> true
end;
_ -> false
@@ -2837,7 +2877,7 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole, Items,
Lang, StateData, Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
MoreRes = [{Jidx, role, SRole, Reason}
@@ -2986,7 +3026,7 @@ send_kickban_presence(UJID, JID, Reason, Code, StateData) ->
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
LJIDs = case LJID of
{U, S, <<"">>} ->
(?DICT):fold(fun (J, _, Js) ->
@@ -3015,14 +3055,14 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
{ok, #user{jid = RealJID, nick = Nick}} =
- (?DICT):find(jlib:jid_tolower(UJID),
+ (?DICT):find(jid:tolower(UJID),
StateData#state.users),
SAffiliation = affiliation_to_list(Affiliation),
- BannedJIDString = jlib:jid_to_string(RealJID),
+ BannedJIDString = jid:to_string(RealJID),
case MJID /= <<"">> of
true ->
{ok, #user{nick = ActorNick}} =
- (?DICT):find(jlib:jid_tolower(MJID),
+ (?DICT):find(jid:tolower(MJID),
StateData#state.users);
false ->
ActorNick = <<"">>
@@ -3075,7 +3115,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
Code}],
children =
[]}]}]},
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
end,
@@ -3089,10 +3129,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, [], StateData};
{?NS_XDATA, <<"submit">>} ->
@@ -3110,8 +3150,8 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
end;
[#xmlel{name = <<"destroy">>} = SubEl1] ->
?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
- [jlib:jid_to_string(StateData#state.jid),
- jlib:jid_to_string(From)]),
+ [jid:to_string(StateData#state.jid),
+ jid:to_string(From)]),
add_to_log(room_existence, destroyed, StateData),
destroy_room(SubEl1, StateData);
Items ->
@@ -3126,10 +3166,10 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[] -> get_config(Lang, StateData, From);
[Item] ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
+ case fxml:get_tag_attr(<<"affiliation">>, Item) of
false -> {error, ?ERR_BAD_REQUEST};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
@@ -3276,7 +3316,7 @@ is_password_settings_correct(XEl, StateData) ->
{<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, jlib:jid_to_string(JID)}]}
+ children = [{xmlcdata, jid:to_string(JID)}]}
|| JID <- JIDList]}).
get_default_room_maxusers(RoomState) ->
@@ -3312,7 +3352,7 @@ get_config(Lang, StateData, From) ->
translate:translate(
Lang,
<<"Configuration of room ~s">>),
- [jlib:jid_to_string(StateData#state.jid)]))}]},
+ [jid:to_string(StateData#state.jid)]))}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"hidden">>},
@@ -3429,6 +3469,53 @@ get_config(Lang, StateData, From) ->
children =
[{xmlcdata,
<<"anyone">>}]}]}]},
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"list-multi">>},
+ {<<"label">>,
+ translate:translate(Lang,
+ <<"Roles for which Presence is Broadcasted">>)},
+ {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}],
+ children =
+ lists:map(
+ fun(Role) ->
+ #xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ atom_to_binary(Role, utf8)}]}
+ end, Config#config.presence_broadcast
+ ) ++
+ [#xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"Moderator">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"moderator">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"Participant">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"participant">>}]}]},
+ #xmlel{name = <<"option">>,
+ attrs =
+ [{<<"label">>,
+ translate:translate(Lang,
+ <<"Visitor">>)}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"visitor">>}]}]}
+ ]},
?BOOLXFIELD(<<"Make room members-only">>,
<<"muc#roomconfig_membersonly">>,
(Config#config.members_only)),
@@ -3519,6 +3606,13 @@ get_config(Lang, StateData, From) ->
<<"captcha_protected">>,
(Config#config.captcha_protected))];
false -> []
+ end ++
+ case gen_mod:is_loaded(StateData#state.server_host, mod_mam) of
+ true ->
+ [?BOOLXFIELD(<<"Enable message archiving">>,
+ <<"muc#roomconfig_mam">>,
+ (Config#config.mam))];
+ false -> []
end
++
[?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
@@ -3695,6 +3789,28 @@ set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
set_xoption([{<<"anonymous">>, [Val]} | Opts],
Config) ->
?SET_BOOL_XOPT(anonymous, Val);
+set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
+ Config) ->
+ Roles =
+ lists:foldl(
+ fun(_S, error) -> error;
+ (S, {M, P, V}) ->
+ case S of
+ <<"moderator">> -> {true, P, V};
+ <<"participant">> -> {M, true, V};
+ <<"visitor">> -> {M, P, true};
+ _ -> error
+ end
+ end, {false, false, false}, Vals),
+ case Roles of
+ error -> {error, ?ERR_BAD_REQUEST};
+ {M, P, V} ->
+ Res =
+ if M -> [moderator]; true -> [] end ++
+ if P -> [participant]; true -> [] end ++
+ if V -> [visitor]; true -> [] end,
+ set_xoption(Opts, Config#config{presence_broadcast = Res})
+ end;
set_xoption([{<<"muc#roomconfig_allowvoicerequests">>,
[Val]}
| Opts],
@@ -3728,11 +3844,13 @@ set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
| Opts],
Config) ->
?SET_BOOL_XOPT(logging, Val);
+set_xoption([{<<"muc#roomconfig_mam">>, [Val]}|Opts], Config) ->
+ ?SET_BOOL_XOPT(mam, Val);
set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
Vals}
| Opts],
Config) ->
- JIDs = [jlib:string_to_jid(Val) || Val <- Vals],
+ JIDs = [jid:from_string(Val) || Val <- Vals],
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) ->
set_xoption(Opts, Config);
@@ -3740,6 +3858,7 @@ set_xoption([_ | _Opts], _Config) ->
{error, ?ERR_BAD_REQUEST}.
change_config(Config, StateData) ->
+ send_config_change_info(Config, StateData),
NSD = StateData#state{config = Config},
case {(StateData#state.config)#config.persistent,
Config#config.persistent}
@@ -3760,6 +3879,39 @@ change_config(Config, StateData) ->
_ -> {result, [], NSD}
end.
+send_config_change_info(Config, #state{config = Config}) -> ok;
+send_config_change_info(New, #state{config = Old} = StateData) ->
+ Codes = case {Old#config.logging, New#config.logging} of
+ {false, true} -> [<<"170">>];
+ {true, false} -> [<<"171">>];
+ _ -> []
+ end
+ ++
+ case {Old#config.anonymous, New#config.anonymous} of
+ {true, false} -> [<<"172">>];
+ {false, true} -> [<<"173">>];
+ _ -> []
+ end
+ ++
+ case Old#config{anonymous = New#config.anonymous,
+ logging = New#config.logging} of
+ New -> [];
+ _ -> [<<"104">>]
+ end,
+ StatusEls = [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, Code}],
+ children = []} || Code <- Codes],
+ Message = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>},
+ {<<"id">>, randoms:get_string()}],
+ children = [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
+ children = StatusEls}]},
+ send_multiple(StateData#state.jid,
+ StateData#state.server_host,
+ StateData#state.users,
+ Message).
+
remove_nonmembers(StateData) ->
lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
Affiliation = get_affiliation(JID, SD),
@@ -3852,10 +4004,17 @@ set_opts([{Opt, Val} | Opts], StateData) ->
StateData#state{config =
(StateData#state.config)#config{anonymous =
Val}};
+ presence_broadcast ->
+ StateData#state{config =
+ (StateData#state.config)#config{presence_broadcast =
+ Val}};
logging ->
StateData#state{config =
(StateData#state.config)#config{logging =
Val}};
+ mam ->
+ StateData#state{config =
+ (StateData#state.config)#config{mam = Val}};
captcha_whitelist ->
StateData#state{config =
(StateData#state.config)#config{captcha_whitelist
@@ -3912,6 +4071,7 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous),
?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users),
?MAKE_CONFIG_OPT(allow_voice_requests),
+ ?MAKE_CONFIG_OPT(mam),
?MAKE_CONFIG_OPT(voice_request_min_interval),
?MAKE_CONFIG_OPT(vcard),
{captcha_whitelist,
@@ -3942,7 +4102,7 @@ destroy_room(DEl, StateData) ->
children =
[]},
DEl]}]},
- ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid,
+ ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
end,
@@ -3995,6 +4155,15 @@ process_iq_disco_info(_From, get, Lang, StateData) ->
<<"muc_moderated">>, <<"muc_unmoderated">>),
?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
<<"muc_passwordprotected">>, <<"muc_unsecured">>)]
+ ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
+ Config#config.mam} of
+ {true, true} ->
+ [?FEATURE(?NS_MAM_TMP),
+ ?FEATURE(?NS_MAM_0),
+ ?FEATURE(?NS_MAM_1)];
+ _ ->
+ []
+ end
++ iq_disco_info_extras(Lang, StateData),
StateData}.
@@ -4056,7 +4225,7 @@ process_iq_captcha(_From, set, _Lang, SubEl,
process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
#state{config = #config{vcard = VCardRaw}} = StateData,
- case xml_stream:parse_element(VCardRaw) of
+ case fxml_stream:parse_element(VCardRaw) of
#xmlel{children = VCardEls} ->
{result, VCardEls, StateData};
{error, _} ->
@@ -4065,7 +4234,7 @@ process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
process_iq_vcard(From, set, Lang, SubEl, StateData) ->
case get_affiliation(From, StateData) of
owner ->
- VCardRaw = xml:element_to_binary(SubEl),
+ VCardRaw = fxml:element_to_binary(SubEl),
Config = StateData#state.config,
NewConfig = Config#config{vcard = VCardRaw},
change_config(NewConfig, StateData);
@@ -4109,7 +4278,7 @@ get_mucroom_disco_items(StateData) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
- jlib:jid_to_string({StateData#state.room,
+ jid:to_string({StateData#state.room,
StateData#state.host,
Nick})},
{<<"name">>, Nick}],
@@ -4124,7 +4293,7 @@ is_voice_request(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fields ->
@@ -4186,7 +4355,7 @@ prepare_request_form(Requester, Nick, Lang) ->
[{xmlcdata,
<<"participant">>}]}]},
?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
- (jlib:jid_to_string(Requester))),
+ (jid:to_string(Requester))),
?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>,
Nick),
?BOOLXFIELD(<<"Grant voice to this person?">>,
@@ -4207,7 +4376,7 @@ is_voice_approvement(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fs ->
@@ -4243,7 +4412,7 @@ extract_jid_from_voice_approvement(Els) ->
Res -> Res
end,
lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) ->
- case jlib:string_to_jid(JIDStr) of
+ case jid:from_string(JIDStr) of
error -> error;
J -> {ok, J}
end;
@@ -4261,9 +4430,9 @@ is_invitation(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER ->
- case xml:get_subtag(El, <<"invite">>) of
+ case fxml:get_subtag(El, <<"invite">>) of
false -> false;
_ -> true
end;
@@ -4279,20 +4448,20 @@ check_invitation(From, Els, Lang, StateData) ->
(StateData#state.config)#config.allow_user_invites
orelse
FAffiliation == admin orelse FAffiliation == owner,
- InviteEl = case xml:remove_cdata(Els) of
+ InviteEl = case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>, children = Els1} = XEl] ->
- case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, XEl) of
?NS_MUC_USER -> ok;
_ -> throw({error, ?ERR_BAD_REQUEST})
end,
- case xml:remove_cdata(Els1) of
+ case fxml:remove_cdata(Els1) of
[#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1;
_ -> throw({error, ?ERR_BAD_REQUEST})
end;
_ -> throw({error, ?ERR_BAD_REQUEST})
end,
JID = case
- jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>,
+ jid:from_string(fxml:get_tag_attr_s(<<"to">>,
InviteEl))
of
error -> throw({error, ?ERR_JID_MALFORMED});
@@ -4301,16 +4470,16 @@ check_invitation(From, Els, Lang, StateData) ->
case CanInvite of
false -> throw({error, ?ERR_NOT_ALLOWED});
true ->
- Reason = xml:get_path_s(InviteEl,
+ Reason = fxml:get_path_s(InviteEl,
[{elem, <<"reason">>}, cdata]),
- ContinueEl = case xml:get_path_s(InviteEl,
+ ContinueEl = case fxml:get_path_s(InviteEl,
[{elem, <<"continue">>}])
of
<<>> -> [];
Continue1 -> [Continue1]
end,
IEl = [#xmlel{name = <<"invite">>,
- attrs = [{<<"from">>, jlib:jid_to_string(From)}],
+ attrs = [{<<"from">>, jid:to_string(From)}],
children =
[#xmlel{name = <<"reason">>, attrs = [],
children = [{xmlcdata, Reason}]}]
@@ -4333,11 +4502,10 @@ check_invitation(From, Els, Lang, StateData) ->
translate:translate(
Lang,
<<"~s invites you to the room ~s">>),
- [jlib:jid_to_string(From),
- jlib:jid_to_string({StateData#state.room,
+ [jid:to_string(From),
+ jid:to_string({StateData#state.room,
StateData#state.host,
<<"">>})]),
-
case
(StateData#state.config)#config.password_protected
of
@@ -4365,7 +4533,7 @@ check_invitation(From, Els, Lang, StateData) ->
attrs =
[{<<"xmlns">>, ?NS_XCONFERENCE},
{<<"jid">>,
- jlib:jid_to_string({StateData#state.room,
+ jid:to_string({StateData#state.room,
StateData#state.host,
<<"">>})}],
children = [{xmlcdata, Reason}]},
@@ -4390,15 +4558,15 @@ handle_roommessage_from_nonparticipant(Packet, Lang,
%% Check in the packet is a decline.
%% If so, also returns the splitted packet.
-%% This function must be catched,
+%% This function must be catched,
%% because it crashes when the packet is not a decline message.
check_decline_invitation(Packet) ->
#xmlel{name = <<"message">>} = Packet,
- XEl = xml:get_subtag(Packet, <<"x">>),
- (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl),
- DEl = xml:get_subtag(XEl, <<"decline">>),
- ToString = xml:get_tag_attr_s(<<"to">>, DEl),
- ToJID = jlib:string_to_jid(ToString),
+ XEl = fxml:get_subtag(Packet, <<"x">>),
+ (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ DEl = fxml:get_subtag(XEl, <<"decline">>),
+ ToString = fxml:get_tag_attr_s(<<"to">>, DEl),
+ ToJID = jid:from_string(ToString),
{true, {Packet, XEl, DEl, ToJID}}.
%% Send the decline to the inviter user.
@@ -4406,7 +4574,7 @@ check_decline_invitation(Packet) ->
send_decline_invitation({Packet, XEl, DEl, ToJID},
RoomJID, FromJID) ->
FromString =
- jlib:jid_to_string(jlib:jid_remove_resource(FromJID)),
+ jid:to_string(jid:remove_resource(FromJID)),
#xmlel{name = <<"decline">>, attrs = DAttrs,
children = DEls} =
DEl,
@@ -4418,7 +4586,7 @@ send_decline_invitation({Packet, XEl, DEl, ToJID},
Packet2 = replace_subelement(Packet, XEl2),
ejabberd_router:route(RoomJID, ToJID, Packet2).
-%% Given an element and a new subelement,
+%% Given an element and a new subelement,
%% replace the instance of the subelement in element with the new subelement.
replace_subelement(#xmlel{name = Name, attrs = Attrs,
children = SubEls},
@@ -4456,7 +4624,7 @@ add_to_log(Type, Data, StateData) ->
%% Users number checking
tab_add_online_user(JID, StateData) ->
- {LUser, LServer, LResource} = jlib:jid_tolower(JID),
+ {LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
@@ -4465,7 +4633,7 @@ tab_add_online_user(JID, StateData) ->
room = Room, host = Host}).
tab_remove_online_user(JID, StateData) ->
- {LUser, LServer, LResource} = jlib:jid_tolower(JID),
+ {LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
@@ -4474,7 +4642,7 @@ tab_remove_online_user(JID, StateData) ->
room = Room, host = Host}).
tab_count_user(JID) ->
- {LUser, LServer, _} = jlib:jid_tolower(JID),
+ {LUser, LServer, _} = jid:tolower(JID),
US = {LUser, LServer},
case catch ets:select(muc_online_users,
[{#muc_online_users{us = US, _ = '_'}, [], [[]]}])
@@ -4484,7 +4652,14 @@ tab_count_user(JID) ->
end.
element_size(El) ->
- byte_size(xml:element_to_binary(El)).
+ byte_size(fxml:element_to_binary(El)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Multicast
+
+send_multiple(From, Server, Users, Packet) ->
+ JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
+ ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaninful content
@@ -4495,10 +4670,3 @@ has_body_or_subject(Packet) ->
(#xmlel{name = <<"subject">>}) -> false;
(_) -> true
end, Packet#xmlel.children).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Multicast
-
-send_multiple(From, Server, Users, Packet) ->
- JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
- ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index 8a196008..83520c0b 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -3,12 +3,32 @@
%%% Author : Badlop <badlop@process-one.net>
%%% Purpose : Extended Stanza Addressing (XEP-0033) support
%%% Created : 29 May 2007 by Badlop <badlop@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(mod_multicast).
-author('badlop@process-one.net').
+-protocol({xep, 33, '1.1'}).
+
-behaviour(gen_server).
-behaviour(gen_mod).
@@ -20,7 +40,7 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
--export([purge_loop/1]).
+-export([purge_loop/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -131,7 +151,7 @@ init([LServerS, Opts]) ->
try_start_loop(),
create_pool(),
ejabberd_router_multicast:register_route(LServerS),
- ejabberd_router:register_route(LServiceS),
+ ejabberd_router:register_route(LServiceS, LServerS),
{ok,
#state{lservice = LServiceS, lserver = LServerS,
access = Access, service_limits = SLimits}}.
@@ -151,24 +171,11 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To,
#xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
State) ->
- IQ = jlib:iq_query_info(Packet),
- case catch process_iq(From, IQ, State) of
- Result when is_record(Result, iq) ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
- {'EXIT', Reason} ->
- ?ERROR_MSG("Error when processing IQ stanza: ~p",
- [Reason]),
- Err = jlib:make_error_reply(Packet,
- ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err);
- reply ->
- LServiceS = jts(To),
- case xml:get_attr_s(<<"type">>, Attrs) of
- <<"result">> ->
- process_iqreply_result(From, LServiceS, Packet, State);
- <<"error">> ->
- process_iqreply_error(From, LServiceS, Packet)
- end
+ case catch handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error when processing IQ stanza: ~p",
+ [Reason]);
+ _ -> ok
end,
{noreply, State};
%% XEP33 allows only 'message' and 'presence' stanza type
@@ -186,11 +193,16 @@ handle_info({route, From, To,
handle_info({route_trusted, From, Destinations, Packet},
#state{lservice = LServiceS, lserver = LServerS} =
State) ->
- route_trusted(LServiceS, LServerS, From, Destinations,
- Packet),
+ case catch route_trusted(LServiceS, LServerS, From, Destinations,
+ Packet) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error in route_trusted: ~p", [Reason]);
+ _ -> ok
+ end,
{noreply, State};
handle_info({get_host, Pid}, State) ->
- Pid ! {my_host, State#state.lservice}, {noreply, State};
+ Pid ! {my_host, State#state.lservice},
+ {noreply, State};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
@@ -208,6 +220,28 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%% IQ Request Processing
%%%------------------------
+handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) ->
+ IQ = jlib:iq_query_info(Packet),
+ case catch process_iq(From, IQ, State) of
+ Result when is_record(Result, iq) ->
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("Error when processing IQ stanza: ~p",
+ [Reason]),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route(To, From, Err);
+ reply ->
+ LServiceS = jts(To),
+ case fxml:get_attr_s(<<"type">>, Attrs) of
+ <<"result">> ->
+ process_iqreply_result(From, LServiceS, Packet, State);
+ <<"error">> ->
+ process_iqreply_error(From, LServiceS, Packet)
+ end;
+ ok -> ok
+ end.
+
process_iq(From,
#iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
IQ,
@@ -270,7 +304,7 @@ iq_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd Multicast service">>))/binary,
- "\nCopyright (c) 2002-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2002-2016 ProcessOne">>}]}].
%%%-------------------------
%%% Route
@@ -361,7 +395,7 @@ act_groups(FromJID, Packet_stripped, AAttrs, LServiceS,
perform(From, Packet, AAttrs, _,
{route_single, Group}) ->
[route_packet(From, ToUser, Packet, AAttrs,
- Group#group.addresses)
+ Group#group.others, Group#group.addresses)
|| ToUser <- Group#group.dests];
perform(From, Packet, AAttrs, _,
{{route_multicast, JID, RLimits}, Group}) ->
@@ -375,6 +409,9 @@ perform(From, Packet, AAttrs, LServiceS,
group = Group, renewal = true, sender = From,
packet = Packet, aattrs = AAttrs,
addresses = Group#group.addresses});
+perform(_From, _Packet, _AAttrs, LServiceS,
+ {{ask, LServiceS, _}, _Group}) ->
+ ok;
perform(From, Packet, AAttrs, LServiceS,
{{ask, Server, not_renewal}, Group}) ->
send_query_info(Server, LServiceS),
@@ -399,17 +436,17 @@ check_access(LServerS, Access, From) ->
%%%-------------------------
strip_addresses_element(Packet) ->
- case xml:get_subtag(Packet, <<"addresses">>) of
+ case fxml:get_subtag(Packet, <<"addresses">>) of
#xmlel{name = <<"addresses">>, attrs = AAttrs,
children = Addresses} ->
- case xml:get_attr_s(<<"xmlns">>, AAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, AAttrs) of
?NS_ADDRESS ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Els_stripped = lists:keydelete(<<"addresses">>, 2, Els),
Packet_stripped = #xmlel{name = Name, attrs = Attrs,
children = Els_stripped},
- {ok, Packet_stripped, AAttrs, Addresses};
+ {ok, Packet_stripped, AAttrs, fxml:remove_cdata(Addresses)};
_ -> throw(ewxmlns)
end;
_ -> throw(eadsele)
@@ -423,10 +460,10 @@ split_addresses_todeliver(Addresses) ->
lists:partition(fun (XML) ->
case XML of
#xmlel{name = <<"address">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"delivered">>, Attrs) of
+ case fxml:get_attr_s(<<"delivered">>, Attrs) of
<<"true">> -> false;
_ ->
- Type = xml:get_attr_s(<<"type">>,
+ Type = fxml:get_attr_s(<<"type">>,
Attrs),
case Type of
<<"to">> -> true;
@@ -462,10 +499,10 @@ check_limit_dests(SLimits, FromJID, Packet,
convert_dest_record(XMLs) ->
lists:map(fun (XML) ->
- case xml:get_tag_attr_s(<<"jid">>, XML) of
+ case fxml:get_tag_attr_s(<<"jid">>, XML) of
<<"">> -> #dest{jid_string = none, full_xml = XML};
JIDS ->
- Type = xml:get_tag_attr_s(<<"type">>, XML),
+ Type = fxml:get_tag_attr_s(<<"type">>, XML),
JIDJ = stj(JIDS),
#dest{jid_string = JIDS, jid_jid = JIDJ,
type = Type, full_xml = XML}
@@ -488,7 +525,7 @@ split_dests_jid(Dests) ->
Dests).
report_not_jid(From, Packet, Dests) ->
- Dests2 = [xml:element_to_binary(Dest#dest.full_xml)
+ Dests2 = [fxml:element_to_binary(Dest#dest.full_xml)
|| Dest <- Dests],
[route_error(From, From, Packet, jid_malformed,
<<"This service can not process the address: ",
@@ -597,13 +634,13 @@ decide_action_group(Group) ->
%%% Route packet
%%%-------------------------
-route_packet(From, ToDest, Packet, AAttrs, Addresses) ->
+route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) ->
Dests = case ToDest#dest.type of
<<"bcc">> -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
- Packet, AAttrs, Addresses).
+ Packet, AAttrs, {Others, Addresses}).
route_packet_multicast(From, ToS, Packet, AAttrs, Dests,
Addresses, Limits) ->
@@ -629,6 +666,8 @@ route_packet2(From, ToS, Dests, Packet, AAttrs,
ToJID = stj(ToS),
ejabberd_router:route(From, ToJID, Packet2).
+append_dests(_Dests, {Others, Addresses}) ->
+ Addresses++Others;
append_dests([], Addresses) -> Addresses;
append_dests([Dest | Dests], Addresses) ->
append_dests(Dests, [Dest#dest.full_xml | Addresses]).
@@ -644,7 +683,8 @@ check_relay(RS, LS, Gs) ->
end.
check_relay_required(RServer, LServerS, Groups) ->
- case str:str(RServer, LServerS) > 0 of
+ case lists:suffix(str:tokens(LServerS, <<".">>),
+ str:tokens(RServer, <<".">>)) of
true -> false;
false -> check_relay_required(LServerS, Groups)
end.
@@ -693,12 +733,11 @@ process_iqreply_error(From, LServiceS, _Packet) ->
%%% Check protocol support: Receive response: Disco
%%%-------------------------
-process_iqreply_result(From, LServiceS, Packet,
- State) ->
+process_iqreply_result(From, LServiceS, Packet, State) ->
#xmlel{name = <<"query">>, attrs = Attrs2,
children = Els2} =
- xml:get_subtag(Packet, <<"query">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs2) of
+ fxml:get_subtag(Packet, <<"query">>),
+ case fxml:get_attr_s(<<"xmlns">>, Attrs2) of
?NS_DISCO_INFO ->
process_discoinfo_result(From, LServiceS, Els2, State);
?NS_DISCO_ITEMS ->
@@ -721,37 +760,35 @@ process_discoinfo_result(From, LServiceS, Els,
process_discoinfo_result2(From, FromS, LServiceS, Els,
Waiter) ->
- Multicast_support = lists:any(fun (XML) ->
- case XML of
- #xmlel{name = <<"feature">>,
- attrs = Attrs} ->
- (?NS_ADDRESS) ==
- xml:get_attr_s(<<"var">>,
- Attrs);
- _ -> false
- end
- end,
- Els),
+ Multicast_support =
+ lists:any(
+ fun(XML) ->
+ case XML of
+ #xmlel{name = <<"feature">>, attrs = Attrs} ->
+ (?NS_ADDRESS) == fxml:get_attr_s(<<"var">>, Attrs);
+ _ -> false
+ end
+ end,
+ Els),
Group = Waiter#waiter.group,
RServer = Group#group.server,
case Multicast_support of
- true ->
- SenderT = sender_type(From),
- RLimits = get_limits_xml(Els, SenderT),
- add_response(RServer,
- {multicast_supported, FromS, RLimits}),
- FromM = Waiter#waiter.sender,
- DestsM = Group#group.dests,
- PacketM = Waiter#waiter.packet,
- AAttrsM = Waiter#waiter.aattrs,
- AddressesM = Waiter#waiter.addresses,
- RServiceM = FromS,
- route_packet_multicast(FromM, RServiceM, PacketM,
- AAttrsM, DestsM, AddressesM, RLimits),
- delo_waiter(Waiter);
- false ->
- case FromS of
- RServer ->
+ true ->
+ SenderT = sender_type(From),
+ RLimits = get_limits_xml(Els, SenderT),
+ add_response(RServer, {multicast_supported, FromS, RLimits}),
+ FromM = Waiter#waiter.sender,
+ DestsM = Group#group.dests,
+ PacketM = Waiter#waiter.packet,
+ AAttrsM = Waiter#waiter.aattrs,
+ AddressesM = Waiter#waiter.addresses,
+ RServiceM = FromS,
+ route_packet_multicast(FromM, RServiceM, PacketM,
+ AAttrsM, DestsM, AddressesM, RLimits),
+ delo_waiter(Waiter);
+ false ->
+ case FromS of
+ RServer ->
send_query_items(FromS, LServiceS),
delo_waiter(Waiter),
add_waiter(Waiter#waiter{awaiting =
@@ -772,10 +809,10 @@ get_limits_els(Els) ->
#xmlel{name = <<"x">>, attrs = Attrs,
children = SubEls} ->
case ((?NS_XDATA) ==
- xml:get_attr_s(<<"xmlns">>, Attrs))
+ fxml:get_attr_s(<<"xmlns">>, Attrs))
and
(<<"result">> ==
- xml:get_attr_s(<<"type">>, Attrs))
+ fxml:get_attr_s(<<"type">>, Attrs))
of
true -> get_limits_fields(SubEls) ++ R;
false -> R
@@ -791,11 +828,11 @@ get_limits_fields(Fields) ->
#xmlel{name = <<"field">>,
attrs = Attrs} ->
(<<"FORM_TYPE">> ==
- xml:get_attr_s(<<"var">>,
+ fxml:get_attr_s(<<"var">>,
Attrs))
and
(<<"hidden">> ==
- xml:get_attr_s(<<"type">>,
+ fxml:get_attr_s(<<"type">>,
Attrs));
_ -> false
end
@@ -813,8 +850,8 @@ get_limits_values(Values) ->
children = SubEls} ->
[#xmlel{name = <<"value">>, children = SubElsV}] =
SubEls,
- Number = xml:get_cdata(SubElsV),
- Name = xml:get_attr_s(<<"var">>, Attrs),
+ Number = fxml:get_cdata(SubElsV),
+ Name = fxml:get_attr_s(<<"var">>, Attrs),
[{jlib:binary_to_atom(Name),
jlib:binary_to_integer(Number)}
| R];
@@ -828,29 +865,44 @@ get_limits_values(Values) ->
%%%-------------------------
process_discoitems_result(From, LServiceS, Els) ->
- List = lists:foldl(fun (XML, Res) ->
- case XML of
- #xmlel{name = <<"item">>, attrs = Attrs} ->
- Res ++ [xml:get_attr_s(<<"jid">>, Attrs)];
- _ -> Res
- end
- end,
- [], Els),
- [send_query_info(Item, LServiceS) || Item <- List],
FromS = jts(From),
- {found_waiter, Waiter} = search_waiter(FromS, LServiceS,
- items),
- delo_waiter(Waiter),
- add_waiter(Waiter#waiter{awaiting =
- {List, LServiceS, info},
- renewal = false}).
+ case search_waiter(FromS, LServiceS, items) of
+ {found_waiter, Waiter} ->
+ List = lists:foldl(
+ fun(XML, Res) ->
+ case XML of
+ #xmlel{name = <<"item">>, attrs = Attrs} ->
+ SJID = fxml:get_attr_s(<<"jid">>, Attrs),
+ case jid:from_string(SJID) of
+ #jid{luser = <<"">>,
+ lresource = <<"">>} ->
+ [SJID | Res];
+ _ -> Res
+ end;
+ _ -> Res
+ end
+ end,
+ [], Els),
+ case List of
+ [] ->
+ received_awaiter(FromS, Waiter, LServiceS);
+ _ ->
+ [send_query_info(Item, LServiceS) || Item <- List],
+ delo_waiter(Waiter),
+ add_waiter(Waiter#waiter{awaiting =
+ {List, LServiceS, info},
+ renewal = false})
+ end;
+ _ ->
+ ok
+ end.
%%%-------------------------
%%% Check protocol support: Receive response: Received awaiter
%%%-------------------------
received_awaiter(JID, Waiter, LServiceS) ->
- {JIDs, LServiceS, info} = Waiter#waiter.awaiting,
+ {JIDs, LServiceS, _} = Waiter#waiter.awaiting,
delo_waiter(Waiter),
Group = Waiter#waiter.group,
RServer = Group#group.server,
@@ -862,8 +914,9 @@ received_awaiter(JID, Waiter, LServiceS) ->
From = Waiter#waiter.sender,
Packet = Waiter#waiter.packet,
AAttrs = Waiter#waiter.aattrs,
+ Others = Group#group.others,
Addresses = Waiter#waiter.addresses,
- [route_packet(From, ToUser, Packet, AAttrs, Addresses)
+ [route_packet(From, ToUser, Packet, AAttrs, Others, Addresses)
|| ToUser <- Group#group.dests];
true ->
send_query_info(RServer, LServiceS),
@@ -887,8 +940,7 @@ create_cache() ->
{attributes, record_info(fields, multicastc)}]).
add_response(RServer, Response) ->
- Secs =
- calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(now())),
+ Secs = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
mnesia:dirty_write(#multicastc{rserver = RServer,
response = Response, ts = Secs}).
@@ -899,8 +951,7 @@ search_server_on_cache(RServer, _LServerS, Maxmins) ->
case look_server(RServer) of
not_cached -> not_cached;
{cached, Response, Ts} ->
- Now =
- calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(now())),
+ Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
case is_obsolete(Response, Ts, Now, Maxmins) of
false -> {cached, Response};
true -> {obsolete, Response}
@@ -928,7 +979,7 @@ purge() ->
Maxmins_positive = (?MAXTIME_CACHE_POSITIVE),
Maxmins_negative = (?MAXTIME_CACHE_NEGATIVE),
Now =
- calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(now())),
+ calendar:datetime_to_gregorian_seconds(calendar:local_time()),
purge(Now, {Maxmins_positive, Maxmins_negative}).
purge(Now, Maxmins) ->
@@ -982,8 +1033,12 @@ purge_loop(NM) ->
%%%-------------------------
create_pool() ->
- catch ets:new(multicastp,
- [duplicate_bag, public, named_table, {keypos, 2}]).
+ catch
+ begin
+ ets:new(multicastp,
+ [duplicate_bag, public, named_table, {keypos, 2}]),
+ ets:give_away(multicastp, whereis(ejabberd), ok)
+ end.
add_waiter(Waiter) ->
true = ets:insert(multicastp, Waiter).
@@ -991,6 +1046,9 @@ add_waiter(Waiter) ->
delo_waiter(Waiter) ->
true = ets:delete_object(multicastp, Waiter).
+-spec search_waiter(binary(), binary(), info | items) ->
+ {found_waiter, #waiter{}} | waiter_not_found.
+
search_waiter(JID, LServiceS, Type) ->
Rs = ets:foldl(fun (W, Res) ->
{JIDs, LServiceS1, Type1} = W#waiter.awaiting,
@@ -1141,7 +1199,7 @@ to_binary(A) -> list_to_binary(hd(io_lib:format("~p", [A]))).
route_error(From, To, Packet, ErrType, ErrText) ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
Reply = make_reply(ErrType, Lang, ErrText),
Err = jlib:make_error_reply(Packet, Reply),
ejabberd_router:route(From, To, Err).
@@ -1157,6 +1215,13 @@ make_reply(internal_server_error, Lang, ErrText) ->
make_reply(forbidden, Lang, ErrText) ->
?ERRT_FORBIDDEN(Lang, ErrText).
-stj(String) -> jlib:string_to_jid(String).
+stj(String) -> jid:from_string(String).
+
+jts(String) -> jid:to_string(String).
-jts(String) -> jlib:jid_to_string(String).
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(limits) ->
+ fun (A) when is_list(A) -> A end;
+mod_opt_type(_) -> [access, host, limits].
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 7f9a81a0..fa6a961f 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -5,7 +5,7 @@
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,36 +25,51 @@
-module(mod_offline).
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
-author('alexey@process-one.net').
+
+-protocol({xep, 13, '1.2'}).
+-protocol({xep, 22, '1.4'}).
+-protocol({xep, 23, '1.3'}).
+-protocol({xep, 160, '1.0'}).
+-protocol({xep, 334, '0.2'}).
+
-define(GEN_SERVER, p1_server).
-behaviour(?GEN_SERVER).
-behaviour(gen_mod).
--export([count_offline_messages/2]).
-
-export([start/2,
- start_link/2,
+ start_link/2,
stop/1,
store_packet/3,
+ store_offline_msg/5,
resend_offline_messages/2,
pop_offline_messages/3,
get_sm_features/5,
+ get_sm_identity/5,
+ get_sm_items/5,
+ get_info/5,
+ handle_offline_query/3,
remove_expired_messages/1,
remove_old_messages/2,
remove_user/2,
- import/1,
- import/3,
- export/1,
+ import/1,
+ import/3,
+ export/1,
get_queue_length/2,
- get_offline_els/2,
+ count_offline_messages/2,
+ get_offline_els/2,
webadmin_page/3,
webadmin_user/4,
webadmin_user_parse_query/5]).
-%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+ handle_info/2, terminate/2, code_change/3,
+ mod_opt_type/1]).
+
+-deprecated({get_queue_length,2}).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -65,17 +80,9 @@
-include("ejabberd_web_admin.hrl").
--record(offline_msg,
- {us = {<<"">>, <<"">>} :: {binary(), binary()},
- timestamp = now() :: erlang:timestamp() | '_',
- expire = now() :: erlang:timestamp() | never | '_',
- from = #jid{} :: jid() | '_',
- to = #jid{} :: jid() | '_',
- packet = #xmlel{} :: xmlel() | '_'}).
+-include("mod_offline.hrl").
--record(state,
- {host = <<"">> :: binary(),
- access_max_offline_messages}).
+-include("ejabberd_sql_pt.hrl").
-define(PROCNAME, ejabberd_offline).
@@ -116,6 +123,8 @@ init([Host, Opts]) ->
update_table();
_ -> ok
end,
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ no_queue),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
@@ -128,13 +137,23 @@ init([Host, Opts]) ->
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, get_sm_features, 50),
+ ejabberd_hooks:add(disco_sm_identity, Host,
+ ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:add(disco_sm_items, Host,
+ ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
- ?MODULE, webadmin_user_parse_query, 50),
- AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, fun(A) -> A end, max_user_offline_messages),
+ ?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
+ ?MODULE, handle_offline_query, IQDisc),
+ AccessMaxOfflineMsgs =
+ gen_mod:get_opt(access_max_user_messages, Opts,
+ fun(A) when is_atom(A) -> A end,
+ max_user_offline_messages),
{ok,
#state{host = Host,
access_max_offline_messages = AccessMaxOfflineMsgs}}.
@@ -175,17 +194,24 @@ terminate(_Reason, State) ->
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
+store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) ->
+ DBType = gen_mod:db_type(Host, ?MODULE),
+ store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs, DBType).
store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
mnesia) ->
@@ -224,7 +250,7 @@ store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
M#offline_msg.timestamp,
<<"Offline Storage">>),
XML =
- ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
+ ejabberd_odbc:escape(fxml:element_to_binary(NewPacket)),
odbc_queries:add_spool_sql(Username, XML)
end,
Msgs),
@@ -248,10 +274,9 @@ store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
end, Msgs)
end.
-%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, {User, Server}, Host) ->
case acl:match_rule(
- Host, AccessRule, jlib:make_jid(User, Server, <<"">>)) of
+ Host, AccessRule, jid:make(User, Server, <<"">>)) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
@@ -274,27 +299,229 @@ get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
{result, I} -> I;
_ -> []
end,
- {result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
+ {result, Feats ++ [?NS_FEATURE_MSGOFFLINE, ?NS_FLEX_OFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
+get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ {result, [?NS_FLEX_OFFLINE]};
+
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
+get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ Identity = #xmlel{name = <<"identity">>,
+ attrs = [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"message-list">>}]},
+ [Identity];
+get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
+ #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Hdrs = read_message_headers(U, S),
+ BareJID = jid:to_string(jid:remove_resource(JID)),
+ Pid ! dont_ask_offline,
+ {result, lists:map(
+ fun({Node, From, _OfflineMsg}) ->
+ #xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, BareJID},
+ {<<"node">>, Node},
+ {<<"name">>, From}]}
+ end, Hdrs)};
+ none ->
+ {result, []}
+ end;
+get_sm_items(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_info(_Acc, #jid{luser = U, lserver = S, lresource = R},
+ #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) ->
+ N = jlib:integer_to_binary(count_offline_messages(U, S)),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline;
+ none ->
+ ok
+ end,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_FLEX_OFFLINE}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"number_of_messages">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata, N}]}]}]}];
+get_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+handle_offline_query(#jid{luser = U, lserver = S} = From,
+ #jid{luser = U, lserver = S} = _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
+ case Type of
+ get ->
+ case fxml:get_subtag(SubEl, <<"fetch">>) of
+ #xmlel{} ->
+ handle_offline_fetch(From);
+ false ->
+ handle_offline_items_view(From, SubEl)
+ end;
+ set ->
+ case fxml:get_subtag(SubEl, <<"purge">>) of
+ #xmlel{} ->
+ delete_all_msgs(U, S);
+ false ->
+ handle_offline_items_remove(From, SubEl)
+ end
+ end,
+ IQ#iq{type = result, sub_el = []};
+handle_offline_query(_From, _To, #iq{sub_el = SubEl} = IQ) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}.
+
+handle_offline_items_view(JID, #xmlel{children = Items}) ->
+ {U, S, R} = jid:tolower(JID),
+ lists:foreach(
+ fun(Node) ->
+ case fetch_msg_by_node(JID, Node) of
+ {ok, OfflineMsg} ->
+ case offline_msg_to_route(S, OfflineMsg) of
+ {route, From, To, El} ->
+ NewEl = set_offline_tag(El, Node),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! {route, From, To, NewEl};
+ none ->
+ ok
+ end;
+ error ->
+ ok
+ end;
+ error ->
+ ok
+ end
+ end, get_nodes_from_items(Items, <<"view">>)).
+
+handle_offline_items_remove(JID, #xmlel{children = Items}) ->
+ lists:foreach(
+ fun(Node) ->
+ remove_msg_by_node(JID, Node)
+ end, get_nodes_from_items(Items, <<"remove">>)).
+
+get_nodes_from_items(Items, Action) ->
+ lists:flatmap(
+ fun(#xmlel{name = <<"item">>, attrs = Attrs}) ->
+ case fxml:get_attr_s(<<"action">>, Attrs) of
+ Action ->
+ case fxml:get_attr_s(<<"node">>, Attrs) of
+ <<"">> ->
+ [];
+ TS ->
+ [TS]
+ end;
+ _ ->
+ []
+ end;
+ (_) ->
+ []
+ end, Items).
+
+set_offline_tag(#xmlel{children = Els} = El, Node) ->
+ OfflineEl = #xmlel{name = <<"offline">>,
+ attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}],
+ children = [#xmlel{name = <<"item">>,
+ attrs = [{<<"node">>, Node}]}]},
+ El#xmlel{children = [OfflineEl|Els]}.
+
+handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ none ->
+ ok;
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline,
+ lists:foreach(
+ fun({Node, _, Msg}) ->
+ case offline_msg_to_route(S, Msg) of
+ {route, From, To, El} ->
+ NewEl = set_offline_tag(El, Node),
+ Pid ! {route, From, To, NewEl};
+ error ->
+ ok
+ end
+ end, read_message_headers(U, S))
+ end.
+
+fetch_msg_by_node(To, <<Seq:20/binary, "+", From_s/binary>>) ->
+ case jid:from_string(From_s) of
+ From = #jid{} ->
+ case gen_mod:db_type(To#jid.lserver, ?MODULE) of
+ odbc ->
+ read_message(From, To, Seq, odbc);
+ DBType ->
+ case binary_to_timestamp(Seq) of
+ undefined -> ok;
+ TS -> read_message(From, To, TS, DBType)
+ end
+ end;
+ error ->
+ ok
+ end.
+
+remove_msg_by_node(To, <<Seq:20/binary, "+", From_s/binary>>) ->
+ case jid:from_string(From_s) of
+ From = #jid{} ->
+ case gen_mod:db_type(To#jid.lserver, ?MODULE) of
+ odbc ->
+ remove_message(From, To, Seq, odbc);
+ DBType ->
+ case binary_to_timestamp(Seq) of
+ undefined -> ok;
+ TS -> remove_message(From, To, TS, DBType)
+ end
+ end;
+ error ->
+ ok
+ end.
+
need_to_store(LServer, Packet) ->
- Type = xml:get_tag_attr_s(<<"type">>, Packet),
+ Type = fxml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
- case gen_mod:get_module_opt(
- LServer, ?MODULE, store_empty_body,
- fun(V) when is_boolean(V) -> V end,
- true) of
+ case has_offline_tag(Packet) of
false ->
- xml:get_subtag(Packet, <<"body">>) /= false;
+ case check_store_hint(Packet) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case gen_mod:get_module_opt(
+ LServer, ?MODULE, store_empty_body,
+ fun(V) when is_boolean(V) -> V;
+ (unless_chat_state) -> unless_chat_state
+ end,
+ unless_chat_state) of
+ false ->
+ fxml:get_subtag(Packet, <<"body">>) /= false;
+ unless_chat_state ->
+ not jlib:is_standalone_chat_state(Packet);
+ true ->
+ true
+ end
+ end;
true ->
- true
+ false
end;
true ->
false
@@ -303,52 +530,59 @@ need_to_store(LServer, Packet) ->
store_packet(From, To, Packet) ->
case need_to_store(To#jid.lserver, Packet) of
true ->
- case has_no_storage_hint(Packet) of
- false ->
- case check_event(From, To, Packet) of
- true ->
- #jid{luser = LUser, lserver = LServer} = To,
- TimeStamp = now(),
- #xmlel{children = Els} = Packet,
- Expire = find_x_expire(TimeStamp, Els),
- gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
- #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp, expire = Expire,
- from = From, to = To, packet = Packet},
- stop;
- _ -> ok
- end;
- _ -> ok
- end;
- false -> ok
+ case check_event(From, To, Packet) of
+ true ->
+ #jid{luser = LUser, lserver = LServer} = To,
+ TimeStamp = p1_time_compat:timestamp(),
+ #xmlel{children = Els} = Packet,
+ Expire = find_x_expire(TimeStamp, Els),
+ gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
+ #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp, expire = Expire,
+ from = From, to = To, packet = Packet},
+ stop;
+ _ -> ok
+ end;
+ false -> ok
end.
-has_no_storage_hint(Packet) ->
- case xml:get_subtag(Packet, <<"no-store">>) of
- #xmlel{attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_HINTS ->
- true;
- _ ->
- false
- end;
- _ ->
- false
+check_store_hint(Packet) ->
+ case has_store_hint(Packet) of
+ true ->
+ store;
+ false ->
+ case has_no_store_hint(Packet) of
+ true ->
+ no_store;
+ false ->
+ none
+ end
end.
-%% Check if the packet has any content about XEP-0022 or XEP-0085
+has_store_hint(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
+
+has_no_store_hint(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false
+ orelse
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false.
+
+has_offline_tag(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"offline">>, ?NS_FLEX_OFFLINE) =/= false.
+
+%% Check if the packet has any content about XEP-0022
check_event(From, To, Packet) ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
case find_x_event(Els) of
false -> true;
El ->
- case xml:get_subtag(El, <<"id">>) of
+ case fxml:get_subtag(El, <<"id">>) of
false ->
- case xml:get_subtag(El, <<"offline">>) of
+ case fxml:get_subtag(El, <<"offline">>) of
false -> true;
_ ->
- ID = case xml:get_tag_attr_s(<<"id">>, Packet) of
+ ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of
<<"">> ->
#xmlel{name = <<"id">>, attrs = [],
children = []};
@@ -380,12 +614,12 @@ check_event(From, To, Packet) ->
end
end.
-%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
+%% Check if the packet has subelements about XEP-0022
find_x_event([]) -> false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EVENT -> El;
_ -> find_x_event(Els)
end.
@@ -394,9 +628,9 @@ find_x_expire(_, []) -> never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EXPIRE ->
- Val = xml:get_tag_attr_s(<<"seconds">>, El),
+ Val = fxml:get_tag_attr_s(<<"seconds">>, El),
case catch jlib:binary_to_integer(Val) of
{'EXIT', _} -> never;
Int when Int > 0 ->
@@ -411,8 +645,8 @@ find_x_expire(TimeStamp, [El | Els]) ->
end.
resend_offline_messages(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
F = fun () ->
Rs = mnesia:wread({offline_msg, US}),
@@ -434,8 +668,8 @@ resend_offline_messages(User, Server) ->
end.
pop_offline_messages(Ls, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
pop_offline_messages(Ls, LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -448,7 +682,7 @@ pop_offline_messages(Ls, LUser, LServer, mnesia) ->
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
- TS = now(),
+ TS = p1_time_compat:timestamp(),
Ls ++
lists:map(fun (R) ->
offline_msg_to_route(LServer, R)
@@ -463,14 +697,11 @@ pop_offline_messages(Ls, LUser, LServer, mnesia) ->
_ -> Ls
end;
pop_offline_messages(Ls, LUser, LServer, odbc) ->
- EUser = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_and_del_spool_msg_t(LServer,
- EUser)
- of
- {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} ->
+ case odbc_queries:get_and_del_spool_msg_t(LServer, LUser) of
+ {atomic, {selected, Rs}} ->
Ls ++
- lists:flatmap(fun ([_, XML]) ->
- case xml_stream:parse_element(XML) of
+ lists:flatmap(fun ({_, XML}) ->
+ case fxml_stream:parse_element(XML) of
{error, _Reason} ->
[];
El ->
@@ -494,7 +725,7 @@ pop_offline_messages(Ls, LUser, LServer, riak) ->
fun(#offline_msg{timestamp = T}) ->
ok = ejabberd_riak:delete(offline_msg, T)
end, Rs),
- TS = now(),
+ TS = p1_time_compat:timestamp(),
Ls ++ lists:map(
fun (R) ->
offline_msg_to_route(LServer, R)
@@ -515,12 +746,12 @@ pop_offline_messages(Ls, LUser, LServer, riak) ->
end.
remove_expired_messages(Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
remove_expired_messages(LServer,
gen_mod:db_type(LServer, ?MODULE)).
remove_expired_messages(_LServer, mnesia) ->
- TimeStamp = now(),
+ TimeStamp = p1_time_compat:timestamp(),
F = fun () ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(fun (Rec, _Acc) ->
@@ -540,13 +771,12 @@ remove_expired_messages(_LServer, odbc) -> {atomic, ok};
remove_expired_messages(_LServer, riak) -> {atomic, ok}.
remove_old_messages(Days, Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
remove_old_messages(Days, LServer,
gen_mod:db_type(LServer, ?MODULE)).
remove_old_messages(Days, _LServer, mnesia) ->
- {MegaSecs, Secs, _MicroSecs} = now(),
- S = MegaSecs * 1000000 + Secs - 60 * 60 * 24 * Days,
+ S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
TimeStamp = {MegaSecs1, Secs1, 0},
@@ -579,8 +809,8 @@ remove_old_messages(_Days, _LServer, riak) ->
{atomic, ok}.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -589,8 +819,7 @@ remove_user(LUser, LServer, mnesia) ->
F = fun () -> mnesia:delete({offline_msg, US}) end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username);
+ odbc_queries:del_spool_msg(LServer, LUser);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(offline_msg,
<<"us">>, {LUser, LServer})}.
@@ -619,7 +848,7 @@ update_table() ->
iolist_to_binary(S)},
from = jid_to_binary(From),
to = jid_to_binary(To),
- packet = xml:to_xmlel(El)}
+ packet = fxml:to_xmlel(El)}
end);
_ ->
?INFO_MSG("Recreating offline_msg table", []),
@@ -634,7 +863,7 @@ discard_warn_sender(Msgs) ->
packet = Packet}) ->
ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>,
- Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Err = jlib:make_error_reply(Packet,
?ERRT_RESOURCE_CONSTRAINT(Lang,
ErrText)),
@@ -661,14 +890,14 @@ get_offline_els(LUser, LServer, DBType)
jlib:replace_from_to(From, To, Packet)
end, Msgs);
get_offline_els(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>]) of
- {selected, [<<"xml">>], Rs} ->
+ case catch ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(xml)s from spool where "
+ "username=%(LUser)s order by seq")) of
+ {selected, Rs} ->
lists:flatmap(
- fun([XML]) ->
- case xml_stream:parse_element(XML) of
+ fun({XML}) ->
+ case fxml_stream:parse_element(XML) of
#xmlel{} = El ->
case offline_msg_to_route(LServer, El) of
{route, _, _, NewEl} ->
@@ -689,14 +918,131 @@ offline_msg_to_route(LServer, #offline_msg{} = R) ->
jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp,
<<"Offline Storage">>)};
offline_msg_to_route(_LServer, #xmlel{} = El) ->
- To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, El)),
- From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>, El)),
+ To = jid:from_string(fxml:get_tag_attr_s(<<"to">>, El)),
+ From = jid:from_string(fxml:get_tag_attr_s(<<"from">>, El)),
if (To /= error) and (From /= error) ->
{route, From, To, El};
true ->
error
end.
+binary_to_timestamp(TS) ->
+ case catch jlib:binary_to_integer(TS) of
+ Int when is_integer(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec};
+ _ ->
+ undefined
+ end.
+
+timestamp_to_binary({MS, S, US}) ->
+ format_timestamp(integer_to_list((MS * 1000000 + S) * 1000000 + US)).
+
+format_timestamp(TS) ->
+ iolist_to_binary(io_lib:format("~20..0s", [TS])).
+
+offline_msg_to_header(#offline_msg{from = From, timestamp = Int} = Msg) ->
+ TS = timestamp_to_binary(Int),
+ From_s = jid:to_string(From),
+ {<<TS/binary, "+", From_s/binary>>, From_s, Msg}.
+
+read_message_headers(LUser, LServer) ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ read_message_headers(LUser, LServer, DBType).
+
+read_message_headers(LUser, LServer, mnesia) ->
+ Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}),
+ Hdrs = lists:map(fun offline_msg_to_header/1, Msgs),
+ lists:keysort(1, Hdrs);
+read_message_headers(LUser, LServer, riak) ->
+ case ejabberd_riak:get_by_index(
+ offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ Hdrs = lists:map(fun offline_msg_to_header/1, Rs),
+ lists:keysort(1, Hdrs);
+ _Err ->
+ []
+ end;
+read_message_headers(LUser, LServer, odbc) ->
+ Username = ejabberd_odbc:escape(LUser),
+ case catch ejabberd_odbc:sql_query(
+ LServer, [<<"select xml, seq from spool where username ='">>,
+ Username, <<"' order by seq;">>]) of
+ {selected, [<<"xml">>, <<"seq">>], Rows} ->
+ Hdrs = lists:flatmap(
+ fun([XML, Seq]) ->
+ try
+ #xmlel{} = El = fxml_stream:parse_element(XML),
+ From = fxml:get_tag_attr_s(<<"from">>, El),
+ #jid{} = jid:from_string(From),
+ TS = format_timestamp(Seq),
+ [{<<TS/binary, "+", From/binary>>, From, El}]
+ catch _:_ -> []
+ end
+ end, Rows),
+ lists:keysort(1, Hdrs);
+ _Err ->
+ []
+ end.
+
+read_message(_From, To, TS, mnesia) ->
+ {U, S, _} = jid:tolower(To),
+ case mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}) of
+ [Msg|_] ->
+ {ok, Msg};
+ _ ->
+ error
+ end;
+read_message(_From, _To, TS, riak) ->
+ case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of
+ {ok, Msg} ->
+ {ok, Msg};
+ _ ->
+ error
+ end;
+read_message(_From, To, Seq, odbc) ->
+ {LUser, LServer, _} = jid:tolower(To),
+ Username = ejabberd_odbc:escape(LUser),
+ SSeq = ejabberd_odbc:escape(Seq),
+ case ejabberd_odbc:sql_query(
+ LServer,
+ [<<"select xml from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]) of
+ {selected, [<<"xml">>], [[RawXML]|_]} ->
+ case fxml_stream:parse_element(RawXML) of
+ #xmlel{} = El -> {ok, El};
+ {error, _} -> error
+ end;
+ _ ->
+ error
+ end.
+
+remove_message(_From, To, TS, mnesia) ->
+ {U, S, _} = jid:tolower(To),
+ Msgs = mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}),
+ lists:foreach(
+ fun(Msg) ->
+ mnesia:dirty_delete_object(Msg)
+ end, Msgs);
+remove_message(_From, _To, TS, riak) ->
+ ejabberd_riak:delete(offline_msg, TS),
+ ok;
+remove_message(_From, To, Seq, odbc) ->
+ {LUser, LServer, _} = jid:tolower(To),
+ Username = ejabberd_odbc:escape(LUser),
+ SSeq = ejabberd_odbc:escape(Seq),
+ ejabberd_odbc:sql_query(
+ LServer,
+ [<<"delete from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]),
+ ok.
+
read_all_msgs(LUser, LServer, mnesia) ->
US = {LUser, LServer},
lists:keysort(#offline_msg.timestamp,
@@ -711,20 +1057,20 @@ read_all_msgs(LUser, LServer, riak) ->
[]
end;
read_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>])
- of
- {selected, [<<"xml">>], Rs} ->
- lists:flatmap(fun ([XML]) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
- end
- end,
- Rs);
- _ -> []
+ case catch ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(xml)s from spool where "
+ "username=%(LUser)s order by seq")) of
+ {selected, Rs} ->
+ lists:flatmap(
+ fun({XML}) ->
+ case fxml_stream:parse_element(XML) of
+ {error, _Reason} -> [];
+ El -> [El]
+ end
+ end,
+ Rs);
+ _ -> []
end.
format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
@@ -742,8 +1088,8 @@ format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
[Year, Month, Day,
Hour, Minute,
Second])),
- SFrom = jlib:jid_to_string(From),
- STo = jlib:jid_to_string(To),
+ SFrom = jid:to_string(From),
+ STo = jid:to_string(To),
Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
Packet = #xmlel{name = Name, attrs = Attrs2,
children = Els},
@@ -772,8 +1118,8 @@ format_user_queue(Msgs, odbc) ->
Msgs).
user_queue(User, Server, Query, Lang) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
DBType = gen_mod:db_type(LServer, ?MODULE),
Res = user_queue_parse_query(LUser, LServer, Query,
@@ -866,7 +1212,7 @@ user_queue_parse_query(LUser, LServer, Query, odbc) ->
of
{selected, [<<"xml">>, <<"seq">>], Rs} ->
lists:flatmap(fun ([XML, Seq]) ->
- case xml_stream:parse_element(XML)
+ case fxml_stream:parse_element(XML)
of
{error, _Reason} -> [];
El -> [{El, Seq}]
@@ -904,33 +1250,10 @@ user_queue_parse_query(LUser, LServer, Query, odbc) ->
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, <<"">>}).
+ jid:to_string({User, Server, <<"">>}).
get_queue_length(LUser, LServer) ->
- get_queue_length(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_queue_length(LUser, LServer, mnesia) ->
- length(mnesia:dirty_read({offline_msg,
- {LUser, LServer}}));
-get_queue_length(LUser, LServer, riak) ->
- case ejabberd_riak:count_by_index(offline_msg,
- <<"us">>, {LUser, LServer}) of
- {ok, N} ->
- N;
- _ ->
- 0
- end;
-get_queue_length(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from spool where username='">>,
- Username, <<"';">>])
- of
- {selected, [_], [[SCount]]} ->
- jlib:binary_to_integer(SCount);
- _ -> 0
- end.
+ count_offline_messages(LUser, LServer).
get_messages_subset(User, Host, MsgsAll, DBType) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
@@ -955,8 +1278,8 @@ get_messages_subset2(Max, Length, MsgsAll, DBType)
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
Msgs2),
- NoJID = jlib:make_jid(<<"...">>, <<"...">>, <<"">>),
- IntermediateMsg = #offline_msg{timestamp = now(),
+ NoJID = jid:make(<<"...">>, <<"...">>, <<"">>),
+ IntermediateMsg = #offline_msg{timestamp = p1_time_compat:timestamp(),
from = NoJID, to = NoJID,
packet =
#xmlel{name = <<"...">>, attrs = [],
@@ -972,8 +1295,8 @@ get_messages_subset2(Max, Length, MsgsAll, odbc) ->
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
- QueueLen = get_queue_length(jlib:nodeprep(User),
- jlib:nameprep(Server)),
+ QueueLen = count_offline_messages(jid:nodeprep(User),
+ jid:nameprep(Server)),
FQueueLen = [?AC(<<"queue/">>,
(iolist_to_binary(integer_to_list(QueueLen))))],
Acc ++
@@ -984,8 +1307,8 @@ webadmin_user(Acc, User, Server, Lang) ->
<<"Remove All Offline Messages">>)].
delete_all_msgs(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
delete_all_msgs(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -1003,8 +1326,7 @@ delete_all_msgs(LUser, LServer, riak) ->
<<"us">>, {LUser, LServer}),
{atomic, Res};
delete_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username),
+ odbc_queries:del_spool_msg(LServer, LUser),
{atomic, ok}.
webadmin_user_parse_query(_, <<"removealloffline">>,
@@ -1025,8 +1347,8 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
%% Returns as integer the number of offline messages for a given user
count_offline_messages(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
count_offline_messages(LUser, LServer, DBType).
@@ -1040,15 +1362,13 @@ count_offline_messages(LUser, LServer, mnesia) ->
_ -> 0
end;
count_offline_messages(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:count_records_where(LServer,
- <<"spool">>,
- <<"where username='",
- Username/binary, "'">>)
- of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
+ case catch ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from spool "
+ "where username=%(LUser)s")) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
end;
count_offline_messages(LUser, LServer, riak) ->
case ejabberd_riak:count_by_index(
@@ -1098,7 +1418,7 @@ export(_Server) ->
Packet1 = jlib:replace_from_to(From, To, Packet),
Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
<<"Offline Storage">>),
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet2)),
+ XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet2)),
[[<<"delete from spool where username='">>, Username, <<"';">>],
[<<"insert into spool(username, xml) values ('">>,
Username, <<"', '">>, XML, <<"');">>]];
@@ -1109,18 +1429,18 @@ export(_Server) ->
import(LServer) ->
[{<<"select username, xml from spool;">>,
fun([LUser, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
- From = #jid{} = jlib:string_to_jid(
- xml:get_attr_s(<<"from">>, El#xmlel.attrs)),
- To = #jid{} = jlib:string_to_jid(
- xml:get_attr_s(<<"to">>, El#xmlel.attrs)),
- Stamp = xml:get_path_s(El, [{elem, <<"delay">>},
+ El = #xmlel{} = fxml_stream:parse_element(XML),
+ From = #jid{} = jid:from_string(
+ fxml:get_attr_s(<<"from">>, El#xmlel.attrs)),
+ To = #jid{} = jid:from_string(
+ fxml:get_attr_s(<<"to">>, El#xmlel.attrs)),
+ Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
{attr, <<"stamp">>}]),
TS = case jlib:datetime_string_to_timestamp(Stamp) of
{_, _, _} = Now ->
Now;
undefined ->
- now()
+ p1_time_compat:timestamp()
end,
Expire = find_x_expire(TS, El#xmlel.children),
#offline_msg{us = {LUser, LServer},
@@ -1135,3 +1455,13 @@ import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) ->
[{i, TS}, {'2i', [{<<"us">>, US}]}]);
import(_, _, _) ->
pass.
+
+mod_opt_type(access_max_user_messages) ->
+ fun (A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(store_empty_body) ->
+ fun (V) when is_boolean(V) -> V;
+ (unless_chat_state) -> unless_chat_state
+ end;
+mod_opt_type(_) ->
+ [access_max_user_messages, db_type, store_empty_body].
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index f493dccb..e8a977de 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Jul 2009 by Brian Cully <bjc@kublai.com>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,8 @@
-author('bjc@kublai.com').
+-protocol({xep, 199, '2.0'}).
+
-behavior(gen_mod).
-behavior(gen_server).
@@ -54,14 +56,14 @@
-export([init/1, terminate/2, handle_call/3,
handle_cast/2, handle_info/2, code_change/3]).
-%% Hook callbacks
-export([iq_ping/3, user_online/3, user_offline/3,
- user_send/3]).
+ user_send/4, mod_opt_type/1]).
-record(state,
{host = <<"">>,
send_pings = ?DEFAULT_SEND_PINGS :: boolean(),
ping_interval = ?DEFAULT_PING_INTERVAL :: non_neg_integer(),
+ ping_ack_timeout = undefined :: non_neg_integer(),
timeout_action = none :: none | kill,
timers = (?DICT):new() :: ?TDICT}).
@@ -105,6 +107,9 @@ init([Host, Opts]) ->
PingInterval = gen_mod:get_opt(ping_interval, Opts,
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_PING_INTERVAL),
+ PingAckTimeout = gen_mod:get_opt(ping_ack_timeout, Opts,
+ fun(I) when is_integer(I), I>0 -> I * 1000 end,
+ undefined),
TimeoutAction = gen_mod:get_opt(timeout_action, Opts,
fun(none) -> none;
(kill) -> kill
@@ -130,6 +135,7 @@ init([Host, Opts]) ->
#state{host = Host, send_pings = SendPings,
ping_interval = PingInterval,
timeout_action = TimeoutAction,
+ ping_ack_timeout = PingAckTimeout,
timers = (?DICT):new()}}.
terminate(_Reason, #state{host = Host}) ->
@@ -168,7 +174,7 @@ handle_cast({iq_pong, JID, timeout}, State) ->
JID,
case ejabberd_sm:get_session_pid(User, Server, Resource)
of
- Pid when is_pid(Pid) -> ejabberd_c2s:stop(Pid);
+ Pid when is_pid(Pid) -> ejabberd_c2s:close(Pid);
_ -> ok
end;
_ -> ok
@@ -185,8 +191,8 @@ handle_info({timeout, _TRef, {ping, JID}}, State) ->
F = fun (Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response})
end,
- From = jlib:make_jid(<<"">>, State#state.host, <<"">>),
- ejabberd_local:route_iq(From, JID, IQ, F),
+ From = jid:make(<<"">>, State#state.host, <<"">>),
+ ejabberd_local:route_iq(From, JID, IQ, F, State#state.ping_ack_timeout),
Timers = add_timer(JID, State#state.ping_interval,
State#state.timers),
{noreply, State#state{timers = Timers}};
@@ -213,14 +219,15 @@ user_online(_SID, JID, _Info) ->
user_offline(_SID, JID, _Info) ->
stop_ping(JID#jid.lserver, JID).
-user_send(JID, _From, _Packet) ->
- start_ping(JID#jid.lserver, JID).
+user_send(Packet, _C2SState, JID, _From) ->
+ start_ping(JID#jid.lserver, JID),
+ Packet.
%%====================================================================
%% Internal functions
%%====================================================================
add_timer(JID, Interval, Timers) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
NewTimers = case (?DICT):find(LJID, Timers) of
{ok, OldTRef} ->
cancel_timer(OldTRef), (?DICT):erase(LJID, Timers);
@@ -231,7 +238,7 @@ add_timer(JID, Interval, Timers) ->
(?DICT):store(LJID, TRef, NewTimers).
del_timer(JID, Timers) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
case (?DICT):find(LJID, Timers) of
{ok, TRef} ->
cancel_timer(TRef), (?DICT):erase(LJID, Timers);
@@ -244,3 +251,17 @@ cancel_timer(TRef) ->
receive {timeout, TRef, _} -> ok after 0 -> ok end;
_ -> ok
end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(ping_interval) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ping_ack_timeout) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(send_pings) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(timeout_action) ->
+ fun (none) -> none;
+ (kill) -> kill
+ end;
+mod_opt_type(_) ->
+ [iqdisc, ping_interval, ping_ack_timeout, send_pings, timeout_action].
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
index e904ab95..1118b7bb 100644
--- a/src/mod_pres_counter.erl
+++ b/src/mod_pres_counter.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Sep 2010 by Ahmed Omar
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,7 +27,8 @@
-behavior(gen_mod).
--export([start/2, stop/1, check_packet/6]).
+-export([start/2, stop/1, check_packet/6,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -51,7 +52,7 @@ check_packet(_, _User, Server, _PrivacyList,
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
case Name of
<<"presence">> ->
- IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
+ IsSubscription = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> -> true;
<<"subscribed">> -> true;
@@ -77,8 +78,7 @@ update(Server, JID, Dir) ->
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval,
fun(I) when is_integer(I), I>0 -> I end,
60),
- {MegaSecs, Secs, _MicroSecs} = now(),
- TimeStamp = MegaSecs * 1000000 + Secs,
+ TimeStamp = p1_time_compat:system_time(seconds),
case read(Dir) of
undefined ->
write(Dir,
@@ -98,14 +98,14 @@ update(Server, JID, Dir) ->
in ->
?WARNING_MSG("User ~s is being flooded, ignoring received "
"presence subscriptions",
- [jlib:jid_to_string(JID)]);
+ [jid:to_string(JID)]);
out ->
IP = ejabberd_sm:get_user_ip(JID#jid.luser,
JID#jid.lserver,
JID#jid.lresource),
?WARNING_MSG("Flooder detected: ~s, on IP: ~s ignoring "
"sent presence subscriptions~n",
- [jlib:jid_to_string(JID),
+ [jid:to_string(JID),
jlib:ip_to_list(IP)])
end,
{stop, deny};
@@ -119,3 +119,9 @@ update(Server, JID, Dir) ->
read(K) -> get({pres_counter, K}).
write(K, V) -> put({pres_counter, K}, V).
+
+mod_opt_type(count) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(interval) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(_) -> [count, interval].
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index fd3f6024..193befe8 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -5,7 +5,7 @@
%%% Created : 21 Jul 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,23 +27,25 @@
-author('alexey@process-one.net').
+-protocol({xep, 16, '1.6'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, process_iq/3, export/1, import/1,
process_iq_set/4, process_iq_get/5, get_user_list/3,
check_packet/6, remove_user/2, item_to_raw/1,
raw_to_item/1, is_list_needdb/1, updated_list/3,
- item_to_xml/1, get_user_lists/2, import/3]).
+ item_to_xml/1, get_user_lists/2, import/3,
+ set_privacy_list/1]).
-%% For mod_blocking
-export([sql_add_privacy_list/2,
sql_get_default_privacy_list/2,
sql_get_default_privacy_list_t/1,
sql_get_privacy_list_data/3,
sql_get_privacy_list_data_by_id_t/1,
sql_get_privacy_list_id_t/2,
- sql_set_default_privacy_list/2,
- sql_set_privacy_list/2, privacy_schema/0]).
+ sql_set_default_privacy_list/2, sql_set_privacy_list/2,
+ privacy_schema/0, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -107,12 +109,12 @@ process_iq_get(_, From, _To, #iq{sub_el = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[] -> process_lists_get(LUser, LServer, Active);
[#xmlel{name = Name, attrs = Attrs}] ->
case Name of
<<"list">> ->
- ListName = xml:get_attr(<<"name">>, Attrs),
+ ListName = fxml:get_attr(<<"name">>, Attrs),
process_list_get(LUser, LServer, ListName);
_ -> {error, ?ERR_BAD_REQUEST}
end;
@@ -179,16 +181,14 @@ process_lists_get(LUser, LServer, _Active, riak) ->
error
end;
process_lists_get(LUser, LServer, _Active, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser,
- LServer)
- of
- {selected, [<<"name">>], []} -> none;
- {selected, [<<"name">>], [[DefName]]} -> DefName;
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} -> none;
+ {selected, [{DefName}]} -> DefName;
_ -> none
end,
case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
- LItems = lists:map(fun ([N]) ->
+ {selected, Names} ->
+ LItems = lists:map(fun ({N}) ->
#xmlel{name = <<"list">>,
attrs = [{<<"name">>, N}],
children = []}
@@ -240,17 +240,11 @@ process_list_get(LUser, LServer, Name, riak) ->
error
end;
process_list_get(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> not_found;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> not_found;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
lists:flatmap(fun raw_to_item/1, RItems);
_ -> error
end;
@@ -321,7 +315,7 @@ type_to_list(Type) ->
value_to_list(Type, Val) ->
case Type of
- jid -> jlib:jid_to_string(Val);
+ jid -> jid:to_string(Val);
group -> Val;
subscription ->
case Val of
@@ -341,14 +335,14 @@ list_to_action(S) ->
process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = Name, attrs = Attrs,
children = SubEls}] ->
- ListName = xml:get_attr(<<"name">>, Attrs),
+ ListName = fxml:get_attr(<<"name">>, Attrs),
case Name of
<<"list">> ->
process_list_set(LUser, LServer, ListName,
- xml:remove_cdata(SubEls));
+ fxml:remove_cdata(SubEls));
<<"active">> ->
process_active_set(LUser, LServer, ListName);
<<"default">> ->
@@ -403,9 +397,9 @@ process_default_set(LUser, LServer, {value, Name},
odbc) ->
F = fun () ->
case sql_get_privacy_list_names_t(LUser) of
- {selected, [<<"name">>], []} -> not_found;
- {selected, [<<"name">>], Names} ->
- case lists:member([Name], Names) of
+ {selected, []} -> not_found;
+ {selected, Names} ->
+ case lists:member({Name}, Names) of
true -> sql_set_default_privacy_list(LUser, Name), ok;
false -> not_found
end
@@ -471,17 +465,11 @@ process_active_set(LUser, LServer, Name, riak) ->
error
end;
process_active_set(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> error;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> error;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
lists:flatmap(fun raw_to_item/1, RItems);
_ -> error
end;
@@ -518,9 +506,9 @@ remove_privacy_list(LUser, LServer, Name, riak) ->
remove_privacy_list(LUser, LServer, Name, odbc) ->
F = fun () ->
case sql_get_default_privacy_list_t(LUser) of
- {selected, [<<"name">>], []} ->
+ {selected, []} ->
sql_remove_privacy_list(LUser, Name), ok;
- {selected, [<<"name">>], [[Default]]} ->
+ {selected, [{Default}]} ->
if Name == Default -> conflict;
true -> sql_remove_privacy_list(LUser, Name), ok
end
@@ -528,6 +516,35 @@ remove_privacy_list(LUser, LServer, Name, odbc) ->
end,
odbc_queries:sql_transaction(LServer, F).
+set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ set_privacy_list(Privacy, DBType).
+
+set_privacy_list(Privacy, mnesia) ->
+ mnesia:dirty_write(Privacy);
+set_privacy_list(Privacy, riak) ->
+ ejabberd_riak:put(Privacy, privacy_schema());
+set_privacy_list(#privacy{us = {LUser, LServer},
+ default = Default,
+ lists = Lists}, odbc) ->
+ F = fun() ->
+ lists:foreach(
+ fun({Name, List}) ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [<<"id">>], [[I]]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ sql_set_privacy_list(I, RItems),
+ if is_binary(Default) ->
+ sql_set_default_privacy_list(LUser, Default),
+ ok;
+ true ->
+ ok
+ end
+ end, Lists)
+ end,
+ odbc_queries:sql_transaction(LServer, F).
+
set_privacy_list(LUser, LServer, Name, List, mnesia) ->
F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of
@@ -559,12 +576,12 @@ set_privacy_list(LUser, LServer, Name, List, odbc) ->
RItems = lists:map(fun item_to_raw/1, List),
F = fun () ->
ID = case sql_get_privacy_list_id_t(LUser, Name) of
- {selected, [<<"id">>], []} ->
+ {selected, []} ->
sql_add_privacy_list(LUser, Name),
- {selected, [<<"id">>], [[I]]} =
+ {selected, [{I}]} =
sql_get_privacy_list_id_t(LUser, Name),
I;
- {selected, [<<"id">>], [[I]]} -> I
+ {selected, [{I}]} -> I
end,
sql_set_privacy_list(ID, RItems),
ok
@@ -580,14 +597,13 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
of
{atomic, conflict} -> {error, ?ERR_CONFLICT};
{atomic, ok} ->
- ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>),
- jlib:make_jid(LUser, LServer, <<"">>),
- {broadcast,
- {privacy_list,
- #userlist{name = Name,
- list = []},
- Name}}),
+ jid:make(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = []},
+ Name}}),
{result, []};
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end;
@@ -597,15 +613,14 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
of
{atomic, ok} ->
NeedDb = is_list_needdb(List),
- ejabberd_sm:route(jlib:make_jid(LUser, LServer,
+ ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>),
- jlib:make_jid(LUser, LServer, <<"">>),
- {broadcast,
- {privacy_list,
- #userlist{name = Name,
- list = List,
- needdb = NeedDb},
- Name}}),
+ jid:make(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = List,
+ needdb = NeedDb},
+ Name}}),
{result, []};
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end
@@ -622,10 +637,10 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
children = SubEls}
| Els],
Res) ->
- Type = xml:get_attr(<<"type">>, Attrs),
- Value = xml:get_attr(<<"value">>, Attrs),
- SAction = xml:get_attr(<<"action">>, Attrs),
- SOrder = xml:get_attr(<<"order">>, Attrs),
+ Type = fxml:get_attr(<<"type">>, Attrs),
+ Value = fxml:get_attr(<<"value">>, Attrs),
+ SAction = fxml:get_attr(<<"action">>, Attrs),
+ SOrder = fxml:get_attr(<<"order">>, Attrs),
Action = case catch list_to_action(element(2, SAction))
of
{'EXIT', _} -> false;
@@ -646,11 +661,11 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
{{value, T}, {value, V}} ->
case T of
<<"jid">> ->
- case jlib:string_to_jid(V) of
+ case jid:from_string(V) of
error -> false;
JID ->
I1#listitem{type = jid,
- value = jlib:jid_tolower(JID)}
+ value = jid:tolower(JID)}
end;
<<"group">> -> I1#listitem{type = group, value = V};
<<"subscription">> ->
@@ -675,7 +690,7 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
case I2 of
false -> false;
_ ->
- case parse_matches(I2, xml:remove_cdata(SubEls)) of
+ case parse_matches(I2, fxml:remove_cdata(SubEls)) of
false -> false;
I3 -> parse_items(Els, [I3 | Res])
end
@@ -716,8 +731,8 @@ is_list_needdb(Items) ->
Items).
get_user_list(Acc, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
{Default, Items} = get_user_list(Acc, LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)),
NeedDb = is_list_needdb(Items),
@@ -756,16 +771,11 @@ get_user_list(_, LUser, LServer, riak) ->
get_user_list(_, LUser, LServer, odbc) ->
case catch sql_get_default_privacy_list(LUser, LServer)
of
- {selected, [<<"name">>], []} -> {none, []};
- {selected, [<<"name">>], [[Default]]} ->
+ {selected, []} -> {none, []};
+ {selected, [{Default}]} ->
case catch sql_get_privacy_list_data(LUser, LServer,
- Default)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
+ Default) of
+ {selected, RItems} ->
{Default, lists:flatmap(fun raw_to_item/1, RItems)};
_ -> {none, []}
end;
@@ -773,8 +783,8 @@ get_user_list(_, LUser, LServer, odbc) ->
end.
get_user_lists(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
get_user_lists(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
get_user_lists(LUser, LServer, mnesia) ->
@@ -793,26 +803,21 @@ get_user_lists(LUser, LServer, riak) ->
end;
get_user_lists(LUser, LServer, odbc) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
- {selected, [<<"name">>], []} ->
+ {selected, []} ->
none;
- {selected, [<<"name">>], [[DefName]]} ->
+ {selected, [{DefName}]} ->
DefName;
_ ->
none
end,
case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
+ {selected, Names} ->
Lists =
lists:flatmap(
- fun([Name]) ->
+ fun({Name}) ->
case catch sql_get_privacy_list_data(
LUser, LServer, Name) of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>,
- <<"ord">>, <<"match_all">>, <<"match_iq">>,
- <<"match_message">>, <<"match_presence_in">>,
- <<"match_presence_out">>],
- RItems} ->
+ {selected, RItems} ->
[{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
_ ->
[]
@@ -853,7 +858,7 @@ check_packet(_, User, Server,
<<"message">> -> message;
<<"iq">> -> iq;
<<"presence">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
%% notification
<<"">> -> presence;
<<"unavailable">> -> presence;
@@ -870,13 +875,13 @@ check_packet(_, User, Server,
{_, _} -> other
end,
LJID = case Dir of
- in -> jlib:jid_tolower(From);
- out -> jlib:jid_tolower(To)
+ in -> jid:tolower(From);
+ out -> jid:tolower(To)
end,
{Subscription, Groups} = case NeedDb of
true ->
ejabberd_hooks:run_fold(roster_get_jid_info,
- jlib:nameprep(Server),
+ jid:nameprep(Server),
{none, []},
[User, Server,
LJID]);
@@ -945,8 +950,8 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
end.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -965,16 +970,16 @@ updated_list(_, #userlist{name = OldName} = Old,
true -> Old
end.
-raw_to_item([SType, SValue, SAction, SOrder, SMatchAll,
- SMatchIQ, SMatchMessage, SMatchPresenceIn,
- SMatchPresenceOut] = Row) ->
+raw_to_item({SType, SValue, SAction, Order, MatchAll,
+ MatchIQ, MatchMessage, MatchPresenceIn,
+ MatchPresenceOut} = Row) ->
try
{Type, Value} = case SType of
<<"n">> -> {none, none};
<<"j">> ->
- case jlib:string_to_jid(SValue) of
+ case jid:from_string(SValue) of
#jid{} = JID ->
- {jid, jlib:jid_tolower(JID)}
+ {jid, jid:tolower(JID)}
end;
<<"g">> -> {group, SValue};
<<"s">> ->
@@ -989,12 +994,6 @@ raw_to_item([SType, SValue, SAction, SOrder, SMatchAll,
<<"a">> -> allow;
<<"d">> -> deny
end,
- Order = jlib:binary_to_integer(SOrder),
- MatchAll = ejabberd_odbc:to_bool(SMatchAll),
- MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
- MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
- MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
- MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
[#listitem{type = Type, value = Value, action = Action,
order = Order, match_all = MatchAll, match_iq = MatchIQ,
match_message = MatchMessage,
@@ -1014,7 +1013,7 @@ item_to_raw(#listitem{type = Type, value = Value,
none -> {<<"n">>, <<"">>};
jid ->
{<<"j">>,
- ejabberd_odbc:escape(jlib:jid_to_string(Value))};
+ ejabberd_odbc:escape(jid:to_string(Value))};
group -> {<<"g">>, ejabberd_odbc:escape(Value)};
subscription ->
case Value of
@@ -1028,58 +1027,29 @@ item_to_raw(#listitem{type = Type, value = Value,
allow -> <<"a">>;
deny -> <<"d">>
end,
- SOrder = iolist_to_binary(integer_to_list(Order)),
- SMatchAll = if MatchAll -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchIQ = if MatchIQ -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchMessage = if MatchMessage -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceIn = if MatchPresenceIn -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceOut = if MatchPresenceOut -> <<"1">>;
- true -> <<"0">>
- end,
- [SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
- SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
+ {SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}.
sql_get_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list(LServer,
- Username).
+ odbc_queries:get_default_privacy_list(LServer, LUser).
sql_get_default_privacy_list_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list_t(Username).
+ odbc_queries:get_default_privacy_list_t(LUser).
sql_get_privacy_list_names(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names(LServer, Username).
+ odbc_queries:get_privacy_list_names(LServer, LUser).
sql_get_privacy_list_names_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names_t(Username).
+ odbc_queries:get_privacy_list_names_t(LUser).
sql_get_privacy_list_id(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id(LServer, Username,
- SName).
+ odbc_queries:get_privacy_list_id(LServer, LUser, Name).
sql_get_privacy_list_id_t(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id_t(Username, SName).
+ odbc_queries:get_privacy_list_id_t(LUser, Name).
sql_get_privacy_list_data(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data(LServer, Username,
- SName).
+ odbc_queries:get_privacy_list_data(LServer, LUser, Name).
sql_get_privacy_list_data_t(LUser, Name) ->
Username = ejabberd_odbc:escape(LUser),
@@ -1093,33 +1063,22 @@ sql_get_privacy_list_data_by_id_t(ID) ->
odbc_queries:get_privacy_list_data_by_id_t(ID).
sql_set_default_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:set_default_privacy_list(Username, SName).
+ odbc_queries:set_default_privacy_list(LUser, Name).
sql_unset_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:unset_default_privacy_list(LServer,
- Username).
+ odbc_queries:unset_default_privacy_list(LServer, LUser).
sql_remove_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:remove_privacy_list(Username, SName).
+ odbc_queries:remove_privacy_list(LUser, Name).
sql_add_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:add_privacy_list(Username, SName).
+ odbc_queries:add_privacy_list(LUser, Name).
sql_set_privacy_list(ID, RItems) ->
odbc_queries:set_privacy_list(ID, RItems).
sql_del_privacy_lists(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- Server = ejabberd_odbc:escape(LServer),
- odbc_queries:del_privacy_lists(LServer, Server,
- Username).
+ odbc_queries:del_privacy_lists(LServer, LUser).
update_table() ->
Fields = record_info(fields, privacy),
@@ -1165,7 +1124,7 @@ update_table() ->
end.
export(Server) ->
- case ejabberd_odbc:sql_query(jlib:nameprep(Server),
+ case catch ejabberd_odbc:sql_query(jid:nameprep(Server),
[<<"select id from privacy_list order by "
"id desc limit 1;">>]) of
{selected, [<<"id">>], [[I]]} ->
@@ -1255,3 +1214,7 @@ import(_LServer, riak, #privacy{} = P) ->
ejabberd_riak:put(P, privacy_schema());
import(_, _, _) ->
pass.
+
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [db_type, iqdisc].
diff --git a/src/mod_private.erl b/src/mod_private.erl
index e127c202..f3dceeaa 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -5,7 +5,7 @@
%%% Created : 16 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,10 +27,13 @@
-author('alexey@process-one.net').
+-protocol({xep, 49, '1.2'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, process_sm_iq/3, import/3,
- remove_user/2, get_data/2, export/1, import/1]).
+ remove_user/2, get_data/2, export/1, import/1,
+ mod_opt_type/1, set_data/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -60,15 +63,12 @@ start(Host, Opts) ->
end,
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_PRIVATE, ?MODULE, process_sm_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_PRIVATE, ?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PRIVATE),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVATE).
@@ -82,19 +82,7 @@ process_sm_iq(#jid{luser = LUser, lserver = LServer},
IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]};
Data ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- F = fun () ->
- lists:foreach(fun (Datum) ->
- set_data(LUser, LServer,
- Datum, DBType)
- end,
- Data)
- end,
- case DBType of
- odbc -> ejabberd_odbc:sql_transaction(LServer, F);
- mnesia -> mnesia:transaction(F);
- riak -> F()
- end,
+ set_data(LUser, LServer, Data),
IQ#iq{type = result, sub_el = []}
end;
_ ->
@@ -137,23 +125,35 @@ filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
filter_xmlels([], Data) -> lists:reverse(Data);
filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
Data) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> [];
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
end;
filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data).
+set_data(LUser, LServer, Data) ->
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ F = fun () ->
+ lists:foreach(fun (Datum) ->
+ set_data(LUser, LServer,
+ Datum, DBType)
+ end,
+ Data)
+ end,
+ case DBType of
+ odbc -> ejabberd_odbc:sql_transaction(LServer, F);
+ mnesia -> mnesia:transaction(F);
+ riak -> F()
+ end.
+
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
mnesia:write(#private_storage{usns =
{LUser, LServer, XmlNS},
xml = Xmlel});
set_data(LUser, LServer, {XMLNS, El}, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
- odbc_queries:set_private_data(LServer, Username, LXMLNS,
- SData);
+ SData = fxml:element_to_binary(El),
+ odbc_queries:set_private_data(LServer, LUser, XMLNS, SData);
set_data(LUser, LServer, {XMLNS, El}, riak) ->
ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
xml = El},
@@ -181,13 +181,11 @@ get_data(LUser, LServer, mnesia,
end;
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
Res) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
case catch odbc_queries:get_private_data(LServer,
- Username, LXMLNS)
+ LUser, XMLNS)
of
- {selected, [<<"data">>], [[SData]]} ->
- case xml_stream:parse_element(SData) of
+ {selected, [{SData}]} ->
+ case fxml_stream:parse_element(SData) of
Data when is_record(Data, xmlel) ->
get_data(LUser, LServer, odbc, Els, [Data | Res])
end;
@@ -214,12 +212,11 @@ get_all_data(LUser, LServer, mnesia) ->
xml = '$1'},
[], ['$1']}]));
get_all_data(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_private_data(LServer, Username) of
- {selected, [<<"namespace">>, <<"data">>], Res} ->
+ case catch odbc_queries:get_private_data(LServer, LUser) of
+ {selected, Res} ->
lists:flatmap(
- fun([_, SData]) ->
- case xml_stream:parse_element(SData) of
+ fun({_, SData}) ->
+ case fxml_stream:parse_element(SData) of
#xmlel{} = El ->
[El];
_ ->
@@ -243,8 +240,8 @@ private_storage_schema() ->
{record_info(fields, private_storage), #private_storage{}}.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
remove_user(LUser, LServer,
gen_mod:db_type(Server, ?MODULE)).
@@ -266,9 +263,7 @@ remove_user(LUser, LServer, mnesia) ->
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_private_storage(LServer,
- Username);
+ odbc_queries:del_user_private_storage(LServer, LUser);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(private_storage,
<<"us">>, {LUser, LServer})}.
@@ -284,7 +279,7 @@ update_table() ->
R#private_storage{usns = {iolist_to_binary(U),
iolist_to_binary(S),
iolist_to_binary(NS)},
- xml = xml:to_xmlel(El)}
+ xml = fxml:to_xmlel(El)}
end);
_ ->
?INFO_MSG("Recreating private_storage table", []),
@@ -299,7 +294,7 @@ export(_Server) ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData =
- ejabberd_odbc:escape(xml:element_to_binary(Data)),
+ ejabberd_odbc:escape(fxml:element_to_binary(Data)),
odbc_queries:set_private_data_sql(Username, LXMLNS,
SData);
(_Host, _R) ->
@@ -309,7 +304,7 @@ export(_Server) ->
import(LServer) ->
[{<<"select username, namespace, data from private_storage;">>,
fun([LUser, XMLNS, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
+ El = #xmlel{} = fxml_stream:parse_element(XML),
#private_storage{usns = {LUser, LServer, XMLNS},
xml = El}
end}].
@@ -322,3 +317,7 @@ import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
import(_, _, _) ->
pass.
+
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [db_type, iqdisc].
diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl
index 6eced10b..2737de7a 100644
--- a/src/mod_proxy65.erl
+++ b/src/mod_proxy65.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,6 +27,8 @@
-author('xram@jabber.ru').
+-protocol({xep, 65, '1.8'}).
+
-behaviour(gen_mod).
-behaviour(supervisor).
@@ -37,8 +39,7 @@
%% supervisor callbacks.
-export([init/1]).
-%% API.
--export([start_link/2]).
+-export([start_link/2, mod_opt_type/1]).
-define(PROCNAME, ejabberd_mod_proxy65).
@@ -82,3 +83,35 @@ init([Host, Opts]) ->
{ok,
{{one_for_one, 10, 1},
[StreamManager, StreamSupervisor, Service]}}.
+
+mod_opt_type(auth_type) ->
+ fun (plain) -> plain;
+ (anonymous) -> anonymous
+ end;
+mod_opt_type(recbuf) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(shaper) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(sndbuf) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hostname) -> fun iolist_to_binary/1;
+mod_opt_type(ip) ->
+ fun (S) ->
+ {ok, Addr} =
+ inet_parse:address(binary_to_list(iolist_to_binary(S))),
+ Addr
+ end;
+mod_opt_type(name) -> fun iolist_to_binary/1;
+mod_opt_type(port) ->
+ fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
+mod_opt_type(max_connections) ->
+ fun (I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(_) ->
+ [auth_type, recbuf, shaper, sndbuf,
+ access, host, hostname, ip, name, port,
+ max_connections].
diff --git a/src/mod_proxy65_lib.erl b/src/mod_proxy65_lib.erl
index 6c596768..3cacbf98 100644
--- a/src/mod_proxy65_lib.erl
+++ b/src/mod_proxy65_lib.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 1e7735c5..8b4644ba 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -33,9 +33,8 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-%% API.
--export([start_link/2, add_listener/2, transform_module_options/1,
- delete_listener/1]).
+-export([start_link/2, add_listener/2,
+ transform_module_options/1, delete_listener/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -64,7 +63,7 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- ejabberd_router:register_route(State#state.myhost),
+ ejabberd_router:register_route(State#state.myhost, Host),
{ok, State}.
terminate(_Reason, #state{myhost = MyHost}) ->
@@ -176,19 +175,19 @@ process_iq(InitiatorJID,
#state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow ->
- ActivateEl = xml:get_path_s(SubEl,
+ ActivateEl = fxml:get_path_s(SubEl,
[{elem, <<"activate">>}]),
- SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
+ SID = fxml:get_tag_attr_s(<<"sid">>, SubEl),
case catch
- jlib:string_to_jid(xml:get_tag_cdata(ActivateEl))
+ jid:from_string(fxml:get_tag_cdata(ActivateEl))
of
TargetJID
when is_record(TargetJID, jid), SID /= <<"">>,
byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
Target =
- jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
+ jid:to_string(jid:tolower(TargetJID)),
Initiator =
- jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
+ jid:to_string(jid:tolower(InitiatorJID)),
SHA1 = p1_sha:sha(<<SID/binary, Initiator/binary, Target/binary>>),
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID,
TargetJID, ServerHost)
@@ -247,7 +246,7 @@ iq_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd SOCKS5 Bytestreams module">>))/binary,
- "\nCopyright (c) 2003-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
parse_options(ServerHost, Opts) ->
MyHost = gen_mod:get_opt_host(ServerHost, Opts,
diff --git a/src/mod_proxy65_sm.erl b/src/mod_proxy65_sm.erl
index 367f7f0b..d86b06c4 100644
--- a/src/mod_proxy65_sm.erl
+++ b/src/mod_proxy65_sm.erl
@@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -33,7 +33,6 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-%% API.
-export([start_link/2, register_stream/1,
unregister_stream/1, activate_stream/4]).
@@ -65,8 +64,9 @@ start_link(Host, Opts) ->
[]).
init([Opts]) ->
- mnesia:create_table(bytestream, [{ram_copies, [node()]},
- {attributes, record_info(fields, bytestream)}]),
+ mnesia:create_table(bytestream,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, bytestream)}]),
mnesia:add_table_copy(bytestream, node(), ram_copies),
MaxConnections = gen_mod:get_opt(max_connections, Opts,
fun(I) when is_integer(I), I>0 ->
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index db894de7..84017329 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -4,7 +4,7 @@
%%% Purpose : Bytestream process.
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -37,7 +37,6 @@
wait_for_request/2, wait_for_activation/2,
stream_established/2]).
-%% API.
-export([start/2, stop/1, start_link/3, activate/2,
relay/3, socket_type/0]).
@@ -121,8 +120,8 @@ activate({P1, J1}, {P2, J2}) ->
{S1, S2} when is_port(S1), is_port(S2) ->
P1 ! {activate, P2, S2, J1, J2},
P2 ! {activate, P1, S1, J1, J2},
- JID1 = jlib:jid_to_string(J1),
- JID2 = jlib:jid_to_string(J2),
+ JID1 = jid:to_string(J1),
+ JID2 = jid:to_string(J2),
?INFO_MSG("(~w:~w) Activated bytestream for ~s "
"-> ~s",
[P1, P2, JID1, JID2]),
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 80f6c05a..6531ed87 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -1,40 +1,28 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : mod_pubsub.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Publish Subscribe service (XEP-0060)
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
-
-%%% @doc The module <strong>{@module}</strong> is the core of the PubSub
-%%% extension. It relies on PubSub plugins for a large part of its functions.
+%%% 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.
%%%
-%%% @headerfile "pubsub.hrl"
+%%% 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.
%%%
-%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for
-%%% the latest version of the PubSub specification.
-%%% This module uses version 1.12 of the specification as a base.
-%%% Most of the specification is implemented.
-%%% Functions concerning configuration should be rewritten.
+%%% 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.
%%%
+%%%----------------------------------------------------------------------
+
%%% Support for subscription-options and multi-subscribe features was
%%% added by Brian Cully (bjc AT kublai.com). Subscriptions and options are
%%% stored in the pubsub_subscription table, with a link to them provided
@@ -47,7 +35,9 @@
-behaviour(gen_mod).
-behaviour(gen_server).
-author('christophe.romain@process-one.net').
--version('1.13-1').
+-protocol({xep, 60, '1.13-1'}).
+-protocol({xep, 163, '1.2'}).
+-protocol({xep, 248, '0.2'}).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -60,7 +50,7 @@
-define(PEPNODE, <<"pep">>).
%% exports for hooks
--export([presence_probe/3, caps_update/3,
+-export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4,
on_user_offline/3, remove_user/2,
disco_local_identity/5, disco_local_features/5,
@@ -73,7 +63,7 @@
%% exports for console debug manual use
-export([create_node/5, create_node/7, delete_node/3,
subscribe_node/5, unsubscribe_node/5, publish_item/6,
- delete_item/4, send_items/7, get_items/2, get_item/3,
+ delete_item/4, delete_item/5, send_items/7, get_items/2, get_item/3,
get_cached_item/2, get_configure/5, set_configure/5,
tree_action/3, node_action/4, node_call/4]).
@@ -81,15 +71,15 @@
-export([subscription_to_string/1, affiliation_to_string/1,
string_to_subscription/1, string_to_affiliation/1,
extended_error/2, extended_error/3, service_jid/1,
- tree/1, tree/2, plugin/2, config/3, host/1, serverhost/1]).
+ tree/1, tree/2, plugin/2, plugins/1, config/3,
+ host/1, serverhost/1]).
%% API and gen_server callbacks
-export([start_link/2, start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-%% calls for parallel sending of last items
--export([send_loop/1]).
+-export([send_loop/1, mod_opt_type/1]).
-define(PROCNAME, ejabberd_mod_pubsub).
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
@@ -190,6 +180,8 @@
ignore_pep_from_offline = true,
last_item_cache = false,
max_items_node = ?MAXITEMS,
+ max_subscriptions_node = undefined,
+ default_node_config = [],
nodetree = <<"nodetree_", (?STDTREE)/binary>>,
plugins = [?STDNODE],
db_type
@@ -204,6 +196,8 @@
ignore_pep_from_offline :: boolean(),
last_item_cache :: boolean(),
max_items_node :: non_neg_integer(),
+ max_subscriptions_node :: non_neg_integer()|undefined,
+ default_node_config :: [{atom(), binary()|boolean()|integer()|atom()}],
nodetree :: binary(),
plugins :: [binary(),...],
db_type :: atom()
@@ -247,6 +241,7 @@ stop(Host) ->
init([ServerHost, Opts]) ->
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
+ ejabberd_router:register_route(Host, ServerHost),
Access = gen_mod:get_opt(access_createnode, Opts,
fun(A) when is_atom(A) -> A end, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
@@ -257,21 +252,31 @@ init([ServerHost, Opts]) ->
fun(A) when is_boolean(A) -> A end, false),
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts,
fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS),
+ MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts,
+ fun(A) when is_integer(A) andalso A >= 0 -> A end, undefined),
+ DefaultNodeCfg = gen_mod:get_opt(default_node_config, Opts,
+ fun(A) when is_list(A) -> filter_node_options(A) end, []),
pubsub_index:init(Host, ServerHost, Opts),
- ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
mnesia:create_table(pubsub_last_item,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {access, Access}),
+ lists:foreach(
+ fun(H) ->
+ T = gen_mod:get_module_proc(H, config),
+ ets:new(T, [set, named_table]),
+ ets:insert(T, {nodetree, NodeTree}),
+ ets:insert(T, {plugins, Plugins}),
+ ets:insert(T, {last_item_cache, LastItemCache}),
+ ets:insert(T, {max_items_node, MaxItemsNode}),
+ ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
+ ets:insert(T, {default_node_config, DefaultNodeCfg}),
+ ets:insert(T, {pep_mapping, PepMapping}),
+ ets:insert(T, {ignore_pep_from_offline, PepOffline}),
+ ets:insert(T, {host, Host}),
+ ets:insert(T, {access, Access})
+ end, [Host, ServerHost]),
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
@@ -292,6 +297,8 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50),
case lists:member(?PEPNODE, Plugins) of
true ->
+ ejabberd_hooks:add(caps_add, ServerHost,
+ ?MODULE, caps_add, 80),
ejabberd_hooks:add(caps_update, ServerHost,
?MODULE, caps_update, 80),
ejabberd_hooks:add(disco_sm_identity, ServerHost,
@@ -307,11 +314,9 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
- ejabberd_router:register_route(Host),
pubsub_migrate:update_node_database(Host, ServerHost),
pubsub_migrate:update_state_database(Host, ServerHost),
pubsub_migrate:update_lastitem_database(Host, ServerHost),
- init_nodes(Host, ServerHost, NodeTree, Plugins),
{_, State} = init_send_loop(ServerHost),
{ok, State}.
@@ -385,22 +390,14 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
-init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
- case lists:member(<<"hometree">>, Plugins) of
- true ->
- create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>),
- create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), <<"hometree">>);
- false -> ok
- end.
-
send_loop(State) ->
receive
{presence, JID, Pid} ->
Host = State#state.host,
ServerHost = State#state.server_host,
DBType = State#state.db_type,
- LJID = jlib:jid_tolower(JID),
- BJID = jlib:jid_remove_resource(LJID),
+ LJID = jid:tolower(JID),
+ BJID = jid:remove_resource(LJID),
lists:foreach(
fun(PType) ->
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
@@ -422,7 +419,7 @@ send_loop(State) ->
fun({U, S, R}) when S == ServerHost ->
case user_resources(U, S) of
[] -> %% offline
- PeerJID = jlib:make_jid(U, S, R),
+ PeerJID = jid:make(U, S, R),
self() ! {presence, User, Server, [Resource], PeerJID};
_ -> %% online
% this is already handled by presence probe
@@ -443,7 +440,7 @@ send_loop(State) ->
{presence, User, Server, Resources, JID} ->
spawn(fun() ->
Host = State#state.host,
- Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
+ Owner = jid:remove_resource(jid:tolower(JID)),
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
true ->
@@ -489,7 +486,7 @@ send_loop(State) ->
-> [xmlel()]
).
disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
- case lists:member(?PEPNODE, plugins(To#jid.lserver)) of
+ case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of
true ->
[#xmlel{name = <<"identity">>,
attrs = [{<<"category">>, <<"pubsub">>},
@@ -511,7 +508,7 @@ disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
-> [binary(),...]
).
disco_local_features(Acc, _From, To, <<>>, _Lang) ->
- Host = To#jid.lserver,
+ Host = host(To#jid.lserver),
Feats = case Acc of
{result, I} -> I;
_ -> []
@@ -539,7 +536,7 @@ disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc.
disco_sm_identity(empty, From, To, Node, Lang) ->
disco_sm_identity([], From, To, Node, Lang);
disco_sm_identity(Acc, From, To, Node, _Lang) ->
- disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)
+ disco_identity(jid:tolower(jid:remove_resource(To)), Node, From)
++ Acc.
disco_identity(_Host, <<>>, _From) ->
@@ -588,7 +585,7 @@ disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) ->
{result,
OtherFeatures ++
- disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)};
+ disco_features(jid:tolower(jid:remove_resource(To)), Node, From)};
disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
disco_features(Host, <<>>, _From) ->
@@ -623,7 +620,7 @@ disco_sm_items(empty, From, To, Node, Lang) ->
disco_sm_items({result, []}, From, To, Node, Lang);
disco_sm_items({result, OtherItems}, From, To, Node, _Lang) ->
{result, lists:usort(OtherItems ++
- disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))};
+ disco_items(jid:tolower(jid:remove_resource(To)), Node, From))};
disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
-spec(disco_items/3 ::
@@ -642,7 +639,7 @@ disco_items(Host, <<>>, From) ->
{result, _} ->
[#xmlel{name = <<"item">>,
attrs = [{<<"node">>, (Node)},
- {<<"jid">>, jlib:jid_to_string(Host)}
+ {<<"jid">>, jid:to_string(Host)}
| case get_option(Options, title) of
false -> [];
[Title] -> [{<<"name">>, Title}]
@@ -666,7 +663,7 @@ disco_items(Host, Node, From) ->
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
{result, Items} ->
{result, [#xmlel{name = <<"item">>,
- attrs = [{<<"jid">>, jlib:jid_to_string(Host)},
+ attrs = [{<<"jid">>, jid:to_string(Host)},
{<<"name">>, ItemId}]}
|| #pubsub_item{itemid = {ItemId, _}} <- Items]};
_ ->
@@ -682,12 +679,24 @@ disco_items(Host, Node, From) ->
%% presence hooks handling functions
%%
-caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
+caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
when Host =/= S ->
+ %% When a remote contact goes online while the local user is offline, the
+ %% remote contact won't receive last items from the local user even if
+ %% ignore_pep_from_offline is set to false. To work around this issue a bit,
+ %% we'll also send the last items to remote contacts when the local user
+ %% connects. That's the reason to use the caps_add hook instead of the
+ %% presence_probe_hook for remote contacts: The latter is only called when a
+ %% contact becomes available; the former is also executed when the local
+ %% user goes online (because that triggers the contact to send a presence
+ %% packet with CAPS).
presence(Host, {presence, U, S, [R], JID});
-caps_update(_From, _To, _Feature) ->
+caps_add(_From, _To, _Feature) ->
ok.
+caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
+ presence(Host, {presence, U, S, [R], JID}).
+
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
presence(S, {presence, JID, Pid}),
presence(S, {presence, U, S, [R], JID});
@@ -699,7 +708,7 @@ presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} =
presence(S, {presence, U, S, [R], JID});
presence_probe(_Host, _JID, _Pid) ->
%% ignore presence_probe from remote contacts,
- %% those are handled via caps_update
+ %% those are handled via caps_add
ok.
presence(ServerHost, Presence) ->
@@ -714,8 +723,8 @@ presence(ServerHost, Presence) ->
%%
out_subscription(User, Server, JID, subscribed) ->
- Owner = jlib:make_jid(User, Server, <<>>),
- {PUser, PServer, PResource} = jlib:jid_tolower(JID),
+ Owner = jid:make(User, Server, <<>>),
+ {PUser, PServer, PResource} = jid:tolower(JID),
PResources = case PResource of
<<>> -> user_resources(PUser, PServer);
_ -> [PResource]
@@ -726,54 +735,57 @@ out_subscription(_, _, _, _) ->
true.
in_subscription(_, User, Server, Owner, unsubscribed, _) ->
- unsubscribe_user(jlib:make_jid(User, Server, <<>>), Owner),
+ unsubscribe_user(jid:make(User, Server, <<>>), Owner),
true;
in_subscription(_, _, _, _, _, _) ->
true.
unsubscribe_user(Entity, Owner) ->
- BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- Host = host(element(2, BJID)),
spawn(fun () ->
- lists:foreach(fun (PType) ->
- {result, Subs} = node_action(Host, PType,
- get_entity_subscriptions,
- [Host, Entity]),
- lists:foreach(fun
- ({#pubsub_node{options = Options,
- owners = O,
- id = Nidx},
- subscribed, _, JID}) ->
- case match_option(Options, access_model, presence) of
- true ->
- Owners = node_owners_action(Host, PType, Nidx, O),
- case lists:member(BJID, Owners) of
- true ->
- node_action(Host, PType,
- unsubscribe_node,
- [Nidx, Entity, JID, all]);
- false ->
- {result, ok}
- end;
- _ ->
- {result, ok}
- end;
- (_) ->
- ok
- end,
- Subs)
- end,
- plugins(Host))
+ [unsubscribe_user(ServerHost, Entity, Owner) ||
+ ServerHost <- lists:usort(lists:foldl(
+ fun(UserHost, Acc) ->
+ case gen_mod:is_loaded(UserHost, mod_pubsub) of
+ true -> [UserHost|Acc];
+ false -> Acc
+ end
+ end, [], [Entity#jid.lserver, Owner#jid.lserver]))]
end).
+unsubscribe_user(Host, Entity, Owner) ->
+ BJID = jid:tolower(jid:remove_resource(Owner)),
+ lists:foreach(fun (PType) ->
+ {result, Subs} = node_action(Host, PType,
+ get_entity_subscriptions,
+ [Host, Entity]),
+ lists:foreach(fun
+ ({#pubsub_node{options = Options,
+ owners = O,
+ id = Nidx},
+ subscribed, _, JID}) ->
+ Unsubscribe = match_option(Options, access_model, presence)
+ andalso lists:member(BJID, node_owners_action(Host, PType, Nidx, O)),
+ case Unsubscribe of
+ true ->
+ node_action(Host, PType,
+ unsubscribe_node, [Nidx, Entity, JID, all]);
+ false ->
+ ok
+ end;
+ (_) ->
+ ok
+ end,
+ Subs)
+ end,
+ plugins(Host)).
%% -------
%% user remove hook handling function
%%
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- Entity = jlib:make_jid(LUser, LServer, <<>>),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Entity = jid:make(LUser, LServer, <<>>),
Host = host(LServer),
HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>,
spawn(fun () ->
@@ -865,9 +877,10 @@ handle_info(_Info, State) ->
%% @private
terminate(_Reason,
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
- ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
true ->
+ ejabberd_hooks:delete(caps_add, ServerHost,
+ ?MODULE, caps_add, 80),
ejabberd_hooks:delete(caps_update, ServerHost,
?MODULE, caps_update, 80),
ejabberd_hooks:delete(disco_sm_identity, ServerHost,
@@ -908,7 +921,8 @@ terminate(_Reason,
Pid ->
Pid ! stop
end,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin).
+ terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
+ ejabberd_router:unregister_route(Host).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -941,7 +955,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE, <<>>, <<>>]),
@@ -958,7 +972,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ejabberd_router:route(To, From, Res);
#iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of
{result, IQRes} ->
jlib:iq_to_xml(IQ#iq{type = result,
@@ -1012,7 +1026,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
_ ->
@@ -1030,7 +1044,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
<<"result">> ->
@@ -1190,7 +1204,7 @@ iq_disco_items(Host, Item, From, RSM) ->
).
iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
ServerHost = To#jid.lserver,
- LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+ LOwner = jid:tolower(jid:remove_resource(To)),
Res = case XMLNS of
?NS_PUBSUB ->
iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
@@ -1210,7 +1224,7 @@ iq_get_vcard(Lang) ->
#xmlel{name = <<"DESC">>, attrs = [],
children = [{xmlcdata,
<<(translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>))/binary,
- "\nCopyright (c) 2004-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2004-2016 ProcessOne">>}]}].
-spec(iq_pubsub/6 ::
(
@@ -1226,7 +1240,7 @@ iq_get_vcard(Lang) ->
).
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
- iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
+ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)).
-spec(iq_pubsub/8 ::
(
@@ -1245,16 +1259,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
#xmlel{children = SubEls} = SubEl,
- case xml:remove_cdata(SubEls) of
+ case fxml:remove_cdata(SubEls) of
[#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{set, <<"create">>} ->
Config = case Rest of
[#xmlel{name = <<"configure">>, children = C}] -> C;
_ -> []
end,
- Type = case xml:get_attr_s(<<"type">>, Attrs) of
+ Type = case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> hd(Plugins);
T -> T
end,
@@ -1266,10 +1280,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
create_node(Host, ServerHost, Node, From, Type, Access, Config)
end;
{set, <<"publish">>} ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs,
children = Payload}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access);
[] ->
{error,
@@ -1279,14 +1293,14 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
end;
{set, <<"retract">>} ->
- ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) of
+ ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of
<<"1">> -> true;
<<"true">> -> true;
_ -> false
end,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
delete_item(Host, Node, From, ItemId, ForceNotify);
_ ->
{error,
@@ -1297,37 +1311,37 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
[#xmlel{name = <<"options">>, children = C}] -> C;
_ -> []
end,
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
subscribe_node(Host, Node, From, JID, Config);
{set, <<"unsubscribe">>} ->
- JID = xml:get_attr_s(<<"jid">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
unsubscribe_node(Host, Node, From, JID, SubId);
{get, <<"items">>} ->
- MaxItems = xml:get_attr_s(<<"max_items">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ MaxItems = fxml:get_attr_s(<<"max_items">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
ItemIds = lists:foldl(fun
(#xmlel{name = <<"item">>, attrs = ItemAttrs}, Acc) ->
- case xml:get_attr_s(<<"id">>, ItemAttrs) of
+ case fxml:get_attr_s(<<"id">>, ItemAttrs) of
<<>> -> Acc;
ItemId -> [ItemId | Acc]
end;
(_, Acc) ->
Acc
end,
- [], xml:remove_cdata(Els)),
+ [], fxml:remove_cdata(Els)),
get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl));
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From, Plugins);
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From, Plugins);
{get, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
get_options(Host, Node, JID, SubId, Lang);
{set, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
set_options(Host, Node, JID, SubId, Els);
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
@@ -1352,10 +1366,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
#xmlel{children = SubEls} = SubEl,
- Action = xml:remove_cdata(SubEls),
+ Action = fxml:remove_cdata(SubEls),
case Action of
[#xmlel{name = Name, attrs = Attrs, children = Els}] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{get, <<"configure">>} ->
get_configure(Host, ServerHost, Node, From, Lang);
@@ -1370,11 +1384,11 @@ iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From);
{set, <<"subscriptions">>} ->
- set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
+ set_subscriptions(Host, Node, From, fxml:remove_cdata(Els));
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From);
{set, <<"affiliations">>} ->
- set_affiliations(Host, Node, From, xml:remove_cdata(Els));
+ set_affiliations(Host, Node, From, fxml:remove_cdata(Els));
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
end;
@@ -1484,7 +1498,7 @@ get_pending_nodes(Host, Owner, Plugins) ->
%% subscriptions on Host and Node.</p>
send_pending_auth_events(Host, Node, Owner) ->
?DEBUG("Sending pending auth events for ~s on ~s:~s",
- [jlib:jid_to_string(Owner), Host, Node]),
+ [jid:to_string(Owner), Host, Node]),
Action = fun (#pubsub_node{id = Nidx, type = Type}) ->
case lists:member(<<"get-pending">>, plugin_features(Host, Type)) of
true ->
@@ -1499,8 +1513,8 @@ send_pending_auth_events(Host, Node, Owner) ->
case transaction(Host, Node, Action, sync_dirty) of
{result, {N, Subs}} ->
lists:foreach(fun
- ({J, pending, _SubId}) -> send_authorization_request(N, jlib:make_jid(J));
- ({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
+ ({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J));
+ ({J, pending}) -> send_authorization_request(N, jid:make(J));
(_) -> ok
end,
Subs),
@@ -1563,7 +1577,7 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id =
[#xmlel{name = <<"value">>,
attrs = [],
children =
- [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]},
+ [{xmlcdata, jid:to_string(Subscriber)}]}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"var">>,
@@ -1579,7 +1593,7 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id =
children =
[{xmlcdata, <<"false">>}]}]}]}]},
lists:foreach(fun (Owner) ->
- ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
+ ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza)
end,
node_owners_action(Host, Type, Nidx, O)).
@@ -1587,9 +1601,9 @@ find_authorization_response(Packet) ->
#xmlel{children = Els} = Packet,
XData1 = lists:map(fun
(#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) ->
- case xml:get_attr_s(<<"xmlns">>, XAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, XAttrs) of
?NS_XDATA ->
- case xml:get_attr_s(<<"type">>, XAttrs) of
+ case fxml:get_attr_s(<<"type">>, XAttrs) of
<<"cancel">> -> none;
_ -> jlib:parse_xdata_submit(XEl)
end;
@@ -1599,7 +1613,7 @@ find_authorization_response(Packet) ->
(_) ->
none
end,
- xml:remove_cdata(Els)),
+ fxml:remove_cdata(Els)),
XData = lists:filter(fun (E) -> E /= none end, XData1),
case XData of
[invalid] ->
@@ -1624,7 +1638,7 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
[{<<"subscription">>, subscription_to_string(S)}]
end,
Stanza = event_stanza(<<"subscription">>,
- [{<<"jid">>, jlib:jid_to_string(JID)}
+ [{<<"jid">>, jid:to_string(JID)}
| nodeAttr(SNode)]
++ SubAttrs),
ejabberd_router:route(service_jid(Host), JID, Stanza).
@@ -1637,8 +1651,8 @@ handle_authorization_response(Host, From, To, Packet, XFields) ->
{{value, {_, [Node]}},
{value, {_, [SSubscriber]}},
{value, {_, [SAllow]}}} ->
- FromLJID = jlib:jid_tolower(jlib:jid_remove_resource(From)),
- Subscriber = jlib:string_to_jid(SSubscriber),
+ FromLJID = jid:tolower(jid:remove_resource(From)),
+ Subscriber = jid:from_string(SSubscriber),
Allow = case SAllow of
<<"1">> -> true;
<<"true">> -> true;
@@ -1765,6 +1779,20 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%<li>nodetree create_node checks if nodeid already exists</li>
%%<li>node plugin create_node just sets default affiliation/subscription</li>
%%</ul>
+-spec(create_node/5 ::
+ (
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+ ).
+create_node(Host, ServerHost, Node, Owner, Type) ->
+ create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
-spec(create_node/7 ::
(
Host :: mod_pubsub:host(),
@@ -1778,8 +1806,6 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%%
| {error, xmlel()}
).
-create_node(Host, ServerHost, Node, Owner, Type) ->
- create_node(Host, ServerHost, Node, Owner, Type, all, []).
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of
true ->
@@ -1798,7 +1824,7 @@ create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- ParseOptions = case xml:remove_cdata(Configuration) of
+ ParseOptions = case fxml:remove_cdata(Configuration) of
[] ->
{result, node_options(Host, Type)};
[#xmlel{name = <<"x">>} = XEl] ->
@@ -2001,6 +2027,21 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
AccessModel = get_option(Options, access_model),
SendLast = get_option(Options, send_last_published_item),
AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ CanSubscribe = case get_max_subscriptions_node(Host) of
+ Max when is_integer(Max) ->
+ case node_call(Host, Type, get_node_subscriptions, [Nidx]) of
+ {result, NodeSubs} ->
+ SubsNum = lists:foldl(
+ fun ({_, subscribed, _}, Acc) -> Acc+1;
+ (_, Acc) -> Acc
+ end, 0, NodeSubs),
+ SubsNum < Max;
+ _ ->
+ true
+ end;
+ _ ->
+ true
+ end,
if not SubscribeFeature ->
{error,
extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscribe">>)};
@@ -2013,6 +2054,10 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
SubOpts == invalid ->
{error,
extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
+ not CanSubscribe ->
+ %% fallback to closest XEP compatible result, assume we are not allowed to subscribe
+ {error,
+ extended_error(?ERR_NOT_ALLOWED, <<"closed-node">>)};
true ->
Owners = node_owners_call(Host, Type, Nidx, O),
{PS, RG} = get_presence_and_roster_permissions(Host, Subscriber,
@@ -2034,7 +2079,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
[#xmlel{name = <<"pubsub">>,
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
children = [#xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>, jlib:jid_to_string(Subscriber)}
+ attrs = [{<<"jid">>, jid:to_string(Subscriber)}
| SubAttrs]}]}]
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2365,7 +2410,10 @@ purge_node(Host, Node, Owner) ->
).
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
MaxItems = if SMaxItems == <<>> ->
- get_max_items_node(Host);
+ case get_max_items_node(Host) of
+ undefined -> ?MAXITEMS;
+ Max -> Max
+ end;
true ->
case catch jlib:binary_to_integer(SMaxItems) of
{'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
@@ -2439,7 +2487,7 @@ get_item(Host, Node, ItemId) ->
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) ->
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, none) of
- {result, {I, none}} -> {result, I};
+ {result, {Items, _RSM}} -> {result, Items};
Error -> Error
end.
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
@@ -2488,18 +2536,18 @@ send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
undefined ->
ok;
LastItem ->
- Stanza = items_event_stanza(Node, [LastItem]),
- dispatch_items(Host, LJID, Node, Options, Stanza)
+ Stanza = items_event_stanza(Node, Options, [LastItem]),
+ dispatch_items(Host, LJID, Node, Stanza)
end;
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
- Stanza = items_event_stanza(Node, get_last_items(Host, Type, Nidx, Number, LJID)),
- dispatch_items(Host, LJID, Node, Options, Stanza);
+ Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)),
+ dispatch_items(Host, LJID, Node, Stanza);
send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
- Stanza = items_event_stanza(Node, []),
- dispatch_items(Host, LJID, Node, Options, Stanza).
+ Stanza = items_event_stanza(Node, Options, []),
+ dispatch_items(Host, LJID, Node, Stanza).
-dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To, Node,
- Options, Stanza) ->
+dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To,
+ Node, Stanza) ->
C2SPid = case ejabberd_sm:get_session_pid(ToU, ToS, ToR) of
ToPid when is_pid(ToPid) -> ToPid;
_ ->
@@ -2511,17 +2559,13 @@ dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To, Node,
end,
if C2SPid == undefined -> ok;
true ->
- NotificationType = get_option(Options, notification_type, headline),
- Message = add_message_type(Stanza, NotificationType),
ejabberd_c2s:send_filtered(C2SPid,
{pep_message, <<Node/binary, "+notify">>},
- service_jid(From), jlib:make_jid(To),
- Message)
+ service_jid(From), jid:make(To),
+ Stanza)
end;
-dispatch_items(From, To, _Node, Options, Stanza) ->
- NotificationType = get_option(Options, notification_type, headline),
- Message = add_message_type(Stanza, NotificationType),
- ejabberd_router:route(service_jid(From), jlib:make_jid(To), Message).
+dispatch_items(From, To, _Node, Stanza) ->
+ ejabberd_router:route(service_jid(From), jid:make(To), Stanza).
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-spec(get_affiliations/4 ::
@@ -2609,7 +2653,7 @@ get_affiliations(Host, Node, JID) ->
[];
({AJID, Aff}) ->
[#xmlel{name = <<"affiliation">>,
- attrs = [{<<"jid">>, jlib:jid_to_string(AJID)},
+ attrs = [{<<"jid">>, jid:to_string(AJID)},
{<<"affiliation">>, affiliation_to_string(Aff)}]}]
end,
Affs),
@@ -2633,17 +2677,17 @@ get_affiliations(Host, Node, JID) ->
| {error, xmlel()}
).
set_affiliations(Host, Node, From, EntitiesEls) ->
- Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ Owner = jid:tolower(jid:remove_resource(From)),
Entities = lists:foldl(fun
(_, error) ->
error;
(El, Acc) ->
case El of
#xmlel{name = <<"affiliation">>, attrs = Attrs} ->
- JID = jlib:string_to_jid(xml:get_attr_s(<<"jid">>, Attrs)),
- Affiliation = string_to_affiliation(xml:get_attr_s(<<"affiliation">>, Attrs)),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Affiliation = string_to_affiliation(fxml:get_attr_s(<<"affiliation">>, Attrs)),
if (JID == error) or (Affiliation == false) -> error;
- true -> [{jlib:jid_tolower(JID), Affiliation} | Acc]
+ true -> [{jid:tolower(JID), Affiliation} | Acc]
end
end
end,
@@ -2656,7 +2700,7 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
Owners = node_owners_call(Host, Type, Nidx, O),
case lists:member(Owner, Owners) of
true ->
- OwnerJID = jlib:make_jid(Owner),
+ OwnerJID = jid:make(Owner),
FilteredEntities = case Owners of
[Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
_ -> Entities
@@ -2665,13 +2709,13 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]),
case Affiliation of
owner ->
- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ NewOwner = jid:tolower(jid:remove_resource(JID)),
NewOwners = [NewOwner | Owners],
tree_call(Host,
set_node,
[N#pubsub_node{owners = NewOwners}]);
none ->
- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ OldOwner = jid:tolower(jid:remove_resource(JID)),
case lists:member(OldOwner, Owners) of
true ->
NewOwners = Owners -- [OldOwner],
@@ -2745,7 +2789,7 @@ read_sub(Host, Node, Nidx, Subscriber, SubId, Lang) ->
[XdataEl]
end,
OptionsEl = #xmlel{name = <<"options">>,
- attrs = [{<<"jid">>, jlib:jid_to_string(Subscriber)},
+ attrs = [{<<"jid">>, jid:to_string(Subscriber)},
{<<"subid">>, SubId}
| nodeAttr(Node)],
children = Children},
@@ -2821,7 +2865,7 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
unsupported, <<"retrieve-subscriptions">>)},
Acc};
true ->
- Subscriber = jlib:jid_remove_resource(JID),
+ Subscriber = jid:remove_resource(JID),
{result, Subs} = node_action(Host, Type,
get_entity_subscriptions,
[Host, Subscriber]),
@@ -2855,14 +2899,14 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
<<>> ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(SubJID)},
+ [{<<"jid">>, jid:to_string(SubJID)},
{<<"subid">>, SubId},
{<<"subscription">>, subscription_to_string(Sub)}
| nodeAttr(SubsNode)]}];
SubsNode ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(SubJID)},
+ [{<<"jid">>, jid:to_string(SubJID)},
{<<"subid">>, SubId},
{<<"subscription">>, subscription_to_string(Sub)}]}];
_ ->
@@ -2873,13 +2917,13 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
<<>> ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(SubJID)},
+ [{<<"jid">>, jid:to_string(SubJID)},
{<<"subscription">>, subscription_to_string(Sub)}
| nodeAttr(SubsNode)]}];
SubsNode ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(SubJID)},
+ [{<<"jid">>, jid:to_string(SubJID)},
{<<"subscription">>, subscription_to_string(Sub)}]}];
_ ->
[]
@@ -2919,12 +2963,12 @@ get_subscriptions(Host, Node, JID) ->
({AJID, Sub}) ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(AJID)},
+ [{<<"jid">>, jid:to_string(AJID)},
{<<"subscription">>, subscription_to_string(Sub)}]}];
({AJID, Sub, SubId}) ->
[#xmlel{name = <<"subscription">>,
attrs =
- [{<<"jid">>, jlib:jid_to_string(AJID)},
+ [{<<"jid">>, jid:to_string(AJID)},
{<<"subscription">>, subscription_to_string(Sub)},
{<<"subid">>, SubId}]}]
end,
@@ -2963,18 +3007,18 @@ get_subscriptions_for_send_last(_Host, _PType, _, _JID, _LJID, _BJID) ->
[].
set_subscriptions(Host, Node, From, EntitiesEls) ->
- Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+ Owner = jid:tolower(jid:remove_resource(From)),
Entities = lists:foldl(fun
(_, error) ->
error;
(El, Acc) ->
case El of
#xmlel{name = <<"subscription">>, attrs = Attrs} ->
- JID = jlib:string_to_jid(xml:get_attr_s(<<"jid">>, Attrs)),
- Sub = string_to_subscription(xml:get_attr_s(<<"subscription">>, Attrs)),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Sub = string_to_subscription(fxml:get_attr_s(<<"subscription">>, Attrs)),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
if (JID == error) or (Sub == false) -> error;
- true -> [{jlib:jid_tolower(JID), Sub, SubId} | Acc]
+ true -> [{jid:tolower(JID), Sub, SubId} | Acc]
end
end
end,
@@ -2990,10 +3034,10 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
children =
[#xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>, jlib:jid_to_string(JID)},
+ attrs = [{<<"jid">>, jid:to_string(JID)},
{<<"subscription">>, subscription_to_string(Sub)}
| nodeAttr(Node)]}]}]},
- ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
+ ejabberd_router:route(service_jid(Host), jid:make(JID), Stanza)
end,
Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O}) ->
Owners = node_owners_call(Host, Type, Nidx, O),
@@ -3065,7 +3109,7 @@ get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, A
Groups),
{PresenceSubscription, RosterGroup};
get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
- get_roster_info(OwnerUser, OwnerServer, jlib:jid_tolower(JID), AllowedGroups).
+ get_roster_info(OwnerUser, OwnerServer, jid:tolower(JID), AllowedGroups).
string_to_affiliation(<<"owner">>) -> owner;
string_to_affiliation(<<"publisher">>) -> publisher;
@@ -3130,7 +3174,7 @@ sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D;
sub_option_can_deliver(_, _, {deliver, false}) -> false;
-sub_option_can_deliver(_, _, {expire, When}) -> now() < When;
+sub_option_can_deliver(_, _, {expire, When}) -> p1_time_compat:timestamp() < When;
sub_option_can_deliver(_, _, _) -> true.
-spec(presence_can_deliver/2 ::
@@ -3142,17 +3186,15 @@ sub_option_can_deliver(_, _, _) -> true.
presence_can_deliver(_, false) ->
true;
presence_can_deliver({User, Server, Resource}, true) ->
- case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
+ case ejabberd_sm:get_user_present_resources(User, Server) of
[] ->
false;
Ss ->
lists:foldl(fun
(_, true) ->
true;
- ({session, _, _, _, undefined, _}, _Acc) ->
- false;
- ({session, {_, _, R}, _, _, _Priority, _}, _Acc) ->
- case Resource of
+ ({_, R}, _Acc) ->
+ case Resource of
<<>> -> true;
R -> true;
_ -> false
@@ -3225,23 +3267,25 @@ payload_xmlelements([#xmlel{} | Tail], Count) ->
payload_xmlelements([_ | Tail], Count) ->
payload_xmlelements(Tail, Count).
-items_event_stanza(Node, Items) ->
+items_event_stanza(Node, Options, Items) ->
MoreEls = case Items of
[LastItem] ->
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
DateTime = calendar:now_to_datetime(ModifNow),
{T_string, Tz_string} = jlib:timestamp_to_iso(DateTime, utc),
[#xmlel{name = <<"delay">>, attrs = [{<<"xmlns">>, ?NS_DELAY},
- {<<"from">>, jlib:jid_to_string(ModifUSR)},
+ {<<"from">>, jid:to_string(ModifUSR)},
{<<"stamp">>, <<T_string/binary, Tz_string/binary>>}],
children = [{xmlcdata, <<>>}]}];
_ ->
[]
end,
- event_stanza_with_els([#xmlel{name = <<"items">>,
- attrs = [{<<"type">>, <<"headline">>} | nodeAttr(Node)],
+ BaseStanza = event_stanza_with_els([#xmlel{name = <<"items">>,
+ attrs = nodeAttr(Node),
children = itemsEls(Items)}],
- MoreEls).
+ MoreEls),
+ NotificationType = get_option(Options, notification_type, headline),
+ add_message_type(BaseStanza, NotificationType).
event_stanza(Els) ->
event_stanza_with_els(Els, []).
@@ -3264,9 +3308,14 @@ broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payloa
true -> Payload;
false -> []
end,
+ Attrs = case get_option(NodeOptions, itemreply, none) of
+ owner -> itemAttr(ItemId); %% owner not supported
+ publisher -> itemAttr(ItemId, {<<"publisher">>, jid:to_string(From)});
+ none -> itemAttr(ItemId)
+ end,
Stanza = event_stanza(
[#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId),
+ children = [#xmlel{name = <<"item">>, attrs = Attrs,
children = Content}]}]),
broadcast_stanza(Host, From, Node, Nidx, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
@@ -3392,12 +3441,19 @@ get_node_subs_by_depth(Host, Node, From) ->
[{Depth, [{N, get_node_subs(Host, N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree].
get_node_subs(Host, #pubsub_node{type = Type, id = Nidx}) ->
+ WithOptions = lists:member(<<"subscription-options">>, plugin_features(Host, Type)),
case node_call(Host, Type, get_node_subscriptions, [Nidx]) of
- {result, Subs} -> get_options_for_subs(Host, Nidx, Subs);
+ {result, Subs} -> get_options_for_subs(Host, Nidx, Subs, WithOptions);
Other -> Other
end.
-get_options_for_subs(Host, Nidx, Subs) ->
+get_options_for_subs(_Host, _Nidx, Subs, false) ->
+ lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
+ [{JID, SubID, []} | Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Subs);
+get_options_for_subs(Host, Nidx, Subs, true) ->
SubModule = subscription_plugin(Host),
lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
case SubModule:get_subscription(JID, Nidx, SubID) of
@@ -3434,7 +3490,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
add_shim_headers(Stanza, subid_shim(SubIDs))
end,
lists:foreach(fun(To) ->
- ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend)
+ ejabberd_router:route(From, jid:make(To), StanzaToSend)
end, LJIDs)
end, SubIDsByJID).
@@ -3451,9 +3507,9 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
%% See XEP-0163 1.1 section 4.3.1
ejabberd_c2s:broadcast(C2SPid,
{pep_message, <<((Node))/binary, "+notify">>},
- _Sender = jlib:make_jid(LUser, LServer, <<"">>),
+ _Sender = jid:make(LUser, LServer, <<"">>),
_StanzaToSend = add_extended_headers(Stanza,
- _ReplyTo = extended_headers([jlib:jid_to_string(Publisher)])));
+ _ReplyTo = extended_headers([jid:to_string(Publisher)])));
_ ->
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza])
end;
@@ -3469,10 +3525,9 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
NodeOptions = Node#pubsub_node.options,
lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
- true ->
- %% If is to deliver :
+ true ->
case state_can_deliver(LJID, SubOptions) of
- [] -> {JIDs, Recipients};
+ [] -> {JIDs, Recipients};
JIDsToDeliver ->
lists:foldl(
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
@@ -3483,11 +3538,14 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
%% - add the Jid to JIDs list co-accumulator ;
%% - create a tuple of the Jid, Nidx, and SubID (as list),
%% and add the tuple to the Recipients list co-accumulator
- {[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]};
+ {[JIDToDeliver | JIDsAcc],
+ [{JIDToDeliver, NodeName, [SubID]}
+ | RecipientsAcc]};
true ->
%% - if the JIDs co-accumulator contains the Jid
%% get the tuple containing the Jid from the Recipient list co-accumulator
- {_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
+ {_, {JIDToDeliver, NodeName1, SubIDs}} =
+ lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
%% delete the tuple from the Recipients list
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}),
@@ -3496,7 +3554,11 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
% v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])}
% v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]}
% v2: {JIDs, Recipients1}
- {JIDsAcc, lists:keyreplace(JIDToDeliver, 1, RecipientsAcc, {JIDToDeliver, NodeName1, [SubID | SubIDs]})}
+ {JIDsAcc,
+ lists:keyreplace(JIDToDeliver, 1,
+ RecipientsAcc,
+ {JIDToDeliver, NodeName1,
+ [SubID | SubIDs]})}
end
end, {JIDs, Recipients}, JIDsToDeliver)
end;
@@ -3583,6 +3645,12 @@ get_option(Options, Var, Def) ->
end.
node_options(Host, Type) ->
+ case config(Host, default_node_config) of
+ undefined -> node_plugin_options(Host, Type);
+ [] -> node_plugin_options(Host, Type);
+ Config -> Config
+ end.
+node_plugin_options(Host, Type) ->
Module = plugin(Host, Type),
case catch Module:options() of
{'EXIT', {undef, _}} ->
@@ -3591,6 +3659,11 @@ node_options(Host, Type) ->
Result ->
Result
end.
+filter_node_options(Options) ->
+ lists:foldl(fun({Key, Val}, Acc) ->
+ DefaultValue = proplists:get_value(Key, Options, Val),
+ [{Key, DefaultValue}|Acc]
+ end, [], node_flat:options()).
node_owners_action(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
@@ -3633,9 +3706,9 @@ max_items(Host, Options) ->
case get_option(Options, persist_items) of
true ->
case get_option(Options, max_items) of
- false -> unlimited;
- Result when Result < 0 -> 0;
- Result -> Result
+ I when is_integer(I), I < 0 -> 0;
+ I when is_integer(I) -> I;
+ _ -> ?MAXITEMS
end;
false ->
case get_option(Options, send_last_published_item) of
@@ -3662,13 +3735,13 @@ max_items(Host, Options) ->
-define(INTEGER_CONFIG_FIELD(Label, Var),
?STRINGXFIELD(Label,
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (integer_to_binary(get_option(Options, Var))))).
+ (jlib:integer_to_binary(get_option(Options, Var))))).
-define(JLIST_CONFIG_FIELD(Label, Var, Opts),
?LISTXFIELD(Label,
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (jlib:jid_to_string(get_option(Options, Var))),
- [jlib:jid_to_string(O) || O <- Opts])).
+ (jid:to_string(get_option(Options, Var))),
+ [jid:to_string(O) || O <- Opts])).
-define(ALIST_CONFIG_FIELD(Label, Var, Opts),
?LISTXFIELD(Label,
@@ -3723,7 +3796,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
presence_based_delivery),
?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
- collection)].
+ collection),
+ ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>,
+ itemreply, [none, owner, publisher])].
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3734,9 +3809,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%<li>The specified node does not exist.</li>
%%</ul>
set_configure(Host, Node, From, Els, Lang) ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl), xml:get_tag_attr_s(<<"type">>, XEl)} of
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of
{?NS_XDATA, <<"cancel">>} ->
{result, []};
{?NS_XDATA, <<"submit">>} ->
@@ -3806,8 +3881,12 @@ add_opt(Key, Value, Opts) ->
-define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
case catch jlib:binary_to_integer(Val) of
- IVal when is_integer(IVal), IVal >= Min, IVal =< Max ->
- set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
+ IVal when is_integer(IVal), IVal >= Min ->
+ if (Max =:= undefined) orelse (IVal =< Max) ->
+ set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
+ true ->
+ {error, ?ERR_NOT_ACCEPTABLE}
+ end;
_ ->
{error, ?ERR_NOT_ACCEPTABLE}
end).
@@ -3873,27 +3952,28 @@ set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) ->
% NewValue = string_to_node(Value),
?SET_LIST_XOPT(node, Value);
+set_xoption(Host, [{<<"pubsub#itemreply">>, [Val]} | Opts], NewOpts) ->
+ ?SET_ALIST_XOPT(itemreply, Val, [none, owner, publisher]);
set_xoption(Host, [_ | Opts], NewOpts) ->
set_xoption(Host, Opts, NewOpts).
-get_max_items_node({_, ServerHost, _}) ->
- get_max_items_node(ServerHost);
get_max_items_node(Host) ->
- config(serverhost(Host), max_items_node, ?MAXITEMS).
+ config(Host, max_items_node, undefined).
+
+get_max_subscriptions_node(Host) ->
+ config(Host, max_subscriptions_node, undefined).
%%%% last item cache handling
-is_last_item_cache_enabled({_, ServerHost, _}) ->
- is_last_item_cache_enabled(ServerHost);
is_last_item_cache_enabled(Host) ->
- config(serverhost(Host), last_item_cache, false).
+ config(Host, last_item_cache, false).
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
case is_last_item_cache_enabled(Host) of
true -> mnesia:dirty_write({pubsub_last_item, Nidx, ItemId,
- {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))},
+ {p1_time_compat:timestamp(), jid:tolower(jid:remove_resource(Publisher))},
Payload});
_ -> ok
end.
@@ -3936,23 +4016,19 @@ get_cached_item(Host, Nidx) ->
host(ServerHost) ->
config(ServerHost, host, <<"pubsub.", ServerHost/binary>>).
-serverhost({_U, Server, _R})->
- Server;
+serverhost({_U, ServerHost, _R})->
+ serverhost(ServerHost);
serverhost(Host) ->
- case binary:match(Host, <<"pubsub.">>) of
- {0,7} ->
- [_,ServerHost] = binary:split(Host, <<".">>),
- ServerHost;
- _ ->
- Host
- end.
+ ejabberd_router:host_of_route(Host).
tree(Host) ->
- case config(serverhost(Host), nodetree) of
+ case config(Host, nodetree) of
undefined -> tree(Host, ?STDTREE);
Tree -> Tree
end.
+tree(_Host, <<"virtual">>) ->
+ nodetree_virtual; % special case, virtual does not use any backend
tree(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"nodetree_", Name/binary>>);
@@ -3968,7 +4044,7 @@ plugin(Host, Name) ->
end.
plugins(Host) ->
- case config(serverhost(Host), plugins) of
+ case config(Host, plugins) of
undefined -> [?STDNODE];
[] -> [?STDNODE];
Plugins -> Plugins
@@ -3983,6 +4059,9 @@ subscription_plugin(Host) ->
config(ServerHost, Key) ->
config(ServerHost, Key, undefined).
+
+config({_User, Host, _Resource}, Key, Default) ->
+ config(Host, Key, Default);
config(ServerHost, Key, Default) ->
case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of
[{Key, Value}] -> Value;
@@ -3999,14 +4078,14 @@ select_type(ServerHost, Host, Node, Type) ->
_ ->
Type
end,
- ConfiguredTypes = plugins(ServerHost),
+ ConfiguredTypes = plugins(Host),
case lists:member(SelectedType, ConfiguredTypes) of
true -> SelectedType;
false -> hd(ConfiguredTypes)
end.
select_type(ServerHost, Host, Node) ->
- select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
+ select_type(ServerHost, Host, Node, hd(plugins(Host))).
feature(<<"rsm">>) -> ?NS_RSM;
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
@@ -4189,11 +4268,11 @@ extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtA
children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}.
string_to_ljid(JID) ->
- case jlib:string_to_jid(JID) of
+ case jid:from_string(JID) of
error ->
{<<>>, <<>>, <<>>};
J ->
- case jlib:jid_tolower(J) of
+ case jid:tolower(J) of
error -> {<<>>, <<>>, <<>>};
J1 -> J1
end
@@ -4201,13 +4280,14 @@ string_to_ljid(JID) ->
-spec(uniqid/0 :: () -> mod_pubsub:itemId()).
uniqid() ->
- {T1, T2, T3} = now(),
+ {T1, T2, T3} = p1_time_compat:timestamp(),
iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
nodeAttr(Node) -> [{<<"node">>, Node}].
itemAttr([]) -> [];
itemAttr(ItemId) -> [{<<"id">>, ItemId}].
+itemAttr(ItemId, From) -> [{<<"id">>, ItemId}, From].
itemsEls(Items) ->
[#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
@@ -4262,7 +4342,7 @@ extended_headers(Jids) ->
|| Jid <- Jids].
on_user_offline(_, JID, _) ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
+ {User, Server, Resource} = jid:tolower(JID),
case user_resources(User, Server) of
[] -> purge_offline({User, Server, Resource});
_ -> true
@@ -4343,3 +4423,30 @@ purge_offline(Host, LJID, Node) ->
Error ->
Error
end.
+
+mod_opt_type(access_createnode) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(ignore_pep_from_offline) ->
+ fun (A) when is_boolean(A) -> A end;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(last_item_cache) ->
+ fun (A) when is_boolean(A) -> A end;
+mod_opt_type(max_items_node) ->
+ fun (A) when is_integer(A) andalso A >= 0 -> A end;
+mod_opt_type(max_subscriptions_node) ->
+ fun (A) when is_integer(A) andalso A >= 0 -> A end;
+mod_opt_type(default_node_config) ->
+ fun (A) when is_list(A) -> A end;
+mod_opt_type(nodetree) ->
+ fun (A) when is_binary(A) -> A end;
+mod_opt_type(pep_mapping) ->
+ fun (A) when is_list(A) -> A end;
+mod_opt_type(plugins) ->
+ fun (A) when is_list(A) -> A end;
+mod_opt_type(_) ->
+ [access_createnode, db_type, host,
+ ignore_pep_from_offline, iqdisc, last_item_cache,
+ max_items_node, nodetree, pep_mapping, plugins,
+ max_subscriptions_node, default_node_config].
diff --git a/src/mod_register.erl b/src/mod_register.erl
index cd68af93..56c5f720 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -5,7 +5,7 @@
%%% Created : 8 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,14 +25,19 @@
-module(mod_register).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
+-protocol({xep, 77, '2.4'}).
+
-behaviour(gen_mod).
-export([start/2, stop/1, stream_feature_register/2,
unauthenticated_iq_register/4, try_register/5,
process_iq/3, send_registration_notifications/3,
- transform_options/1, transform_module_options/1]).
+ transform_options/1, transform_module_options/1,
+ mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -67,11 +72,19 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_REGISTER).
-stream_feature_register(Acc, _Host) ->
- [#xmlel{name = <<"register">>,
- attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}],
- children = []}
- | Acc].
+stream_feature_register(Acc, Host) ->
+ AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
+ true ->
+ [#xmlel{name = <<"register">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}],
+ children = []}
+ | Acc];
+ false ->
+ Acc
+ end.
unauthenticated_iq_register(_Acc, Server,
#iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
@@ -79,19 +92,19 @@ unauthenticated_iq_register(_Acc, Server,
{A, _Port} -> A;
_ -> undefined
end,
- ResIQ = process_iq(jlib:make_jid(<<"">>, <<"">>,
+ ResIQ = process_iq(jid:make(<<"">>, <<"">>,
<<"">>),
- jlib:make_jid(<<"">>, Server, <<"">>), IQ, Address),
- Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>,
+ jid:make(<<"">>, Server, <<"">>), IQ, Address),
+ Res1 = jlib:replace_from_to(jid:make(<<"">>,
Server, <<"">>),
- jlib:make_jid(<<"">>, <<"">>, <<"">>),
+ jid:make(<<"">>, <<"">>, <<"">>),
jlib:iq_to_xml(ResIQ)),
jlib:remove_attr(<<"to">>, Res1);
unauthenticated_iq_register(Acc, _Server, _IQ, _IP) ->
Acc.
process_iq(From, To, IQ) ->
- process_iq(From, To, IQ, jlib:jid_tolower(From)).
+ process_iq(From, To, IQ, jid:tolower(From)).
process_iq(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} =
@@ -108,9 +121,9 @@ process_iq(From, To,
end,
case Type of
set ->
- UTag = xml:get_subtag(SubEl, <<"username">>),
- PTag = xml:get_subtag(SubEl, <<"password">>),
- RTag = xml:get_subtag(SubEl, <<"remove">>),
+ UTag = fxml:get_subtag(SubEl, <<"username">>),
+ PTag = fxml:get_subtag(SubEl, <<"password">>),
+ RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
@@ -119,14 +132,14 @@ process_iq(From, To,
acl:match_rule(Server, Access, From),
if (UTag /= false) and (RTag /= false) and
AllowRemove ->
- User = xml:get_tag_cdata(UTag),
+ User = fxml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = []};
_ ->
if PTag /= false ->
- Password = xml:get_tag_cdata(PTag),
+ Password = fxml:get_tag_cdata(PTag),
case ejabberd_auth:remove_user(User, Server,
Password)
of
@@ -161,9 +174,9 @@ process_iq(From, To,
resource = Resource} ->
ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
id = ID, sub_el = []},
- ejabberd_router:route(jlib:make_jid(User, Server,
+ ejabberd_router:route(jid:make(User, Server,
Resource),
- jlib:make_jid(User, Server,
+ jid:make(User, Server,
Resource),
jlib:iq_to_xml(ResIQ)),
ejabberd_auth:remove_user(User, Server),
@@ -172,8 +185,8 @@ process_iq(From, To,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end;
(UTag /= false) and (PTag /= false) ->
- User = xml:get_tag_cdata(UTag),
- Password = xml:get_tag_cdata(PTag),
+ User = fxml:get_tag_cdata(UTag),
+ Password = fxml:get_tag_cdata(PTag),
try_register_or_set_password(User, Server, Password,
From, IQ, SubEl, Source, Lang,
not IsCaptchaEnabled);
@@ -275,7 +288,7 @@ process_iq(From, To,
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
- <<"jabber:iq:register">>}],
+ ?NS_REGISTER}],
children =
[TopInstrEl | CaptchaEls]}]};
{error, limit} ->
@@ -297,7 +310,7 @@ process_iq(From, To,
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
- <<"jabber:iq:register">>}],
+ ?NS_REGISTER}],
children =
[#xmlel{name = <<"instructions">>,
attrs = [],
@@ -363,10 +376,10 @@ try_set_password(User, Server, Password, IQ, SubEl,
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
- case jlib:is_nodename(User) of
+ case jid:is_nodename(User) of
false -> {error, ?ERR_BAD_REQUEST};
_ ->
- JID = jlib:make_jid(User, Server, <<"">>),
+ JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
all),
@@ -398,6 +411,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
{error, ?ERR_JID_MALFORMED};
{error, not_allowed} ->
{error, ?ERR_NOT_ALLOWED};
+ {error, too_many_users} ->
+ {error, ?ERR_NOT_ALLOWED};
{error, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
@@ -429,7 +444,7 @@ send_welcome_message(JID) ->
of
{<<"">>, <<"">>} -> ok;
{Subj, Body} ->
- ejabberd_router:route(jlib:make_jid(<<"">>, Host,
+ ejabberd_router:route(jid:make(<<"">>, Host,
<<"">>),
JID,
#xmlel{name = <<"message">>,
@@ -451,7 +466,7 @@ send_registration_notifications(Mod, UJID, Source) ->
case gen_mod:get_module_opt(
Host, Mod, registration_watchers,
fun(Ss) ->
- [#jid{} = jlib:string_to_jid(iolist_to_binary(S))
+ [#jid{} = jid:from_string(iolist_to_binary(S))
|| S <- Ss]
end, []) of
[] -> ok;
@@ -460,13 +475,13 @@ send_registration_notifications(Mod, UJID, Source) ->
iolist_to_binary(io_lib:format("[~s] The account ~s was registered from "
"IP address ~s on node ~w using ~p.",
[get_time_string(),
- jlib:jid_to_string(UJID),
+ jid:to_string(UJID),
ip_to_string(Source), node(),
Mod])),
lists:foreach(
fun(JID) ->
ejabberd_router:route(
- jlib:make_jid(<<"">>, Host, <<"">>),
+ jid:make(<<"">>, Host, <<"">>),
JID,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
@@ -497,8 +512,7 @@ check_timeout(Source) ->
infinity
end, 600),
if is_integer(Timeout) ->
- {MSec, Sec, _USec} = now(),
- Priority = -(MSec * 1000000 + Sec),
+ Priority = -p1_time_compat:system_time(seconds),
CleanPriority = Priority + Timeout,
F = fun () ->
Treap = case mnesia:read(mod_register_ip, treap, write)
@@ -585,7 +599,7 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) ->
[Y, Mo, D, H, Mi, S]).
process_xdata_submit(El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> error;
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
@@ -598,7 +612,7 @@ process_xdata_submit(El) ->
end.
is_strong_password(Server, Password) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength,
fun(N) when is_number(N), N>=0 -> N end,
0) of
@@ -661,7 +675,7 @@ transform_module_options(Opts) ->
%%%
may_remove_resource({_, _, _} = From) ->
- jlib:jid_remove_resource(From);
+ jid:remove_resource(From);
may_remove_resource(From) -> From.
get_ip_access(Host) ->
@@ -680,3 +694,37 @@ check_ip_access(undefined, _IPAccess) ->
deny;
check_ip_access(IPAddress, IPAccess) ->
acl:match_rule(global, IPAccess, IPAddress).
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(access_from) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(captcha_protected) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(ip_access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(password_strength) ->
+ fun (N) when is_number(N), N >= 0 -> N end;
+mod_opt_type(registration_watchers) ->
+ fun (Ss) ->
+ [#jid{} = jid:from_string(iolist_to_binary(S))
+ || S <- Ss]
+ end;
+mod_opt_type(welcome_message) ->
+ fun (Opts) ->
+ S = proplists:get_value(subject, Opts, <<>>),
+ B = proplists:get_value(body, Opts, <<>>),
+ {iolist_to_binary(S), iolist_to_binary(B)}
+ end;
+mod_opt_type(_) ->
+ [access, access_from, captcha_protected, ip_access,
+ iqdisc, password_strength, registration_watchers,
+ welcome_message].
+
+opt_type(registration_timeout) ->
+ fun (TO) when is_integer(TO), TO > 0 -> TO;
+ (infinity) -> infinity;
+ (unlimited) -> infinity
+ end;
+opt_type(_) -> [registration_timeout].
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index b415d85e..d9314031 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -5,7 +5,7 @@
%%% Created : 4 May 2008 by Badlop <badlop@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -55,7 +55,7 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process/2]).
+-export([start/2, stop/1, process/2, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -100,7 +100,7 @@ process([<<"new">>],
lang = Lang, host = _HTTPHost}) ->
case form_new_post(Q) of
{success, ok, {Username, Host, _Password}} ->
- Jid = jlib:make_jid(Username, Host, <<"">>),
+ Jid = jid:make(Username, Host, <<"">>),
mod_register:send_registration_notifications(?MODULE, Jid, Ip),
Text = (?T(<<"Your Jabber account was successfully "
"created.">>)),
@@ -113,8 +113,8 @@ process([<<"new">>],
end;
process([<<"delete">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_del_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_del_post(Q) of
{atomic, ok} ->
Text = (?T(<<"Your Jabber account was successfully "
"deleted.">>)),
@@ -129,8 +129,8 @@ process([<<"delete">>],
%% should include the host where the POST was sent.
process([<<"change_password">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_changepass_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_changepass_post(Q) of
{atomic, ok} ->
Text = (?T(<<"The password of your Jabber account "
"was successfully changed.">>)),
@@ -250,8 +250,8 @@ form_new_get(Host, Lang, IP) ->
"a Jabber client.">>),
?XCT(<<"li">>,
<<"Some Jabber clients can store your password "
- "in your computer. Use that feature only "
- "if you trust your computer is safe.">>),
+ "in the computer, but you should do this only "
+ "in your personal computer for safety reasons.">>),
?XCT(<<"li">>,
<<"Memorize your password, or write it "
"in a paper placed in a safe place. In "
@@ -282,9 +282,9 @@ form_new_post(Q) ->
case catch get_register_parameters(Q) of
[Username, Host, Password, Password, Id, Key] ->
form_new_post(Username, Host, Password, {Id, Key});
- [_Username, _Password, _Password2, false, false] ->
+ [_Username, _Host, _Password, _Password2, false, false] ->
{error, passwords_not_identical};
- [_Username, _Password, _Password2, Id, Key] ->
+ [_Username, _Host, _Password, _Password2, Id, Key] ->
ejabberd_captcha:check_captcha(Id, Key),
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
@@ -361,7 +361,8 @@ form_changepass_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Old Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
@@ -386,12 +387,12 @@ form_changepass_get(Host, Lang) ->
%%% Formulary change password POST
%%%----------------------------------------------------------------------
-form_changepass_post(Q, Host) ->
+form_changepass_post(Q) ->
case catch get_changepass_parameters(Q) of
- [Username, PasswordOld, Password, Password] ->
+ [Username, Host, PasswordOld, Password, Password] ->
try_change_password(Username, Host, PasswordOld,
Password);
- [_Username, _PasswordOld, _Password, _Password2] ->
+ [_Username, _Host, _PasswordOld, _Password, _Password2] ->
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
end.
@@ -405,7 +406,7 @@ get_changepass_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"passwordold">>, <<"password">>,
+ [<<"username">>, <<"host">>, <<"passwordold">>, <<"password">>,
<<"password2">>]).
try_change_password(Username, Host, PasswordOld,
@@ -470,7 +471,8 @@ form_del_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"password">>, <<"">>,
@@ -491,7 +493,7 @@ register_account(Username, Host, Password) ->
Access = gen_mod:get_module_opt(Host, mod_register, access,
fun(A) when is_atom(A) -> A end,
all),
- case jlib:make_jid(Username, Host, <<"">>) of
+ case jid:make(Username, Host, <<"">>) of
error -> {error, invalid_jid};
JID ->
case acl:match_rule(Host, Access, JID) of
@@ -513,9 +515,9 @@ register_account2(Username, Host, Password) ->
%%% Formulary delete POST
%%%----------------------------------------------------------------------
-form_del_post(Q, Host) ->
+form_del_post(Q) ->
case catch get_unregister_parameters(Q) of
- [Username, Password] ->
+ [Username, Host, Password] ->
try_unregister_account(Username, Host, Password);
_ -> {error, wrong_parameters}
end.
@@ -529,7 +531,7 @@ get_unregister_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"password">>]).
+ [<<"username">>, <<"host">>, <<"password">>]).
try_unregister_account(Username, Host, Password) ->
try unregister_account(Username, Host, Password) of
@@ -574,3 +576,5 @@ get_error_text({error, passwords_not_identical}) ->
<<"The passwords are different">>;
get_error_text({error, wrong_parameters}) ->
<<"Wrong parameters in the web formulary">>.
+
+mod_opt_type(_) -> [].
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index 2f5d771c..997544b1 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -34,19 +34,23 @@
-module(mod_roster).
+-protocol({xep, 237, '1.3'}).
+-protocol({xep, 321, '0.1'}).
+
-author('alexey@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1, process_iq/3, export/1, import/1,
- process_local_iq/3, get_user_roster/2, import/3,
- get_subscription_lists/3, get_roster/2,
+-export([start/2, stop/1, process_iq/3, export/1,
+ import/1, process_local_iq/3, get_user_roster/2,
+ import/3, get_subscription_lists/3, get_roster/2,
get_in_pending_subscriptions/3, in_subscription/6,
out_subscription/4, set_items/3, remove_user/2,
get_jid_info/4, item_to_xml/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
- record_to_string/1, groups_to_string/1]).
+ record_to_string/1, groups_to_string/1,
+ mod_opt_type/1, set_roster/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -100,8 +104,6 @@ start(Host, Opts) ->
webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host, ?MODULE,
webadmin_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_ROSTER, ?MODULE, process_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_ROSTER, ?MODULE, process_iq, IQDisc).
@@ -128,7 +130,6 @@ stop(Host) ->
webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host, ?MODULE,
webadmin_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ROSTER),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_ROSTER).
@@ -202,11 +203,9 @@ read_roster_version(LUser, LServer, mnesia) ->
[] -> error
end;
read_roster_version(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_roster_version(LServer, Username)
- of
- {selected, [<<"version">>], [[Version]]} -> Version;
- {selected, [<<"version">>], []} -> error
+ case odbc_queries:get_roster_version(LServer, LUser) of
+ {selected, [{Version}]} -> Version;
+ {selected, []} -> error
end;
read_roster_version(LServer, LUser, riak) ->
case ejabberd_riak:get(roster_version, roster_version_schema(),
@@ -222,7 +221,7 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, true).
write_roster_version(LUser, LServer, InTransaction) ->
- Ver = p1_sha:sha(term_to_binary(now())),
+ Ver = p1_sha:sha(term_to_binary(p1_time_compat:unique_integer())),
write_roster_version(LUser, LServer, InTransaction, Ver,
gen_mod:db_type(LServer, ?MODULE)),
Ver.
@@ -255,7 +254,7 @@ write_roster_version(LUser, LServer, _InTransaction, Ver,
ejabberd_riak:put(#roster_version{us = US, version = Ver},
roster_version_schema()).
-%% Load roster from DB only if neccesary.
+%% Load roster from DB only if neccesary.
%% It is neccesary if
%% - roster versioning is disabled in server OR
%% - roster versioning is not used by the client OR
@@ -266,7 +265,7 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
LServer = From#jid.lserver,
US = {LUser, LServer},
try {ItemsToSend, VersionToSend} = case
- {xml:get_tag_attr(<<"ver">>, SubEl),
+ {fxml:get_tag_attr(<<"ver">>, SubEl),
roster_versioning_enabled(LServer),
roster_version_on_db(LServer)}
of
@@ -368,51 +367,49 @@ get_roster(LUser, LServer, riak) ->
_Err -> []
end;
get_roster(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- JIDGroups = case catch
- odbc_queries:get_roster_jid_groups(LServer,
- Username)
- of
- {selected, [<<"jid">>, <<"grp">>], JGrps}
- when is_list(JGrps) ->
- JGrps;
- _ -> []
- end,
- GroupsDict = lists:foldl(fun ([J, G], Acc) ->
- dict:append(J, G, Acc)
- end,
- dict:new(), JIDGroups),
- RItems = lists:flatmap(fun (I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error -> [];
- R ->
- SJID =
- jlib:jid_to_string(R#roster.jid),
- Groups = case dict:find(SJID,
- GroupsDict)
- of
- {ok, Gs} -> Gs;
- error -> []
- end,
- [R#roster{groups = Groups}]
- end
- end,
- Items),
- RItems;
- _ -> []
+ case catch odbc_queries:get_roster(LServer, LUser) of
+ {selected, Items} when is_list(Items) ->
+ JIDGroups = case catch odbc_queries:get_roster_jid_groups(
+ LServer, LUser) of
+ {selected, JGrps}
+ when is_list(JGrps) ->
+ JGrps;
+ _ -> []
+ end,
+ GroupsDict = lists:foldl(fun({J, G}, Acc) ->
+ dict:append(J, G, Acc)
+ end,
+ dict:new(), JIDGroups),
+ RItems =
+ lists:flatmap(
+ fun(I) ->
+ case raw_to_record(LServer, I) of
+ %% Bad JID in database:
+ error -> [];
+ R ->
+ SJID = jid:to_string(R#roster.jid),
+ Groups = case dict:find(SJID, GroupsDict) of
+ {ok, Gs} -> Gs;
+ error -> []
+ end,
+ [R#roster{groups = Groups}]
+ end
+ end,
+ Items),
+ RItems;
+ _ -> []
end.
+set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
+ transaction(
+ LServer,
+ fun() ->
+ roster_subscribe_t(LUser, LServer, LJID, Item)
+ end).
+
item_to_xml(Item) ->
Attrs1 = [{<<"jid">>,
- jlib:jid_to_string(Item#roster.jid)}],
+ jid:to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
<<"">> -> Attrs1;
Name -> [{<<"name">>, Name} | Attrs1]
@@ -452,14 +449,8 @@ get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
xs = []}
end;
get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Res} =
- odbc_queries:get_roster_by_jid(LServer, Username, SJID),
+ {selected, Res} =
+ odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)),
case Res of
[] ->
#roster{usj = {LUser, LServer, LJID},
@@ -508,14 +499,14 @@ process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
process_item_set(From, To,
#xmlel{attrs = Attrs, children = Els}, Managed) ->
- JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
#jid{user = User, luser = LUser, lserver = LServer} =
From,
case JID1 of
error -> ok;
_ ->
- LJID = jlib:jid_tolower(JID1),
+ LJID = jid:tolower(JID1),
F = fun () ->
Item = get_roster_by_jid_t(LUser, LServer, LJID),
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
@@ -551,7 +542,7 @@ process_item_set(_From, _To, _, _Managed) -> ok.
process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
case Attr of
<<"jid">> ->
- case jlib:string_to_jid(Val) of
+ case jid:from_string(Val) of
error -> process_item_attrs(Item, Attrs);
JID1 ->
JID = {JID1#jid.luser, JID1#jid.lserver,
@@ -577,10 +568,10 @@ process_item_els(Item,
| Els]) ->
case Name of
<<"group">> ->
- Groups = [xml:get_cdata(SEls) | Item#roster.groups],
+ Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
process_item_els(Item#roster{groups = Groups}, Els);
_ ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> process_item_els(Item, Els);
_ ->
XEls = [#xmlel{name = Name, attrs = Attrs,
@@ -594,8 +585,8 @@ process_item_els(Item, [{xmlcdata, _} | Els]) ->
process_item_els(Item, []) -> Item.
push_item(User, Server, From, Item) ->
- ejabberd_sm:route(jlib:make_jid(<<"">>, <<"">>, <<"">>),
- jlib:make_jid(User, Server, <<"">>),
+ ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
+ jid:make(User, Server, <<"">>),
{broadcast, {item, Item#roster.jid,
Item#roster.subscription}}),
case roster_versioning_enabled(Server) of
@@ -628,7 +619,7 @@ push_item(User, Server, Resource, From, Item,
attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
children = [item_to_xml(Item)]}]},
ejabberd_router:route(From,
- jlib:make_jid(User, Server, Resource),
+ jid:make(User, Server, Resource),
jlib:iq_to_xml(ResIQ)).
push_item_version(Server, User, From, Item,
@@ -640,8 +631,8 @@ push_item_version(Server, User, From, Item,
ejabberd_sm:get_user_resources(User, Server)).
get_subscription_lists(Acc, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
Items = get_subscription_lists(Acc, LUser, LServer,
DBType),
@@ -654,14 +645,8 @@ get_subscription_lists(_, LUser, LServer, mnesia) ->
_ -> []
end;
get_subscription_lists(_, LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
+ case catch odbc_queries:get_roster(LServer, LUser) of
+ {selected, Items} when is_list(Items) ->
lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
_ -> []
end;
@@ -703,12 +688,9 @@ roster_subscribe_t(LUser, LServer, LJID, Item) ->
roster_subscribe_t(_LUser, _LServer, _LJID, Item,
mnesia) ->
mnesia:write(Item);
-roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
- ItemVals = record_to_string(Item),
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- odbc_queries:roster_subscribe(LServer, Username, SJID,
- ItemVals);
+roster_subscribe_t(_LUser, _LServer, _LJID, Item, odbc) ->
+ ItemVals = record_to_row(Item),
+ odbc_queries:roster_subscribe(ItemVals);
roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
@@ -742,30 +724,18 @@ get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
end;
get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- case odbc_queries:get_roster_by_jid(LServer, Username,
- SJID)
- of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- [I]} ->
- R = raw_to_record(LServer, I),
- Groups = case odbc_queries:get_roster_groups(LServer,
- Username, SJID)
- of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
- _ -> []
- end,
- R#roster{groups = Groups};
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- []} ->
+ SJID = jid:to_string(LJID),
+ case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of
+ {selected, [I]} ->
+ R = raw_to_record(LServer, I),
+ Groups =
+ case odbc_queries:get_roster_groups(LServer, LUser, SJID) of
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
+ _ -> []
+ end,
+ R#roster{groups = Groups};
+ {selected, []} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID}
end;
@@ -782,9 +752,9 @@ get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
process_subscription(Direction, User, Server, JID1,
Type, Reason) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- LJID = jlib:jid_tolower(JID1),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LJID = jid:tolower(JID1),
F = fun () ->
Item = get_roster_by_jid_with_groups_t(LUser, LServer,
LJID),
@@ -835,7 +805,7 @@ process_subscription(Direction, User, Server, JID1,
subscribed -> <<"subscribed">>;
unsubscribed -> <<"unsubscribed">>
end,
- ejabberd_router:route(jlib:make_jid(User, Server,
+ ejabberd_router:route(jid:make(User, Server,
<<"">>),
JID1,
#xmlel{name = <<"presence">>,
@@ -849,7 +819,7 @@ process_subscription(Direction, User, Server, JID1,
ok;
true ->
push_item(User, Server,
- jlib:make_jid(User, Server, <<"">>), Item)
+ jid:make(User, Server, <<"">>), Item)
end,
true;
none -> false
@@ -973,8 +943,8 @@ in_auto_reply(both, none, unsubscribe) -> unsubscribed;
in_auto_reply(_, _, _) -> none.
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -987,8 +957,7 @@ remove_user(LUser, LServer, mnesia) ->
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_roster_t(LServer, Username),
+ odbc_queries:del_user_roster_t(LServer, LUser),
ok;
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
@@ -998,13 +967,12 @@ remove_user(LUser, LServer, riak) ->
%% Both or To, send a "unsubscribe" presence stanza.
send_unsubscription_to_rosteritems(LUser, LServer) ->
RosterItems = get_user_roster([], {LUser, LServer}),
- From = jlib:make_jid({LUser, LServer, <<"">>}),
+ From = jid:make({LUser, LServer, <<"">>}),
lists:foreach(fun (RosterItem) ->
send_unsubscribing_presence(From, RosterItem)
end,
RosterItems).
-%% @spec (From::jid(), Item::roster()) -> ok
send_unsubscribing_presence(From, Item) ->
IsTo = case Item#roster.subscription of
both -> true;
@@ -1017,14 +985,14 @@ send_unsubscribing_presence(From, Item) ->
_ -> false
end,
if IsTo ->
- send_presence_type(jlib:jid_remove_resource(From),
- jlib:make_jid(Item#roster.jid),
+ send_presence_type(jid:remove_resource(From),
+ jid:make(Item#roster.jid),
<<"unsubscribe">>);
true -> ok
end,
if IsFrom ->
- send_presence_type(jlib:jid_remove_resource(From),
- jlib:make_jid(Item#roster.jid),
+ send_presence_type(jid:remove_resource(From),
+ jid:make(Item#roster.jid),
<<"unsubscribed">>);
true -> ok
end,
@@ -1039,8 +1007,8 @@ send_presence_type(From, To, Type) ->
set_items(User, Server, SubEl) ->
#xmlel{children = Els} = SubEl,
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
F = fun () ->
lists:foreach(fun (El) ->
process_item_set_t(LUser, LServer, El)
@@ -1057,11 +1025,10 @@ update_roster_t(_LUser, _LServer, _LJID, Item,
mnesia) ->
mnesia:write(Item);
update_roster_t(LUser, LServer, LJID, Item, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- ItemVals = record_to_string(Item),
- ItemGroups = groups_to_string(Item),
- odbc_queries:update_roster(LServer, Username, SJID, ItemVals,
+ SJID = jid:to_string(LJID),
+ ItemVals = record_to_row(Item),
+ ItemGroups = Item#roster.groups,
+ odbc_queries:update_roster(LServer, LUser, SJID, ItemVals,
ItemGroups);
update_roster_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
@@ -1074,15 +1041,14 @@ del_roster_t(LUser, LServer, LJID) ->
del_roster_t(LUser, LServer, LJID, mnesia) ->
mnesia:delete({roster, {LUser, LServer, LJID}});
del_roster_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- odbc_queries:del_roster(LServer, Username, SJID);
+ SJID = jid:to_string(LJID),
+ odbc_queries:del_roster(LServer, LUser, SJID);
del_roster_t(LUser, LServer, LJID, riak) ->
ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
process_item_set_t(LUser, LServer,
#xmlel{attrs = Attrs, children = Els}) ->
- JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
case JID1 of
error -> ok;
@@ -1105,7 +1071,7 @@ process_item_set_t(_LUser, _LServer, _) -> ok.
process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
case Attr of
<<"jid">> ->
- case jlib:string_to_jid(Val) of
+ case jid:from_string(Val) of
error -> process_item_attrs_ws(Item, Attrs);
JID1 ->
JID = {JID1#jid.luser, JID1#jid.lserver,
@@ -1140,13 +1106,13 @@ process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) ->
- LServer = jlib:nameprep(Server),
+ LServer = jid:nameprep(Server),
get_in_pending_subscriptions(Ls, User, Server,
gen_mod:db_type(LServer, ?MODULE)).
get_in_pending_subscriptions(Ls, User, Server, DBType)
when DBType == mnesia; DBType == riak ->
- JID = jlib:make_jid(User, Server, <<"">>),
+ JID = jid:make(User, Server, <<"">>),
Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
Ls ++ lists:map(fun (R) ->
Message = R#roster.askmessage,
@@ -1156,8 +1122,8 @@ get_in_pending_subscriptions(Ls, User, Server, DBType)
#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
- jlib:jid_to_string(R#roster.jid)},
- {<<"to">>, jlib:jid_to_string(JID)},
+ jid:to_string(R#roster.jid)},
+ {<<"to">>, jid:to_string(JID)},
{<<"type">>, <<"subscribe">>}],
children =
[#xmlel{name = <<"status">>,
@@ -1174,25 +1140,19 @@ get_in_pending_subscriptions(Ls, User, Server, DBType)
end,
Result));
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
- JID = jlib:make_jid(User, Server, <<"">>),
+ JID = jid:make(User, Server, <<"">>),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
+ case catch odbc_queries:get_roster(LServer, LUser) of
+ {selected, Items} when is_list(Items) ->
Ls ++
lists:map(fun (R) ->
Message = R#roster.askmessage,
#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
- jlib:jid_to_string(R#roster.jid)},
- {<<"to">>, jlib:jid_to_string(JID)},
+ jid:to_string(R#roster.jid)},
+ {<<"to">>, jid:to_string(JID)},
{<<"type">>, <<"subscribe">>}],
children =
[#xmlel{name = <<"status">>,
@@ -1219,8 +1179,8 @@ get_in_pending_subscriptions(Ls, User, Server, odbc) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_subscription_and_groups(User, Server, LJID) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
read_subscription_and_groups(LUser, LServer, LJID,
gen_mod:db_type(LServer, ?MODULE)).
@@ -1236,12 +1196,9 @@ read_subscription_and_groups(LUser, LServer, LJID,
end;
read_subscription_and_groups(LUser, LServer, LJID,
odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
- case catch odbc_queries:get_subscription(LServer,
- Username, SJID)
- of
- {selected, [<<"subscription">>], [[SSubscription]]} ->
+ SJID = jid:to_string(LJID),
+ case catch odbc_queries:get_subscription(LServer, LUser, SJID) of
+ {selected, [{SSubscription}]} ->
Subscription = case SSubscription of
<<"B">> -> both;
<<"T">> -> to;
@@ -1249,11 +1206,11 @@ read_subscription_and_groups(LUser, LServer, LJID,
_ -> none
end,
Groups = case catch
- odbc_queries:get_rostergroup_by_jid(LServer, Username,
+ odbc_queries:get_rostergroup_by_jid(LServer, LUser,
SJID)
of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
_ -> []
end,
{Subscription, Groups};
@@ -1270,11 +1227,11 @@ read_subscription_and_groups(LUser, LServer, LJID,
end.
get_jid_info(_, User, Server, JID) ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
case read_subscription_and_groups(User, Server, LJID) of
{Subscription, Groups} -> {Subscription, Groups};
error ->
- LRJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ LRJID = jid:tolower(jid:remove_resource(JID)),
if LRJID == LJID -> {none, []};
true ->
case read_subscription_and_groups(User, Server, LRJID)
@@ -1290,10 +1247,16 @@ get_jid_info(_, User, Server, JID) ->
raw_to_record(LServer,
[User, SJID, Nick, SSubscription, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType]) ->
- case jlib:string_to_jid(SJID) of
+ raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType});
+raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType}) ->
+ case jid:from_string(SJID) of
error -> error;
JID ->
- LJID = jlib:jid_tolower(JID),
+ LJID = jid:tolower(JID),
Subscription = case SSubscription of
<<"B">> -> both;
<<"T">> -> to;
@@ -1319,7 +1282,7 @@ record_to_string(#roster{us = {User, _Server},
ask = Ask, askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID =
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
both -> <<"B">>;
@@ -1339,11 +1302,32 @@ record_to_string(#roster{us = {User, _Server},
[Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
<<"N">>, <<"">>, <<"item">>].
+record_to_row(
+ #roster{us = {LUser, _LServer},
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
+ SJID = jid:to_string(jid:tolower(JID)),
+ SSubscription = case Subscription of
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
+ end,
+ SAsk = case Ask of
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
+ end,
+ {LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
+
groups_to_string(#roster{us = {User, _Server},
jid = JID, groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
SJID =
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
lists:foldl(fun (<<"">>, Acc) -> Acc;
(Group, Acc) ->
G = ejabberd_odbc:escape(Group),
@@ -1383,7 +1367,7 @@ update_roster_table() ->
groups = [iolist_to_binary(G) || G <- Gs],
askmessage = try iolist_to_binary(Ask)
catch _:_ -> <<"">> end,
- xs = [xml:to_xmlel(X) || X <- Xs]}
+ xs = [fxml:to_xmlel(X) || X <- Xs]}
end);
_ ->
?INFO_MSG("Recreating roster table", []),
@@ -1391,7 +1375,7 @@ update_roster_table() ->
end.
%% Convert roster table to support virtual host
-%% Convert roster table: xattrs fields become
+%% Convert roster table: xattrs fields become
update_roster_version_table() ->
Fields = record_info(fields, roster_version),
case mnesia:table_info(roster_version, attributes) of
@@ -1417,8 +1401,8 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
user_roster(User, Server, Query, Lang) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
Items1 = get_roster(LUser, LServer),
Res = user_roster_parse_query(User, Server, Items1,
@@ -1504,7 +1488,7 @@ user_roster(User, Server, Query, Lang) ->
<<"Add Jabber ID">>)]))].
build_contact_jid_td(RosterJID) ->
- ContactJID = jlib:make_jid(RosterJID),
+ ContactJID = jid:make(RosterJID),
JIDURI = case {ContactJID#jid.luser,
ContactJID#jid.lserver}
of
@@ -1520,10 +1504,10 @@ build_contact_jid_td(RosterJID) ->
case JIDURI of
<<>> ->
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}],
- (jlib:jid_to_string(RosterJID)));
+ (jid:to_string(RosterJID)));
URI when is_binary(URI) ->
?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?AC(JIDURI, (jlib:jid_to_string(RosterJID)))])
+ [?AC(JIDURI, (jid:to_string(RosterJID)))])
end.
user_roster_parse_query(User, Server, Items, Query) ->
@@ -1531,7 +1515,7 @@ user_roster_parse_query(User, Server, Items, Query) ->
{value, _} ->
case lists:keysearch(<<"newjid">>, 1, Query) of
{value, {_, SJID}} ->
- case jlib:string_to_jid(SJID) of
+ case jid:from_string(SJID) of
JID when is_record(JID, jid) ->
user_roster_subscribe_jid(User, Server, JID), ok;
error -> error
@@ -1550,7 +1534,7 @@ user_roster_parse_query(User, Server, Items, Query) ->
user_roster_subscribe_jid(User, Server, JID) ->
out_subscription(User, Server, JID, subscribe),
- UJID = jlib:make_jid(User, Server, <<"">>),
+ UJID = jid:make(User, Server, <<"">>),
ejabberd_router:route(UJID, JID,
#xmlel{name = <<"presence">>,
attrs = [{<<"type">>, <<"subscribe">>}],
@@ -1565,10 +1549,10 @@ user_roster_item_parse_query(User, Server, Items,
1, Query)
of
{value, _} ->
- JID1 = jlib:make_jid(JID),
+ JID1 = jid:make(JID),
out_subscription(User, Server, JID1,
subscribed),
- UJID = jlib:make_jid(User, Server, <<"">>),
+ UJID = jid:make(User, Server, <<"">>),
ejabberd_router:route(UJID, JID1,
#xmlel{name =
<<"presence">>,
@@ -1583,7 +1567,7 @@ user_roster_item_parse_query(User, Server, Items,
1, Query)
of
{value, _} ->
- UJID = jlib:make_jid(User, Server,
+ UJID = jid:make(User, Server,
<<"">>),
process_iq_set(UJID, UJID,
#iq{type = set,
@@ -1600,7 +1584,7 @@ user_roster_item_parse_query(User, Server, Items,
attrs
=
[{<<"jid">>,
- jlib:jid_to_string(JID)},
+ jid:to_string(JID)},
{<<"subscription">>,
<<"remove">>}],
children
@@ -1615,7 +1599,7 @@ user_roster_item_parse_query(User, Server, Items,
nothing.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, <<"">>}).
+ jid:to_string({User, Server, <<"">>}).
webadmin_user(Acc, _User, _Server, Lang) ->
Acc ++
@@ -1676,7 +1660,7 @@ is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
false.
is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) ->
- case jlib:string_to_jid(JIDString) of
+ case jid:from_string(JIDString) of
JID when JID#jid.lserver == MatchDomain -> true;
_ -> false
end;
@@ -1716,7 +1700,7 @@ export(_Server) ->
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
+ SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
ItemVals = record_to_string(R),
ItemGroups = groups_to_string(R),
odbc_queries:update_roster_sql(Username, SJID,
@@ -1767,3 +1751,17 @@ import(_LServer, riak, #roster_version{} = RV) ->
ejabberd_riak:put(RV, roster_version_schema());
import(_, _, _) ->
pass.
+
+mod_opt_type(access) ->
+ fun (A) when is_atom(A) -> A end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(managers) ->
+ fun (B) when is_list(B) -> B end;
+mod_opt_type(store_current_id) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(versioning) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [access, db_type, iqdisc, managers, store_current_id,
+ versioning].
diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl
index 7a23b401..3e1da3a4 100644
--- a/src/mod_service_log.erl
+++ b/src/mod_service_log.erl
@@ -5,7 +5,7 @@
%%% Created : 24 Aug 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -29,10 +29,8 @@
-behaviour(gen_mod).
--export([start/2,
- stop/1,
- log_user_send/3,
- log_user_receive/4]).
+-export([start/2, stop/1, log_user_send/4,
+ log_user_receive/5, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -53,11 +51,13 @@ stop(Host) ->
?MODULE, log_user_receive, 50),
ok.
-log_user_send(From, To, Packet) ->
- log_packet(From, To, Packet, From#jid.lserver).
+log_user_send(Packet, _C2SState, From, To) ->
+ log_packet(From, To, Packet, From#jid.lserver),
+ Packet.
-log_user_receive(_JID, From, To, Packet) ->
- log_packet(From, To, Packet, To#jid.lserver).
+log_user_receive(Packet, _C2SState, _JID, From, To) ->
+ log_packet(From, To, Packet, To#jid.lserver),
+ Packet.
log_packet(From, To,
#xmlel{name = Name, attrs = Attrs, children = Els},
@@ -67,7 +67,7 @@ log_packet(From, To,
lists:map(
fun(S) ->
B = iolist_to_binary(S),
- N = jlib:nameprep(B),
+ N = jid:nameprep(B),
if N /= error ->
N
end
@@ -77,8 +77,8 @@ log_packet(From, To,
resource = <<"">>, luser = <<"">>, lserver = Host,
lresource = <<"">>},
NewAttrs =
- jlib:replace_from_to_attrs(jlib:jid_to_string(From),
- jlib:jid_to_string(To), Attrs),
+ jlib:replace_from_to_attrs(jid:to_string(From),
+ jid:to_string(To), Attrs),
FixedPacket = #xmlel{name = Name, attrs = NewAttrs,
children = Els},
lists:foreach(fun (Logger) ->
@@ -95,3 +95,14 @@ log_packet(From, To,
[FixedPacket]})
end,
Loggers).
+
+mod_opt_type(loggers) ->
+ fun (L) ->
+ lists:map(fun (S) ->
+ B = iolist_to_binary(S),
+ N = jid:nameprep(B),
+ if N /= error -> N end
+ end,
+ L)
+ end;
+mod_opt_type(_) -> [loggers].
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 4c3f177e..212a7d47 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -5,7 +5,7 @@
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -29,16 +29,17 @@
-behaviour(gen_mod).
--export([start/2, stop/1, item_to_xml/1, export/1, import/1,
- webadmin_menu/3, webadmin_page/3, get_user_roster/2,
- get_subscription_lists/3, get_jid_info/4, import/3,
- process_item/2, in_subscription/6, out_subscription/4,
- user_available/1, unset_presence/4, register_user/2,
- remove_user/2, list_groups/1, create_group/2,
- create_group/3, delete_group/2, get_group_opts/2,
- set_group_opts/3, get_group_users/2,
- get_group_explicit_users/2, is_user_in_group/3,
- add_user_to_group/3, remove_user_from_group/3]).
+-export([start/2, stop/1, item_to_xml/1, export/1,
+ import/1, webadmin_menu/3, webadmin_page/3,
+ get_user_roster/2, get_subscription_lists/3,
+ get_jid_info/4, import/3, process_item/2,
+ in_subscription/6, out_subscription/4, user_available/1,
+ unset_presence/4, register_user/2, remove_user/2,
+ list_groups/1, create_group/2, create_group/3,
+ delete_group/2, get_group_opts/2, set_group_opts/3,
+ get_group_users/2, get_group_explicit_users/2,
+ is_user_in_group/3, add_user_to_group/3,
+ remove_user_from_group/3, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -97,9 +98,6 @@ start(Host, Opts) ->
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50).
-%%ejabberd_hooks:add(remove_user, Host,
-%% ?MODULE, remove_user, 50),
-
stop(Host) ->
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE,
webadmin_menu, 70),
@@ -125,12 +123,11 @@ stop(Host) ->
register_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
-%%ejabberd_hooks:delete(remove_user, Host,
-%% ?MODULE, remove_user, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user,
- 50).%%ejabberd_hooks:delete(remove_user, Host,
- %% ?MODULE, remove_user, 50),
+ 50).
+ %%ejabberd_hooks:delete(remove_user, Host,
+ %% ?MODULE, remove_user, 50),
get_user_roster(Items, US) ->
{U, S} = US,
@@ -185,8 +182,8 @@ get_vcard_module(Server) ->
get_rosteritem_name([], _, _) -> <<"">>;
get_rosteritem_name([ModVcard], U, S) ->
- From = jlib:make_jid(<<"">>, S, jlib:atom_to_binary(?MODULE)),
- To = jlib:make_jid(U, S, <<"">>),
+ From = jid:make(<<"">>, S, jlib:atom_to_binary(?MODULE)),
+ To = jid:make(U, S, <<"">>),
case lists:member(To#jid.lserver, ?MYHOSTS) of
true ->
IQ = {iq, <<"">>, get, <<"vcard-temp">>, <<"">>,
@@ -209,11 +206,11 @@ get_rosteritem_name([ModVcard], U, S) ->
get_rosteritem_name_vcard([]) -> <<"">>;
get_rosteritem_name_vcard([Vcard]) ->
- case xml:get_path_s(Vcard,
+ case fxml:get_path_s(Vcard,
[{elem, <<"NICKNAME">>}, cdata])
of
<<"">> ->
- xml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
+ fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
Nickname -> Nickname
end.
@@ -249,11 +246,11 @@ process_item(RosterItem, Host) ->
%% existing roster groups.
[] ->
mod_roster:out_subscription(UserTo, ServerTo,
- jlib:make_jid(UserFrom, ServerFrom,
+ jid:make(UserFrom, ServerFrom,
<<"">>),
unsubscribe),
mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
- jlib:make_jid(UserTo, ServerTo,
+ jid:make(UserTo, ServerTo,
<<"">>),
unsubscribe, <<"">>),
RosterItem#roster{subscription = both, ask = none};
@@ -278,8 +275,8 @@ set_new_rosteritems(UserFrom, ServerFrom, UserTo,
RIFrom = build_roster_record(UserFrom, ServerFrom,
UserTo, ServerTo, NameTo, GroupsFrom),
set_item(UserFrom, ServerFrom, ResourceTo, RIFrom),
- JIDTo = jlib:make_jid(UserTo, ServerTo, <<"">>),
- JIDFrom = jlib:make_jid(UserFrom, ServerFrom, <<"">>),
+ JIDTo = jid:make(UserTo, ServerTo, <<"">>),
+ JIDFrom = jid:make(UserFrom, ServerFrom, <<"">>),
RITo = build_roster_record(UserTo, ServerTo, UserFrom,
ServerFrom, UserFrom, []),
set_item(UserTo, ServerTo, <<"">>, RITo),
@@ -308,14 +305,14 @@ set_item(User, Server, Resource, Item) ->
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
children = [mod_roster:item_to_xml(Item)]}]},
- ejabberd_router:route(jlib:make_jid(User, Server,
+ ejabberd_router:route(jid:make(User, Server,
Resource),
- jlib:make_jid(<<"">>, Server, <<"">>),
+ jid:make(<<"">>, Server, <<"">>),
jlib:iq_to_xml(ResIQ)).
get_subscription_lists({F, T}, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers = lists:usort(lists:flatmap(fun (Group) ->
@@ -327,10 +324,10 @@ get_subscription_lists({F, T}, User, Server) ->
get_jid_info({Subscription, Groups}, User, Server,
JID) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
- {U1, S1, _} = jlib:jid_tolower(JID),
+ {U1, S1, _} = jid:tolower(JID),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers = lists:foldl(fun (Group, Acc1) ->
@@ -360,7 +357,7 @@ in_subscription(Acc, User, Server, JID, Type,
out_subscription(UserFrom, ServerFrom, JIDTo,
unsubscribed) ->
#jid{luser = UserTo, lserver = ServerTo} = JIDTo,
- JIDFrom = jlib:make_jid(UserFrom, ServerFrom, <<"">>),
+ JIDFrom = jid:make(UserFrom, ServerFrom, <<"">>),
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
unsubscribe),
mod_roster:in_subscription(aaaa, UserFrom, ServerFrom,
@@ -373,11 +370,11 @@ out_subscription(User, Server, JID, Type) ->
process_subscription(Direction, User, Server, JID,
_Type, Acc) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
{U1, S1, _} =
- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ jid:tolower(jid:remove_resource(JID)),
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers = lists:usort(lists:flatmap(fun (Group) ->
@@ -621,7 +618,6 @@ get_group_users(Host1, Group1) ->
get_group_users(Host, Group, GroupOpts) ->
case proplists:get_value(all_users, GroupOpts, false) of
-%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
true -> ejabberd_auth:get_vh_registered_users(Host);
false -> []
end
@@ -661,7 +657,7 @@ get_group_explicit_users(Host, Group, odbc) ->
{selected, [<<"jid">>], Rs} ->
lists:map(fun ([JID]) ->
{U, S, _} =
- jlib:jid_tolower(jlib:string_to_jid(JID)),
+ jid:tolower(jid:from_string(JID)),
{U, S}
end,
Rs);
@@ -674,25 +670,22 @@ get_group_name(Host1, Group1) ->
%% Get list of names of groups that have @all@/@online@/etc in the memberlist
get_special_users_groups(Host) ->
-%% Get list of names of groups that have @online@ in the memberlist
lists:filter(fun (Group) ->
get_group_opt(Host, Group, all_users, false) orelse
get_group_opt(Host, Group, online_users, false)
end,
list_groups(Host)).
+%% Get list of names of groups that have @online@ in the memberlist
get_special_users_groups_online(Host) ->
-%% Given two lists of groupnames and their options,
-%% return the list of displayed groups to the second list
lists:filter(fun (Group) ->
get_group_opt(Host, Group, online_users, false)
end,
list_groups(Host)).
+%% Given two lists of groupnames and their options,
+%% return the list of displayed groups to the second list
displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
-%% Given a list of group names with options,
-%% for those that have @all@ in memberlist,
-%% get the list of groups displayed
DisplayedGroups = lists:usort(lists:flatmap(fun
({_Group, Opts}) ->
[G
@@ -711,6 +704,9 @@ displayed_groups(GroupsOpts, SelectedGroupsOpts) ->
lists:member(disabled,
proplists:get_value(G, GroupsOpts, []))].
+%% Given a list of group names with options,
+%% for those that have @all@ in memberlist,
+%% get the list of groups displayed
get_special_displayed_groups(GroupsOpts) ->
Groups = lists:filter(fun ({_Group, Opts}) ->
proplists:get_value(all_users, Opts, false)
@@ -824,7 +820,7 @@ is_user_in_group(US, Group, Host, odbc) ->
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
add_user_to_group(Host, US, Group) ->
{LUser, LServer} = US,
- case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of
+ case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
GroupOpts = (?MODULE):get_group_opts(Host, Group),
MoreGroupOpts = case LUser of
@@ -883,7 +879,7 @@ push_displayed_to_user(LUser, LServer, Host, Subscription, DisplayedGroups) ->
remove_user_from_group(Host, US, Group) ->
{LUser, LServer} = US,
- case ejabberd_regexp:run(LUser, <<"^@.+@$">>) of
+ case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
GroupOpts = (?MODULE):get_group_opts(Host, Group),
NewGroupOpts = case LUser of
@@ -953,8 +949,8 @@ remove_user(User, Server) ->
push_user_to_members(User, Server, remove).
push_user_to_members(User, Server, Subscription) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
GroupsOpts = groups_with_opts(LServer),
SpecialGroups =
get_special_displayed_groups(GroupsOpts),
@@ -984,11 +980,11 @@ push_user_to_displayed(LUser, LServer, Group, Host, Subscription, DisplayedToGro
GroupName = proplists:get_value(name, GroupOpts, Group),
[push_user_to_group(LUser, LServer, GroupD, Host,
GroupName, Subscription)
- || {GroupD, _Opts} <- DisplayedToGroupsOpts].
+ || GroupD <- DisplayedToGroupsOpts].
broadcast_user_to_displayed(LUser, LServer, Host, Subscription, DisplayedToGroupsOpts) ->
[broadcast_user_to_group(LUser, LServer, GroupD, Host, Subscription)
- || {GroupD, _Opts} <- DisplayedToGroupsOpts].
+ || GroupD <- DisplayedToGroupsOpts].
push_user_to_group(LUser, LServer, Group, Host,
GroupName, Subscription) ->
@@ -1011,12 +1007,13 @@ broadcast_user_to_group(LUser, LServer, Group, Host, Subscription) ->
%% Get list of groups to which this group is displayed
displayed_to_groups(GroupName, LServer) ->
GroupsOpts = groups_with_opts(LServer),
- lists:filter(fun ({_Group, Opts}) ->
+ Gs = lists:filter(fun ({_Group, Opts}) ->
lists:member(GroupName,
proplists:get_value(displayed_groups,
Opts, []))
end,
- GroupsOpts).
+ GroupsOpts),
+ [Name || {Name, _} <- Gs].
push_item(User, Server, Item) ->
Stanza = jlib:iq_to_xml(#iq{type = set,
@@ -1027,8 +1024,8 @@ push_item(User, Server, Item) ->
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
children = [item_to_xml(Item)]}]}),
lists:foreach(fun (Resource) ->
- JID = jlib:make_jid(User, Server, Resource),
- ejabberd_router:route(JID, JID, Stanza)
+ JID = jid:make(User, Server, Resource),
+ ejabberd_router:route(jid:remove_resource(JID), JID, Stanza)
end,
ejabberd_sm:get_user_resources(User, Server)).
@@ -1043,7 +1040,7 @@ push_roster_item(User, Server, ContactU, ContactS,
item_to_xml(Item) ->
Attrs1 = [{<<"jid">>,
- jlib:jid_to_string(Item#roster.jid)}],
+ jid:to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
<<"">> -> Attrs1;
Name -> [{<<"name">>, Name} | Attrs1]
@@ -1163,7 +1160,7 @@ list_shared_roster_groups(Host, Query, Lang) ->
[?INPUTT(<<"submit">>, <<"addnew">>,
<<"Add New">>)])])]))])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -1257,7 +1254,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
<<"20">>,
list_to_binary(FDisplayedGroups))])])])])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
[?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++
case Res of
@@ -1301,7 +1298,7 @@ shared_roster_group_parse_query(Host, Group, Query) ->
<<"@all@">> -> USs;
<<"@online@">> -> USs;
_ ->
- case jlib:string_to_jid(SJID)
+ case jid:from_string(SJID)
of
JID
when is_record(JID,
@@ -1361,7 +1358,7 @@ get_opt(Opts, Opt, Default) ->
end.
us_to_list({User, Server}) ->
- jlib:jid_to_string({User, Server, <<"">>}).
+ jid:to_string({User, Server, <<"">>}).
split_grouphost(Host, Group) ->
case str:tokens(Group, <<"@">>) of
@@ -1371,8 +1368,8 @@ split_grouphost(Host, Group) ->
broadcast_subscription(User, Server, ContactJid, Subscription) ->
ejabberd_sm:route(
- jlib:make_jid(<<"">>, Server, <<"">>),
- jlib:make_jid(User, Server, <<"">>),
+ jid:make(<<"">>, Server, <<"">>),
+ jid:make(User, Server, <<"">>),
{broadcast, {item, ContactJid,
Subscription}}).
@@ -1389,7 +1386,7 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) ->
end, Members).
make_jid_s(U, S) ->
- ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:make_jid(U,
+ ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U,
S,
<<"">>)))).
@@ -1469,9 +1466,9 @@ export(_Server) ->
when LServer == Host ->
SGroup = ejabberd_odbc:escape(Group),
SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:make_jid(U, S, <<"">>)))),
+ jid:to_string(
+ jid:tolower(
+ jid:make(U, S, <<"">>)))),
[[<<"delete from sr_user where jid='">>, SJID,
<<"'and grp='">>, Group, <<"';">>],
[<<"insert into sr_user(jid, grp) values ('">>,
@@ -1488,7 +1485,7 @@ import(LServer) ->
end},
{<<"select jid, grp from sr_user;">>,
fun([SJID, Group]) ->
- #jid{luser = U, lserver = S} = jlib:string_to_jid(SJID),
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
#sr_user{us = {U, S}, group_host = {Group, LServer}}
end}].
@@ -1506,3 +1503,6 @@ import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
{<<"group_host">>, {Group, Host}}]}]);
import(_, _, _) ->
pass.
+
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(_) -> [db_type].
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index af85e4d4..dd6a7f6d 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -3,11 +3,12 @@
%%% Author : Realloc <realloc@realloc.spb.ru>
%%% Marcin Owsiany <marcin@owsiany.pl>
%%% Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Contributor : Mike Kaganski <mikekaganski@hotmail.com>
%%% Description : LDAP shared roster management
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -39,22 +40,18 @@
-export([get_user_roster/2, get_subscription_lists/3,
get_jid_info/4, process_item/2, in_subscription/6,
- out_subscription/4]).
+ out_subscription/4, mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
--include("logger.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-
-include("eldap.hrl").
+-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
--define(CACHE_SIZE, 1000).
-
--define(USER_CACHE_VALIDITY, 300).
-
--define(GROUP_CACHE_VALIDITY, 300).
-
--define(LDAP_SEARCH_TIMEOUT, 5).
+-define(CACHE_SIZE, 1).
+-define(CACHE_VALIDITY, 300). %% in seconds
+-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
+-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function.").
-record(state,
{host = <<"">> :: binary(),
@@ -72,7 +69,6 @@
group_attr = <<"">> :: binary(),
group_desc = <<"">> :: binary(),
user_desc = <<"">> :: binary(),
- user_uid = <<"">> :: binary(),
uid_format = <<"">> :: binary(),
uid_format_re = <<"">> :: binary(),
filter = <<"">> :: binary(),
@@ -80,25 +76,51 @@
rfilter = <<"">> :: binary(),
gfilter = <<"">> :: binary(),
auth_check = true :: boolean(),
- user_cache_size = ?CACHE_SIZE :: non_neg_integer(),
- group_cache_size = ?CACHE_SIZE :: non_neg_integer(),
- user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(),
- group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}).
-
+ %% Group data parameters
+ group_base = <<"">> :: binary(),
+ %% - Subgroup of roster filter
+ %% This filter defines which groups are displayed in the shared roster
+ %% Valid values are 'all' or "LDAP filter string" or "LDAP filter string containing %g"
+ shgfilter = <<"">> :: binary(),
+ shg_attr = <<"">> :: binary(),
+ %% - Subgroup of group filter
+ group_is_dn = true :: boolean(),
+ member_attr = <<"">> :: binary(),
+ %% User data parameters
+ member_selection_mode = memberattr_dn :: memberattr_normal | memberattr_dn |
+ group_children,
+ %% Algorithm control parameters
+ subscribe_all = false :: binary(),
+ roster_cache_size = ?CACHE_SIZE :: non_neg_integer(),
+ roster_cache_validity = ?CACHE_VALIDITY :: non_neg_integer()}).
+
+%% If #state.member_selection_mode is memberattr_normal or memberattr_dn,
+%% then members is list of member_attr values;
+%% if #state.member_selection_mode is group_children,
+%% then members is dn of the group (to make it possible to search for its subtree)
-record(group_info, {desc, members}).
+-record(user_info, {us, name}).
+
+-record(shared_roster_item, {us, name, groups}).
+
+% Groups visible to this group
+% grp may be atom 'all' or a group name string.
+% shgrps is a list containing one or more grp
+-record(shg_data, {grp, shgrps}).
+
%%====================================================================
%% API
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
- permanent, 1000, worker, [?MODULE]},
+ permanent, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -110,109 +132,92 @@ stop(Host) ->
%% Hooks
%%--------------------------------------------------------------------
get_user_roster(Items, {U, S} = US) ->
- SRUsers = get_user_to_groups_map(US, true),
- {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item,
- SRUsers1) ->
- {_, _, {U1, S1, _}} =
- Item#roster.usj,
- US1 = {U1, S1},
- case dict:find(US1,
- SRUsers1)
- of
- {ok, _GroupNames} ->
- {Item#roster{subscription
- =
- both,
- ask =
- none},
- dict:erase(US1,
- SRUsers1)};
- error ->
- {Item, SRUsers1}
- end
- end,
- SRUsers, Items),
+ {ok, State} = eldap_utils:get_state(S, ?MODULE),
+ SRUsers = get_shared_roster(State, US),
+ %%?ERROR_MSG("XXXXXX get_user_roster: SRUsers=~p", [SRUsers]),
+ %% If partially subscribed users are also in shared roster,
+ %% show them as totally subscribed:
+ {NewItems1, SRUsersRest} = lists:mapfoldl(
+ fun(Item, SRUsers1) ->
+ {_, _, {U1, S1, _}} = Item#roster.usj,
+ US1 = {U1, S1},
+ case lists:keytake(US1, #shared_roster_item.us, SRUsers1) of
+ %%case dict:find(US1, SRUsers1) of
+ {value, _, SRUsers2} -> {Item#roster{subscription = both, ask = none}, SRUsers2};
+ %%{ok, _GroupNames} -> {Item#roster{subscription = both, ask = none}, dict:erase(US1, SRUsers1)};
+ false -> {Item, SRUsers1}
+ end
+ end,
+ SRUsers, Items),
+ %% Export items in roster format:
SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
- us = US, jid = {U1, S1, <<"">>},
- name = get_user_name(U1, S1), subscription = both,
- ask = none, groups = GroupNames}
- || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
+ us = US,
+ jid = {U1, S1, <<"">>},
+ name = Name,
+ subscription = both,
+ ask = none,
+ groups = Groups} ||
+ #shared_roster_item{us = {U1, S1}, name = Name, groups = Groups} <- SRUsersRest],
+ %% SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
+ %% us = US, jid = {U1, S1, <<"">>},
+ %% name = get_user_name(U1, S1), subscription = both,
+ %% ask = none, groups = GroupNames}
+ %% || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
%% This function in use to rewrite the roster entries when moving or renaming
%% them in the user contact list.
process_item(RosterItem, _Host) ->
- USFrom = RosterItem#roster.us,
- {User, Server, _Resource} = RosterItem#roster.jid,
- USTo = {User, Server},
- Map = get_user_to_groups_map(USFrom, false),
- case dict:find(USTo, Map) of
- error -> RosterItem;
- {ok, []} -> RosterItem;
- {ok, GroupNames}
- when RosterItem#roster.subscription == remove ->
- RosterItem#roster{subscription = both, ask = none,
- groups = GroupNames};
- _ -> RosterItem#roster{subscription = both, ask = none}
+ {ok, State} = eldap_utils:get_state(_Host, ?MODULE),
+ {User,Server,_Resource} = RosterItem#roster.jid,
+ USTo = {User,Server},
+ SR = get_shared_roster(State, RosterItem#roster.us),
+ case lists:keysearch(USTo, #shared_roster_item.us, SR) of
+ false ->
+ RosterItem;
+ {value, #shared_roster_item{groups = Groups}} when RosterItem#roster.subscription == remove ->
+ %% Roster item cannot be removed:
+ %% We simply reset the original groups:
+ RosterItem#roster{subscription = both, ask = none,
+ groups=Groups};
+ _ ->
+ RosterItem#roster{subscription = both, ask = none}
end.
get_subscription_lists({F, T}, User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
- get_group_users(LServer, Group)
- end,
- DisplayedGroups)),
- SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
+ U = jid:nodeprep(User),
+ S = jid:nameprep(Server),
+ {ok, State} = eldap_utils:get_state(S, ?MODULE),
+ SRJIDs = get_presense_subscribers(State, {U, S}),
+%?INFO_MSG("SRJIDs: ~p", [SRJIDs]),
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
-get_jid_info({Subscription, Groups}, User, Server,
- JID) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- {U1, S1, _} = jlib:jid_tolower(JID),
+get_jid_info({Subscription, Groups}, User, Server, JID) ->
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ {U1, S1, _} = jid:tolower(JID),
US1 = {U1, S1},
- SRUsers = get_user_to_groups_map(US, false),
- case dict:find(US1, SRUsers) of
- {ok, GroupNames} ->
- NewGroups = if Groups == [] -> GroupNames;
- true -> Groups
- end,
- {both, NewGroups};
- error -> {Subscription, Groups}
+ SR = get_shared_roster(State, {User, Server}),
+ case lists:keysearch(US1, #shared_roster_item.us, SR) of
+ false -> {Subscription, Groups};
+ {value, #shared_roster_item{groups = GroupNames}} when Groups == [] -> {both, GroupNames};
+ _ -> {both, Groups}
end.
-in_subscription(Acc, User, Server, JID, Type,
- _Reason) ->
+in_subscription(Acc, User, Server, JID, Type, _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type,
- false).
-
-process_subscription(Direction, User, Server, JID,
- _Type, Acc) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- {U1, S1, _} =
- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ process_subscription(out, User, Server, JID, Type, false).
+
+process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ {U1, S1, _} = jid:tolower(JID),
US1 = {U1, S1},
- DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
- get_group_users(LServer, Group)
- end,
- DisplayedGroups)),
- case lists:member(US1, SRUsers) of
- true ->
- case Direction of
- in -> {stop, false};
- out -> stop
- end;
- false -> Acc
+ SR = get_shared_roster(State, {User, Server}),
+ case lists:keysearch(US1, #shared_roster_item.us, SR) of
+ false -> Acc;
+ _ when Direction == in -> {stop, false};
+ _ -> stop
end.
%%====================================================================
@@ -220,274 +225,509 @@ process_subscription(Direction, User, Server, JID,
%%====================================================================
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- cache_tab:new(shared_roster_ldap_user,
- [{max_size, State#state.user_cache_size}, {lru, false},
- {life_time, State#state.user_cache_validity}]),
- cache_tab:new(shared_roster_ldap_group,
- [{max_size, State#state.group_cache_size}, {lru, false},
- {life_time, State#state.group_cache_validity}]),
- ejabberd_hooks:add(roster_get, Host, ?MODULE,
- get_user_roster, 70),
+ if
+ State#state.roster_cache_size > 0 ->
+ cache_tab:new(shared_roster_ldap_sr,
+ [{max_size, State#state.roster_cache_size},
+ {lru, false}, % We don't need LRU algorithm
+ {life_time, State#state.roster_cache_validity}]);
+ true ->
+ false
+ end,
+ ejabberd_hooks:add(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:add(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
+ ?MODULE, out_subscription, 30),
ejabberd_hooks:add(roster_get_subscription_lists, Host,
- ?MODULE, get_subscription_lists, 70),
- ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE,
- get_jid_info, 70),
- ejabberd_hooks:add(roster_process_item, Host, ?MODULE,
- process_item, 50),
+ ?MODULE, get_subscription_lists, 70),
+ ejabberd_hooks:add(roster_get_jid_info, Host,
+ ?MODULE, get_jid_info, 70),
+ ejabberd_hooks:add(roster_process_item, Host,
+ ?MODULE, process_item, 50),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers, State#state.backups,
- State#state.port, State#state.dn,
- State#state.password, State#state.tls_options),
+ State#state.servers,
+ State#state.backups,
+ State#state.port,
+ State#state.dn,
+ State#state.password,
+ State#state.tls_options),
{ok, State}.
handle_call(get_state, _From, State) ->
{reply, {ok, State}, State};
+
handle_call(_Request, _From, State) ->
{reply, {error, badarg}, State}.
-handle_cast(_Msg, State) -> {noreply, State}.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
-handle_info(_Info, State) -> {noreply, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
terminate(_Reason, State) ->
Host = State#state.host,
- ejabberd_hooks:delete(roster_get, Host, ?MODULE,
- get_user_roster, 70),
+ ejabberd_hooks:delete(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:delete(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
- ejabberd_hooks:delete(roster_get_subscription_lists,
- Host, ?MODULE, get_subscription_lists, 70),
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:delete(roster_get_subscription_lists, Host,
+ ?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
+ ?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
- ?MODULE, process_item, 50).
+ ?MODULE, process_item, 50).
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-%% For a given user, map all his shared roster contacts to groups they are
-%% members of. Skip the user himself iff SkipUS is true.
-get_user_to_groups_map({_, Server} = US, SkipUS) ->
- DisplayedGroups = get_user_displayed_groups(US),
-%% Pass given FilterParseArgs to eldap_filter:parse, and if successful, run and
-%% return the resulting filter, retrieving given AttributesList. Return the
-%% result entries. On any error silently return an empty list of results.
-%%
-%% Eldap server ID and base DN for the query are both retrieved from the State
-%% record.
- lists:foldl(fun (Group, Dict1) ->
- GroupName = get_group_name(Server, Group),
- lists:foldl(fun (Contact, Dict) ->
- if SkipUS, Contact == US -> Dict;
- true ->
- dict:append(Contact,
- GroupName, Dict)
- end
- end,
- Dict1, get_group_users(Server, Group))
- end,
- dict:new(), DisplayedGroups).
-
-eldap_search(State, FilterParseArgs, AttributesList) ->
- case apply(eldap_filter, parse, FilterParseArgs) of
- {ok, EldapFilter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, EldapFilter},
- {timeout, ?LDAP_SEARCH_TIMEOUT},
- {deref_aliases, State#state.deref_aliases},
- {attributes, AttributesList}])
- of
- #eldap_search_result{entries = Es} ->
- %% A result with entries. Return their list.
- Es;
- _ ->
- %% Something else. Pretend we got no results.
- []
- end;
- _ ->
- %% Filter parsing failed. Pretend we got no results.
- []
+
+do_eldap_search(PoolName, Opts) ->
+ case eldap_pool:search(PoolName, Opts) of
+ #eldap_search_result{entries = Es} ->
+ %% A result with entries. Return their list.
+ Es;
+ Err ->
+ %% Something else. Pretend we got no results.
+ ?ERROR_MSG("Error searching: ~p ~p", [Err, Opts]),
+ []
+ end.
+
+%% Pass given Filter or FilterTemplate and SubstList to eldap_filter:parse,
+%% and if successful, run LDAP search on the whole subtree of Base, using
+%% resulting filter, retrieving given AttributesList. Return the result entries.
+%% On any error, print an error message and return an empty list of results.
+eldap_search(State, Base, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+ do_eldap_search(State#state.eldap_id,
+ [{base, Base},
+%% {scope, wholeSubtree} %% This is the default
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, AttributesList}]);
+%% Filter is string
+eldap_search(State, Base, Filter, AttributesList) ->
+ eldap_search(State, Base, Filter, [], AttributesList).
+
+eldap_search(State, Base, FilterTemplate, SubstList, AttributesList) ->
+ case apply(eldap_filter, parse, [eldap_filter:do_sub(FilterTemplate, SubstList)]) of
+ {ok, EldapFilter} ->
+ %% Filter parsing succeeded
+ eldap_search(State, Base, EldapFilter, AttributesList);
+ Err ->
+ %% Filter parsing failed. Pretend we got no results.
+ ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+ []
+ end.
+
+%% The same as above, but gets the Attributes for the specified DN.
+%% Note that this function doesn't honor the State's base DN;
+%% TODO: fix this (create a custom check?)
+eldap_search_dn(State, DN, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+ do_eldap_search(State#state.eldap_id,
+ [{scope, baseObject},
+ {base, DN},
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, AttributesList}]);
+%% Filter is string.
+eldap_search_dn(State, DN, Filter, AttributesList) ->
+ case eldap_filter:parse(Filter) of
+ {ok, EldapFilter} ->
+ %% Filter parsing succeeded
+ eldap_search_dn(State, DN, EldapFilter, AttributesList);
+ Err ->
+ %% Filter parsing failed. Pretend we got no results.
+ ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+ []
+ end.
+
+intersection(L1,L2) -> lists:filter(fun(X) -> lists:member(X,L1) end, L2).
+
+filter_roster(Roster, all) -> Roster;
+filter_roster(_, []) -> [];
+filter_roster(Roster, IncludeGroups) when is_list(IncludeGroups) ->
+ lists:foldl(
+ fun(RosterItem, Acc) ->
+ case intersection(IncludeGroups, RosterItem#shared_roster_item.groups) of
+ [] -> Acc;
+ CommonGroups -> [RosterItem#shared_roster_item{groups=CommonGroups} | Acc]
+ end
+ end,
+ [], Roster).
+
+get_user_visible_groups(UserGroups, VisibilityMap) ->
+ lists:foldl(
+ fun(Group, Acc) ->
+ case (lists:keysearch(Group, #shg_data.grp, VisibilityMap)) of
+ {value, #shg_data{shgrps=Gs}} when is_list(Gs) -> Gs ++ Acc;
+ _ -> Acc
+ end
+ end,
+ UserGroups, UserGroups).
+
+%% Returns [#shared_roster_item];
+%% Removes the US from returned data
+%% If State#state.user_groups_only is 'true', then it removes all users that are not in US's groups,
+%% and also removes the groups from the users that the US is not member of.
+get_shared_roster(State, {_, Server} = US) ->
+ case (catch get_full_roster(State, Server)) of
+ {ok, {VisibilityMap, FullRoster}} ->
+ %%?ERROR_MSG("XXXXXX get_shared_roster: VMap=~p FullRoster=~p", [VisibilityMap, FullRoster]),
+ CommonRosterGroups = lists:foldl(
+ fun(_, all) -> all;
+ (#shg_data{grp=all, shgrps=all}, _) -> all;
+ (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) -> Gs ++ Acc;
+ (_, Acc) -> Acc
+ end,
+ [], VisibilityMap),
+ case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+ false -> filter_roster(FullRoster, CommonRosterGroups);
+ {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+ VisibleGroups = case (CommonRosterGroups) of
+ all -> all;
+ CRG -> get_user_visible_groups(UserGroups, VisibilityMap) ++ CRG
+ end,
+ filter_roster(Roster2, VisibleGroups)
+ end;
+ {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+ _Unexpected -> []
end.
-get_user_displayed_groups({User, Host}) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- GroupAttr = State#state.group_attr,
- Entries = eldap_search(State,
- [eldap_filter:do_sub(State#state.rfilter,
- [{<<"%u">>, User}])],
- [GroupAttr]),
- Reply = lists:flatmap(fun (#eldap_entry{attributes =
- Attrs}) ->
- case Attrs of
- [{GroupAttr, ValuesList}] -> ValuesList;
- _ -> []
- end
- end,
- Entries),
- lists:usort(Reply).
-
-get_group_users(Host, Group) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_group,
- {Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{members = Members}}
- when Members /= undefined ->
- Members;
- _ -> []
+%% 1. If user is not a member of shared roster -> no additional subscriptions
+%% 2. Else if ldap_subscribe_all is set AND this user is member of a group published to all ->
+%% add all registered users of this vhost
+%% 3. Else add only those groups this user' groups are published to
+get_presense_subscribers(State, {_, Server} = US) ->
+ case (catch get_full_roster(State, Server)) of
+ {ok, {VisibilityMap, FullRoster}} ->
+ case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+ false -> []; % Case #1
+ {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+ AllGroups = lists:usort(lists:foldl(
+ fun(#shared_roster_item{groups=Gs}, Acc) -> Gs ++ Acc end,
+ [], FullRoster)),
+ Fun = case (State#state.subscribe_all) of
+ true -> % Possible case 2
+ fun(_, all) -> all;
+ (#shg_data{grp=all, shgrps=all}, _) -> all;
+ (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> all
+ end;
+ (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> [G | Acc]
+ end;
+ (_, Acc) -> Acc
+ end;
+ _False -> % Case 3
+ fun(#shg_data{grp=all}, Acc) -> AllGroups ++ Acc;
+ (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> [G | Acc]
+ end;
+ (_, Acc) -> Acc
+ end
+ end,
+ PublishTo = lists:foldl(Fun, [], VisibilityMap),
+ case (PublishTo) of
+ all ->
+ [{U1, S1, <<"">>} || {U1, S1} <- ejabberd_auth:get_vh_registered_users(Server)];
+ Groups ->
+ [{U1, S1, <<"">>} || #shared_roster_item{us = {U1, S1}} <- filter_roster(Roster2, UserGroups ++ Groups)]
+ end
+ end;
+ {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+ _Unexpected -> []
end.
-get_group_name(Host, Group) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_group,
- {Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{desc = GroupName}}
- when GroupName /= undefined ->
- GroupName;
- _ -> Group
+get_full_roster(State, Server) when State#state.roster_cache_size > 0 ->
+ cache_tab:dirty_lookup(shared_roster_ldap_sr,
+ {Server},
+ fun() -> search_roster_info(State, Server) end);
+get_full_roster(State, Server) ->
+ search_roster_info(State, Server).
+
+search_visible_groups(State, _) when State#state.shgfilter == all ->
+ [{all, all}];
+search_visible_groups(State, _) when State#state.shgfilter == none ->
+ [{all, none}];
+search_visible_groups(State, Groups) ->
+ case (string:str(State#state.shgfilter, "%g")) of
+ 0 -> [{all, search_group_visible_groups(State, "")}];
+ _ -> lists:map(
+ fun(Group) -> {Group, search_group_visible_groups(State, Group)} end,
+ Groups)
end.
-get_user_name(User, Host) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_user,
- {User, Host},
- fun () -> search_user_name(State, User) end)
- of
- {ok, UserName} -> UserName;
- error -> User
+search_group_visible_groups(State, Group) ->
+ Entries = eldap_search(State, State#state.group_base, State#state.shgfilter, [{<<"%g">>, Group}], [State#state.shg_attr]),
+ lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{_GroupAttr, ValuesList}] ->
+ ValuesList;
+ _ ->
+ []
+ end
+ end, Entries)).
+
+group2name(all, _) -> all;
+group2name(none, _) -> none;
+group2name(Group, GroupNames) ->
+ case (lists:keysearch(Group, 1, GroupNames)) of
+ {value, {_, Name}} -> Name;
+ _ -> false
end.
+groups2names(all, _) -> all;
+groups2names(none, _) -> none;
+groups2names(GroupList, GroupNames) ->
+ lists:foldl(
+ fun(G, Acc) ->
+ case (group2name(G, GroupNames)) of
+ false -> Acc;
+ Name -> [Name | Acc]
+ end
+ end,
+ [], GroupList).
+
+prep_vis_map(VisGroups, GroupNames) ->
+ lists:foldl(
+ fun({G, Gs}, Acc) ->
+ case (group2name(G, GroupNames)) of
+ false -> Acc;
+ Name -> [#shg_data{grp=Name, shgrps=groups2names(Gs, GroupNames)} | Acc]
+ end
+ end,
+ [], VisGroups).
+
+search_roster_info(State, _Host) ->
+ Entries = eldap_search(State, State#state.group_base, State#state.rfilter, [State#state.group_attr]),
+ AllGroupIds = lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{_GroupAttr, ValuesList}] ->
+ ValuesList;
+ _ ->
+ []
+ end
+ end, Entries)),
+ VisGroups = search_visible_groups(State, AllGroupIds),
+ %%?ERROR_MSG("XXXXXX search_roster_info: VisGroups=~p", [VisGroups]),
+
+ {GroupNames, RosterItems} = case State#state.member_selection_mode of
+ group_children ->
+ {GroupNames0, UsersDict0} = lists:foldl(
+ fun(Group, {GrNAcc, Dict1} = Acc) ->
+ case search_group_info(State, Group) of
+ {ok, #group_info{desc = GroupName, members = GroupDN}} ->
+ {[{Group, GroupName} | GrNAcc], search_users_info(State, GroupDN, GroupName, Dict1)};
+ _ -> Acc %% Error getting group data -> No users!
+ end
+ end,
+ {[], dict:new()}, AllGroupIds),
+
+ {GroupNames0, dict:fold(
+ fun(#user_info{us=US, name=UserName}, Groups, AccIn) ->
+ [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn]
+ end,
+ [], UsersDict0)};
+ _ ->
+ {GroupNames1, UsersDict1} = lists:foldl(
+ fun(Group, {GrNAcc, Dict1} = Acc) ->
+ case search_group_info(State, Group) of
+ {ok, #group_info{desc = GroupName, members = Members}} ->
+ {[{Group, GroupName} | GrNAcc], lists:foldl(
+ fun(Member, Dict) -> dict:append(Member, GroupName, Dict) end,
+ Dict1, Members)};
+ _ -> Acc %% Error getting group data -> No users!
+ end
+ end,
+ {[], dict:new()}, AllGroupIds),
+
+ %%?ERROR_MSG("UsersDict1: ~p", [UsersDict1]),
+ %%?ERROR_MSG("GroupNames1: ~p", [GroupNames1]),
+
+ {GroupNames1, dict:fold(
+ fun(Member, Groups, AccIn) ->
+ case search_user_info(State, Member) of
+ {ok, #user_info{us=US, name=UserName}} ->
+ %%?ERROR_MSG("XXXX found user: ~p ~p ~p", [UserName, Groups, US]),
+ [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn];
+ _ -> AccIn
+ end
+ end,
+ [], UsersDict1)}
+ end,
+
+ VisibilityMap = prep_vis_map(VisGroups, GroupNames),
+ {ok, {VisibilityMap, RosterItems}}.
+
search_group_info(State, Group) ->
+ AttList = case State#state.member_selection_mode of
+ group_children -> [State#state.group_desc];
+ _ -> [State#state.group_desc, State#state.member_attr]
+ end,
+ SearchResult = case State#state.group_is_dn of
+ true -> eldap_search_dn(State,
+ Group,
+ State#state.gfilter,
+ AttList);
+ _ -> eldap_search(State,
+ State#state.group_base,
+ State#state.gfilter,
+ [{<<"%g">>, Group}],
+ AttList)
+ end,
+ case SearchResult of
+ [] ->
+ error;
+ LDAPEntries ->
+ case State#state.member_selection_mode of
+ group_children ->
+ [#eldap_entry{object_name=Name, attributes=Attrs} | _] = LDAPEntries,
+ {ok, #group_info{desc = eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ members = Name}};
+ _ ->
+ {GroupDesc, MembersLists} = lists:foldl(
+ fun(#eldap_entry{attributes=Attrs}, {DescAcc, MembersAcc}) ->
+ case {eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ lists:keysearch(State#state.member_attr, 1, Attrs)} of
+ {Desc, {value, {GroupMemberAttr, Members}}}
+ when GroupMemberAttr == State#state.member_attr ->
+ {Desc, lists:usort(Members ++ MembersAcc)};
+ _ ->
+ {DescAcc, MembersAcc}
+ end
+ end,
+ {Group, []}, LDAPEntries),
+ {ok, #group_info{desc = GroupDesc,
+ members = lists:usort(MembersLists)}}
+ end
+ end.
+
+%% Takes the attributes from LDAP user search;
+%% returns error or {ok, #user_info}
+construct_user(State, Attrs) ->
Extractor = case State#state.uid_format_re of
- <<"">> ->
- fun (UID) ->
- catch eldap_utils:get_user_part(UID,
- State#state.uid_format)
- end;
- _ ->
- fun (UID) ->
- catch get_user_part_re(UID,
- State#state.uid_format_re)
- end
- end,
+ <<"">> -> fun(UID) ->
+ catch eldap_utils:get_user_part(UID, State#state.uid_format)
+ end;
+ _ -> fun(UID) ->
+ catch get_user_part_re(UID, State#state.uid_format_re)
+ end
+ end,
AuthChecker = case State#state.auth_check of
- true -> fun ejabberd_auth:is_user_exists/2;
- _ -> fun (_U, _S) -> true end
- end,
+ true -> fun ejabberd_auth:is_user_exists/2;
+ _ -> fun(_U, _S) -> true end
+ end,
Host = State#state.host,
- case eldap_search(State,
- [eldap_filter:do_sub(State#state.gfilter,
- [{<<"%g">>, Group}])],
- [State#state.group_attr, State#state.group_desc,
- State#state.uid])
- of
- [] -> error;
- LDAPEntries ->
- {GroupDesc, MembersLists} = lists:foldl(fun
- (#eldap_entry{attributes =
- Attrs},
- {DescAcc, JIDsAcc}) ->
- case
- {eldap_utils:get_ldap_attr(State#state.group_attr,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.group_desc,
- Attrs),
- lists:keysearch(State#state.uid,
- 1,
- Attrs)}
- of
- {ID, Desc,
- {value,
- {GroupMemberAttr,
- Members}}}
- when ID /= <<"">>,
- GroupMemberAttr
- ==
- State#state.uid ->
- JIDs =
- lists:foldl(fun
- ({ok,
- UID},
- L) ->
- PUID =
- jlib:nodeprep(UID),
- case
- PUID
- of
- error ->
- L;
- _ ->
- case
- AuthChecker(PUID,
- Host)
- of
- true ->
- [{PUID,
- Host}
- | L];
- _ ->
- L
- end
- end;
- (_,
- L) ->
- L
- end,
- [],
- lists:map(Extractor,
- Members)),
- {Desc,
- [JIDs
- | JIDsAcc]};
- _ ->
- {DescAcc, JIDsAcc}
- end
- end,
- {Group, []}, LDAPEntries),
- {ok,
- #group_info{desc = GroupDesc,
- members = lists:usort(lists:flatten(MembersLists))}}
+
+ case {eldap_utils:get_ldap_attr(State#state.uid, Attrs),
+ eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} of
+ {UID, Desc} when UID /= "" ->
+ %% By returning "" get_ldap_attr means "not found"
+ case Extractor(UID) of
+ {ok, UID1} ->
+ UID2 = jid:nodeprep(UID1),
+ case UID2 of
+ error -> error;
+ _ ->
+ case AuthChecker(UID2, Host) of
+ true -> {ok, #user_info{us={UID2, Host}, name=Desc}};
+ _ -> error
+ end
+ end;
+ _ -> error
+ end;
+ _ ->
+ error
end.
-search_user_name(State, User) ->
- case eldap_search(State,
- [eldap_filter:do_sub(State#state.ufilter,
- [{<<"%u">>, User}])],
- [State#state.user_desc, State#state.user_uid])
- of
- [#eldap_entry{attributes = Attrs} | _] ->
- case {eldap_utils:get_ldap_attr(State#state.user_uid,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)}
- of
- {UID, Desc} when UID /= <<"">> -> {ok, Desc};
- _ -> error
- end;
- [] -> error
+%% This function is used when State#state.member_selection_mode is group_children
+%% Returns UsersDict to which the users (#user_info) of this group are added
+%%search_users_info(State, GroupInfo) ->
+search_users_info(State, GroupDN, GroupName, UsersDict) ->
+ SearchResult = eldap_search(State,
+ GroupDN,
+ State#state.ufilter,
+ [State#state.user_desc, State#state.uid]),
+ lists:foldl(
+ fun(#eldap_entry{attributes=Attrs}, Dict1) ->
+ case construct_user(State, Attrs) of
+ {ok, UserInfo} ->
+ dict:append(UserInfo, GroupName, Dict1);
+ _ -> Dict1
+ end
+ end, UsersDict, SearchResult).
+
+%% This function is used when State#state.member_selection_mode is either memberattr_normal or memberattr_dn
+search_user_info(State, User) ->
+ %%?ERROR_MSG("XXX search_user_info: searching for ~p", [User]),
+ SearchResult = case State#state.member_selection_mode of
+ memberattr_dn -> eldap_search_dn(State,
+ User,
+ State#state.ufilter,
+ [State#state.user_desc, State#state.uid]);
+ memberattr_normal -> eldap_search(State,
+ State#state.base,
+ State#state.ufilter,
+ [{<<"%u">>, User}],
+ [State#state.user_desc, State#state.uid])
+ end,
+ case SearchResult of
+ [#eldap_entry{attributes=Attrs}|_] ->
+ construct_user(State, Attrs);
+ [] ->
+ %%?ERROR_MSG("XX not found", []),
+ error
end.
%% Getting User ID part by regex pattern
get_user_part_re(String, Pattern) ->
case catch re:run(String, Pattern) of
- {match, Captured} ->
- {First, Len} = lists:nth(2, Captured),
- Result = str:sub_string(String, First + 1, First + Len),
- {ok, Result};
- _ -> {error, badmatch}
+ {match, Captured} ->
+ {First, Len} = lists:nth(2,Captured),
+ Result = string:sub_string(String, First+1, First+Len),
+ {ok,Result};
+ _ -> {error, badmatch}
end.
+% select(SelectFirst, First, Second) ->
+% case SelectFirst of
+% true -> First;
+% _ -> Second
+% end.
+
+% prepare_filter(Opts, Name, Default, ReturnParsed) ->
+% F = gen_mod:get_opt(Name, Opts, Default),
+% prepare_filter(F, Name, ReturnParsed).
+
+% prepare_filter(F, Name, ReturnParsed) ->
+% case eldap_filter:parse(F) of
+% {ok, EldapFilter} ->
+% case ReturnParsed of
+% true -> EldapFilter;
+% _ -> F
+% end;
+% _ ->
+% ?ERROR_MSG(?INVALID_SETTING_MSG, [atom_to_list(Name), ?MODULE]),
+% []
+% end.
+
parse_options(Host, Opts) ->
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
Cfg = eldap_utils:get_config(Host, Opts),
@@ -521,81 +761,223 @@ parse_options(Host, Opts) ->
(false) -> false;
(true) -> true
end, true),
- UserCacheValidity = eldap_utils:get_opt(
- {ldap_user_cache_validity, Host}, Opts,
- fun(I) when is_integer(I), I>0 -> I end,
- ?USER_CACHE_VALIDITY),
- GroupCacheValidity = eldap_utils:get_opt(
+ RosterCacheValidity = gen_mod:get_opt(
{ldap_group_cache_validity, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
- ?GROUP_CACHE_VALIDITY),
- UserCacheSize = eldap_utils:get_opt(
- {ldap_user_cache_size, Host}, Opts,
- fun(I) when is_integer(I), I>0 -> I end,
- ?CACHE_SIZE),
- GroupCacheSize = eldap_utils:get_opt(
- {ldap_group_cache_size, Host}, Opts,
+ ?CACHE_VALIDITY),
+ RosterCacheSize = gen_mod:get_opt(
+ {ldap_roster_cache_size, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
?CACHE_SIZE),
- ConfigFilter = eldap_utils:get_opt({ldap_filter, Host}, Opts,
- fun check_filter/1, <<"">>),
- ConfigUserFilter = eldap_utils:get_opt({ldap_ufilter, Host}, Opts,
- fun check_filter/1, <<"">>),
- ConfigGroupFilter = eldap_utils:get_opt({ldap_gfilter, Host}, Opts,
- fun check_filter/1, <<"">>),
- RosterFilter = eldap_utils:get_opt({ldap_rfilter, Host}, Opts,
- fun check_filter/1, <<"">>),
+ ConfigFilter = gen_mod:get_opt({ldap_filter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ ConfigUserFilter = gen_mod:get_opt({ldap_ufilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ ConfigGroupFilter = gen_mod:get_opt({ldap_gfilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
+ RosterFilter = gen_mod:get_opt({ldap_rfilter, Host}, Opts,
+ fun check_filter/1, <<"">>),
SubFilter = <<"(&(", UIDAttr/binary, "=",
- UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
+ UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
UserSubFilter = case ConfigUserFilter of
- <<"">> ->
- eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
- UString -> UString
- end,
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
+ UString -> UString
+ end,
GroupSubFilter = case ConfigGroupFilter of
- <<"">> ->
- eldap_filter:do_sub(SubFilter,
- [{<<"%u">>, <<"*">>}]);
- GString -> GString
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter,
+ [{<<"%u">>, <<"*">>}]);
+ GString -> GString
end,
Filter = case ConfigFilter of
- <<"">> -> SubFilter;
- _ ->
- <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
- end,
+ <<"">> -> SubFilter;
+ _ ->
+ <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
+ end,
UserFilter = case ConfigFilter of
- <<"">> -> UserSubFilter;
- _ ->
- <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
- end,
+ <<"">> -> UserSubFilter;
+ _ ->
+ <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
+ end,
GroupFilter = case ConfigFilter of
- <<"">> -> GroupSubFilter;
- _ ->
- <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
- ")">>
- end,
+ <<"">> -> GroupSubFilter;
+ _ ->
+ <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
+ ")">>
+ end,
+%%%%%%%%%%%%%
+ GroupBase = gen_mod:get_opt(ldap_group_base, Opts, fun iolist_to_binary/1,
+ Cfg#eldap_config.base),
+ GroupIsDN = gen_mod:get_opt(ldap_group_is_dn, Opts,
+ fun(on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end, true),
+ MemberSelMode = gen_mod:get_opt(ldap_member_selection_mode, Opts,
+ fun(memberattr_normal) -> memberattr_normal;
+ (memberattr_dn) -> memberattr_dn;
+ (group_children) -> group_children;
+ (Invalid) ->
+ ?ERROR_MSG("Invalid ldap_member_selection_mode '~p'. "
+ "Value 'memberattr_normal' will be used instead.",
+ [Invalid])
+ end, memberattr_normal),
+ SubscribeAll = gen_mod:get_opt(ldap_subscribe_all, Opts,
+ fun(on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end, false),
+ % MemberIsDN = (MemberSelMode == member_attr_dn) or (MemberSelMode == group_children),
+ ShGFilter = gen_mod:get_opt(ldap_shgfilter, Opts,
+ fun(all) -> all;
+ (none) -> none;
+ (S) -> check_filter(S)
+ end, all),
+ ShGAttr = gen_mod:get_opt(ldap_shgattr, Opts,
+ fun iolist_to_binary/1,
+ << GroupAttr/binary >>),
+%%%%%%
#state{host = Host, eldap_id = Eldap_ID,
- servers = Cfg#eldap_config.servers,
- backups = Cfg#eldap_config.backups,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
port = Cfg#eldap_config.port,
- tls_options = Cfg#eldap_config.tls_options,
- dn = Cfg#eldap_config.dn,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
password = Cfg#eldap_config.password,
base = Cfg#eldap_config.base,
deref_aliases = Cfg#eldap_config.deref_aliases,
- uid = UIDAttr,
- group_attr = GroupAttr, group_desc = GroupDesc,
- user_desc = UserDesc, user_uid = UserUID,
- uid_format = UIDAttrFormat,
- uid_format_re = UIDAttrFormatRe, filter = Filter,
- ufilter = UserFilter, rfilter = RosterFilter,
- gfilter = GroupFilter, auth_check = AuthCheck,
- user_cache_size = UserCacheSize,
- user_cache_validity = UserCacheValidity,
- group_cache_size = GroupCacheSize,
- group_cache_validity = GroupCacheValidity}.
+ group_attr = GroupAttr, group_desc = GroupDesc,
+ user_desc = UserDesc, uid = UserUID,
+ uid_format = UIDAttrFormat,
+ uid_format_re = UIDAttrFormatRe, filter = Filter,
+ ufilter = UserFilter, rfilter = RosterFilter,
+ gfilter = GroupFilter, auth_check = AuthCheck,
+ group_base = GroupBase,
+ member_attr = UIDAttr,
+ member_selection_mode = MemberSelMode,
+ group_is_dn = GroupIsDN,
+ shgfilter = ShGFilter,
+ shg_attr = ShGAttr,
+ subscribe_all = SubscribeAll,
+ roster_cache_size = RosterCacheSize,
+ roster_cache_validity = RosterCacheValidity}.
check_filter(F) ->
- NewF = iolist_to_binary(F),
- {ok, _} = eldap_filter:parse(NewF),
- NewF.
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.
+
+mod_opt_type(deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+mod_opt_type(ldap_backups) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+mod_opt_type(ldap_base) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+mod_opt_type(ldap_encrypt) ->
+ fun (tls) -> tls;
+ (starttls) -> starttls;
+ (none) -> none
+ end;
+mod_opt_type(ldap_password) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_port) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_servers) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+mod_opt_type(ldap_tls_cacertfile) ->
+ fun iolist_to_binary/1;
+mod_opt_type(ldap_tls_certfile) ->
+ fun iolist_to_binary/1;
+mod_opt_type(ldap_tls_depth) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+mod_opt_type(ldap_tls_verify) ->
+ fun (hard) -> hard;
+ (soft) -> soft;
+ (false) -> false
+ end;
+mod_opt_type(ldap_auth_check) ->
+ fun (on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end;
+mod_opt_type(ldap_filter) -> fun check_filter/1;
+mod_opt_type(ldap_gfilter) -> fun check_filter/1;
+mod_opt_type(ldap_group_cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_group_cache_validity) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_groupattr) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_groupdesc) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_memberattr) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_memberattr_format) ->
+ fun iolist_to_binary/1;
+mod_opt_type(ldap_memberattr_format_re) ->
+ fun (S) ->
+ Re = iolist_to_binary(S), {ok, MP} = re:compile(Re), MP
+ end;
+mod_opt_type(ldap_rfilter) -> fun check_filter/1;
+mod_opt_type(ldap_ufilter) -> fun check_filter/1;
+mod_opt_type(ldap_user_cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_user_cache_validity) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_userdesc) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_useruid) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_base) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_is_dn) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_member_selection_mode) ->
+ fun(memberattr_normal) -> memberattr_normal;
+ (memberattr_dn) -> memberattr_dn;
+ (group_children) -> group_children
+ end;
+mod_opt_type(ldap_subscribe_all) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_shgfilter) ->
+ fun(all) -> all;
+ (none) -> none;
+ (S) -> check_filter(S)
+ end;
+mod_opt_type(ldap_shgattr) -> fun iolist_to_binary/1;
+mod_opt_type(_) ->
+ [ldap_auth_check, ldap_filter, ldap_gfilter,
+ ldap_group_cache_size, ldap_group_cache_validity,
+ ldap_groupattr, ldap_groupdesc, ldap_memberattr,
+ ldap_memberattr_format, ldap_memberattr_format_re,
+ ldap_rfilter, ldap_ufilter, ldap_user_cache_size,
+ ldap_user_cache_validity, ldap_userdesc, ldap_useruid,
+ deref_aliases, ldap_backups, ldap_base,
+ ldap_deref_aliases, ldap_encrypt, ldap_password,
+ ldap_port, ldap_rootdn, ldap_servers,
+ ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
+ ldap_tls_verify, ldap_group_base, ldap_group_is_dn,
+ ldap_member_selection_mode, ldap_subscribe_all,
+ ldap_shgfilter, ldap_shgattr].
+
+opt_type(ldap_filter) -> fun check_filter/1;
+opt_type(ldap_gfilter) -> fun check_filter/1;
+opt_type(ldap_group_cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(ldap_group_cache_validity) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(ldap_rfilter) -> fun check_filter/1;
+opt_type(ldap_ufilter) -> fun check_filter/1;
+opt_type(ldap_user_cache_size) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(ldap_user_cache_validity) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(_) ->
+ [ldap_filter, ldap_gfilter, ldap_group_cache_size,
+ ldap_group_cache_validity, ldap_rfilter, ldap_ufilter,
+ ldap_user_cache_size, ldap_user_cache_validity].
diff --git a/src/mod_sic.erl b/src/mod_sic.erl
index ed44f850..3ffd9c91 100644
--- a/src/mod_sic.erl
+++ b/src/mod_sic.erl
@@ -5,7 +5,7 @@
%%% Created : 6 Mar 2010 by Karim Gemayel <karim.gemayel@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,12 +25,14 @@
-module(mod_sic).
+-protocol({xep, 279, '0.2'}).
+
-author('karim.gemayel@process-one.net').
-behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3,
- process_sm_iq/3]).
+ process_sm_iq/3, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -89,3 +91,6 @@ get_ip({User, Server, Resource},
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_sip.erl b/src/mod_sip.erl
index f7f2b8ed..6ffe5633 100644
--- a/src/mod_sip.erl
+++ b/src/mod_sip.erl
@@ -1,12 +1,12 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2014, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2014-2015 ProcessOne
+%%%
+%%% ejabberd, Copyright (C) 2014-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
@@ -24,6 +24,7 @@
%%%-------------------------------------------------------------------
-module(mod_sip).
+-protocol({rfc, 3261}).
-behaviour(gen_mod).
-behaviour(esip).
@@ -31,9 +32,9 @@
%% API
-export([start/2, stop/1, make_response/2, is_my_host/1, at_my_host/1]).
-%% esip_callbacks
--export([data_in/2, data_out/2, message_in/2, message_out/2,
- request/2, request/3, response/2, locate/1]).
+-export([data_in/2, data_out/2, message_in/2,
+ message_out/2, request/2, request/3, response/2,
+ locate/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -159,8 +160,8 @@ locate(_SIPMsg) ->
ok.
find(#uri{user = User, host = Host}) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Host),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
if LUser == <<"">> ->
to_me;
true ->
@@ -191,7 +192,7 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
true ->
register;
false ->
- {auth, jlib:nameprep(ToURI#uri.host)}
+ {auth, jid:nameprep(ToURI#uri.host)}
end;
false ->
deny
@@ -222,7 +223,7 @@ action(#sip{method = Method, hdrs = Hdrs, type = request} = Req, SIPSock) ->
true ->
find(ToURI);
false ->
- LServer = jlib:nameprep(FromURI#uri.host),
+ LServer = jid:nameprep(FromURI#uri.host),
{relay, LServer}
end;
false ->
@@ -249,8 +250,8 @@ check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) -
from
end,
{_, #uri{user = User, host = Host}, _} = esip:get_hdr(Issuer, Hdrs),
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Host),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
case lists:filter(
fun({_, Params}) ->
Username = esip:get_param(<<"username">>, Params),
@@ -262,8 +263,12 @@ check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) -
case ejabberd_auth:get_password_s(LUser, LServer) of
<<"">> ->
false;
- Password ->
- esip:check_auth(Auth, Method, Body, Password)
+ Password when is_binary(Password) ->
+ esip:check_auth(Auth, Method, Body, Password);
+ _ScramedPassword ->
+ ?ERROR_MSG("unable to authenticate ~s@~s against SCRAM'ed "
+ "password", [LUser, LServer]),
+ false
end;
[] ->
false
@@ -294,7 +299,48 @@ make_response(Req, Resp) ->
esip:make_response(Req, Resp, esip:make_tag()).
at_my_host(#uri{host = Host}) ->
- is_my_host(jlib:nameprep(Host)).
+ is_my_host(jid:nameprep(Host)).
is_my_host(LServer) ->
gen_mod:is_loaded(LServer, ?MODULE).
+
+mod_opt_type(always_record_route) ->
+ fun (true) -> true;
+ (false) -> false
+ end;
+mod_opt_type(flow_timeout_tcp) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(flow_timeout_udp) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(record_route) ->
+ fun (IOList) ->
+ S = iolist_to_binary(IOList),
+ #uri{} = esip:decode_uri(S)
+ end;
+mod_opt_type(routes) ->
+ fun (L) ->
+ lists:map(fun (IOList) ->
+ S = iolist_to_binary(IOList),
+ #uri{} = esip:decode_uri(S)
+ end,
+ L)
+ end;
+mod_opt_type(via) ->
+ fun (L) ->
+ lists:map(fun (Opts) ->
+ Type = proplists:get_value(type, Opts),
+ Host = proplists:get_value(host, Opts),
+ Port = proplists:get_value(port, Opts),
+ true = (Type == tcp) or (Type == tls) or
+ (Type == udp),
+ true = is_binary(Host) and (Host /= <<"">>),
+ true = is_integer(Port) and (Port > 0) and
+ (Port < 65536)
+ or (Port == undefined),
+ {Type, {Host, Port}}
+ end,
+ L)
+ end;
+mod_opt_type(_) ->
+ [always_record_route, flow_timeout_tcp,
+ flow_timeout_udp, record_route, routes, via].
diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl
index 6168d997..04ae55ae 100644
--- a/src/mod_sip_proxy.erl
+++ b/src/mod_sip_proxy.erl
@@ -1,12 +1,12 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2014, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2014-2015 ProcessOne
+%%%
+%%% ejabberd, Copyright (C) 2014-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
@@ -25,16 +25,18 @@
%%%-------------------------------------------------------------------
-module(mod_sip_proxy).
+-behaviour(ejabberd_config).
+
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% API
-export([start/2, start_link/2, route/3, route/4]).
-%% gen_fsm callbacks
--export([init/1, wait_for_request/2, wait_for_response/2,
- handle_event/3, handle_sync_event/4,
- handle_info/3, terminate/3, code_change/4]).
+-export([init/1, wait_for_request/2,
+ wait_for_response/2, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3,
+ code_change/4, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -247,8 +249,8 @@ connect(#sip{hdrs = Hdrs} = Req, Opts) ->
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
case mod_sip:at_my_host(ToURI) of
true ->
- LUser = jlib:nodeprep(ToURI#uri.user),
- LServer = jlib:nameprep(ToURI#uri.host),
+ LUser = jid:nodeprep(ToURI#uri.user),
+ LServer = jid:nameprep(ToURI#uri.host),
case mod_sip_registrar:find_sockets(LUser, LServer) of
[_|_] = SIPSocks ->
{ok, SIPSocks};
@@ -297,8 +299,7 @@ add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) ->
case need_record_route(LServer) of
true ->
RR_URI = get_configured_record_route(LServer),
- {MSecs, Secs, _} = now(),
- TS = list_to_binary(integer_to_list(MSecs*1000000 + Secs)),
+ TS = list_to_binary(integer_to_list(p1_time_compat:system_time(seconds))),
Sign = make_sign(TS, Hdrs),
User = <<TS/binary, $-, Sign/binary>>,
NewRR_URI = RR_URI#uri{user = User},
@@ -339,8 +340,7 @@ is_signed_by_me(TS_Sign, Hdrs) ->
try
[TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
TS = list_to_integer(binary_to_list(TSBin)),
- {MSecs, Secs, _} = now(),
- NowTS = MSecs*1000000 + Secs,
+ NowTS = p1_time_compat:system_time(seconds),
true = (NowTS - TS) =< ?SIGN_LIFETIME,
Sign == make_sign(TSBin, Hdrs)
catch _:_ ->
@@ -410,7 +410,7 @@ choose_best_response(#state{responses = Responses} = State) ->
%% Just compare host part only.
cmp_uri(#uri{host = H1}, #uri{host = H2}) ->
- jlib:nameprep(H1) == jlib:nameprep(H2).
+ jid:nameprep(H1) == jid:nameprep(H2).
is_my_route(URI, URIs) ->
lists:any(fun(U) -> cmp_uri(URI, U) end, URIs).
@@ -439,20 +439,24 @@ prepare_request(LServer, #sip{hdrs = Hdrs} = Req) ->
Hdrs3 = lists:filter(
fun({'proxy-authorization', {_, Params}}) ->
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
- not mod_sip:is_my_host(jlib:nameprep(Realm));
+ not mod_sip:is_my_host(jid:nameprep(Realm));
(_) ->
true
end, Hdrs2),
Req#sip{hdrs = Hdrs3}.
safe_nodeprep(S) ->
- case jlib:nodeprep(S) of
+ case jid:nodeprep(S) of
error -> S;
S1 -> S1
end.
safe_nameprep(S) ->
- case jlib:nameprep(S) of
+ case jid:nameprep(S) of
error -> S;
S1 -> S1
end.
+
+opt_type(domain_certfile) -> fun iolist_to_binary/1;
+opt_type(shared_key) -> fun (V) -> V end;
+opt_type(_) -> [domain_certfile, shared_key].
diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl
index a534c61c..fcaa4236 100644
--- a/src/mod_sip_registrar.erl
+++ b/src/mod_sip_registrar.erl
@@ -1,12 +1,12 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% @copyright (C) 2014, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 23 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
-%%% ejabberd, Copyright (C) 2014-2015 ProcessOne
+%%%
+%%% ejabberd, Copyright (C) 2014-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
@@ -48,7 +48,7 @@
socket = #sip_socket{} :: #sip_socket{},
call_id = <<"">> :: binary(),
cseq = 0 :: non_neg_integer(),
- timestamp = now() :: erlang:timestamp(),
+ timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
contact :: {binary(), #uri{}, [{binary(), binary()}]},
flow_tref :: reference(),
reg_tref = make_ref() :: reference(),
@@ -65,8 +65,8 @@ start_link() ->
request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
{_, #uri{user = U, host = S}, _} = esip:get_hdr('to', Hdrs),
- LUser = jlib:nodeprep(U),
- LServer = jlib:nameprep(S),
+ LUser = jid:nodeprep(U),
+ LServer = jid:nameprep(S),
{PeerIP, _} = SIPSock#sip_socket.peer,
US = {LUser, LServer},
CallID = esip:get_hdr('call-id', Hdrs),
@@ -242,7 +242,7 @@ register_session(US, SIPSocket, CallID, CSeq, IsOutboundSupported,
socket = SIPSocket,
call_id = CallID,
cseq = CSeq,
- timestamp = now(),
+ timestamp = p1_time_compat:timestamp(),
contact = Contact,
expires = Expires}
end, ContactsWithExpires),
@@ -490,15 +490,18 @@ need_ob_hdrs(Contacts, _IsOutboundSupported = true) ->
end, Contacts).
get_flow_timeout(LServer, #sip_socket{type = Type}) ->
- {Option, Default} =
- case Type of
- udp -> {flow_timeout_udp, ?FLOW_TIMEOUT_UDP};
- _ -> {flow_timeout_tcp, ?FLOW_TIMEOUT_TCP}
- end,
- gen_mod:get_module_opt(
- LServer, mod_sip, Option,
- fun(I) when is_integer(I), I>0 -> I end,
- Default).
+ case Type of
+ udp ->
+ gen_mod:get_module_opt(
+ LServer, mod_sip, flow_timeout_udp,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?FLOW_TIMEOUT_UDP);
+ _ ->
+ gen_mod:get_module_opt(
+ LServer, mod_sip, flow_timeout_tcp,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?FLOW_TIMEOUT_TCP)
+ end.
update_table() ->
Fields = record_info(fields, sip_session),
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index 4317e9e9..0328aec3 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -5,7 +5,7 @@
%%% Created : 11 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,9 +27,12 @@
-author('alexey@process-one.net').
+-protocol({xep, 39, '0.6.0'}).
+
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -54,7 +57,7 @@ process_local_iq(_From, To,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
#xmlel{children = Els} = SubEl,
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
Names = get_names(Els, []),
case get_local_stats(To#jid.server, Node, Names) of
@@ -73,7 +76,7 @@ get_names([], Res) -> Res;
get_names([#xmlel{name = <<"stat">>, attrs = Attrs}
| Els],
Res) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
case Name of
<<"">> -> get_names(Els, Res);
_ -> get_names(Els, [Name | Res])
@@ -172,7 +175,7 @@ get_local_stat(_Server, _, Name) ->
get_node_stat(Node, Name)
when Name == <<"time/uptime">> ->
- case catch rpc:call(Node, erlang, statistics,
+ case catch ejabberd_cluster:call(Node, erlang, statistics,
[wall_clock])
of
{badrpc, _Reason} ->
@@ -185,7 +188,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"time/cputime">> ->
- case catch rpc:call(Node, erlang, statistics, [runtime])
+ case catch ejabberd_cluster:call(Node, erlang, statistics, [runtime])
of
{badrpc, _Reason} ->
?STATERR(<<"500">>, <<"Internal Server Error">>);
@@ -197,7 +200,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"users/online">> ->
- case catch rpc:call(Node, ejabberd_sm,
+ case catch ejabberd_cluster:call(Node, ejabberd_sm,
dirty_get_my_sessions_list, [])
of
{badrpc, _Reason} ->
@@ -208,7 +211,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"transactions/committed">> ->
- case catch rpc:call(Node, mnesia, system_info,
+ case catch ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_commits])
of
{badrpc, _Reason} ->
@@ -219,7 +222,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"transactions/aborted">> ->
- case catch rpc:call(Node, mnesia, system_info,
+ case catch ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_failures])
of
{badrpc, _Reason} ->
@@ -230,7 +233,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"transactions/restarted">> ->
- case catch rpc:call(Node, mnesia, system_info,
+ case catch ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_restarts])
of
{badrpc, _Reason} ->
@@ -241,7 +244,7 @@ get_node_stat(Node, Name)
end;
get_node_stat(Node, Name)
when Name == <<"transactions/logged">> ->
- case catch rpc:call(Node, mnesia, system_info,
+ case catch ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_log_writes])
of
{badrpc, _Reason} ->
@@ -263,3 +266,6 @@ search_running_node(SNode, [Node | Nodes]) ->
SNode -> Node;
_ -> search_running_node(SNode, Nodes)
end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_time.erl b/src/mod_time.erl
index c82fde41..8bfe9f9f 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -6,7 +6,7 @@
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -28,12 +28,12 @@
-author('alexey@process-one.net').
--behaviour(gen_mod).
+-protocol({xep, 202, '2.0'}).
--export([start/2, stop/1, process_local_iq90/3,
- process_local_iq/3]).
+-behaviour(gen_mod).
- % TODO: Remove once XEP-0090 is Obsolete
+-export([start/2, stop/1, process_local_iq/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -44,43 +44,20 @@ start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_TIME90, ?MODULE, process_local_iq90,
- IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_TIME, ?MODULE, process_local_iq, IQDisc).
stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_TIME90),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_TIME).
-%% TODO: Remove this function once XEP-0090 is Obsolete
-process_local_iq90(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- get ->
- UTC = jlib:timestamp_to_iso(calendar:universal_time()),
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_TIME90}],
- children =
- [#xmlel{name = <<"utc">>, attrs = [],
- children = [{xmlcdata, UTC}]}]}]}
- end.
-
process_local_iq(_From, _To,
#iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
- Now = now(),
- Now_universal = calendar:now_to_universal_time(Now),
- Now_local = calendar:now_to_local_time(Now),
+ Now_universal = calendar:universal_time(),
+ Now_local = calendar:universal_time_to_local_time(Now_universal),
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal,
utc),
Seconds_diff =
@@ -107,3 +84,6 @@ process_local_iq(_From, _To,
sign(N) when N < 0 -> <<"-">>;
sign(_) -> <<"+">>.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index ba23d068..256dc5de 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -5,7 +5,7 @@
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,16 +25,23 @@
-module(mod_vcard).
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
-author('alexey@process-one.net').
+-protocol({xep, 54, '1.2'}).
+-protocol({xep, 55, '1.3'}).
+
-behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5,
process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
- remove_user/2, export/1, import/1, import/3]).
+ remove_user/2, export/1, import/1, import/3,
+ mod_opt_type/1, set_vcard/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
-include("jlib.hrl").
@@ -90,7 +97,7 @@ start(Host, Opts) ->
<<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts,
fun(B) when is_boolean(B) -> B end,
- true),
+ false),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost, Host, Search])).
@@ -98,7 +105,7 @@ init(Host, ServerHost, Search) ->
case Search of
false -> loop(Host, ServerHost);
_ ->
- ejabberd_router:register_route(Host),
+ ejabberd_router:register_route(Host, ServerHost),
loop(Host, ServerHost)
end.
@@ -162,7 +169,7 @@ process_local_iq(_From, _To,
[{xmlcdata,
<<(translate:translate(Lang,
<<"Erlang Jabber Server">>))/binary,
- "\nCopyright (c) 2002-2015 ProcessOne">>}]},
+ "\nCopyright (c) 2002-2016 ProcessOne">>}]},
#xmlel{name = <<"BDAY">>, attrs = [],
children =
[{xmlcdata, <<"2002-11-16">>}]}]}]}
@@ -208,14 +215,13 @@ get_vcard(LUser, LServer, mnesia) ->
{aborted, _Reason} -> error
end;
get_vcard(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_vcard(LServer, Username) of
- {selected, [<<"vcard">>], [[SVCARD]]} ->
- case xml_stream:parse_element(SVCARD) of
+ case catch odbc_queries:get_vcard(LServer, LUser) of
+ {selected, [{SVCARD}]} ->
+ case fxml_stream:parse_element(SVCARD) of
{error, _Reason} -> error;
VCARD -> [VCARD]
end;
- {selected, [<<"vcard">>], []} -> [];
+ {selected, []} -> [];
_ -> error
end;
get_vcard(LUser, LServer, riak) ->
@@ -229,35 +235,35 @@ get_vcard(LUser, LServer, riak) ->
end.
set_vcard(User, LServer, VCARD) ->
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
+ FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
+ Given = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
+ Middle = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
+ Nickname = fxml:get_path_s(VCARD,
[{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
+ BDay = fxml:get_path_s(VCARD,
[{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
+ CTRY = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
+ Locality = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
- EMail1 = xml:get_path_s(VCARD,
+ EMail1 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(VCARD,
+ EMail2 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
+ OrgName = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
+ OrgUnit = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
_ -> EMail1
end,
- LUser = jlib:nodeprep(User),
+ LUser = jid:nodeprep(User),
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
@@ -332,39 +338,14 @@ set_vcard(User, LServer, VCARD) ->
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
odbc ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
- odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY,
- SEMail, SFN, SFamily, SGiven, SLBDay,
- SLCTRY, SLEMail, SLFN, SLFamily,
- SLGiven, SLLocality, SLMiddle,
- SLNickname, SLOrgName, SLOrgUnit,
- SLocality, SMiddle, SNickname, SOrgName,
- SOrgUnit, SVCARD, Username)
+ SVCARD = fxml:element_to_binary(VCARD),
+ odbc_queries:set_vcard(LServer, LUser, BDay, CTRY,
+ EMail, FN, Family, Given, LBDay,
+ LCTRY, LEMail, LFN, LFamily,
+ LGiven, LLocality, LMiddle,
+ LNickname, LOrgName, LOrgUnit,
+ Locality, Middle, Nickname, OrgName,
+ OrgUnit, SVCARD, User)
end,
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
@@ -400,7 +381,7 @@ string2lower(String) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search users in ">>))/binary,
- (jlib:jid_to_string(JID))/binary>>}]},
+ (jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
@@ -574,7 +555,7 @@ iq_get_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd vCard module">>))/binary,
- "\nCopyright (c) 2003-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
@@ -583,7 +564,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -603,7 +584,7 @@ search_result(Lang, JID, ServerHost, Data) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search Results for ">>))/binary,
- (jlib:jid_to_string(JID))/binary>>}]},
+ (jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"reported">>, attrs = [],
children =
[?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
@@ -858,27 +839,27 @@ set_vcard_t(R, _) ->
US = R#vcard.us,
User = US,
VCARD = R#vcard.vcard,
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
+ FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
+ Given = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
+ Middle = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
+ Nickname = fxml:get_path_s(VCARD,
[{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
+ BDay = fxml:get_path_s(VCARD,
[{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
+ CTRY = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
+ Locality = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
- EMail = xml:get_path_s(VCARD,
+ EMail = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
+ OrgName = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
+ OrgUnit = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
{LUser, _LServer} = US,
LFN = string2lower(FN),
@@ -912,8 +893,8 @@ reindex_vcards() ->
mnesia:transaction(F).
remove_user(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -925,12 +906,14 @@ remove_user(LUser, LServer, mnesia) ->
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_transaction(LServer,
- [[<<"delete from vcard where username='">>,
- Username, <<"';">>],
- [<<"delete from vcard_search where lusername='">>,
- Username, <<"';">>]]);
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun() ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from vcard where username=%(LUser)s")),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from vcard_search where lusername=%(LUser)s"))
+ end);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
@@ -948,7 +931,7 @@ update_vcard_table() ->
fun(#vcard{us = {U, S}, vcard = El} = R) ->
R#vcard{us = {iolist_to_binary(U),
iolist_to_binary(S)},
- vcard = xml:to_xmlel(El)}
+ vcard = fxml:to_xmlel(El)}
end);
_ ->
?INFO_MSG("Recreating vcard table", []),
@@ -987,7 +970,7 @@ export(_Server) ->
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
+ ejabberd_odbc:escape(fxml:element_to_binary(VCARD)),
[[<<"delete from vcard where username='">>, Username, <<"';">>],
[<<"insert into vcard(username, vcard) values ('">>,
Username, <<"', '">>, SVCARD, <<"');">>]];
@@ -1060,7 +1043,7 @@ export(_Server) ->
import(LServer) ->
[{<<"select username, vcard from vcard;">>,
fun([LUser, SVCard]) ->
- #xmlel{} = VCARD = xml_stream:parse_element(SVCard),
+ #xmlel{} = VCARD = fxml_stream:parse_element(SVCard),
#vcard{us = {LUser, LServer}, vcard = VCARD}
end},
{<<"select username, lusername, fn, lfn, family, lfamily, "
@@ -1091,29 +1074,29 @@ import(_LServer, mnesia, #vcard{} = VCard) ->
import(_LServer, mnesia, #vcard_search{} = S) ->
mnesia:dirty_write(S);
import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
- FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(El,
+ FN = fxml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
+ Family = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(El,
+ Given = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(El,
+ Middle = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(El,
+ Nickname = fxml:get_path_s(El,
[{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(El,
+ BDay = fxml:get_path_s(El,
[{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(El,
+ CTRY = fxml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(El,
+ Locality = fxml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
- EMail1 = xml:get_path_s(El,
+ EMail1 = fxml:get_path_s(El,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(El,
+ EMail2 = fxml:get_path_s(El,
[{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(El,
+ OrgName = fxml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(El,
+ OrgUnit = fxml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
@@ -1159,3 +1142,20 @@ import(_LServer, riak, #vcard_search{}) ->
ok;
import(_, _, _) ->
pass.
+
+mod_opt_type(allow_return_all) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(matches) ->
+ fun (infinity) -> infinity;
+ (I) when is_integer(I), I > 0 -> I
+ end;
+mod_opt_type(search) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(search_all_hosts) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [allow_return_all, db_type, host, iqdisc, matches,
+ search, search_all_hosts].
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index 61db3897..98aaf936 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -5,7 +5,7 @@
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -21,10 +21,12 @@
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
-%%%----------------------------------------------------------------------
+%%%---------------------u-------------------------------------------------
-module(mod_vcard_ldap).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
@@ -37,7 +39,8 @@
-export([start/2, start_link/2, stop/1,
get_sm_features/5, process_local_iq/3, process_sm_iq/3,
- remove_user/1, route/4, transform_module_options/1]).
+ remove_user/1, route/4, transform_module_options/1,
+ mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -52,7 +55,7 @@
{serverhost = <<"">> :: binary(),
myhost = <<"">> :: binary(),
eldap_id = <<"">> :: binary(),
- search = true :: boolean(),
+ search = false :: boolean(),
servers = [] :: [binary()],
backups = [] :: [binary()],
port = ?LDAP_PORT :: inet:port_number(),
@@ -170,7 +173,7 @@ init([Host, Opts]) ->
State#state.password, State#state.tls_options),
case State#state.search of
true ->
- ejabberd_router:register_route(State#state.myhost);
+ ejabberd_router:register_route(State#state.myhost, Host);
_ -> ok
end,
{ok, State}.
@@ -220,7 +223,7 @@ process_local_iq(_From, _To,
[{xmlcdata,
<<(translate:translate(Lang,
<<"Erlang Jabber Server">>))/binary,
- "\nCopyright (c) 2002-2015 ProcessOne">>}]},
+ "\nCopyright (c) 2002-2016 ProcessOne">>}]},
#xmlel{name = <<"BDAY">>, attrs = [],
children =
[{xmlcdata, <<"2002-11-16">>}]}]}]}
@@ -419,7 +422,7 @@ ldap_attribute_to_vcard(_, _) -> none.
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search users in ">>))/binary,
- (jlib:jid_to_string(JID))/binary>>}]},
+ (jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
@@ -581,7 +584,7 @@ iq_get_vcard(Lang) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd vCard module">>))/binary,
- "\nCopyright (c) 2003-2015 ProcessOne">>}]}].
+ "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-define(LFIELD(Label, Var),
#xmlel{name = <<"field">>,
@@ -597,7 +600,7 @@ search_result(Lang, JID, State, Data) ->
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search Results for ">>))/binary,
- (jlib:jid_to_string(JID))/binary>>}]},
+ (jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"reported">>, attrs = [],
children =
[?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
@@ -720,7 +723,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -732,14 +735,14 @@ parse_options(Host, Opts) ->
<<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts,
fun(B) when is_boolean(B) -> B end,
- true),
+ false),
Matches = gen_mod:get_opt(matches, Opts,
fun(infinity) -> 0;
(I) when is_integer(I), I>0 -> I
end, 30),
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
Cfg = eldap_utils:get_config(Host, Opts),
- UIDsTemp = eldap_utils:get_opt(
+ UIDsTemp = gen_mod:get_opt(
{ldap_uids, Host}, Opts,
fun(Us) ->
lists:map(
@@ -752,7 +755,7 @@ parse_options(Host, Opts) ->
end, [{<<"uid">>, <<"%u">>}]),
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
SubFilter = eldap_utils:generate_subfilter(UIDs),
- UserFilter = case eldap_utils:get_opt(
+ UserFilter = case gen_mod:get_opt(
{ldap_filter, Host}, Opts,
fun check_filter/1, <<"">>) of
<<"">> ->
@@ -840,3 +843,100 @@ check_filter(F) ->
NewF = iolist_to_binary(F),
{ok, _} = eldap_filter:parse(NewF),
NewF.
+
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(ldap_filter) -> fun check_filter/1;
+mod_opt_type(ldap_search_fields) ->
+ fun (Ls) ->
+ [{iolist_to_binary(S), iolist_to_binary(P)}
+ || {S, P} <- Ls]
+ end;
+mod_opt_type(ldap_search_reported) ->
+ fun (Ls) ->
+ [{iolist_to_binary(S), iolist_to_binary(P)}
+ || {S, P} <- Ls]
+ end;
+mod_opt_type(ldap_uids) ->
+ fun (Us) ->
+ lists:map(fun ({U, P}) ->
+ {iolist_to_binary(U), iolist_to_binary(P)};
+ ({U}) -> {iolist_to_binary(U)}
+ end,
+ Us)
+ end;
+mod_opt_type(ldap_vcard_map) ->
+ fun (Ls) ->
+ lists:map(fun ({S, [{P, L}]}) ->
+ {iolist_to_binary(S), iolist_to_binary(P),
+ [iolist_to_binary(E) || E <- L]}
+ end,
+ Ls)
+ end;
+mod_opt_type(matches) ->
+ fun (infinity) -> 0;
+ (I) when is_integer(I), I > 0 -> I
+ end;
+mod_opt_type(search) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+mod_opt_type(ldap_backups) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+mod_opt_type(ldap_base) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_deref_aliases) ->
+ fun (never) -> never;
+ (searching) -> searching;
+ (finding) -> finding;
+ (always) -> always
+ end;
+mod_opt_type(ldap_encrypt) ->
+ fun (tls) -> tls;
+ (starttls) -> starttls;
+ (none) -> none
+ end;
+mod_opt_type(ldap_password) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_port) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+mod_opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_servers) ->
+ fun (L) -> [iolist_to_binary(H) || H <- L] end;
+mod_opt_type(ldap_tls_cacertfile) ->
+ fun iolist_to_binary/1;
+mod_opt_type(ldap_tls_certfile) ->
+ fun iolist_to_binary/1;
+mod_opt_type(ldap_tls_depth) ->
+ fun (I) when is_integer(I), I >= 0 -> I end;
+mod_opt_type(ldap_tls_verify) ->
+ fun (hard) -> hard;
+ (soft) -> soft;
+ (false) -> false
+ end;
+mod_opt_type(_) ->
+ [host, iqdisc, ldap_filter, ldap_search_fields,
+ ldap_search_reported, ldap_uids, ldap_vcard_map,
+ matches, search, deref_aliases, ldap_backups, ldap_base,
+ ldap_deref_aliases, ldap_encrypt, ldap_password,
+ ldap_port, ldap_rootdn, ldap_servers,
+ ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
+ ldap_tls_verify].
+
+opt_type(ldap_filter) -> fun check_filter/1;
+opt_type(ldap_uids) ->
+ fun (Us) ->
+ lists:map(fun ({U, P}) ->
+ {iolist_to_binary(U), iolist_to_binary(P)};
+ ({U}) -> {iolist_to_binary(U)}
+ end,
+ Us)
+ end;
+opt_type(_) ->
+ [ldap_filter, ldap_uids, deref_aliases, ldap_backups, ldap_base,
+ ldap_deref_aliases, ldap_encrypt, ldap_password,
+ ldap_port, ldap_rootdn, ldap_servers,
+ ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
+ ldap_tls_verify].
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 41a07bbc..18fb09a5 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -12,8 +12,8 @@
%% gen_mod callbacks
-export([start/2, stop/1]).
-%% hooks
--export([update_presence/3, vcard_set/3, export/1, import/1, import/3]).
+-export([update_presence/3, vcard_set/3, export/1,
+ import/1, import/3, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -56,7 +56,7 @@ stop(Host) ->
update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
User, Host) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> presence_with_xupdate(Packet, User, Host);
_ -> Packet
end;
@@ -64,7 +64,7 @@ update_presence(Packet, _User, _Host) -> Packet.
vcard_set(LUser, LServer, VCARD) ->
US = {LUser, LServer},
- case xml:get_path_s(VCARD,
+ case fxml:get_path_s(VCARD,
[{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata])
of
<<>> -> remove_xupdate(LUser, LServer);
@@ -231,3 +231,6 @@ import(_LServer, riak, #vcard_xupdate{} = R) ->
ejabberd_riak:put(R, vcard_xupdate_schema());
import(_, _, _) ->
pass.
+
+mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(_) -> [db_type].
diff --git a/src/mod_version.erl b/src/mod_version.erl
index e46262a2..0e3b96bd 100644
--- a/src/mod_version.erl
+++ b/src/mod_version.erl
@@ -5,7 +5,7 @@
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -27,9 +27,12 @@
-author('alexey@process-one.net').
+-protocol({xep, 92, '1.1'}).
+
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3]).
+-export([start/2, stop/1, process_local_iq/3,
+ mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -88,3 +91,8 @@ get_os() ->
OS = <<OSType/binary, " ", OSVersion/binary>>,
#xmlel{name = <<"os">>, attrs = [],
children = [{xmlcdata, OS}]}.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(show_os) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) -> [iqdisc, show_os].
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index a6ff0d45..1ebef4e1 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -1,28 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_buddy.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose :
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_buddy).
-behaviour(gen_pubsub_node).
@@ -45,10 +44,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -86,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_club.erl b/src/node_club.erl
index 6917312f..a7ef35bd 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -1,28 +1,27 @@
-%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_club.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose :
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_club).
-behaviour(gen_pubsub_node).
@@ -45,10 +44,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +84,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_dag.erl b/src/node_dag.erl
index f0cbf17b..cbb8e18c 100644
--- a/src/node_dag.erl
+++ b/src/node_dag.erl
@@ -1,21 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_dag.erl
+%%% Author : Brian Cully <bjc@kublai.com>
+%%% Purpose : experimental support of XEP-248
+%%% Created : 15 Jun 2009 by Brian Cully <bjc@kublai.com>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% @author Brian Cully <bjc@kublai.com>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_dag).
-behaviour(gen_pubsub_node).
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index 7a123184..f9bfaf4f 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -1,28 +1,33 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_dispatch.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Publish item to node and all child subnodes
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose
+%%% goal is to republished each published item to all its children.</p>
+%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to
+%%% its children.</p>
+%%% This module can not work with virtual nodetree
-module(node_dispatch).
-behaviour(gen_pubsub_node).
@@ -31,12 +36,6 @@
-include("pubsub.hrl").
-include("jlib.hrl").
-%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose
-%%% goal is to republished each published item to all its children.</p>
-%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to
-%%% its children.</p>
-%%% This module can not work with virtual nodetree
-
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
@@ -179,7 +178,7 @@ get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_to_path(Node) ->
- node_flat:node_to_path(Node).
+ node_hometree:node_to_path(Node).
path_to_node(Path) ->
- node_flat:path_to_node(Path).
+ node_hometree:path_to_node(Path).
diff --git a/src/node_flat.erl b/src/node_flat.erl
index e2615438..3687bb64 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -1,28 +1,33 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_flat.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard PubSub node plugin
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-module(node_flat).
-behaviour(gen_pubsub_node).
@@ -42,10 +47,10 @@
get_pending_nodes/2, get_states/1, get_state/2,
set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
- path_to_node/1]).
+ path_to_node/1, can_fetch_item/2, is_subscribed/1]).
init(_Host, _ServerHost, _Opts) ->
- pubsub_subscription:init(),
+ %pubsub_subscription:init(),
mnesia:create_table(pubsub_state,
[{disc_copies, [node()]},
{type, ordered_set},
@@ -60,20 +65,56 @@ init(_Host, _ServerHost, _Opts) ->
end,
ok.
-terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+terminate(_Host, _ServerHost) ->
+ ok.
options() ->
- node_hometree:options().
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, publishers},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, on_sub_and_presence},
+ {deliver_notifications, true},
+ {presence_based_delivery, false}].
features() ->
- node_hometree:features().
-
-%% use same code as node_hometree, but do not limite node to
-%% the home/localhost/user/... hierarchy
-%% any node is allowed
+ [<<"create-nodes">>,
+ <<"auto-create">>,
+ <<"access-authorize">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"get-pending">>,
+ <<"instant-nodes">>,
+ <<"manage-subscriptions">>,
+ <<"modify-affiliations">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"publish-only-affiliation">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+%%<<"subscription-options">>
+
+%% @doc Checks if the current user has the permission to create the requested node
+%% <p>In flat node, any unused node name is allowed. The access parameter is also
+%% checked. This parameter depends on the value of the
+%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
- LOwner = jlib:jid_tolower(Owner),
+ LOwner = jid:tolower(Owner),
Allowed = case LOwner of
{<<"">>, Host, <<"">>} ->
true; % pubsub service always allowed
@@ -83,88 +124,684 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
-
-delete_node(Removed) ->
- node_hometree:delete_node(Removed).
-
+ OwnerKey = jid:tolower(jid:remove_resource(Owner)),
+ set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
+ affiliation = owner}),
+ {result, {default, broadcast}}.
+
+delete_node(Nodes) ->
+ Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
+ lists:map(fun (S) -> {J, S} end, Ss)
+ end,
+ Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
+ del_items(Nidx, Items),
+ del_state(Nidx, LJID)
+ end, States),
+ {PubsubNode, lists:flatmap(Tr, States)}
+ end, Nodes),
+ {result, {default, broadcast, Reply}}.
+
+%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the subscription and passes the
+%% result of the preparation as a record.</li>
+%% <li>This function gets the prepared record and several other parameters and
+%% can decide to:<ul>
+%% <li>reject the subscription;</li>
+%% <li>allow it as is, letting the main module perform the database
+%% persistance;</li>
+%% <li>allow it, modifying the record. The main module will store the
+%% modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
+%% subscription will actually be performed.</li>
+%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
+%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
+%% parameter contains an error, no subscription will be performed.</li>
+%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
+%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
+%% passed in parameter <tt>SubscribeResult</tt>.</li>
+%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
+%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
- SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber,
- AccessModel, SendLast, PresenceSubscription,
- RosterGroup, Options).
+ SendLast, PresenceSubscription, RosterGroup, _Options) ->
+ SubKey = jid:tolower(Subscriber),
+ GenKey = jid:remove_resource(SubKey),
+ Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscriptions = SubState#pubsub_state.subscriptions,
+ Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
+ PendingSubscription = lists:any(fun
+ ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ Owner = Affiliation == owner,
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ %%SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
+ {NewSub, SubId} = case Subscriptions of
+ [{subscribed, Id}|_] ->
+ {subscribed, Id};
+ [] ->
+ Id = pubsub_subscription:make_subid(),
+ Sub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ set_state(SubState#pubsub_state{subscriptions =
+ [{Sub, Id} | Subscriptions]}),
+ {Sub, Id}
+ end,
+ case {NewSub, SendLast} of
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} ->
+ {result, {default, pending, SubId}}
+ end
+ end.
+%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ SubKey = jid:tolower(Subscriber),
+ GenKey = jid:remove_resource(SubKey),
+ Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Subscriptions = lists:filter(fun
+ ({_Sub, _SubId}) -> true;
+ (_SubId) -> false
+ end,
+ SubState#pubsub_state.subscriptions),
+ SubIdExists = case SubId of
+ <<>> -> false;
+ Binary when is_binary(Binary) -> true;
+ _ -> false
+ end,
+ if
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized ->
+ {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun
+ ({_, S}) when S == SubId -> true;
+ (_) -> false
+ end,
+ SubState#pubsub_state.subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscriptions(SubKey, Nidx, [S], SubState),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
+ {result, default};
+ %% No subid supplied, but there's only one matching subscription
+ length(Subscriptions) == 1 ->
+ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ end.
-publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
+ NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
+ %%pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
+ Acc -- [{Subscription, SubId}]
+ end, SubState#pubsub_state.subscriptions, Subscriptions),
+ case {SubState#pubsub_state.affiliation, NewSubs} of
+ {none, []} -> del_state(Nidx, SubKey);
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
-remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+%% @doc <p>Publishes the item passed as parameter.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the item to publish and passes the
+%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
+%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
+%% <li>reject the publication;</li>
+%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
+%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
+%% publication is actually performed.</li>
+%% <li><tt>true</tt>: Publication operation is allowed, based on the
+%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
+%% parameter contains an error, no subscription will actually be
+%% performed.</li>
+%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
+%% in parameter <tt>Item</tt>. The persistance will be performed by the main
+%% module.</li>
+%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+ SubKey = jid:tolower(Publisher),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscribed = case PublishModel of
+ subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
+ is_subscribed(SubState#pubsub_state.subscriptions);
+ _ -> undefined
+ end,
+ if not ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner)
+ or (Affiliation == publisher)
+ or (Affiliation == publish_only))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ Now = p1_time_compat:timestamp(),
+ PubId = {Now, SubKey},
+ Item = case get_item(Nidx, ItemId) of
+ {result, OldItem} ->
+ OldItem#pubsub_item{modification = PubId,
+ payload = Payload};
+ _ ->
+ #pubsub_item{itemid = {ItemId, Nidx},
+ creation = {Now, GenKey},
+ modification = PubId,
+ payload = Payload}
+ end,
+ Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
+ {result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
+ set_item(Item),
+ set_state(GenState#pubsub_state{items = NI}),
+ {result, {default, broadcast, OI}};
+ true ->
+ {result, {default, broadcast, []}}
+ end
+ end.
+%% @doc <p>This function is used to remove extra items, most notably when the
+%% maximum number of items has been reached.</p>
+%% <p>This function is used internally by the core PubSub module, as no
+%% permission check is performed.</p>
+%% <p>In the default plugin module, the oldest items are removed, but other
+%% rules can be used.</p>
+%% <p>If another PubSub plugin wants to delegate the item removal (and if the
+%% plugin is using the default pubsub storage), it can implements this function like this:
+%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
+%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
+remove_extra_items(_Nidx, unlimited, ItemIds) ->
+ {result, {ItemIds, []}};
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ NewItems = lists:sublist(ItemIds, MaxItems),
+ OldItems = lists:nthtail(length(NewItems), ItemIds),
+ del_items(Nidx, OldItems),
+ {result, {NewItems, OldItems}}.
+
+%% @doc <p>Triggers item deletion.</p>
+%% <p>Default plugin: The user performing the deletion must be the node owner
+%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ SubKey = jid:tolower(Publisher),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(Nidx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ case lists:member(ItemId, Items) of
+ true ->
+ del_item(Nidx, ItemId),
+ set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
+ {result, {default, broadcast}};
+ false ->
+ case Affiliation of
+ owner ->
+ {result, States} = get_states(Nidx),
+ lists:foldl(fun
+ (#pubsub_state{items = PI} = S, Res) ->
+ case lists:member(ItemId, PI) of
+ true ->
+ Nitems = lists:delete(ItemId, PI),
+ del_item(Nidx, ItemId),
+ set_state(S#pubsub_state{items = Nitems}),
+ {result, {default, broadcast}};
+ false ->
+ Res
+ end;
+ (_, Res) ->
+ Res
+ end,
+ {error, ?ERR_ITEM_NOT_FOUND}, States);
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end
+ end.
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case GenState of
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun
+ (#pubsub_state{items = []}) ->
+ ok;
+ (#pubsub_state{items = Items} = S) ->
+ del_items(Nidx, Items),
+ set_state(S#pubsub_state{items = []})
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ ->
+ {error, ?ERR_FORBIDDEN}
+ end.
+%% @doc <p>Return the current affiliations for the given user</p>
+%% <p>The default module reads affiliations in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], States),
+ {result, Reply}.
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ {result, States} = get_states(Nidx),
+ Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
+ {result, lists:map(Tr, States)}.
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ #pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
+ {result, Affiliation}.
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case {Affiliation, GenState#pubsub_state.subscriptions} of
+ {none, []} -> del_state(Nidx, GenKey);
+ _ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
+ end.
+%% @doc <p>Return the current subscriptions for the given user</p>
+%% <p>The default module reads subscriptions in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ {U, D, _} = SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ States = case SubKey of
+ GenKey ->
+ mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
+ _ ->
+ mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
+ ++
+ mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
+ end,
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, J} | Acc2]
+ end,
+ Acc, Ss);
+ _ ->
+ Acc
+ end
+ end,
+ [], States),
+ {result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ {result, States} = get_states(Nidx),
+ Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
+ case Subscriptions of
+ [_ | _] ->
+ lists:foldl(fun ({S, SubId}, Acc) ->
+ [{J, S, SubId} | Acc]
+ end,
+ [], Subscriptions);
+ [] ->
+ [];
+ _ ->
+ [{J, none}]
+ end
+ end,
+ {result, lists:flatmap(Tr, States)}.
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ SubState = get_state(Nidx, SubKey),
+ {result, SubState#pubsub_state.subscriptions}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ SubKey = jid:tolower(Owner),
+ SubState = get_state(Nidx, SubKey),
+ case {SubId, SubState#pubsub_state.subscriptions} of
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ _ ->
+ new_subscription(Nidx, Owner, Subscription, SubState)
+ end;
+ {<<>>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<>>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SubId, SubState);
+ _ -> replace_subscription({Subscription, SubId}, SubState)
+ end
+ end.
+replace_subscription(NewSub, SubState) ->
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
+ set_state(SubState#pubsub_state{subscriptions = NewSubs}).
+
+replace_subscription(_, [], Acc) -> Acc;
+replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
+ replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
+
+new_subscription(_Nidx, _Owner, Sub, SubState) ->
+ %%SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
+ SubId = pubsub_subscription:make_subid(),
+ Subs = SubState#pubsub_state.subscriptions,
+ set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
+ {Sub, SubId}.
+
+unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
+ %%pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
+ NewSubs = [{S, Sid}
+ || {S, Sid} <- SubState#pubsub_state.subscriptions,
+ SubId =/= Sid],
+ case {NewSubs, SubState#pubsub_state.affiliation} of
+ {[], none} -> del_state(Nidx, Entity);
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
+
+%% @doc <p>Returns a list of Owner's nodes on Host with pending
+%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ GenKey = jid:remove_resource(jid:tolower(Owner)),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
+ affiliation = owner,
+ _ = '_'}),
+ NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
+ case lists:member(Nidx, NodeIdxs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false ->
+ Acc
+ end
+ end,
+ [], pubsub_state),
+ {result, Reply}.
+
+get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ HasPending = fun
+ ({pending, _}) -> true;
+ (pending) -> true;
+ (_) -> false
+ end,
+ case lists:any(HasPending, Subs) of
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false ->
+ false
+ end.
+%% @doc Returns the list of stored states for a given node.
+%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_state table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the states where they wants (for example in a
+%% relational database).</p>
+%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
+%% they can implement this function like this:
+%% ```get_states(Nidx) ->
+%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ States = case catch mnesia:match_object(
+ #pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
+ List when is_list(List) -> List;
+ _ -> []
+ end,
+ {result, States}.
+
+%% @doc <p>Returns a state (one state list), given its reference.</p>
+get_state(Nidx, Key) ->
+ StateId = {Key, Nidx},
+ case catch mnesia:read({pubsub_state, StateId}) of
+ [State] when is_record(State, pubsub_state) -> State;
+ _ -> #pubsub_state{stateid = StateId}
+ end.
-get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+%% @doc <p>Write a state into database.</p>
+set_state(State) when is_record(State, pubsub_state) ->
+ mnesia:write(State).
+%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+%% @doc <p>Delete a state from database.</p>
+del_state(Nidx, Key) ->
+ mnesia:delete({pubsub_state, {Key, Nidx}}).
+
+%% @doc Returns the list of stored items for a given node.
+%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_item table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the items where they wants (for example in a
+%% relational database), or they can even decide not to persist any items.</p>
+get_items(Nidx, _From, _RSM) ->
+ Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
+ {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
+ SubKey = jid:tolower(JID),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ SubState = get_state(Nidx, SubKey),
+ Affiliation = GenState#pubsub_state.affiliation,
+ BareSubscriptions = GenState#pubsub_state.subscriptions,
+ FullSubscriptions = SubState#pubsub_state.subscriptions,
+ Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
+ can_fetch_item(Affiliation, FullSubscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_items(Nidx, JID, RSM)
+ end.
-set_state(State) ->
- node_hometree:set_state(State).
+%% @doc <p>Returns an item (one item list), given its reference.</p>
-get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+get_item(Nidx, ItemId) ->
+ case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
+ [Item] when is_record(Item, pubsub_item) -> {result, Item};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end.
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId, RSM).
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+ SubKey = jid:tolower(JID),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscriptions = GenState#pubsub_state.subscriptions,
+ Whitelisted = can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_item(Nidx, ItemId)
+ end.
-get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+%% @doc <p>Write an item into database.</p>
+set_item(Item) when is_record(Item, pubsub_item) ->
+ mnesia:write(Item).
+%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId).
+%% @doc <p>Delete an item from database.</p>
+del_item(Nidx, ItemId) ->
+ mnesia:delete({pubsub_item, {ItemId, Nidx}}).
-set_item(Item) ->
- node_hometree:set_item(Item).
+del_items(Nidx, ItemIds) ->
+ lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
+ end,
+ ItemIds).
-get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+get_item_name(_Host, _Node, Id) ->
+ Id.
+%% @doc <p>Return the path of the node. In flat it's just node id.</p>
node_to_path(Node) ->
[(Node)].
@@ -178,3 +815,26 @@ path_to_node(Path) ->
% default case (used by PEP for example)
_ -> iolist_to_binary(Path)
end.
+
+can_fetch_item(owner, _) -> true;
+can_fetch_item(member, _) -> true;
+can_fetch_item(publisher, _) -> true;
+can_fetch_item(publish_only, _) -> false;
+can_fetch_item(outcast, _) -> false;
+can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
+%can_fetch_item(_Affiliation, _Subscription) -> false.
+
+is_subscribed(Subscriptions) ->
+ lists:any(fun
+ ({subscribed, _SubId}) -> true;
+ (_) -> false
+ end,
+ Subscriptions).
+
+first_in_list(_Pred, []) ->
+ false;
+first_in_list(Pred, [H | T]) ->
+ case Pred(H) of
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
+ end.
diff --git a/src/node_flat_odbc.erl b/src/node_flat_odbc.erl
index 9ac2643f..e3c57938 100644
--- a/src/node_flat_odbc.erl
+++ b/src/node_flat_odbc.erl
@@ -1,28 +1,33 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_flat_odbc.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard PubSub node plugin with ODBC backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-module(node_flat_odbc).
-behaviour(gen_pubsub_node).
@@ -45,11 +50,17 @@
path_to_node/1,
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
-init(Host, ServerHost, Opts) ->
- node_hometree_odbc:init(Host, ServerHost, Opts).
+-export([decode_jid/1, encode_jid/1,
+ decode_affiliation/1, decode_subscriptions/1,
+ encode_affiliation/1, encode_subscriptions/1,
+ encode_host/1]).
-terminate(Host, ServerHost) ->
- node_hometree_odbc:terminate(Host, ServerHost).
+init(_Host, _ServerHost, _Opts) ->
+ %%pubsub_subscription_odbc:init(),
+ ok.
+
+terminate(_Host, _ServerHost) ->
+ ok.
options() ->
[{odbc, true}, {rsm, true} | node_flat:options()].
@@ -57,110 +68,984 @@ options() ->
features() ->
[<<"rsm">> | node_flat:features()].
-%% use same code as node_hometree_odbc, but do not limite node to
-%% the home/localhost/user/... hierarchy
-%% any node is allowed
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree_odbc:create_node(Nidx, Owner).
+ {_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
+ State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
+ catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values(">>, state_to_raw(Nidx, State), <<");">>]),
+ {result, {default, broadcast}}.
-delete_node(Removed) ->
- node_hometree_odbc:delete_node(Removed).
+delete_node(Nodes) ->
+ Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
+ Subscriptions = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
+ "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
+ _ ->
+ []
+ end,
+ {PubsubNode, Subscriptions}
+ end, Nodes),
+ {result, {default, broadcast, Reply}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
- SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options).
+ SendLast, PresenceSubscription, RosterGroup, _Options) ->
+ SubKey = jid:tolower(Subscriber),
+ GenKey = jid:remove_resource(SubKey),
+ Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
+ PendingSubscription = lists:any(fun
+ ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ Owner = Affiliation == owner,
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
+ {NewSub, SubId} = case Subscriptions of
+ [{subscribed, Id}|_] ->
+ {subscribed, Id};
+ [] ->
+ Id = pubsub_subscription_odbc:make_subid(),
+ Sub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ update_subscription(Nidx, SubKey, [{Sub, Id} | Subscriptions]),
+ {Sub, Id}
+ end,
+ case {NewSub, SendLast} of
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} ->
+ {result, {default, pending, SubId}}
+ end
+ end.
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ SubKey = jid:tolower(Subscriber),
+ GenKey = jid:remove_resource(SubKey),
+ Authorized = jid:tolower(jid:remove_resource(Sender)) == GenKey,
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
+ SubIdExists = case SubId of
+ <<>> -> false;
+ Binary when is_binary(Binary) -> true;
+ _ -> false
+ end,
+ if
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized ->
+ {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun
+ ({_, S}) when S == SubId -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
+ || S <- Subscriptions],
+ {result, default};
+ %% No subid supplied, but there's only one matching subscription
+ length(Subscriptions) == 1 ->
+ delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ end.
+
+delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
+ NewSubs = Subscriptions -- [{Subscription, SubId}],
+ %%pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
+ case {Affiliation, NewSubs} of
+ {none, []} -> del_state(Nidx, SubKey);
+ _ -> update_subscription(Nidx, SubKey, NewSubs)
+ end.
-publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+ SubKey = jid:tolower(Publisher),
+ GenKey = jid:remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Subscribed = case PublishModel of
+ subscribers -> node_flat:is_subscribed(Subscriptions);
+ _ -> undefined
+ end,
+ if not ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner)
+ or (Affiliation == publisher)
+ or (Affiliation == publish_only))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ PubId = {p1_time_compat:timestamp(), SubKey},
+ set_item(#pubsub_item{itemid = {ItemId, Nidx},
+ creation = {p1_time_compat:timestamp(), GenKey},
+ modification = PubId,
+ payload = Payload}),
+ Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]],
+ {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items),
+ {result, {default, broadcast, OI}};
+ true ->
+ {result, {default, broadcast, []}}
+ end
+ end.
+remove_extra_items(_Nidx, unlimited, ItemIds) ->
+ {result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ NewItems = lists:sublist(ItemIds, MaxItems),
+ OldItems = lists:nthtail(length(NewItems), ItemIds),
+ del_items(Nidx, OldItems),
+ {result, {NewItems, OldItems}}.
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ SubKey = jid:tolower(Publisher),
+ GenKey = jid:remove_resource(SubKey),
+ {result, Affiliation} = get_affiliation(Nidx, GenKey),
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(Nidx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ case del_item(Nidx, ItemId) of
+ {updated, 1} -> {result, {default, broadcast}};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end.
purge_node(Nidx, Owner) ->
- node_hometree_odbc:purge_node(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case GenState of
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun
+ (#pubsub_state{items = []}) -> ok;
+ (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ ->
+ {error, ?ERR_FORBIDDEN}
+ end.
get_entity_affiliations(Host, Owner) ->
- node_hometree_odbc:get_entity_affiliations(Host, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ H = encode_host(Host),
+ J = encode_jid(GenKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ "from pubsub_state i, pubsub_node n where "
+ "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
+ of
+ {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
+ [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
+ || [N, T, I, A] <- RItems];
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_node_affiliations(Nidx) ->
- node_hometree_odbc:get_node_affiliations(Nidx).
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ "where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
+ [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_affiliation(Nidx, Owner) ->
- node_hometree_odbc:get_affiliation(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ J = encode_jid(GenKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>], [[A]]} ->
+ decode_affiliation(A);
+ _ ->
+ none
+ end,
+ {result, Reply}.
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
+ case {Affiliation, Subscriptions} of
+ {none, []} -> del_state(Nidx, GenKey);
+ _ -> update_affiliation(Nidx, GenKey, Affiliation)
+ end.
get_entity_subscriptions(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions(Host, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ H = encode_host(Host),
+ SJ = encode_jid(SubKey),
+ GJ = encode_jid(GenKey),
+ Query = case SubKey of
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n "
+ "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n "
+ "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
+ Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([N, T, I, J, S], Acc) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
+-spec(get_entity_subscriptions_for_send_last/2 ::
+ (
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+ ).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ H = encode_host(Host),
+ SJ = encode_jid(SubKey),
+ GJ = encode_jid(GenKey),
+ Query = case SubKey of
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n, pubsub_node_option o "
+ "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
+ "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n, pubsub_node_option o "
+ "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
+ "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
+ Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([N, T, I, J, S], Acc) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid}| Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree_odbc:get_node_subscriptions(Nidx).
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([J, S], Acc) ->
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Jid, none} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Jid, Sub, SubId} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_subscriptions(Nidx, Owner) ->
- node_hometree_odbc:get_subscriptions(Nidx, Owner).
+ SubKey = jid:tolower(Owner),
+ J = encode_jid(SubKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
+ "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"subscriptions">>], [[S]]} ->
+ decode_subscriptions(S);
+ _ ->
+ []
+ end,
+ {result, Reply}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ SubKey = jid:tolower(Owner),
+ SubState = get_state_without_itemids(Nidx, SubKey),
+ case {SubId, SubState#pubsub_state.subscriptions} of
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ _ ->
+ new_subscription(Nidx, Owner, Subscription, SubState)
+ end;
+ {<<>>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<>>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SubId, SubState);
+ _ -> replace_subscription({Subscription, SubId}, SubState)
+ end
+ end.
+
+replace_subscription(NewSub, SubState) ->
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
+ set_state(SubState#pubsub_state{subscriptions = NewSubs}).
+
+replace_subscription(_, [], Acc) -> Acc;
+replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
+ replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
+
+new_subscription(_Nidx, _Owner, Subscription, SubState) ->
+ %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
+ SubId = pubsub_subscription_odbc:make_subid(),
+ Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
+ set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
+ {Subscription, SubId}.
+
+unsub_with_subid(Nidx, SubId, SubState) ->
+ %%pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
+ NewSubs = [{S, Sid}
+ || {S, Sid} <- SubState#pubsub_state.subscriptions,
+ SubId =/= Sid],
+ case {NewSubs, SubState#pubsub_state.affiliation} of
+ {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid));
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
get_pending_nodes(Host, Owner) ->
- node_hometree_odbc:get_pending_nodes(Host, Owner).
+ GenKey = jid:remove_resource(jid:tolower(Owner)),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
+ affiliation = owner, _ = '_'}),
+ Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
+ case lists:member(Nidx, Nidxxs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false ->
+ Acc
+ end
+ end,
+ [], pubsub_state),
+ {result, Reply}.
+
+get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ HasPending = fun
+ ({pending, _}) -> true;
+ (pending) -> true;
+ (_) -> false
+ end,
+ case lists:any(HasPending, Subs) of
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false ->
+ false
+ end.
get_states(Nidx) ->
- node_hometree_odbc:get_states(Nidx).
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
+ {result,
+ lists:map(fun ([SJID, Aff, Subs]) ->
+ #pubsub_state{stateid = {decode_jid(SJID), Nidx},
+ items = itemids(Nidx, SJID),
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)}
+ end,
+ RItems)};
+ _ ->
+ {result, []}
+ end.
get_state(Nidx, JID) ->
- node_hometree_odbc:get_state(Nidx, JID).
+ State = get_state_without_itemids(Nidx, JID),
+ {SJID, _} = State#pubsub_state.stateid,
+ State#pubsub_state{items = itemids(Nidx, SJID)}.
+
+-spec(get_state_without_itemids/2 ::
+ (Nidx :: mod_pubsub:nodeIdx(),
+ Key :: ljid()) ->
+ mod_pubsub:pubsubState()
+ ).
+get_state_without_itemids(Nidx, JID) ->
+ J = encode_jid(JID),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
+ #pubsub_state{stateid = {decode_jid(SJID), Nidx},
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)};
+ _ ->
+ #pubsub_state{stateid = {JID, Nidx}}
+ end.
set_state(State) ->
- node_hometree_odbc:set_state(State).
+ {_, Nidx} = State#pubsub_state.stateid,
+ set_state(Nidx, State).
+
+set_state(Nidx, State) ->
+ {JID, _} = State#pubsub_state.stateid,
+ J = encode_jid(JID),
+ S = encode_subscriptions(State#pubsub_state.subscriptions),
+ A = encode_affiliation(State#pubsub_state.affiliation),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
+ <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>,
+ Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
+ end,
+ ok.
-get_items(Nidx, From, RSM) ->
- node_hometree_odbc:get_items(Nidx, From, RSM).
+del_state(Nidx, JID) ->
+ J = encode_jid(JID),
+ catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, Nidx, <<"';">>]),
+ ok.
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(Nidx, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId, RSM).
+%get_items(Nidx, _From) ->
+% case catch
+% ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+% "from pubsub_item where nodeid='">>, Nidx,
+% <<"' order by modification desc;">>])
+% of
+% {selected,
+% [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+% {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
+% _ ->
+% {result, []}
+% end.
+
+get_items(Nidx, From, none) ->
+ MaxItems = case catch
+ ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
+ "where nodeid='">>, Nidx, <<"' and name='max_items';">>])
+ of
+ {selected, [<<"val">>], [[Value]]} ->
+ Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
+ element(2, erl_parse:parse_term(Tokens));
+ _ ->
+ ?MAXITEMS
+ end,
+ get_items(Nidx, From, #rsm_in{max = MaxItems});
+get_items(Nidx, _From,
+ #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
+ Max = ejabberd_odbc:escape(jlib:i2l(M)),
+ {Way, Order} = case Direction of
+ % aft -> {<<"<">>, <<"desc">>};
+ % before when I == <<>> -> {<<"is not">>, <<"asc">>};
+ % before -> {<<">">>, <<"asc">>};
+ % _ when IncIndex =/= undefined ->
+ % {<<"<">>, <<"desc">>}; % using index
+ _ ->
+ {<<"is not">>, <<"desc">>}% Can be better
+ end,
+ [AttrName, Id] = case I of
+ undefined when IncIndex =/= undefined ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
+ "where exists ( select count(*) as count1 "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and modification > pi.modification having count1 = ">>,
+ ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
+ of
+ {selected, [_], [[O]]} ->
+ [<<"modification">>, <<"'", O/binary, "'">>];
+ _ ->
+ [<<"modification">>, <<"null">>]
+ end;
+ undefined ->
+ [<<"modification">>, <<"null">>];
+ <<>> ->
+ [<<"modification">>, <<"null">>];
+ I ->
+ [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
+ [A, <<"'", B/binary, "'">>]
+ end,
+ Count = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [_], [[C]]} -> C;
+ _ -> <<"0">>
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ case RItems of
+ [[_, _, _, F, _]|_] ->
+ Index = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
+ "where nodeid='">>, Nidx, <<"' and ">>,
+ AttrName, <<" > '">>, F, <<"';">>])
+ of
+ %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
+ {selected, [_], [[In]]} -> In;
+ _ -> <<"0">>
+ end,
+ [_, _, _, L, _] = lists:last(RItems),
+ RsmOut = #rsm_out{count = Count, index = Index,
+ first = <<"modification@", F/binary>>,
+ last = <<"modification@", (jlib:i2l(L))/binary>>},
+ {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
+ [] ->
+ {result, {[], #rsm_out{count = Count}}}
+ end;
+ _ ->
+ {result, {[], none}}
+ end.
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
+ SubKey = jid:tolower(JID),
+ GenKey = jid:remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_items(Nidx, JID, RSM)
+ end.
+
+get_last_items(Nidx, _From, Count) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
+ _ ->
+ {result, []}
+ end.
get_item(Nidx, ItemId) ->
- node_hometree_odbc:get_item(Nidx, ItemId).
+ I = ejabberd_odbc:escape(ItemId),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
+ {result, raw_to_item(Nidx, RItem)};
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end.
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(Nidx, ItemId, JID,
- AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+ SubKey = jid:tolower(JID),
+ GenKey = jid:remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_item(Nidx, ItemId)
+ end.
set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+ {ItemId, Nidx} = Item#pubsub_item.itemid,
+ I = ejabberd_odbc:escape(ItemId),
+ {C, _} = Item#pubsub_item.creation,
+ {M, JID} = Item#pubsub_item.modification,
+ P = encode_jid(JID),
+ Payload = Item#pubsub_item.payload,
+ XML = ejabberd_odbc:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
+ S = fun ({T1, T2, T3}) ->
+ str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
+ <<"', modification='">>, S(M),
+ <<"', payload='">>, XML,
+ <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ "publisher, creation, modification, payload) "
+ "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
+ <<"', '">>, S(C), <<"', '">>, S(M),
+ <<"', '">>, XML, <<"');">>])
+ end,
+ ok.
+
+del_item(Nidx, ItemId) ->
+ I = ejabberd_odbc:escape(ItemId),
+ catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I, <<"' and nodeid='">>, Nidx, <<"';">>]).
-get_item_name(Host, Node, Id) ->
- node_hometree_odbc:get_item_name(Host, Node, Id).
+del_items(_, []) ->
+ ok;
+del_items(Nidx, [ItemId]) ->
+ del_item(Nidx, ItemId);
+del_items(Nidx, ItemIds) ->
+ I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
+ I, <<") and nodeid='">>, Nidx, <<"';">>]).
-get_last_items(Nidx, From, Count) ->
- node_hometree_odbc:get_last_items(Nidx, From, Count).
+get_item_name(_Host, _Node, Id) ->
+ Id.
node_to_path(Node) ->
- [(Node)].
+ node_flat:node_to_path(Node).
path_to_node(Path) ->
- case Path of
- % default slot
- [Node] -> iolist_to_binary(Node);
- % handle old possible entries, used when migrating database content to new format
- [Node | _] when is_binary(Node) ->
- iolist_to_binary(str:join([<<"">> | Path], <<"/">>));
- % default case (used by PEP for example)
- _ -> iolist_to_binary(Path)
+ node_flat:path_to_node(Path).
+
+
+first_in_list(_Pred, []) ->
+ false;
+first_in_list(Pred, [H | T]) ->
+ case Pred(H) of
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
+ end.
+
+itemids(Nidx, {U, S, R}) ->
+ itemids(Nidx, encode_jid({U, S, R}));
+itemids(Nidx, SJID) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
+ <<"%' order by modification desc;">>])
+ of
+ {selected, [<<"itemid">>], RItems} ->
+ [ItemId || [ItemId] <- RItems];
+ _ ->
+ []
+ end.
+
+select_affiliation_subscriptions(Nidx, JID) ->
+ J = encode_jid(JID),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ "pubsub_state where nodeid='">>,
+ Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
+ {decode_affiliation(A), decode_subscriptions(S)};
+ _ ->
+ {none, []}
end.
+
+select_affiliation_subscriptions(Nidx, JID, JID) ->
+ select_affiliation_subscriptions(Nidx, JID);
+select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
+ {result, Affiliation} = get_affiliation(Nidx, GenKey),
+ {result, Subscriptions} = get_subscriptions(Nidx, SubKey),
+ {Affiliation, Subscriptions}.
+
+update_affiliation(Nidx, JID, Affiliation) ->
+ J = encode_jid(JID),
+ A = encode_affiliation(Affiliation),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ A, <<"' where nodeid='">>, Nidx,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
+ end.
+
+update_subscription(Nidx, JID, Subscription) ->
+ J = encode_jid(JID),
+ S = encode_subscriptions(Subscription),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
+ <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
+ end.
+
+-spec(decode_jid/1 ::
+ ( SJID :: binary())
+ -> ljid()
+ ).
+decode_jid(SJID) ->
+ jid:tolower(jid:from_string(SJID)).
+
+-spec(decode_affiliation/1 ::
+ ( Arg :: binary())
+ -> atom()
+ ).
+decode_affiliation(<<"o">>) -> owner;
+decode_affiliation(<<"p">>) -> publisher;
+decode_affiliation(<<"u">>) -> publish_only;
+decode_affiliation(<<"m">>) -> member;
+decode_affiliation(<<"c">>) -> outcast;
+decode_affiliation(_) -> none.
+
+-spec(decode_subscription/1 ::
+ ( Arg :: binary())
+ -> atom()
+ ).
+decode_subscription(<<"s">>) -> subscribed;
+decode_subscription(<<"p">>) -> pending;
+decode_subscription(<<"u">>) -> unconfigured;
+decode_subscription(_) -> none.
+
+-spec(decode_subscriptions/1 ::
+ ( Subscriptions :: binary())
+ -> [] | [{atom(), binary()},...]
+ ).
+decode_subscriptions(Subscriptions) ->
+ lists:foldl(fun (Subscription, Acc) ->
+ case str:tokens(Subscription, <<":">>) of
+ [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], str:tokens(Subscriptions, <<",">>)).
+
+-spec(encode_jid/1 ::
+ ( JID :: ljid())
+ -> binary()
+ ).
+encode_jid(JID) ->
+ ejabberd_odbc:escape(jid:to_string(JID)).
+
+-spec(encode_host/1 ::
+ ( Host :: host())
+ -> binary()
+ ).
+encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
+encode_host(Host) -> ejabberd_odbc:escape(Host).
+
+-spec(encode_affiliation/1 ::
+ ( Arg :: atom())
+ -> binary()
+ ).
+encode_affiliation(owner) -> <<"o">>;
+encode_affiliation(publisher) -> <<"p">>;
+encode_affiliation(publish_only) -> <<"u">>;
+encode_affiliation(member) -> <<"m">>;
+encode_affiliation(outcast) -> <<"c">>;
+encode_affiliation(_) -> <<"n">>.
+
+-spec(encode_subscription/1 ::
+ ( Arg :: atom())
+ -> binary()
+ ).
+encode_subscription(subscribed) -> <<"s">>;
+encode_subscription(pending) -> <<"p">>;
+encode_subscription(unconfigured) -> <<"u">>;
+encode_subscription(_) -> <<"n">>.
+
+-spec(encode_subscriptions/1 ::
+ ( Subscriptions :: [] | [{atom(), binary()},...])
+ -> binary()
+ ).
+encode_subscriptions(Subscriptions) ->
+ str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
+ || {S, SubId} <- Subscriptions], <<",">>).
+
+%%% record getter/setter
+
+state_to_raw(Nidx, State) ->
+ {JID, _} = State#pubsub_state.stateid,
+ J = encode_jid(JID),
+ A = encode_affiliation(State#pubsub_state.affiliation),
+ S = encode_subscriptions(State#pubsub_state.subscriptions),
+ [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
+
+raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
+ JID = decode_jid(SJID),
+ ToTime = fun (Str) ->
+ [T1, T2, T3] = str:tokens(Str, <<":">>),
+ {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
+ end,
+ Payload = case fxml_stream:parse_element(XML) of
+ {error, _Reason} -> [];
+ El -> [El]
+ end,
+ #pubsub_item{itemid = {ItemId, Nidx},
+ creation = {ToTime(Creation), JID},
+ modification = {ToTime(Modification), JID},
+ payload = Payload}.
diff --git a/src/node_hometree.erl b/src/node_hometree.erl
index 296fbdde..2d26c0ee 100644
--- a/src/node_hometree.erl
+++ b/src/node_hometree.erl
@@ -1,43 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_hometree.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard tree ordered node plugin
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
-
-%%% @todo The item table should be handled by the plugin, but plugin that do
-%%% not want to manage it should be able to use the default behaviour.
-%%% @todo Plugin modules should be able to register to receive presence update
-%%% send to pubsub.
-
-%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
-%%% <p>It is used as a default for all unknown PubSub node type. It can serve
-%%% as a developer basis and reference to build its own custom pubsub node
-%%% types.</p>
-%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
-%%% development is still a work in progress. However, the system is already
-%%% useable and useful as is. Please, send us comments, feedback and
-%%% improvements.</p>
+%%% 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(node_hometree).
-behaviour(gen_pubsub_node).
@@ -55,84 +39,35 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/6, get_items/2,
- get_items/7, get_items/3, get_item/7,
+ set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
-init(Host, ServerHost, _Opts) ->
- pubsub_subscription:init(),
- mnesia:create_table(pubsub_state,
- [{disc_copies, [node()]},
- {type, ordered_set},
- {attributes, record_info(fields, pubsub_state)}]),
- mnesia:create_table(pubsub_item,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, pubsub_item)}]),
- ItemsFields = record_info(fields, pubsub_item),
- case mnesia:table_info(pubsub_item, attributes) of
- ItemsFields -> ok;
- _ -> mnesia:transform_table(pubsub_item, ignore, ItemsFields)
- end,
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
- {publish_model, publishers},
- {notification_type, headline},
- {max_payload_size, ?MAX_PAYLOAD_SIZE},
- {send_last_published_item, on_sub_and_presence},
- {deliver_notifications, true},
- {presence_based_delivery, false}].
+ node_flat:options().
features() ->
- [<<"create-nodes">>,
- <<"auto-create">>,
- <<"access-authorize">>,
- <<"delete-nodes">>,
- <<"delete-items">>,
- <<"get-pending">>,
- <<"instant-nodes">>,
- <<"manage-subscriptions">>,
- <<"modify-affiliations">>,
- <<"multi-subscribe">>,
- <<"outcast-affiliation">>,
- <<"persistent-items">>,
- <<"publish">>,
- <<"publish-only-affiliation">>,
- <<"purge-nodes">>,
- <<"retract-items">>,
- <<"retrieve-affiliations">>,
- <<"retrieve-items">>,
- <<"retrieve-subscriptions">>,
- <<"subscribe">>,
- <<"subscription-notifications">>,
- <<"subscription-options">>].
+ node_flat:features().
%% @doc Checks if the current user has the permission to create the requested node
-%% <p>In {@link node_default}, the permission is decided by the place in the
+%% <p>In hometree node, the permission is decided by the place in the
%% hierarchy where the user is creating the node. The access parameter is also
-%% checked in the default module. This parameter depends on the value of the
+%% checked. This parameter depends on the value of the
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
-%% <p>This function also check that node can be created a a children of its
+%% <p>This function also check that node can be created as a children of its
%% parent node</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
- LOwner = jlib:jid_tolower(Owner),
+ LOwner = jid:tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
{<<"">>, Host, <<"">>} ->
@@ -150,707 +85,92 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
- affiliation = owner}),
- {result, {default, broadcast}}.
+ node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
- Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
- lists:map(fun (S) -> {J, S} end, Ss)
- end,
- Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
- del_items(Nidx, Items),
- del_state(Nidx, LJID)
- end, States),
- {PubsubNode, lists:flatmap(Tr, States)}
- end, Nodes),
- {result, {default, broadcast, Reply}}.
-
-%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the subscription and passes the
-%% result of the preparation as a record.</li>
-%% <li>This function gets the prepared record and several other parameters and
-%% can decide to:<ul>
-%% <li>reject the subscription;</li>
-%% <li>allow it as is, letting the main module perform the database
-%% persistance;</li>
-%% <li>allow it, modifying the record. The main module will store the
-%% modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
-%% subscription will actually be performed.</li>
-%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
-%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
-%% parameter contains an error, no subscription will be performed.</li>
-%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
-%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
-%% passed in parameter <tt>SubscribeResult</tt>.</li>
-%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
-%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
+ node_flat:delete_node(Nodes).
+
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = SubState#pubsub_state.subscriptions,
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun
- ({pending, _}) -> true;
- (_) -> false
- end,
- Subscriptions),
- Owner = Affiliation == owner,
- if not Authorized ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
- (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- set_state(SubState#pubsub_state{subscriptions =
- [{NewSub, SubId} | Subscriptions]}),
- case {NewSub, SendLast} of
- {subscribed, never} ->
- {result, {default, subscribed, SubId}};
- {subscribed, _} ->
- {result, {default, subscribed, SubId, send_last}};
- {_, _} ->
- {result, {default, pending, SubId}}
- end
- end.
+ node_flat:subscribe_node(Nidx, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
-%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Subscriptions = lists:filter(fun
- ({_Sub, _SubId}) -> true;
- (_SubId) -> false
- end,
- SubState#pubsub_state.subscriptions),
- SubIdExists = case SubId of
- <<>> -> false;
- Binary when is_binary(Binary) -> true;
- _ -> false
- end,
- if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun
- ({_, S}) when S == SubId -> true;
- (_) -> false
- end,
- SubState#pubsub_state.subscriptions),
- case Sub of
- {value, S} ->
- delete_subscriptions(SubKey, Nidx, [S], SubState),
- {result, default};
- false ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
- {result, default};
- %% No subid supplied, but there's only one matching subscription
- length(Subscriptions) == 1 ->
- delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
- end.
-
-delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
- NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
- pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
- Acc -- [{Subscription, SubId}]
- end, SubState#pubsub_state.subscriptions, Subscriptions),
- case {SubState#pubsub_state.affiliation, NewSubs} of
- {none, []} -> del_state(Nidx, SubKey);
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
-
-%% @doc <p>Publishes the item passed as parameter.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the item to publish and passes the
-%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
-%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
-%% <li>reject the publication;</li>
-%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
-%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
-%% publication is actually performed.</li>
-%% <li><tt>true</tt>: Publication operation is allowed, based on the
-%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
-%% parameter contains an error, no subscription will actually be
-%% performed.</li>
-%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
-%% in parameter <tt>Item</tt>. The persistance will be performed by the main
-%% module.</li>
-%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Affiliation = GenState#pubsub_state.affiliation,
- Subscribed = case PublishModel of
- subscribers -> is_subscribed(SubState#pubsub_state.subscriptions);
- _ -> undefined
- end,
- if not ((PublishModel == open) or
- (PublishModel == publishers) and
- ((Affiliation == owner)
- or (Affiliation == publisher)
- or (Affiliation == publish_only))
- or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
- true ->
- if MaxItems > 0 ->
- Now = now(),
- PubId = {Now, SubKey},
- Item = case get_item(Nidx, ItemId) of
- {result, OldItem} ->
- OldItem#pubsub_item{modification = PubId,
- payload = Payload};
- _ ->
- #pubsub_item{itemid = {ItemId, Nidx},
- creation = {Now, GenKey},
- modification = PubId,
- payload = Payload}
- end,
- Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
- {result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
- set_item(Item),
- set_state(GenState#pubsub_state{items = NI}),
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
- end.
-
-%% @doc <p>This function is used to remove extra items, most notably when the
-%% maximum number of items has been reached.</p>
-%% <p>This function is used internally by the core PubSub module, as no
-%% permission check is performed.</p>
-%% <p>In the default plugin module, the oldest items are removed, but other
-%% rules can be used.</p>
-%% <p>If another PubSub plugin wants to delegate the item removal (and if the
-%% plugin is using the default pubsub storage), it can implements this function like this:
-%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
-%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
-remove_extra_items(_Nidx, unlimited, ItemIds) ->
- {result, {ItemIds, []}};
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- NewItems = lists:sublist(ItemIds, MaxItems),
- OldItems = lists:nthtail(length(NewItems), ItemIds),
- del_items(Nidx, OldItems),
- {result, {NewItems, OldItems}}.
-
-%% @doc <p>Triggers item deletion.</p>
-%% <p>Default plugin: The user performing the deletion must be the node owner
-%% or a publisher, or PublishModel being open.</p>
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
- Allowed = Affiliation == publisher orelse
- Affiliation == owner orelse
- PublishModel == open orelse
- case get_item(Nidx, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if not Allowed ->
- {error, ?ERR_FORBIDDEN};
- true ->
- case lists:member(ItemId, Items) of
- true ->
- del_item(Nidx, ItemId),
- set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
- {result, {default, broadcast}};
- false ->
- case Affiliation of
- owner ->
- {result, States} = get_states(Nidx),
- lists:foldl(fun
- (#pubsub_state{items = PI} = S, Res) ->
- case lists:member(ItemId, PI) of
- true ->
- Nitems = lists:delete(ItemId, PI),
- del_item(Nidx, ItemId),
- set_state(S#pubsub_state{items = Nitems}),
- {result, {default, broadcast}};
- false ->
- Res
- end;
- (_, Res) ->
- Res
- end,
- {error, ?ERR_ITEM_NOT_FOUND}, States);
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
- end
- end
- end.
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun
- (#pubsub_state{items = []}) ->
- ok;
- (#pubsub_state{items = Items} = S) ->
- del_items(Nidx, Items),
- set_state(S#pubsub_state{items = []})
- end,
- States),
- {result, {default, broadcast}};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end.
-
-%% @doc <p>Return the current affiliations for the given user</p>
-%% <p>The default module reads affiliations in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
+ node_flat:purge_node(Nidx, Owner).
+
get_entity_affiliations(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
- NodeTree = mod_pubsub:tree(Host),
- Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
- _ -> Acc
- end
- end,
- [], States),
- {result, Reply}.
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- {result, States} = get_states(Nidx),
- Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
- {result, lists:map(Tr, States)}.
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- #pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
- {result, Affiliation}.
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case {Affiliation, GenState#pubsub_state.subscriptions} of
- {none, []} -> del_state(Nidx, GenKey);
- _ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
- end.
-
-%% @doc <p>Return the current subscriptions for the given user</p>
-%% <p>The default module reads subscriptions in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
get_entity_subscriptions(Host, Owner) ->
- {U, D, _} = SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- States = case SubKey of
- GenKey ->
- mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
- _ ->
- mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
- ++
- mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
- end,
- NodeTree = mod_pubsub:tree(Host),
- Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, J} | Acc2]
- end,
- Acc, Ss);
- _ ->
- Acc
- end
- end,
- [], States),
- {result, Reply}.
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- {result, States} = get_states(Nidx),
- Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
- case Subscriptions of
- [_ | _] ->
- lists:foldl(fun ({S, SubId}, Acc) ->
- [{J, S, SubId} | Acc]
- end,
- [], Subscriptions);
- [] ->
- [];
- _ ->
- [{J, none}]
- end
- end,
- {result, lists:flatmap(Tr, States)}.
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(Nidx, SubKey),
- {result, SubState#pubsub_state.subscriptions}.
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(Nidx, SubKey),
- case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
- _ ->
- new_subscription(Nidx, Owner, Subscription, SubState)
- end;
- {<<>>, [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {<<>>, [_ | _]} ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
- _ ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
- end.
-
-replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
- set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-
-replace_subscription(_, [], Acc) -> Acc;
-replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
- replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
-
-new_subscription(Nidx, Owner, Sub, SubState) ->
- SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
- Subs = SubState#pubsub_state.subscriptions,
- set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
- {Sub, SubId}.
-
-unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
- pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
- NewSubs = [{S, Sid}
- || {S, Sid} <- SubState#pubsub_state.subscriptions,
- SubId =/= Sid],
- case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} -> del_state(Nidx, Entity);
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
-
-%% @doc <p>Returns a list of Owner's nodes on Host with pending
-%% subscriptions.</p>
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
get_pending_nodes(Host, Owner) ->
- GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner,
- _ = '_'}),
- NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
- NodeTree = mod_pubsub:tree(Host),
- Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
- case lists:member(Nidx, NodeIdxs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end,
- [], pubsub_state),
- {result, Reply}.
-
-get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
- HasPending = fun
- ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
- end,
- case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} -> {value, Node};
- _ -> false
- end;
- false ->
- false
- end.
-
-%% @doc Returns the list of stored states for a given node.
-%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_state table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the states where they wants (for example in a
-%% relational database).</p>
-%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
-%% they can implement this function like this:
-%% ```get_states(Nidx) ->
-%% node_default:get_states(Nidx).'''</p>
+ node_flat:get_pending_nodes(Host, Owner).
+
get_states(Nidx) ->
- States = case catch mnesia:match_object(
- #pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
- List when is_list(List) -> List;
- _ -> []
- end,
- {result, States}.
-
-%% @doc <p>Returns a state (one state list), given its reference.</p>
-get_state(Nidx, Key) ->
- StateId = {Key, Nidx},
- case catch mnesia:read({pubsub_state, StateId}) of
- [State] when is_record(State, pubsub_state) -> State;
- _ -> #pubsub_state{stateid = StateId}
- end.
-
-%% @doc <p>Write a state into database.</p>
-set_state(State) when is_record(State, pubsub_state) ->
- mnesia:write(State).
-%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
-%% @doc <p>Delete a state from database.</p>
-del_state(Nidx, Key) ->
- mnesia:delete({pubsub_state, {Key, Nidx}}).
-
-%% @doc Returns the list of stored items for a given node.
-%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_item table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the items where they wants (for example in a
-%% relational database), or they can even decide not to persist any items.</p>
-%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
-%% they can implement this function like this:
-%% ```get_items(Nidx, From) ->
-%% node_default:get_items(Nidx, From).'''</p>
-get_items(Nidx, From) ->
- get_items(Nidx, From, none).
-get_items(Nidx, _From, _RSM) ->
- Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
- {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
-
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, _RSM) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- SubState = get_state(Nidx, SubKey),
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = SubState#pubsub_state.subscriptions,
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(Nidx, JID)
- end.
-
-%% @doc <p>Returns an item (one item list), given its reference.</p>
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
- [Item] when is_record(Item, pubsub_item) -> {result, Item};
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
- end.
-
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = GenState#pubsub_state.subscriptions,
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(Nidx, ItemId)
- end.
-
-%% @doc <p>Write an item into database.</p>
-set_item(Item) when is_record(Item, pubsub_item) ->
- mnesia:write(Item).
-%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
-%% @doc <p>Delete an item from database.</p>
-del_item(Nidx, ItemId) ->
- mnesia:delete({pubsub_item, {ItemId, Nidx}}).
-
-del_items(Nidx, ItemIds) ->
- lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
- end,
- ItemIds).
-
-get_item_name(_Host, _Node, Id) ->
- Id.
-
-%% @doc <p>Return the name of the node if known: Default is to return
-%% node id.</p>
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+%% @doc <p>Return the path of the node.</p>
node_to_path(Node) ->
str:tokens(Node, <<"/">>).
path_to_node([]) -> <<>>;
path_to_node(Path) -> iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(publish_only, _) -> false;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
-%can_fetch_item(_Affiliation, _Subscription) -> false.
-
-is_subscribed(Subscriptions) ->
- lists:any(fun
- ({subscribed, _SubId}) -> true;
- (_) -> false
- end,
- Subscriptions).
-
-first_in_list(_Pred, []) ->
- false;
-first_in_list(Pred, [H | T]) ->
- case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
- end.
diff --git a/src/node_hometree_odbc.erl b/src/node_hometree_odbc.erl
index b9abac20..6ac5c37b 100644
--- a/src/node_hometree_odbc.erl
+++ b/src/node_hometree_odbc.erl
@@ -1,43 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_hometree_odbc.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard tree ordered node plugin with ODBC backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
-
-%%% @todo The item table should be handled by the plugin, but plugin that do
-%%% not want to manage it should be able to use the default behaviour.
-%%% @todo Plugin modules should be able to register to receive presence update
-%%% send to pubsub.
-
-%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
-%%% <p>It is used as a default for all unknown PubSub node type. It can serve
-%%% as a developer basis and reference to build its own custom pubsub node
-%%% types.</p>
-%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
-%%% development is still a work in progress. However, the system is already
-%%% useable and useful as is. Please, send us comments, feedback and
-%%% improvements.</p>
+%%% 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(node_hometree_odbc).
-behaviour(gen_pubsub_node).
@@ -55,26 +39,20 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/7, get_items/3,
- get_items/6, get_items/2, get_item/7,
+ set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1,
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
--export([decode_jid/1, encode_jid/1,
- decode_affiliation/1, decode_subscriptions/1,
- encode_affiliation/1, encode_subscriptions/1,
- encode_host/1]).
-
-init(Host, ServerHost, _Opts) ->
- pubsub_subscription_odbc:init(),
+init(Host, ServerHost, Opts) ->
+ node_flat_odbc:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(Host, ServerHost) ->
+ node_flat_odbc:terminate(Host, ServerHost).
options() ->
[{odbc, true}, {rsm, true} | node_hometree:options()].
@@ -82,921 +60,96 @@ options() ->
features() ->
[<<"rsm">> | node_hometree:features()].
-%% @doc Checks if the current user has the permission to create the requested node
-%% <p>In {@link node_default}, the permission is decided by the place in the
-%% hierarchy where the user is creating the node. The access parameter is also
-%% checked in the default module. This parameter depends on the value of the
-%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
-%% <p>This function also check that node can be created a a children of its
-%% parent node</p>
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- {_U, _S, _R} = OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values(">>, state_to_raw(Nidx, State), <<");">>]),
- {result, {default, broadcast}}.
+ node_flat_odbc:create_node(Nidx, Owner).
delete_node(Nodes) ->
- Reply = lists:map(fun (#pubsub_node{id = Nidx} = Node) ->
- Subscriptions = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
- "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
- [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
- _ ->
- []
- end,
- {Node, Subscriptions}
- end,
- Nodes),
- {result, {default, broadcast, Reply}}.
+ node_flat_odbc:delete_node(Nodes).
-%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the subscription and passes the
-%% result of the preparation as a record.</li>
-%% <li>This function gets the prepared record and several other parameters and
-%% can decide to:<ul>
-%% <li>reject the subscription;</li>
-%% <li>allow it as is, letting the main module perform the database
-%% persistance;</li>
-%% <li>allow it, modifying the record. The main module will store the
-%% modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
-%% subscription will actually be performed.</li>
-%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
-%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
-%% parameter contains an error, no subscription will be performed.</li>
-%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
-%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
-%% passed in parameter <tt>SubscribeResult</tt>.</li>
-%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
-%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun
- ({pending, _}) -> true;
- (_) -> false
- end,
- Subscriptions),
- Owner = Affiliation == owner,
- if not Authorized ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
- (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- update_subscription(Nidx, SubKey, [{NewSub, SubId} | Subscriptions]),
- case {NewSub, SendLast} of
- {subscribed, never} -> {result, {default, subscribed, SubId}};
- {subscribed, _} -> {result, {default, subscribed, SubId, send_last}};
- {_, _} -> {result, {default, pending, SubId}}
- end
- end.
+ node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
-%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
- SubIdExists = case SubId of
- <<>> -> false;
- Binary when is_binary(Binary) -> true;
- _ -> false
- end,
- if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun
- ({_, S}) when S == SubId -> true;
- (_) -> false
- end,
- Subscriptions),
- case Sub of
- {value, S} ->
- delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
- {result, default};
- false ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
- || S <- Subscriptions],
- {result, default};
- %% No subid supplied, but there's only one matching subscription
- length(Subscriptions) == 1 ->
- delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
- end.
+ node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
-delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
- NewSubs = Subscriptions -- [{Subscription, SubId}],
- pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
- case {Affiliation, NewSubs} of
- {none, []} -> del_state(Nidx, SubKey);
- _ -> update_subscription(Nidx, SubKey, NewSubs)
- end.
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
-%% @doc <p>Publishes the item passed as parameter.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the item to publish and passes the
-%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
-%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
-%% <li>reject the publication;</li>
-%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
-%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
-%% publication is actually performed.</li>
-%% <li><tt>true</tt>: Publication operation is allowed, based on the
-%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
-%% parameter contains an error, no subscription will actually be
-%% performed.</li>
-%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
-%% in parameter <tt>Item</tt>. The persistance will be performed by the main
-%% module.</li>
-%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Subscribed = case PublishModel of
- subscribers -> is_subscribed(Subscriptions);
- _ -> undefined
- end,
- if not ((PublishModel == open) or
- (PublishModel == publishers) and
- ((Affiliation == owner)
- or (Affiliation == publisher)
- or (Affiliation == publish_only))
- or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
- true ->
- if MaxItems > 0 ->
- PubId = {now(), SubKey},
- set_item(#pubsub_item{itemid = {ItemId, Nidx},
- creation = {now(), GenKey},
- modification = PubId,
- payload = Payload}),
- Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]],
- {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items),
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
- end.
-
-%% @doc <p>This function is used to remove extra items, most notably when the
-%% maximum number of items has been reached.</p>
-%% <p>This function is used internally by the core PubSub module, as no
-%% permission check is performed.</p>
-%% <p>In the default plugin module, the oldest items are removed, but other
-%% rules can be used.</p>
-%% <p>If another PubSub plugin wants to delegate the item removal (and if the
-%% plugin is using the default pubsub storage), it can implements this function like this:
-%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
-%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
-remove_extra_items(_Nidx, unlimited, ItemIds) ->
- {result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- NewItems = lists:sublist(ItemIds, MaxItems),
- OldItems = lists:nthtail(length(NewItems), ItemIds),
- del_items(Nidx, OldItems),
- {result, {NewItems, OldItems}}.
+ node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
-%% @doc <p>Triggers item deletion.</p>
-%% <p>Default plugin: The user performing the deletion must be the node owner
-%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- {result, Affiliation} = get_affiliation(Nidx, GenKey),
- Allowed = Affiliation == publisher orelse
- Affiliation == owner orelse
- PublishModel == open orelse
- case get_item(Nidx, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if not Allowed ->
- {error, ?ERR_FORBIDDEN};
- true ->
- case del_item(Nidx, ItemId) of
- {updated, 1} -> {result, {default, broadcast}};
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
- end
- end.
+ node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun
- (#pubsub_state{items = []}) -> ok;
- (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
- end,
- States),
- {result, {default, broadcast}};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end.
+ node_flat_odbc:purge_node(Nidx, Owner).
-%% @doc <p>Return the current affiliations for the given user</p>
-%% <p>The default module reads affiliations in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
- "from pubsub_state i, pubsub_node n where "
- "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
- of
- {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
- [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
- || [N, T, I, A] <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
- "where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
- [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
- "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"affiliation">>], [[A]]} ->
- decode_affiliation(A);
- _ ->
- none
- end,
- {result, Reply}.
+ node_flat_odbc:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
- case {Affiliation, Subscriptions} of
- {none, []} -> del_state(Nidx, GenKey);
- _ -> update_affiliation(Nidx, GenKey, Affiliation)
- end.
+ node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
-%% @doc <p>Return the current subscriptions for the given user</p>
-%% <p>The default module reads subscriptions in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- SJ = encode_jid(SubKey),
- GJ = encode_jid(GenKey),
- Query = case SubKey of
- GenKey ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
- _ ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
- end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected,
- [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, Jid} | Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_subscriptions(Host, Owner).
-%% do the same as get_entity_subscriptions but filter result only to
-%% nodes having send_last_published_item=on_sub_and_presence
-%% as this call avoid seeking node, it must return node and type as well
--spec(get_entity_subscriptions_for_send_last/2 ::
- (
- Host :: mod_pubsub:hostPubsub(),
- Owner :: jid())
- -> {result,
- [{mod_pubsub:pubsubNode(),
- mod_pubsub:subscription(),
- mod_pubsub:subId(),
- ljid()}]
- }
- ).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- SJ = encode_jid(SubKey),
- GJ = encode_jid(GenKey),
- Query = case SubKey of
- GenKey ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
- "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
- _ ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
- "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
- end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected,
- [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, Jid}| Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(Nidx) ->
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
- "where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([J, S], Acc) ->
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Jid, none} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Jid, Sub, SubId} | Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- J = encode_jid(SubKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
- "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"subscriptions">>], [[S]]} ->
- decode_subscriptions(S);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state_without_itemids(Nidx, SubKey),
- case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
- _ ->
- new_subscription(Nidx, Owner, Subscription, SubState)
- end;
- {<<>>, [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {<<>>, [_ | _]} ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
- _ ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
- end.
-
-replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
- set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-
-replace_subscription(_, [], Acc) -> Acc;
-replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
- replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
-
-new_subscription(Nidx, Owner, Subscription, SubState) ->
- {result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
- Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
- set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
- {Subscription, SubId}.
-
-unsub_with_subid(Nidx, SubId, SubState) ->
- pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
- NewSubs = [{S, Sid}
- || {S, Sid} <- SubState#pubsub_state.subscriptions,
- SubId =/= Sid],
- case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid));
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
+ node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
-%% @doc <p>Returns a list of Owner's nodes on Host with pending
-%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
- GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner, _ = '_'}),
- Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
- NodeTree = mod_pubsub:tree(Host),
- Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
- case lists:member(Nidx, Nidxxs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end,
- [], pubsub_state),
- {result, Reply}.
+ node_flat_odbc:get_pending_nodes(Host, Owner).
-get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
- HasPending = fun
- ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
- end,
- case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} -> {value, Node};
- _ -> false
- end;
- false ->
- false
- end.
-
-%% @doc Returns the list of stored states for a given node.
-%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_state table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the states where they wants (for example in a
-%% relational database).</p>
-%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
-%% they can implement this function like this:
-%% ```get_states(Nidx) ->
-%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
- "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
- of
- {selected,
- [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
- {result,
- lists:map(fun ([SJID, Aff, Subs]) ->
- #pubsub_state{stateid = {decode_jid(SJID), Nidx},
- items = itemids(Nidx, SJID),
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)}
- end,
- RItems)};
- _ ->
- {result, []}
- end.
+ node_flat_odbc:get_states(Nidx).
-%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Nidx, JID) ->
- State = get_state_without_itemids(Nidx, JID),
- {SJID, _} = State#pubsub_state.stateid,
- State#pubsub_state{items = itemids(Nidx, SJID)}.
-
--spec(get_state_without_itemids/2 ::
- (Nidx :: mod_pubsub:nodeIdx(),
- Key :: ljid()) ->
- mod_pubsub:pubsubState()
- ).
-get_state_without_itemids(Nidx, JID) ->
- J = encode_jid(JID),
- case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
- "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
- of
- {selected,
- [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
- #pubsub_state{stateid = {decode_jid(SJID), Nidx},
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)};
- _ ->
- #pubsub_state{stateid = {JID, Nidx}}
- end.
+ node_flat_odbc:get_state(Nidx, JID).
-%% @doc <p>Write a state into database.</p>
set_state(State) ->
- {_, Nidx} = State#pubsub_state.stateid,
- set_state(Nidx, State).
-
-set_state(Nidx, State) ->
- {JID, _} = State#pubsub_state.stateid,
- J = encode_jid(JID),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- A = encode_affiliation(State#pubsub_state.affiliation),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
- <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>,
- Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
- end,
- ok.
-
-%% @doc <p>Delete a state from database.</p>
-del_state(Nidx, JID) ->
- J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
- J, <<"' and nodeid='">>, Nidx, <<"';">>]),
- ok.
-
-%% @doc Returns the list of stored items for a given node.
-%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_item table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the items where they wants (for example in a
-%% relational database), or they can even decide not to persist any items.</p>
-%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
-%% they can implement this function like this:
-%% ```get_items(Nidx, From) ->
-%% node_default:get_items(Nidx, From).'''</p>
-get_items(Nidx, _From) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc;">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
- _ ->
- {result, []}
- end.
-
-get_items(Nidx, From, none) ->
- MaxItems = case catch
- ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
- "where nodeid='">>, Nidx, <<"' and name='max_items';">>])
- of
- {selected, [<<"val">>], [[Value]]} ->
- Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
- element(2, erl_parse:parse_term(Tokens));
- _ ->
- ?MAXITEMS
- end,
- get_items(Nidx, From, #rsm_in{max = MaxItems});
-get_items(Nidx, _From,
- #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
- Max = ejabberd_odbc:escape(jlib:i2l(M)),
- {Way, Order} = case Direction of
- % aft -> {<<"<">>, <<"desc">>};
- % before when I == <<>> -> {<<"is not">>, <<"asc">>};
- % before -> {<<">">>, <<"asc">>};
- % _ when IncIndex =/= undefined ->
- % {<<"<">>, <<"desc">>}; % using index
- _ ->
- {<<"is not">>, <<"desc">>}% Can be better
- end,
- [AttrName, Id] = case I of
- undefined when IncIndex =/= undefined ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
- "where exists ( select count(*) as count1 "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and modification > pi.modification having count1 = ">>,
- ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
- of
- {selected, [_], [[O]]} ->
- [<<"modification">>, <<"'", O/binary, "'">>];
- _ ->
- [<<"modification">>, <<"null">>]
- end;
- undefined ->
- [<<"modification">>, <<"null">>];
- <<>> ->
- [<<"modification">>, <<"null">>];
- I ->
- [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
- [A, <<"'", B/binary, "'">>]
- end,
- Count = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [_], [[C]]} -> C;
- _ -> <<"0">>
- end,
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
- AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- case RItems of
- [[_, _, _, F, _]|_] ->
- Index = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and ">>,
- AttrName, <<" > '">>, F, <<"';">>])
- of
- %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
- {selected, [_], [[In]]} -> In;
- _ -> <<"0">>
- end,
- [_, _, _, L, _] = lists:last(RItems),
- RsmOut = #rsm_out{count = Count, index = Index,
- first = <<"modification@", F/binary>>,
- last = <<"modification@", (jlib:i2l(L))/binary>>},
- {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
- [] ->
- {result, {[], #rsm_out{count = Count}}}
- end;
- _ ->
- {result, {[], none}}
- end.
+ node_flat_odbc:set_state(State).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
+get_items(Nidx, From, RSM) ->
+ node_flat_odbc:get_items(Nidx, From, RSM).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(Nidx, JID, RSM)
- end.
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
-get_last_items(Nidx, _From, Count) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
- _ ->
- {result, []}
- end.
-
-%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
- "modification, payload from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
- {result, raw_to_item(Nidx, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
- end.
+ node_flat_odbc:get_item(Nidx, ItemId).
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(Nidx, ItemId)
- end.
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat_odbc:get_item(Nidx, ItemId, JID,
+ AccessModel, PresenceSubscription, RosterGroup, SubId).
-%% @doc <p>Write an item into database.</p>
set_item(Item) ->
- {ItemId, Nidx} = Item#pubsub_item.itemid,
- I = ejabberd_odbc:escape(ItemId),
- {C, _} = Item#pubsub_item.creation,
- {M, JID} = Item#pubsub_item.modification,
- P = encode_jid(JID),
- Payload = Item#pubsub_item.payload,
- XML = ejabberd_odbc:escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
- S = fun ({T1, T2, T3}) ->
- str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
- end,
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
- <<"', modification='">>, S(M),
- <<"', payload='">>, XML,
- <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
- "publisher, creation, modification, payload) "
- "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
- <<"', '">>, S(C), <<"', '">>, S(M),
- <<"', '">>, XML, <<"');">>])
- end,
- ok.
+ node_flat_odbc:set_item(Item).
-%% @doc <p>Delete an item from database.</p>
-del_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
- I, <<"' and nodeid='">>, Nidx, <<"';">>]).
+get_item_name(Host, Node, Id) ->
+ node_flat_odbc:get_item_name(Host, Node, Id).
-del_items(_, []) ->
- ok;
-del_items(Nidx, [ItemId]) ->
- del_item(Nidx, ItemId);
-del_items(Nidx, ItemIds) ->
- I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
- catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
- I, <<") and nodeid='">>, Nidx, <<"';">>]).
-
-get_item_name(_Host, _Node, Id) ->
- Id.
+get_last_items(Nidx, From, Count) ->
+ node_flat_odbc:get_last_items(Nidx, From, Count).
node_to_path(Node) ->
node_hometree:node_to_path(Node).
@@ -1004,194 +157,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_hometree:path_to_node(Path).
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(publish_only, _) -> false;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
-%can_fetch_item(_Affiliation, _Subscription) -> false.
-
-is_subscribed(Subscriptions) ->
- lists:any(fun
- ({subscribed, _SubId}) -> true;
- (_) -> false
- end,
- Subscriptions).
-
-first_in_list(_Pred, []) ->
- false;
-first_in_list(Pred, [H | T]) ->
- case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
- end.
-
-itemids(Nidx, {U, S, R}) ->
- itemids(Nidx, encode_jid({U, S, R}));
-itemids(Nidx, SJID) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
- "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
- <<"%' order by modification desc;">>])
- of
- {selected, [<<"itemid">>], RItems} ->
- [ItemId || [ItemId] <- RItems];
- _ ->
- []
- end.
-
-select_affiliation_subscriptions(Nidx, JID) ->
- J = encode_jid(JID),
- case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
- "pubsub_state where nodeid='">>,
- Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
- {decode_affiliation(A), decode_subscriptions(S)};
- _ ->
- {none, []}
- end.
-
-select_affiliation_subscriptions(Nidx, JID, JID) ->
- select_affiliation_subscriptions(Nidx, JID);
-select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
- {result, Affiliation} = get_affiliation(Nidx, GenKey),
- {result, Subscriptions} = get_subscriptions(Nidx, SubKey),
- {Affiliation, Subscriptions}.
-
-update_affiliation(Nidx, JID, Affiliation) ->
- J = encode_jid(JID),
- A = encode_affiliation(Affiliation),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
- A, <<"' where nodeid='">>, Nidx,
- <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
- end.
-
-update_subscription(Nidx, JID, Subscription) ->
- J = encode_jid(JID),
- S = encode_subscriptions(Subscription),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
- <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
- end.
-
--spec(decode_jid/1 ::
- ( SJID :: binary())
- -> ljid()
- ).
-decode_jid(SJID) ->
- jlib:jid_tolower(jlib:string_to_jid(SJID)).
-
--spec(decode_affiliation/1 ::
- ( Arg :: binary())
- -> atom()
- ).
-decode_affiliation(<<"o">>) -> owner;
-decode_affiliation(<<"p">>) -> publisher;
-decode_affiliation(<<"m">>) -> member;
-decode_affiliation(<<"c">>) -> outcast;
-decode_affiliation(_) -> none.
-
--spec(decode_subscription/1 ::
- ( Arg :: binary())
- -> atom()
- ).
-decode_subscription(<<"s">>) -> subscribed;
-decode_subscription(<<"p">>) -> pending;
-decode_subscription(<<"u">>) -> unconfigured;
-decode_subscription(_) -> none.
-
--spec(decode_subscriptions/1 ::
- ( Subscriptions :: binary())
- -> [] | [{atom(), binary()},...]
- ).
-decode_subscriptions(Subscriptions) ->
- lists:foldl(fun (Subscription, Acc) ->
- case str:tokens(Subscription, <<":">>) of
- [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
- _ -> Acc
- end
- end,
- [], str:tokens(Subscriptions, <<",">>)).
-
--spec(encode_jid/1 ::
- ( JID :: ljid())
- -> binary()
- ).
-encode_jid(JID) ->
- ejabberd_odbc:escape(jlib:jid_to_string(JID)).
-
--spec(encode_host/1 ::
- ( Host :: host())
- -> binary()
- ).
-encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
-encode_host(Host) -> ejabberd_odbc:escape(Host).
-
--spec(encode_affiliation/1 ::
- ( Arg :: atom())
- -> binary()
- ).
-encode_affiliation(owner) -> <<"o">>;
-encode_affiliation(publisher) -> <<"p">>;
-encode_affiliation(member) -> <<"m">>;
-encode_affiliation(outcast) -> <<"c">>;
-encode_affiliation(_) -> <<"n">>.
-
--spec(encode_subscription/1 ::
- ( Arg :: atom())
- -> binary()
- ).
-encode_subscription(subscribed) -> <<"s">>;
-encode_subscription(pending) -> <<"p">>;
-encode_subscription(unconfigured) -> <<"u">>;
-encode_subscription(_) -> <<"n">>.
-
--spec(encode_subscriptions/1 ::
- ( Subscriptions :: [] | [{atom(), binary()},...])
- -> binary()
- ).
-encode_subscriptions(Subscriptions) ->
- str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
- || {S, SubId} <- Subscriptions], <<",">>).
-
-%%% record getter/setter
-
-state_to_raw(Nidx, State) ->
- {JID, _} = State#pubsub_state.stateid,
- J = encode_jid(JID),
- A = encode_affiliation(State#pubsub_state.affiliation),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
-
-raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
- JID = decode_jid(SJID),
- ToTime = fun (Str) ->
- [T1, T2, T3] = str:tokens(Str, <<":">>),
- {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
- end,
- Payload = case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
- end,
- #pubsub_item{itemid = {ItemId, Nidx},
- creation = {ToTime(Creation), JID},
- modification = {ToTime(Modification), JID},
- payload = Payload}.
diff --git a/src/node_mb.erl b/src/node_mb.erl
index e48fd796..1213805c 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -1,43 +1,46 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_mb.erl
+%%% Author : Eric Cestari <ecestari@process-one.net>
+%%% Purpose : PEP microglobing experimentation
+%%% Created : 25 Sep 2008 by Eric Cestari <ecestari@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Eric Cestari <eric@ohmforce.com>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_mb).
-behaviour(gen_pubsub_node).
--author('eric@ohmforce.com').
+-author('ecestari@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
%%% @doc The module <strong>{@module}</strong> is the pep microblog PubSub plugin.
-%%% <p> To be used, mod_pubsub must be configured :
-%%% {mod_pubsub, [ % requires mod_caps
-%%% {access_createnode, pubsub_createnode},
-%%% {plugins, ["default", "pep","mb"]},
-%%% {pep_mapping, [{"urn:xmpp:microblog", "mb"}]}
-%%% ]},
-%%% </p>
+%%% <p>To be used, mod_pubsub must be configured:<pre>
+%%% mod_pubsub:
+%%% access_createnode: pubsub_createnode
+%%% ignore_pep_from_offline: false
+%%% plugins:
+%%% - "flat"
+%%% - "pep" # Requires mod_caps.
+%%% pep_mapping:
+%%% "urn:xmpp:microblog:0": "mb"
+%%% </pre></p>
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-export([init/3, terminate/2, options/0, features/0,
diff --git a/src/node_mix.erl b/src/node_mix.erl
new file mode 100644
index 00000000..b0410a8c
--- /dev/null
+++ b/src/node_mix.erl
@@ -0,0 +1,167 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat:path_to_node(Path).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_mix_odbc.erl b/src/node_mix_odbc.erl
new file mode 100644
index 00000000..e7cc6883
--- /dev/null
+++ b/src/node_mix_odbc.erl
@@ -0,0 +1,170 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix_odbc).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1, get_entity_subscriptions_for_send_last/2]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat_odbc:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat_odbc:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat_odbc:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat_odbc:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat_odbc:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat_odbc:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat_odbc:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat_odbc:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat_odbc:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat_odbc:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat_odbc:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat_odbc:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat_odbc:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat_odbc:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat_odbc:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat_odbc:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat_odbc:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat_odbc:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat_odbc:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat_odbc:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat_odbc:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat_odbc:path_to_node(Path).
+
+get_entity_subscriptions_for_send_last(Host, Owner) ->
+ node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_online.erl b/src/node_online.erl
new file mode 100644
index 00000000..1e9e2953
--- /dev/null
+++ b/src/node_online.erl
@@ -0,0 +1,173 @@
+%%%----------------------------------------------------------------------
+%%% File : node_online.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Handle only online users, remove offline subscriptions and nodes
+%%% Created : 15 Dec 2015 by Christophe Romain <christophe.romain@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(node_online).
+-behaviour(gen_pubsub_node).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
+
+-export([user_offline/3]).
+
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts),
+ ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
+ ?MODULE, user_offline, 75),
+ ok.
+
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost),
+ ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost,
+ ?MODULE, user_offline, 75),
+ ok.
+
+user_offline(_SID, #jid{luser=LUser,lserver=LServer}, _Info) ->
+ mod_pubsub:remove_user(LUser, LServer).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, false},
+ {purge_offline, true},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, publishers},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, on_sub_and_presence},
+ {deliver_notifications, true},
+ {presence_based_delivery, true}].
+
+features() ->
+ node_flat:features().
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat:path_to_node(Path).
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 8eeb28f0..da032539 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -1,28 +1,30 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_pep.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard PubSub PEP plugin
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
+%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep).
-behaviour(gen_pubsub_node).
@@ -32,9 +34,6 @@
-include("jlib.hrl").
-include("logger.hrl").
-%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
-%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
@@ -49,12 +48,13 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts),
+ node_flat:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost), ok.
+ node_flat:terminate(Host, ServerHost),
+ ok.
options() ->
[{deliver_payloads, true},
@@ -62,7 +62,7 @@ options() ->
{notify_delete, false},
{notify_retract, false},
{purge_offline, false},
- {persist_items, false},
+ {persist_items, true},
{max_items, 1},
{subscribe, true},
{access_model, presence},
@@ -93,7 +93,7 @@ features() ->
<<"subscribe">>].
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
- LOwner = jlib:jid_tolower(Owner),
+ LOwner = jid:tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
{<<"">>, Host, <<"">>} ->
@@ -112,39 +112,39 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_hometree:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- {_, D, _} = SubKey = jlib:jid_tolower(Owner),
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
+ {_, D, _} = SubKey = jid:tolower(Owner),
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = mod_pubsub:tree(Host),
Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
@@ -158,17 +158,17 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- {U, D, _} = SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
+ {U, D, _} = SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
States = case SubKey of
GenKey ->
mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
@@ -198,45 +198,45 @@ get_entity_subscriptions(Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
@@ -257,7 +257,7 @@ complain_if_modcaps_disabled(ServerHost) ->
false ->
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
"of host ~p. This plugin requires mod_caps "
- "to be enabled, but it isn't.",
+ "but it does not seems enabled, please check config.",
[ServerHost]);
true -> ok
end.
diff --git a/src/node_pep_odbc.erl b/src/node_pep_odbc.erl
index d80e686f..6eb0de04 100644
--- a/src/node_pep_odbc.erl
+++ b/src/node_pep_odbc.erl
@@ -1,28 +1,30 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_pep_odbc.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard PubSub PEP plugin with ODBC backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
+%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep_odbc).
-behaviour(gen_pubsub_node).
@@ -32,9 +34,6 @@
-include("jlib.hrl").
-include("logger.hrl").
-%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
-%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
@@ -50,12 +49,13 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_hometree_odbc:init(Host, ServerHost, Opts),
+ node_flat_odbc:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_hometree_odbc:terminate(Host, ServerHost), ok.
+ node_flat_odbc:terminate(Host, ServerHost),
+ ok.
options() ->
[{odbc, true}, {rsm, true} | node_pep:options()].
@@ -67,56 +67,56 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree_odbc:create_node(Nidx, Owner),
+ node_flat_odbc:create_node(Nidx, Owner),
{result, {default, broadcast}}.
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_hometree_odbc:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_hometree_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree_odbc:purge_node(Nidx, Owner).
+ node_flat_odbc:purge_node(Nidx, Owner).
get_entity_affiliations(_Host, Owner) ->
- OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
+ OwnerKey = jid:tolower(jid:remove_resource(Owner)),
+ node_flat_odbc:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(Nidx) ->
- node_hometree_odbc:get_node_affiliations(Nidx).
+ node_flat_odbc:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree_odbc:get_affiliation(Nidx, Owner).
+ node_flat_odbc:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- Host = node_hometree_odbc:encode_host(element(2, SubKey)),
- SJ = node_hometree_odbc:encode_jid(SubKey),
- GJ = node_hometree_odbc:encode_jid(GenKey),
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ Host = node_flat_odbc:encode_host(element(2, SubKey)),
+ SJ = node_flat_odbc:encode_jid(SubKey),
+ GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -134,11 +134,11 @@ get_entity_subscriptions(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_hometree_odbc:decode_jid(H),
+ O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_hometree_odbc:decode_subscriptions(S),
- node_hometree_odbc:decode_jid(J)}
+ node_flat_odbc:decode_subscriptions(S),
+ node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -147,11 +147,11 @@ get_entity_subscriptions(_Host, Owner) ->
{result, Reply}.
get_entity_subscriptions_for_send_last(_Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- Host = node_hometree_odbc:encode_host(element(2, SubKey)),
- SJ = node_hometree_odbc:encode_jid(SubKey),
- GJ = node_hometree_odbc:encode_jid(GenKey),
+ SubKey = jid:tolower(Owner),
+ GenKey = jid:remove_resource(SubKey),
+ Host = node_flat_odbc:encode_host(element(2, SubKey)),
+ SJ = node_flat_odbc:encode_jid(SubKey),
+ GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -173,11 +173,11 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_hometree_odbc:decode_jid(H),
+ O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_hometree_odbc:decode_subscriptions(S),
- node_hometree_odbc:decode_jid(J)}
+ node_flat_odbc:decode_subscriptions(S),
+ node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -186,48 +186,48 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree_odbc:get_node_subscriptions(Nidx).
+ node_flat_odbc:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_odbc:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree_odbc:get_pending_nodes(Host, Owner).
+ node_flat_odbc:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree_odbc:get_states(Nidx).
+ node_flat_odbc:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree_odbc:get_state(Nidx, JID).
+ node_flat_odbc:get_state(Nidx, JID).
set_state(State) ->
- node_hometree_odbc:set_state(State).
+ node_flat_odbc:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree_odbc:get_items(Nidx, From, RSM).
+ node_flat_odbc:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_odbc:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(Nidx, JID, Count) ->
- node_hometree_odbc:get_last_items(Nidx, JID, Count).
+ node_flat_odbc:get_last_items(Nidx, JID, Count).
get_item(Nidx, ItemId) ->
- node_hometree_odbc:get_item(Nidx, ItemId).
+ node_flat_odbc:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+ node_flat_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree_odbc:get_item_name(Host, Node, Id).
+ node_flat_odbc:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat_odbc:node_to_path(Node).
diff --git a/src/node_private.erl b/src/node_private.erl
index eab06d56..de80d870 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -1,28 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_private.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose :
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_private).
-behaviour(gen_pubsub_node).
@@ -45,10 +44,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +84,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_public.erl b/src/node_public.erl
index efefd67f..df4f1c08 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -1,28 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : node_public.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose :
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(node_public).
-behaviour(gen_pubsub_node).
@@ -45,10 +44,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +84,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl
index b2d4fade..8ac56b27 100644
--- a/src/nodetree_dag.erl
+++ b/src/nodetree_dag.erl
@@ -1,21 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : nodetree_dag.erl
+%%% Author : Brian Cully <bjc@kublai.com>
+%%% Purpose : experimental support of XEP-248
+%%% Created : 15 Jun 2009 by Brian Cully <bjc@kublai.com>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% @author Brian Cully <bjc@kublai.com>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(nodetree_dag).
-behaviour(gen_pubsub_nodetree).
@@ -51,7 +57,7 @@ set_node(#pubsub_node{nodeid = {Key, _}, owners = Owners, options = Options} = N
end.
create_node(Key, Node, Type, Owner, Options, Parents) ->
- OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ OwnerJID = jid:tolower(jid:remove_resource(Owner)),
case find_node(Key, Node) of
false ->
Nidx = pubsub_index:new(node),
diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl
index e34eabf3..e3f7e251 100644
--- a/src/nodetree_tree.erl
+++ b/src/nodetree_tree.erl
@@ -1,29 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : nodetree_tree.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard node tree plugin
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
@@ -148,7 +146,7 @@ get_subnodes_tree(Host, Node) ->
end.
create_node(Host, Node, Type, Owner, Options, Parents) ->
- BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ BJID = jid:tolower(jid:remove_resource(Owner)),
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[] ->
ParentExists = case Host of
diff --git a/src/nodetree_tree_odbc.erl b/src/nodetree_tree_odbc.erl
index 38fb51c2..ef1c20b6 100644
--- a/src/nodetree_tree_odbc.erl
+++ b/src/nodetree_tree_odbc.erl
@@ -1,29 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : nodetree_tree_odbc.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard node tree plugin with ODBC backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
@@ -66,7 +64,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
[First | _] -> First
end,
Type = Record#pubsub_node.type,
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
P = ejabberd_odbc:escape(Parent),
Nidx = case nodeidx(Host, Node) of
@@ -116,7 +114,7 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -151,7 +149,7 @@ get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
@@ -178,7 +176,7 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -196,7 +194,7 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -211,7 +209,7 @@ get_subnodes_tree(Host, Node) ->
end.
create_node(Host, Node, Type, Owner, Options, Parents) ->
- BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ BJID = jid:tolower(jid:remove_resource(Owner)),
case nodeidx(Host, Node) of
{error, ?ERR_ITEM_NOT_FOUND} ->
ParentExists = case Host of
@@ -256,7 +254,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
end.
delete_node(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
Removed = get_subnodes_tree(Host, Node),
catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
@@ -295,7 +293,7 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
id = Nidx, type = Type, options = Options}.
nodeidx(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
@@ -311,5 +309,5 @@ nodeidx(Host, Node) ->
end.
nodeowners(Nidx) ->
- {result, Res} = node_hometree_odbc:get_node_affiliations(Nidx),
+ {result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
[LJID || {LJID, Aff} <- Res, Aff =:= owner].
diff --git a/src/nodetree_virtual.erl b/src/nodetree_virtual.erl
index d56be860..934950dd 100644
--- a/src/nodetree_virtual.erl
+++ b/src/nodetree_virtual.erl
@@ -1,29 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : nodetree_virtual.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Standard node tree plugin using no storage backend
+%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
%%% @doc The module <strong>{@module}</strong> is the PubSub node tree plugin that
%%% allow virtual nodes handling. This prevent storage of nodes.
@@ -61,14 +59,12 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- get_node(nodeidx(Host, Node)).
+ Nidx = nodeidx(Host, Node),
+ node_record(Host, Node, Nidx).
get_node(Nidx) ->
{Host, Node} = nodeid(Nidx),
- Record = #pubsub_node{nodeid = Node, id = Nidx},
- Module = jlib:binary_to_atom(<<"node_", (Record#pubsub_node.type)/binary>>),
- Record#pubsub_node{owners = [{<<"">>, Host, <<"">>}],
- options = Module:options()}.
+ node_record(Host, Node, Nidx).
get_nodes(Host, _From) ->
get_nodes(Host).
@@ -80,10 +76,7 @@ get_parentnodes(_Host, _Node, _From) ->
[].
get_parentnodes_tree(Host, Node, From) ->
- case get_node(Host, Node, From) of
- Node when is_record(Node, pubsub_node) -> [{0, [Node]}];
- _Error -> []
- end.
+ [{0, [get_node(Host, Node, From)]}].
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
@@ -98,12 +91,35 @@ get_subnodes_tree(_Host, _Node) ->
[].
create_node(Host, Node, _Type, _Owner, _Options, _Parents) ->
- {error, {virtual, {Host, Node}}}.
+ {error, {virtual, nodeidx(Host, Node)}}.
delete_node(Host, Node) ->
[get_node(Host, Node)].
%% internal helper
-nodeidx(Host, Node) -> term_to_binary({Host, Node}).
-nodeid(Nidx) -> binary_to_term(Nidx).
+node_record({U,S,R}, Node, Nidx) ->
+ Host = mod_pubsub:host(S),
+ Type = <<"pep">>,
+ Module = mod_pubsub:plugin(Host, Type),
+ #pubsub_node{nodeid = {{U,S,R},Node}, id = Nidx, type = Type,
+ owners = [{U,S,R}],
+ options = Module:options()};
+node_record(Host, Node, Nidx) ->
+ [Type|_] = mod_pubsub:plugins(Host),
+ Module = mod_pubsub:plugin(Host, Type),
+ #pubsub_node{nodeid = {Host, Node}, id = Nidx, type = Type,
+ owners = [{<<"">>, Host, <<"">>}],
+ options = Module:options()}.
+
+nodeidx({U,S,R}, Node) ->
+ JID = jid:to_string(jid:make(U,S,R)),
+ <<JID/binary, ":", Node/binary>>;
+nodeidx(Host, Node) ->
+ <<Host/binary, ":", Node/binary>>.
+nodeid(Nidx) ->
+ [Head, Node] = binary:split(Nidx, <<":">>),
+ case jid:from_string(Head) of
+ {jid,<<>>,Host,<<>>,_,_,_} -> {Host, Node};
+ {jid,U,S,R,_,_,_} -> {{U,S,R}, Node}
+ end.
diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl
index 7dee1a04..c12931c6 100644
--- a/src/odbc_queries.erl
+++ b/src/odbc_queries.erl
@@ -5,7 +5,7 @@
%%% Created : by Mickael Remond <mremond@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,55 +25,44 @@
-module(odbc_queries).
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(ejabberd_config).
+
-author("mremond@process-one.net").
--export([get_db_type/0, update/5, update_t/4, sql_transaction/2,
- get_last/2, set_last_t/4, del_last/2,
- get_password/2, get_password_scram/2,
- set_password_t/3, set_password_scram_t/6,
- add_user/3, add_user_scram/6, del_user/2,
- del_user_return_password/3, list_users/1, list_users/2,
- users_number/1, users_number/2, add_spool_sql/2,
- add_spool/2, get_and_del_spool_msg_t/2, del_spool_msg/2,
- get_roster/2, get_roster_jid_groups/2,
+-export([get_db_type/0, update/5, update_t/4,
+ sql_transaction/2, get_last/2, set_last_t/4, del_last/2,
+ get_password/2, get_password_scram/2, set_password_t/3,
+ set_password_scram_t/6, add_user/3, add_user_scram/6,
+ del_user/2, del_user_return_password/3, list_users/1,
+ list_users/2, users_number/1, users_number/2,
+ add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2,
+ del_spool_msg/2, get_roster/2, get_roster_jid_groups/2,
get_roster_groups/3, del_user_roster_t/2,
get_roster_by_jid/3, get_rostergroup_by_jid/3,
del_roster/3, del_roster_sql/2, update_roster/5,
- update_roster_sql/4, roster_subscribe/4,
+ update_roster_sql/4, roster_subscribe/1,
get_subscription/3, set_private_data/4,
- set_private_data_sql/3, get_private_data/3, get_private_data/2,
- del_user_private_storage/2, get_default_privacy_list/2,
+ set_private_data_sql/3, get_private_data/3,
+ get_private_data/2, del_user_private_storage/2,
+ get_default_privacy_list/2,
get_default_privacy_list_t/1, get_privacy_list_names/2,
get_privacy_list_names_t/1, get_privacy_list_id/3,
get_privacy_list_id_t/2, get_privacy_list_data/3,
- get_privacy_list_data_by_id/2, get_privacy_list_data_t/2,
+ get_privacy_list_data_by_id/2,
+ get_privacy_list_data_t/2,
get_privacy_list_data_by_id_t/1,
set_default_privacy_list/2,
- unset_default_privacy_list/2,
- remove_privacy_list/2,
- add_privacy_list/2,
- set_privacy_list/2,
- del_privacy_lists/3,
- set_vcard/26,
- get_vcard/2,
- escape/1,
- count_records_where/3,
- get_roster_version/2,
- set_roster_version/2]).
-
-%% We have only two compile time options for db queries:
-%-define(generic, true).
-%-define(mssql, true).
--ifndef(mssql).
-
--undef(generic).
-
--define(generic, true).
-
--endif.
+ unset_default_privacy_list/2, remove_privacy_list/2,
+ add_privacy_list/2, set_privacy_list/2,
+ del_privacy_lists/2, set_vcard/26, get_vcard/2,
+ escape/1, count_records_where/3, get_roster_version/2,
+ set_roster_version/2, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
%% Almost a copy of string:join/2.
%% We use this version because string:join/2 is relatively
@@ -81,10 +70,6 @@
join([], _Sep) -> [];
join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
-%% -----------------
-%% Generic queries
--ifdef(generic).
-
get_db_type() -> generic.
%% Safe atomic update.
@@ -137,95 +122,92 @@ update(LServer, Table, Fields, Vals, Where) ->
sql_transaction(LServer, F) ->
ejabberd_odbc:sql_transaction(LServer, F).
-get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select seconds, state from last where "
- "username='">>,
- Username, <<"'">>]).
+get_last(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(seconds)d, @(state)s from last"
+ " where username=%(LUser)s")).
-set_last_t(LServer, Username, Seconds, State) ->
- update(LServer, <<"last">>,
- [<<"username">>, <<"seconds">>, <<"state">>],
- [Username, Seconds, State],
- [<<"username='">>, Username, <<"'">>]).
+set_last_t(LServer, LUser, TimeStamp, Status) ->
+ ?SQL_UPSERT(LServer, "last",
+ ["!username=%(LUser)s",
+ "seconds=%(TimeStamp)d",
+ "state=%(Status)s"]).
-del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from last where username='">>, Username,
- <<"'">>]).
+del_last(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from last where username=%(LUser)s")).
-get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select password from users where username='">>,
- Username, <<"';">>]).
+get_password(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(password)s from users where username=%(LUser)s")).
-get_password_scram(LServer, Username) ->
+get_password_scram(LServer, LUser) ->
ejabberd_odbc:sql_query(
LServer,
- [<<"select password, serverkey, salt, iterationcount from users where "
- "username='">>, Username, <<"';">>]).
-
-set_password_t(LServer, Username, Pass) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>],
- [Username, Pass],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-set_password_scram_t(LServer, Username,
+ ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
+ " from users"
+ " where username=%(LUser)s")).
+
+set_password_t(LServer, LUser, Password) ->
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(Password)s"])
+ end).
+
+set_password_scram_t(LServer, LUser,
StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>,
- <<"serverkey">>,
- <<"salt">>,
- <<"iterationcount">>],
- [Username, StoredKey,
- ServerKey, Salt,
- IterationCount],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password) "
- "values ('">>,
- Username, <<"', '">>, Pass, <<"');">>]).
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(StoredKey)s",
+ "serverkey=%(ServerKey)s",
+ "salt=%(Salt)s",
+ "iterationcount=%(IterationCount)d"])
+ end).
+
+add_user(LServer, LUser, Password) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password) "
+ "values (%(LUser)s, %(Password)s)")).
-add_user_scram(LServer, Username,
+add_user_scram(LServer, LUser,
StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password, serverkey, salt, iterationcount) "
- "values ('">>,
- Username, <<"', '">>, StoredKey, <<"', '">>,
- ServerKey, <<"', '">>,
- Salt, <<"', '">>,
- IterationCount, <<"');">>]).
-
-del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from users where username='">>, Username,
- <<"';">>]).
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password, serverkey, salt, "
+ "iterationcount) "
+ "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
+ " %(Salt)s, %(IterationCount)d)")).
+
+del_user(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from users where username=%(LUser)s")).
-del_user_return_password(_LServer, Username, Pass) ->
+del_user_return_password(_LServer, LUser, Password) ->
P =
- ejabberd_odbc:sql_query_t([<<"select password from users where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from users where username='">>,
- Username, <<"' and password='">>, Pass,
- <<"';">>]),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(password)s from users where username=%(LUser)s")),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from users"
+ " where username=%(LUser)s and password=%(Password)s")),
P.
list_users(LServer) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username from users">>]).
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users")).
list_users(LServer, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
@@ -240,64 +222,54 @@ list_users(LServer,
{offset, Start - 1}]);
list_users(LServer, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "order by username " ++
- "limit ~w offset ~w",
- [Limit, Offset]))]);
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d"));
list_users(LServer,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "where username like '~s%' " ++
- "order by username " ++
- "limit ~w offset ~w ",
- [Prefix, Limit, Offset]))]).
+ SPrefix = ejabberd_odbc:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "where username like %(SPrefix2)s "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d")).
users_number(LServer) ->
- Type = ejabberd_config:get_option({odbc_type, LServer},
- fun(pgsql) -> pgsql;
- (mysql) -> mysql;
- (sqlite) -> sqlite;
- (odbc) -> odbc
- end, odbc),
- case Type of
- pgsql ->
- case
- ejabberd_config:get_option(
- {pgsql_users_number_estimate, LServer},
- fun(V) when is_boolean(V) -> V end,
- false)
- of
- true ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select reltuples from pg_class where "
- "oid = 'users'::regclass::oid">>]);
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
+ ejabberd_odbc:sql_query(
+ LServer,
+ fun(pgsql, _) ->
+ case
+ ejabberd_config:get_option(
+ {pgsql_users_number_estimate, LServer},
+ fun(V) when is_boolean(V) -> V end,
+ false) of
+ true ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(reltuples :: bigint)d from pg_class"
+ " where oid = 'users'::regclass::oid"));
+ _ ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
end;
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
- end.
+ (_Type, _) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
+ end).
users_number(LServer, [{prefix, Prefix}])
when is_binary(Prefix) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:fwrite(
- "select count(*) from users " ++
- %% Warning: Escape prefix at higher level to prevent SQL
- %% injection.
- "where username like '~s%'",
- [Prefix]))]);
+ SPrefix = ejabberd_odbc:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from users "
+ "where username like %(SPrefix2)s"));
users_number(LServer, []) ->
users_number(LServer).
@@ -309,74 +281,71 @@ add_spool_sql(Username, XML) ->
add_spool(LServer, Queries) ->
ejabberd_odbc:sql_transaction(LServer, Queries).
-get_and_del_spool_msg_t(LServer, Username) ->
+get_and_del_spool_msg_t(LServer, LUser) ->
F = fun () ->
Result =
- ejabberd_odbc:sql_query_t([<<"select username, xml from spool where "
- "username='">>,
- Username,
- <<"' order by seq;">>]),
- ejabberd_odbc:sql_query_t([<<"delete from spool where username='">>,
- Username, <<"';">>]),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(username)s, @(xml)s from spool where "
+ "username=%(LUser)s order by seq;")),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from spool where username=%(LUser)s;")),
Result
end,
ejabberd_odbc:sql_transaction(LServer, F).
-del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from spool where username='">>, Username,
- <<"';">>]).
+del_spool_msg(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from spool where username=%(LUser)s")).
-get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"'">>]).
+get_roster(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
+ "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
+ "@(type)s from rosterusers where username=%(LUser)s")).
-get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select jid, grp from rostergroups where "
- "username='">>,
- Username, <<"'">>]).
-
-get_roster_groups(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-del_user_roster_t(LServer, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username,
- <<"';">>])
- end).
-
-get_roster_by_jid(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
-
-del_roster(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]).
+get_roster_jid_groups(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(jid)s, @(grp)s from rostergroups where "
+ "username=%(LUser)s")).
+
+get_roster_groups(_LServer, LUser, SJID) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_user_roster_t(LServer, LUser) ->
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun () ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from rosterusers where username=%(LUser)s")),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from rostergroups where username=%(LUser)s"))
+ end).
+
+get_roster_by_jid(_LServer, LUser, SJID) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
+ " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
+ " @(type)s from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+get_rostergroup_by_jid(LServer, LUser, SJID) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_roster(_LServer, LUser, SJID) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
del_roster_sql(Username, SJID) ->
[[<<"delete from rosterusers where "
@@ -386,27 +355,19 @@ del_roster_sql(Username, SJID) ->
"username='">>,
Username, <<"' and jid='">>, SJID, <<"';">>]].
-update_roster(_LServer, Username, SJID, ItemVals,
+update_roster(_LServer, LUser, SJID, ItemVals,
ItemGroups) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- lists:foreach(fun (ItemGroup) ->
- ejabberd_odbc:sql_query_t([<<"insert into rostergroups( "
- " username, jid, grp) values ('">>,
- join(ItemGroup,
- <<"', '">>),
- <<"');">>])
- end,
- ItemGroups).
+ roster_subscribe(ItemVals),
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ lists:foreach(
+ fun(ItemGroup) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("insert into rostergroups(username, jid, grp) "
+ "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)"))
+ end,
+ ItemGroups).
update_roster_sql(Username, SJID, ItemVals,
ItemGroups) ->
@@ -428,27 +389,31 @@ update_roster_sql(Username, SJID, ItemVals,
join(ItemGroup, <<"', '">>), <<"');">>]
|| ItemGroup <- ItemGroups].
-roster_subscribe(_LServer, Username, SJID, ItemVals) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]).
-
-get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select subscription from rosterusers "
- "where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
+roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) ->
+ ?SQL_UPSERT_T(
+ "rosterusers",
+ ["!username=%(LUser)s",
+ "!jid=%(SJID)s",
+ "nick=%(Name)s",
+ "subscription=%(SSubscription)s",
+ "ask=%(SAsk)s",
+ "askmessage=%(AskMessage)s",
+ "server='N'",
+ "subscribe=''",
+ "type='item'"]).
+
+get_subscription(LServer, LUser, SJID) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(subscription)s from rosterusers "
+ "where username=%(LUser)s and jid=%(SJID)s")).
-set_private_data(_LServer, Username, LXMLNS, SData) ->
- update_t(<<"private_storage">>,
- [<<"username">>, <<"namespace">>, <<"data">>],
- [Username, LXMLNS, SData],
- [<<"username='">>, Username, <<"' and namespace='">>,
- LXMLNS, <<"'">>]).
+set_private_data(_LServer, LUser, XMLNS, SData) ->
+ ?SQL_UPSERT_T(
+ "private_storage",
+ ["!username=%(LUser)s",
+ "!namespace=%(XMLNS)s",
+ "data=%(SData)s"]).
set_private_data_sql(Username, LXMLNS, SData) ->
[[<<"delete from private_storage where username='">>,
@@ -458,187 +423,189 @@ set_private_data_sql(Username, LXMLNS, SData) ->
Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
<<"');">>]].
-get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select data from private_storage where "
- "username='">>,
- Username, <<"' and namespace='">>, LXMLNS,
- <<"';">>]).
+get_private_data(LServer, LUser, XMLNS) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(data)s from private_storage"
+ " where username=%(LUser)s and namespace=%(XMLNS)s")).
-get_private_data(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select namespace, data from private_storage "
- "where username='">>, Username, <<"';">>]).
+get_private_data(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(namespace)s, @(data)s from private_storage"
+ " where username=%(LUser)s")).
-del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from private_storage where username='">>,
- Username, <<"';">>]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
- SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
- SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
- SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
- SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"vcard">>,
- [<<"username">>,
- <<"vcard">>],
- [LUsername, SVCARD],
- [<<"username='">>, LUsername,
- <<"'">>]),
- update_t(<<"vcard_search">>,
- [<<"username">>,
- <<"lusername">>, <<"fn">>,
- <<"lfn">>, <<"family">>,
- <<"lfamily">>, <<"given">>,
- <<"lgiven">>, <<"middle">>,
- <<"lmiddle">>,
- <<"nickname">>,
- <<"lnickname">>, <<"bday">>,
- <<"lbday">>, <<"ctry">>,
- <<"lctry">>, <<"locality">>,
- <<"llocality">>,
- <<"email">>, <<"lemail">>,
- <<"orgname">>,
- <<"lorgname">>,
- <<"orgunit">>,
- <<"lorgunit">>],
- [Username, LUsername, SFN,
- SLFN, SFamily, SLFamily,
- SGiven, SLGiven, SMiddle,
- SLMiddle, SNickname,
- SLNickname, SBDay, SLBDay,
- SCTRY, SLCTRY, SLocality,
- SLLocality, SEMail, SLEMail,
- SOrgName, SLOrgName,
- SOrgUnit, SLOrgUnit],
- [<<"lusername='">>,
- LUsername, <<"'">>])
- end).
-
-get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select vcard from vcard where username='">>,
- Username, <<"';">>]).
+del_user_private_storage(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from private_storage"
+ " where username=%(LUser)s")).
+
+set_vcard(LServer, LUser, BDay, CTRY, EMail, FN,
+ Family, Given, LBDay, LCTRY, LEMail, LFN,
+ LFamily, LGiven, LLocality, LMiddle, LNickname,
+ LOrgName, LOrgUnit, Locality, Middle, Nickname,
+ OrgName, OrgUnit, SVCARD, User) ->
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun() ->
+ ?SQL_UPSERT(LServer, "vcard",
+ ["!username=%(LUser)s",
+ "vcard=%(SVCARD)s"]),
+ ?SQL_UPSERT(LServer, "vcard_search",
+ ["username=%(User)s",
+ "!lusername=%(LUser)s",
+ "fn=%(FN)s",
+ "lfn=%(LFN)s",
+ "family=%(Family)s",
+ "lfamily=%(LFamily)s",
+ "given=%(Given)s",
+ "lgiven=%(LGiven)s",
+ "middle=%(Middle)s",
+ "lmiddle=%(LMiddle)s",
+ "nickname=%(Nickname)s",
+ "lnickname=%(LNickname)s",
+ "bday=%(BDay)s",
+ "lbday=%(LBDay)s",
+ "ctry=%(CTRY)s",
+ "lctry=%(LCTRY)s",
+ "locality=%(Locality)s",
+ "llocality=%(LLocality)s",
+ "email=%(EMail)s",
+ "lemail=%(LEMail)s",
+ "orgname=%(OrgName)s",
+ "lorgname=%(LOrgName)s",
+ "orgunit=%(OrgUnit)s",
+ "lorgunit=%(LOrgUnit)s"])
+ end).
+
+get_vcard(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(vcard)s from vcard where username=%(LUser)s")).
-get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
+get_default_privacy_list(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
-get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
+get_default_privacy_list_t(LUser) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
-get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
+get_privacy_list_names(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
-get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
+get_privacy_list_names_t(LUser) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
-get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
+get_privacy_list_id(LServer, LUser, Name) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
-get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
+get_privacy_list_id_t(LUser, Name) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
-get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
-
-get_privacy_list_data_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
+get_privacy_list_data(LServer, LUser, Name) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
+
+%% Not used?
+get_privacy_list_data_t(LUser, Name) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
-
-set_default_privacy_list(Username, SName) ->
- update_t(<<"privacy_default_list">>,
- [<<"username">>, <<"name">>], [Username, SName],
- [<<"username='">>, Username, <<"'">>]).
-
-unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list "
- " where username='">>,
- Username, <<"';">>]).
+ ejabberd_odbc:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
+
+set_default_privacy_list(LUser, Name) ->
+ ?SQL_UPSERT_T(
+ "privacy_default_list",
+ ["!username=%(LUser)s",
+ "name=%(Name)s"]).
+
+unset_default_privacy_list(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list"
+ " where username=%(LUser)s")).
-remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
+remove_privacy_list(LUser, Name) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from privacy_list where"
+ " username=%(LUser)s and name=%(Name)s")).
-add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list(username, name) "
- "values ('">>,
- Username, <<"', '">>, SName, <<"');">>]).
+add_privacy_list(LUser, Name) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("insert into privacy_list(username, name) "
+ "values (%(LUser)s, %(Name)s)")).
set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list_data where "
- "id='">>,
- ID, <<"';">>]),
- lists:foreach(fun (Items) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list_data(id, t, "
- "value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_prese"
- "nce_out ) values ('">>,
- ID, <<"', '">>,
- join(Items, <<"', '">>),
- <<"');">>])
+ ejabberd_odbc:sql_query_t(
+ ?SQL("delete from privacy_list_data where id=%(ID)d")),
+ lists:foreach(
+ fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
+ ejabberd_odbc:sql_query_t(
+ ?SQL("insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, match_presence_out) "
+ "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
+ " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
+ " %(MatchMessage)b, %(MatchPresenceIn)b,"
+ " %(MatchPresenceOut)b)"))
end,
RItems).
-del_privacy_lists(LServer, Server, Username) ->
-%% Characters to escape
-%% Count number of records in a table given a where clause
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list_data where "
- "value='">>,
- <<Username/binary, "@", Server/binary>>,
- <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list where "
- "username='">>,
- Username, <<"';">>]).
+del_privacy_lists(LServer, LUser) ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from privacy_list where username=%(LUser)s")),
+ %US = <<LUser/binary, "@", LServer/binary>>,
+ %ejabberd_odbc:sql_query(
+ % LServer,
+ % ?SQL("delete from privacy_list_data where value=%(US)s")),
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list where username=%(LUser)s")).
+%% Characters to escape
escape($\000) -> <<"\\0">>;
escape($\n) -> <<"\\n">>;
escape($\t) -> <<"\\t">>;
@@ -649,344 +616,30 @@ escape($") -> <<"\\\"">>;
escape($\\) -> <<"\\\\">>;
escape(C) -> <<C>>.
+%% Count number of records in a table given a where clause
count_records_where(LServer, Table, WhereClause) ->
ejabberd_odbc:sql_query(LServer,
[<<"select count(*) from ">>, Table, <<" ">>,
WhereClause, <<";">>]).
get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select version from roster_version where "
- "username = '">>,
- LUser, <<"'">>]).
+ ejabberd_odbc:sql_query(
+ LServer,
+ ?SQL("select @(version)s from roster_version"
+ " where username = %(LUser)s")).
set_roster_version(LUser, Version) ->
update_t(<<"roster_version">>,
[<<"username">>, <<"version">>], [LUser, Version],
[<<"username = '">>, LUser, <<"'">>]).
--endif.
-
-%% -----------------
-%% MSSQL queries
--ifdef(mssql).
-
-%% Queries can be either a fun or a list of queries
-get_db_type() -> mssql.
-
-sql_transaction(LServer, Queries)
- when is_list(Queries) ->
- F = fun () ->
- lists:foreach(fun (Query) ->
- ejabberd_odbc:sql_query(LServer, Query)
- end,
- Queries)
- end,
- {atomic, catch F()};
-sql_transaction(_LServer, FQueries) ->
- {atomic, catch FQueries()}.
-
-get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_last '">>, Username, <<"'">>]).
-
-set_last_t(LServer, Username, Seconds, State) ->
- Result = ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.set_last '">>, Username,
- <<"', '">>, Seconds, <<"', '">>, State,
- <<"'">>]),
- {atomic, Result}.
-
-del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_last '">>, Username, <<"'">>]).
-
-get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_password '">>, Username,
- <<"'">>]).
-
-set_password_t(LServer, Username, Pass) ->
- Result = ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.set_password '">>,
- Username, <<"', '">>, Pass, <<"'">>]),
- {atomic, Result}.
-
-add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.add_user '">>, Username, <<"', '">>,
- Pass, <<"'">>]).
-
-del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_user '">>, Username, <<"'">>]).
-
-del_user_return_password(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_user_return_password '">>,
- Username, <<"'">>]),
- Pass.
-
-list_users(LServer) ->
- ejabberd_odbc:sql_query(LServer,
- <<"EXECUTE dbo.list_users">>).
-
-list_users(LServer, _) -> list_users(LServer).
-
-users_number(LServer) ->
- ejabberd_odbc:sql_query(LServer,
- <<"select count(*) from users with (nolock)">>).
-
-users_number(LServer, _) -> users_number(LServer).
-
-add_spool_sql(Username, XML) ->
- [<<"EXECUTE dbo.add_spool '">>, Username, <<"' , '">>,
- XML, <<"'">>].
-
-add_spool(LServer, Queries) ->
- lists:foreach(fun (Query) ->
- ejabberd_odbc:sql_query(LServer, Query)
- end,
- Queries).
-
-get_and_del_spool_msg_t(LServer, Username) ->
- [Result] = case ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_and_del_spool_msg '">>,
- Username, <<"'">>])
- of
- Rs when is_list(Rs) ->
- lists:filter(fun ({selected, _Header, _Row}) -> true;
- ({updated, _N}) -> false
- end,
- Rs);
- Rs -> [Rs]
- end,
- {atomic, Result}.
-
-del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_spool_msg '">>, Username,
- <<"'">>]).
-
-get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_roster '">>, Username,
- <<"'">>]).
-
-get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_roster_jid_groups '">>,
- Username, <<"'">>]).
-
-get_roster_groups(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_roster_groups '">>, Username,
- <<"' , '">>, SJID, <<"'">>]).
-
-del_user_roster_t(LServer, Username) ->
- Result = ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_user_roster '">>,
- Username, <<"'">>]),
- {atomic, Result}.
-
-get_roster_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_roster_by_jid '">>, Username,
- <<"' , '">>, SJID, <<"'">>]).
-
-get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_rostergroup_by_jid '">>,
- Username, <<"' , '">>, SJID, <<"'">>]).
-
-del_roster(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_roster '">>, Username,
- <<"', '">>, SJID, <<"'">>]).
-
-del_roster_sql(Username, SJID) ->
- [<<"EXECUTE dbo.del_roster '">>, Username, <<"', '">>,
- SJID, <<"'">>].
-
-update_roster(LServer, Username, SJID, ItemVals,
- ItemGroups) ->
- Query1 = [<<"EXECUTE dbo.del_roster '">>, Username,
- <<"', '">>, SJID, <<"' ">>],
- ejabberd_odbc:sql_query(LServer, lists:flatten(Query1)),
- Query2 = [<<"EXECUTE dbo.add_roster_user ">>, ItemVals],
- ejabberd_odbc:sql_query(LServer, lists:flatten(Query2)),
- Query3 = [<<"EXECUTE dbo.del_roster_groups '">>,
- Username, <<"', '">>, SJID, <<"' ">>],
- ejabberd_odbc:sql_query(LServer, lists:flatten(Query3)),
- lists:foreach(fun (ItemGroup) ->
- Query = [<<"EXECUTE dbo.add_roster_group ">>,
- ItemGroup],
- ejabberd_odbc:sql_query(LServer, lists:flatten(Query))
- end,
- ItemGroups).
-
-update_roster_sql(Username, SJID, ItemVals,
- ItemGroups) ->
- [<<"BEGIN TRANSACTION ">>,
- <<"EXECUTE dbo.del_roster_groups '">>, Username,
- <<"','">>, SJID, <<"' ">>,
- <<"EXECUTE dbo.add_roster_user ">>, ItemVals, <<" ">>]
- ++
- [lists:flatten(<<"EXECUTE dbo.add_roster_group ">>,
- ItemGroup, <<" ">>)
- || ItemGroup <- ItemGroups]
- ++ [<<"COMMIT">>].
-
-roster_subscribe(LServer, _Username, _SJID, ItemVals) ->
- catch ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.add_roster_user ">>,
- ItemVals]).
-
-get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_subscription '">>, Username,
- <<"' , '">>, SJID, <<"'">>]).
-
-set_private_data(LServer, Username, LXMLNS, SData) ->
- ejabberd_odbc:sql_query(LServer,
- set_private_data_sql(Username, LXMLNS, SData)).
-
-set_private_data_sql(Username, LXMLNS, SData) ->
- [<<"EXECUTE dbo.set_private_data '">>, Username,
- <<"' , '">>, LXMLNS, <<"' , '">>, SData, <<"'">>].
-
-get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_private_data '">>, Username,
- <<"' , '">>, LXMLNS, <<"'">>]).
-
-del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_user_storage '">>, Username,
- <<"'">>]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
- SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
- SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
- SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
- SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.set_vcard '">>, SVCARD, <<"' , '">>,
- Username, <<"' , '">>, LUsername, <<"' , '">>, SFN,
- <<"' , '">>, SLFN, <<"' , '">>, SFamily,
- <<"' , '">>, SLFamily, <<"' , '">>, SGiven,
- <<"' , '">>, SLGiven, <<"' , '">>, SMiddle,
- <<"' , '">>, SLMiddle, <<"' , '">>, SNickname,
- <<"' , '">>, SLNickname, <<"' , '">>, SBDay,
- <<"' , '">>, SLBDay, <<"' , '">>, SCTRY,
- <<"' , '">>, SLCTRY, <<"' , '">>, SLocality,
- <<"' , '">>, SLLocality, <<"' , '">>, SEMail,
- <<"' , '">>, SLEMail, <<"' , '">>, SOrgName,
- <<"' , '">>, SLOrgName, <<"' , '">>, SOrgUnit,
- <<"' , '">>, SLOrgUnit, <<"'">>]).
-
-get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_vcard '">>, Username, <<"'">>]).
-
-get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_default_privacy_list '">>,
- Username, <<"'">>]).
-
-get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_default_privacy_list '">>,
- Username, <<"'">>]).
-
-get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_privacy_list_names '">>,
- Username, <<"'">>]).
-
-get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_names '">>,
- Username, <<"'">>]).
-
-get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_privacy_list_id '">>, Username,
- <<"' , '">>, SName, <<"'">>]).
-
-get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_id '">>,
- Username, <<"' , '">>, SName, <<"'">>]).
-
-get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_privacy_list_data '">>,
- Username, <<"' , '">>, SName, <<"'">>]).
-
-get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_privacy_list_data_by_id '">>,
- ID, <<"'">>]).
-
-get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.get_privacy_list_data_by_id '">>,
- ID, <<"'">>]).
-
-set_default_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.set_default_privacy_list '">>,
- Username, <<"' , '">>, SName, <<"'">>]).
-
-unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.unset_default_privacy_list '">>,
- Username, <<"'">>]).
-
-remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.remove_privacy_list '">>,
- Username, <<"' , '">>, SName, <<"'">>]).
-
-add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.add_privacy_list '">>,
- Username, <<"' , '">>, SName, <<"'">>]).
-
-set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.del_privacy_list_by_id '">>,
- ID, <<"'">>]),
- lists:foreach(fun (Items) ->
- ejabberd_odbc:sql_query_t([<<"EXECUTE dbo.set_privacy_list '">>,
- ID, <<"', '">>,
- join(Items, <<"', '">>),
- <<"'">>])
- end,
- RItems).
-
-del_privacy_lists(LServer, Server, Username) ->
-%% Characters to escape
-%% Count number of records in a table given a where clause
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.del_privacy_lists @Server='">>,
- Server, <<"' @username='">>, Username, <<"'">>]).
-
-escape($\000) -> <<"\\0">>;
-escape($\t) -> <<"\\t">>;
-escape($\b) -> <<"\\b">>;
-escape($\r) -> <<"\\r">>;
-escape($') -> <<"''">>;
-escape($") -> <<"\\\"">>;
-escape(C) -> C.
-
-count_records_where(LServer, Table, WhereClause) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from ">>, Table,
- <<" with (nolock) ">>, WhereClause]).
-
-get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.get_roster_version '">>, LUser,
- <<"'">>]).
-
-set_roster_version(Username, Version) ->
- LServer = (?MYNAME),
- ejabberd_odbc:sql_query(LServer,
- [<<"EXECUTE dbo.set_roster_version '">>, Username,
- <<"', '">>, Version, <<"'">>]).
-
--endif.
+opt_type(odbc_type) ->
+ fun (pgsql) -> pgsql;
+ (mysql) -> mysql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(pgsql_users_number_estimate) ->
+ fun (V) when is_boolean(V) -> V end;
+opt_type(_) -> [odbc_type, pgsql_users_number_estimate].
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
new file mode 100644
index 00000000..204cfec2
--- /dev/null
+++ b/src/prosody2ejabberd.erl
@@ -0,0 +1,341 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 20 Jan 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(prosody2ejabberd).
+
+%% API
+-export([from_dir/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("mod_roster.hrl").
+-include("mod_offline.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+from_dir(ProsodyDir) ->
+ case file:list_dir(ProsodyDir) of
+ {ok, HostDirs} ->
+ lists:foreach(
+ fun(HostDir) ->
+ Host = list_to_binary(HostDir),
+ lists:foreach(
+ fun(SubDir) ->
+ Path = filename:join(
+ [ProsodyDir, HostDir, SubDir]),
+ convert_dir(Path, Host, SubDir)
+ end, ["vcard", "accounts", "roster",
+ "private", "config", "offline",
+ "privacy"])
+ end, HostDirs);
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [ProsodyDir, file:format_error(Why)]),
+ Err
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+convert_dir(Path, Host, Type) ->
+ case file:list_dir(Path) of
+ {ok, Files} ->
+ lists:foreach(
+ fun(File) ->
+ FilePath = filename:join(Path, File),
+ case eval_file(FilePath) of
+ {ok, Data} ->
+ Name = iolist_to_binary(filename:rootname(File)),
+ convert_data(Host, Type, Name, Data);
+ Err ->
+ Err
+ end
+ end, Files);
+ {error, enoent} ->
+ ok;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+eval_file(Path) ->
+ case file:read_file(Path) of
+ {ok, Data} ->
+ State0 = luerl:init(),
+ State1 = luerl:set_table([item],
+ fun([X], State) -> {[X], State} end,
+ State0),
+ NewData = case filename:extension(Path) of
+ ".list" ->
+ <<"return {", Data/binary, "};">>;
+ _ ->
+ Data
+ end,
+ case luerl:eval(NewData, State1) of
+ {ok, _} = Res ->
+ Res;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to eval ~s: ~p", [Path, Why]),
+ Err
+ end;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to read file ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+convert_data(Host, "accounts", User, [Data]) ->
+ Password = proplists:get_value(<<"password">>, Data, <<>>),
+ case ejabberd_auth:try_register(User, Host, Password) of
+ {atomic, ok} ->
+ ok;
+ Err ->
+ ?ERROR_MSG("failed to register user ~s@~s: ~p",
+ [User, Host, Err]),
+ Err
+ end;
+convert_data(Host, "roster", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Rosters =
+ lists:flatmap(
+ fun({<<"pending">>, L}) ->
+ convert_pending_item(LUser, LServer, L);
+ ({S, L}) when is_binary(S) ->
+ convert_roster_item(LUser, LServer, S, L);
+ (_) ->
+ []
+ end, Data),
+ lists:foreach(fun mod_roster:set_roster/1, Rosters);
+convert_data(Host, "private", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ PrivData = lists:flatmap(
+ fun({_TagXMLNS, Raw}) ->
+ case deserialize(Raw) of
+ [El] ->
+ XMLNS = fxml:get_tag_attr_s(<<"xmlns">>, El),
+ [{XMLNS, El}];
+ _ ->
+ []
+ end
+ end, Data),
+ mod_private:set_data(LUser, LServer, PrivData);
+convert_data(Host, "vcard", User, [Data]) ->
+ LServer = jid:nameprep(Host),
+ case deserialize(Data) of
+ [VCard] ->
+ mod_vcard:set_vcard(User, LServer, VCard);
+ _ ->
+ ok
+ end;
+convert_data(_Host, "config", _User, [Data]) ->
+ RoomJID = jid:from_string(proplists:get_value(<<"jid">>, Data, <<"">>)),
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ RoomCfg = convert_room_config(Data),
+ case proplists:get_bool(<<"persistent">>, Config) of
+ true when RoomJID /= error ->
+ mod_muc:store_room(?MYNAME, RoomJID#jid.lserver,
+ RoomJID#jid.luser, RoomCfg);
+ _ ->
+ ok
+ end;
+convert_data(Host, "offline", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Msgs = lists:flatmap(
+ fun({_, RawXML}) ->
+ case deserialize(RawXML) of
+ [El] -> el_to_offline_msg(LUser, LServer, El);
+ _ -> []
+ end
+ end, Data),
+ mod_offline:store_offline_msg(
+ LServer, {LUser, LServer}, Msgs, length(Msgs), infinity);
+convert_data(Host, "privacy", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Lists = proplists:get_value(<<"lists">>, Data, []),
+ Priv = #privacy{
+ us = {LUser, LServer},
+ default = proplists:get_value(<<"default">>, Data, none),
+ lists = lists:flatmap(
+ fun({Name, Vals}) ->
+ Items = proplists:get_value(<<"items">>, Vals, []),
+ case lists:map(fun convert_privacy_item/1,
+ Items) of
+ [] -> [];
+ ListItems -> [{Name, ListItems}]
+ end
+ end, Lists)},
+ mod_privacy:set_privacy_list(Priv);
+convert_data(_Host, _Type, _User, _Data) ->
+ ok.
+
+convert_pending_item(LUser, LServer, LuaList) ->
+ lists:flatmap(
+ fun({S, true}) ->
+ case jid:from_string(S) of
+ #jid{} = J ->
+ LJID = jid:tolower(J),
+ [#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID,
+ ask = in}];
+ error ->
+ []
+ end;
+ (_) ->
+ []
+ end, LuaList).
+
+convert_roster_item(LUser, LServer, JIDstring, LuaList) ->
+ case jid:from_string(JIDstring) of
+ #jid{} = JID ->
+ LJID = jid:tolower(JID),
+ InitR = #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID},
+ Roster =
+ lists:foldl(
+ fun({<<"groups">>, Val}, R) ->
+ Gs = lists:flatmap(
+ fun({G, true}) -> [G];
+ (_) -> []
+ end, Val),
+ R#roster{groups = Gs};
+ ({<<"subscription">>, Sub}, R) ->
+ R#roster{subscription = jlib:binary_to_atom(Sub)};
+ ({<<"ask">>, <<"subscribe">>}, R) ->
+ R#roster{ask = out};
+ ({<<"name">>, Name}, R) ->
+ R#roster{name = Name}
+ end, InitR, LuaList),
+ [Roster];
+ error ->
+ []
+ end.
+
+convert_room_affiliations(Data) ->
+ lists:flatmap(
+ fun({J, Aff}) ->
+ case jid:from_string(J) of
+ #jid{luser = U, lserver = S} ->
+ [{{U, S, <<>>}, jlib:binary_to_atom(Aff)}];
+ error ->
+ []
+ end
+ end, proplists:get_value(<<"_affiliations">>, Data, [])).
+
+convert_room_config(Data) ->
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ Pass = case proplists:get_value(<<"password">>, Config, <<"">>) of
+ <<"">> ->
+ [];
+ Password ->
+ [{password_protected, true},
+ {password, Password}]
+ end,
+ Subj = case jid:from_string(
+ proplists:get_value(
+ <<"subject_from">>, Config, <<"">>)) of
+ #jid{lresource = Nick} when Nick /= <<"">> ->
+ [{subject, proplists:get_value(<<"subject">>, Config, <<"">>)},
+ {subject_author, Nick}];
+ _ ->
+ []
+ end,
+ Anonymous = case proplists:get_value(<<"whois">>, Config, <<"moderators">>) of
+ <<"moderators">> -> true;
+ _ -> false
+ end,
+ [{affiliations, convert_room_affiliations(Data)},
+ {allow_change_subj, proplists:get_bool(<<"changesubject">>, Config)},
+ {description, proplists:get_value(<<"description">>, Config, <<"">>)},
+ {members_only, proplists:get_bool(<<"members_only">>, Config)},
+ {moderated, proplists:get_bool(<<"moderated">>, Config)},
+ {anonymous, Anonymous}] ++ Pass ++ Subj.
+
+convert_privacy_item({_, Item}) ->
+ Action = proplists:get_value(<<"action">>, Item, <<"allow">>),
+ Order = proplists:get_value(<<"order">>, Item, 0),
+ T = jlib:binary_to_atom(proplists:get_value(<<"type">>, Item, <<"none">>)),
+ V = proplists:get_value(<<"value">>, Item, <<"">>),
+ MatchIQ = proplists:get_bool(<<"iq">>, Item),
+ MatchMsg = proplists:get_bool(<<"message">>, Item),
+ MatchPresIn = proplists:get_bool(<<"presence-in">>, Item),
+ MatchPresOut = proplists:get_bool(<<"presence-out">>, Item),
+ MatchAll = if (MatchIQ == false) and (MatchMsg == false) and
+ (MatchPresIn == false) and (MatchPresOut == false) ->
+ true;
+ true ->
+ false
+ end,
+ {Type, Value} = try case T of
+ none -> {T, none};
+ group -> {T, V};
+ jid -> {T, jid:tolower(jid:from_string(V))};
+ subscription -> {T, jlib:binary_to_atom(V)}
+ end
+ catch _:_ ->
+ {none, none}
+ end,
+ #listitem{type = Type,
+ value = Value,
+ action = jlib:binary_to_atom(Action),
+ order = erlang:trunc(Order),
+ match_all = MatchAll,
+ match_iq = MatchIQ,
+ match_message = MatchMsg,
+ match_presence_in = MatchPresIn,
+ match_presence_out = MatchPresOut}.
+
+el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
+ case jlib:datetime_string_to_timestamp(
+ fxml:get_attr_s(<<"stamp">>, Attrs)) of
+ {_, _, _} = TS ->
+ Attrs1 = lists:filter(
+ fun(<<"stamp">>) -> false;
+ (<<"stamp_legacy">>) -> false;
+ (_) -> true
+ end, Attrs),
+ Packet = El#xmlel{attrs = Attrs1},
+ case {jid:from_string(fxml:get_attr_s(<<"from">>, Attrs)),
+ jid:from_string(fxml:get_attr_s(<<"to">>, Attrs))} of
+ {#jid{} = From, #jid{} = To} ->
+ [#offline_msg{
+ us = {LUser, LServer},
+ timestamp = TS,
+ expire = never,
+ from = From,
+ to = To,
+ packet = Packet}];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end.
+
+deserialize(L) ->
+ deserialize(L, #xmlel{}, []).
+
+deserialize([{<<"attr">>, Attrs}|T], El, Acc) ->
+ deserialize(T, El#xmlel{attrs = Attrs ++ El#xmlel.attrs}, Acc);
+deserialize([{<<"name">>, Name}|T], El, Acc) ->
+ deserialize(T, El#xmlel{name = Name}, Acc);
+deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) ->
+ deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
+deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
+ deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
+deserialize([], El, Acc) ->
+ [El|Acc].
diff --git a/src/pubsub_db_odbc.erl b/src/pubsub_db_odbc.erl
index 56a500eb..cfdeda1e 100644
--- a/src/pubsub_db_odbc.erl
+++ b/src/pubsub_db_odbc.erl
@@ -1,24 +1,28 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : pubsub_db_odbc.erl
+%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
+%%% Purpose : Provide helpers for PubSub ODBC backend
+%%% Created : 7 Aug 2009 by Pablo Polvorin <pablo.polvorin@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
-%%% @author Pablo Polvorin <pablo.polvorin@process-one.net>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(pubsub_db_odbc).
-author("pablo.polvorin@process-one.net").
diff --git a/src/pubsub_index.erl b/src/pubsub_index.erl
index c9ec94a6..983356a1 100644
--- a/src/pubsub_index.erl
+++ b/src/pubsub_index.erl
@@ -1,29 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
-%%%
+%%%----------------------------------------------------------------------
+%%% File : pubsub_index.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Provide uniq integer index for pubsub node
+%%% Created : 30 Apr 2009 by Christophe Romain <christophe.romain@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
-%%%
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% 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.
%%%
-%%% @copyright 2006-2015 ProcessOne
-%%% @author Christophe Romain <christophe.romain@process-one.net>
-%%% [http://www.process-one.net/]
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
%% important note:
%% new/1 and free/2 MUST be called inside a transaction bloc
diff --git a/src/pubsub_migrate.erl b/src/pubsub_migrate.erl
index e48efcd4..c493b58f 100644
--- a/src/pubsub_migrate.erl
+++ b/src/pubsub_migrate.erl
@@ -5,7 +5,7 @@
%%% Created : 26 Jul 2014 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -17,10 +17,9 @@
%%% 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., 59 Temple Place, Suite 330, Boston, MA
-%%% 02111-1307 USA
+%%% 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.
%%%
%%%----------------------------------------------------------------------
@@ -29,7 +28,8 @@
-include("pubsub.hrl").
-include("logger.hrl").
--export([update_node_database/2, update_state_database/2, update_lastitem_database/2]).
+-export([update_node_database/2, update_state_database/2]).
+-export([update_item_database/2, update_lastitem_database/2]).
update_item_database_binary() ->
F = fun () ->
@@ -57,6 +57,27 @@ update_item_database_binary() ->
?INFO_MSG("Pubsub items table has been binarized: ~p", [Result])
end.
+update_item_database(_Host, _ServerHost) ->
+ F = fun() ->
+ ?INFO_MSG("Migration of old pubsub items...", []),
+ lists:foreach(fun (Key) ->
+ [Item] = mnesia:read({pubsub_item, Key}),
+ Payload = [xmlelement_to_xmlel(El) || El <- Item#pubsub_item.payload],
+ mnesia:write(Item#pubsub_item{payload=Payload})
+ end,
+ mnesia:all_keys(pubsub_item))
+ end,
+ case mnesia:transaction(F) of
+ {aborted, Reason} ->
+ ?ERROR_MSG("Failed to migrate old pubsub items to xmlel: ~p", [Reason]);
+ {atomic, Result} ->
+ ?INFO_MSG("Pubsub items has been migrated: ~p", [Result])
+ end.
+
+xmlelement_to_xmlel({xmlelement, A, B, C}) when is_list(C) ->
+ {xmlel, A, B, [xmlelement_to_xmlel(El) || El <- C]};
+xmlelement_to_xmlel(El) ->
+ El.
update_node_database_binary() ->
F = fun () ->
@@ -136,7 +157,7 @@ update_node_database(Host, ServerHost) ->
{unknown,
Publisher},
M =
- {now(),
+ {p1_time_compat:timestamp(),
Publisher},
mnesia:write(#pubsub_item{itemid
=
diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl
index df156000..22c90414 100644
--- a/src/pubsub_subscription.erl
+++ b/src/pubsub_subscription.erl
@@ -1,24 +1,27 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : pubsub_subscription.erl
+%%% Author : Brian Cully <bjc@kublai.com>
+%%% Purpose : Handle pubsub subscriptions options
+%%% Created : 29 May 2009 by Brian Cully <bjc@kublai.com>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
-%%% @author Brian Cully <bjc@kublai.com>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(pubsub_subscription).
@@ -27,6 +30,7 @@
%% API
-export([init/0, subscribe_node/3, unsubscribe_node/3,
get_subscription/3, set_subscription/4,
+ make_subid/0,
get_options_xform/2, parse_options_xform/1]).
% Internal function also exported for use in transactional bloc from pubsub plugins
@@ -122,7 +126,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -202,7 +206,7 @@ write_subscription(_JID, _NodeId, SubID, Options) ->
-spec(make_subid/0 :: () -> SubId::mod_pubsub:subId()).
make_subid() ->
- {T1, T2, T3} = now(),
+ {T1, T2, T3} = p1_time_compat:timestamp(),
iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
%%
diff --git a/src/pubsub_subscription_odbc.erl b/src/pubsub_subscription_odbc.erl
index 6c99b155..149308ad 100644
--- a/src/pubsub_subscription_odbc.erl
+++ b/src/pubsub_subscription_odbc.erl
@@ -1,25 +1,28 @@
-%%% ====================================================================
-%%% ``The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%----------------------------------------------------------------------
+%%% File : pubsub_subscription_odbc.erl
+%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
+%%% Purpose : Handle pubsub subscriptions options with ODBC backend
+%%% based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
+%%% Created : 7 Aug 2009 by Pablo Polvorin <pablo.polvorin@process-one.net>
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
%%%
-%%% The Initial Developer of the Original Code is ProcessOne.
-%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
-%%% All Rights Reserved.''
-%%% This software is copyright 2006-2015, ProcessOne.
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
-%%% @author Pablo Polvorin <pablo.polvorin@process-one.net>
-%%% @author based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
-%%% @version {@vsn}, {@date} {@time}
-%%% @end
-%%% ====================================================================
+%%% 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(pubsub_subscription_odbc).
-author("pablo.polvorin@process-one.net").
@@ -27,6 +30,7 @@
%% API
-export([init/0, subscribe_node/3, unsubscribe_node/3,
get_subscription/3, set_subscription/4,
+ make_subid/0,
get_options_xform/2, parse_options_xform/1]).
-include("pubsub.hrl").
@@ -147,7 +151,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -165,7 +169,7 @@ create_table() -> ok.
-spec(make_subid/0 :: () -> mod_pubsub:subId()).
make_subid() ->
- {T1, T2, T3} = now(),
+ {T1, T2, T3} = p1_time_compat:timestamp(),
iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
%%
diff --git a/src/randoms.erl b/src/randoms.erl
index 950f29fc..52fceef4 100644
--- a/src/randoms.erl
+++ b/src/randoms.erl
@@ -5,7 +5,7 @@
%%% Created : 13 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -29,23 +29,12 @@
-export([get_string/0]).
--export([start/0, init/0]).
+-export([start/0]).
start() ->
- register(random_generator, spawn(randoms, init, [])).
-
-init() ->
- {A1, A2, A3} = now(), random:seed(A1, A2, A3), loop().
-
-loop() ->
- receive
- {From, get_random, N} ->
- From ! {random, random:uniform(N)}, loop();
- _ -> loop()
- end.
+ ok.
get_string() ->
- random_generator ! {self(), get_random, 65536 * 65536},
- receive
- {random, R} -> jlib:integer_to_binary(R)
- end.
+ R = crypto:rand_uniform(0, 16#10000000000000000),
+ jlib:integer_to_binary(R).
+
diff --git a/src/scram.erl b/src/scram.erl
index 8c793521..2c4d265b 100644
--- a/src/scram.erl
+++ b/src/scram.erl
@@ -5,7 +5,7 @@
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -36,7 +36,7 @@
-spec salted_password(binary(), binary(), non_neg_integer()) -> binary().
salted_password(Password, Salt, IterationCount) ->
- hi(jlib:resourceprep(Password), Salt, IterationCount).
+ hi(jid:resourceprep(Password), Salt, IterationCount).
-spec client_key(binary()) -> binary().
@@ -86,6 +86,4 @@ hi_round(Password, UPrev, IterationCount) ->
IterationCount - 1)))).
sha_mac(Key, Data) ->
- Context1 = crypto:hmac_init(sha, Key),
- Context2 = crypto:hmac_update(Context1, Data),
- crypto:hmac_final(Context2).
+ crypto:hmac(sha, Key, Data).
diff --git a/src/shaper.erl b/src/shaper.erl
index a85c4f11..a136c213 100644
--- a/src/shaper.erl
+++ b/src/shaper.erl
@@ -5,7 +5,7 @@
%%% Created : 9 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
@@ -25,10 +25,13 @@
-module(shaper).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
--export([start/0, new/1, new1/1, update/2, get_max_rate/1,
- transform_options/1, load_from_config/0]).
+-export([start/0, new/1, new1/1, update/2,
+ get_max_rate/1, transform_options/1, load_from_config/0,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -104,7 +107,7 @@ new(Name) ->
new1(none) -> none;
new1(MaxRate) ->
#maxrate{maxrate = MaxRate, lastrate = 0.0,
- lasttime = now_to_usec(now())}.
+ lasttime = p1_time_compat:system_time(micro_seconds)}.
-spec update(shaper(), integer()) -> {shaper(), integer()}.
@@ -112,7 +115,7 @@ update(none, _Size) -> {none, 0};
update(#maxrate{} = State, Size) ->
MinInterv = 1000 * Size /
(2 * State#maxrate.maxrate - State#maxrate.lastrate),
- Interv = (now_to_usec(now()) - State#maxrate.lasttime) /
+ Interv = (p1_time_compat:system_time(micro_seconds) - State#maxrate.lasttime) /
1000,
?DEBUG("State: ~p, Size=~p~nM=~p, I=~p~n",
[State, Size, MinInterv, Interv]),
@@ -120,7 +123,7 @@ update(#maxrate{} = State, Size) ->
1 + trunc(MinInterv - Interv);
true -> 0
end,
- NextNow = now_to_usec(now()) + Pause * 1000,
+ NextNow = p1_time_compat:system_time(micro_seconds) + Pause * 1000,
{State#maxrate{lastrate =
(State#maxrate.lastrate +
1000000 * Size / (NextNow - State#maxrate.lasttime))
@@ -138,5 +141,5 @@ transform_options({OptName, Name, none}, Opts) when OptName == shaper ->
transform_options(Opt, Opts) ->
[Opt|Opts].
-now_to_usec({MSec, Sec, USec}) ->
- (MSec * 1000000 + Sec) * 1000000 + USec.
+opt_type(shaper) -> fun (V) -> V end;
+opt_type(_) -> [shaper].
diff --git a/src/str.erl b/src/str.erl
index 80d7b05b..27d21075 100644
--- a/src/str.erl
+++ b/src/str.erl
@@ -5,7 +5,7 @@
%%% Created : 23 Feb 2012 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/translate.erl b/src/translate.erl
index 277dfa44..e9f61ab8 100644
--- a/src/translate.erl
+++ b/src/translate.erl
@@ -5,7 +5,7 @@
%%% Created : 6 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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
diff --git a/src/win32_dns.erl b/src/win32_dns.erl
index f0e4b5f2..5dda22b9 100644
--- a/src/win32_dns.erl
+++ b/src/win32_dns.erl
@@ -5,7 +5,7 @@
%%% Created : 5 Mar 2009 by Geoff Cant
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
+%%% 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