aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/acl.erl14
-rw-r--r--src/adhoc.erl157
-rw-r--r--src/cyrsasl.erl40
-rw-r--r--src/cyrsasl_anonymous.erl2
-rw-r--r--src/cyrsasl_digest.erl12
-rw-r--r--src/cyrsasl_oauth.erl4
-rw-r--r--src/cyrsasl_plain.erl4
-rw-r--r--src/cyrsasl_scram.erl34
-rw-r--r--src/ejabberd_access_permissions.erl4
-rw-r--r--src/ejabberd_admin.erl7
-rw-r--r--src/ejabberd_app.erl6
-rw-r--r--src/ejabberd_auth.erl25
-rw-r--r--src/ejabberd_auth_anonymous.erl7
-rw-r--r--src/ejabberd_auth_mnesia.erl34
-rw-r--r--src/ejabberd_auth_riak.erl16
-rw-r--r--src/ejabberd_bosh.erl1095
-rw-r--r--src/ejabberd_c2s.erl2645
-rw-r--r--src/ejabberd_captcha.erl271
-rw-r--r--src/ejabberd_commands.erl6
-rw-r--r--src/ejabberd_commands_doc.erl6
-rw-r--r--src/ejabberd_config.erl4
-rw-r--r--src/ejabberd_ctl.erl2
-rw-r--r--src/ejabberd_frontend_socket.erl17
-rw-r--r--src/ejabberd_http.erl42
-rw-r--r--src/ejabberd_http_bind.erl26
-rw-r--r--src/ejabberd_http_ws.erl2
-rw-r--r--src/ejabberd_local.erl134
-rw-r--r--src/ejabberd_mnesia.erl169
-rw-r--r--src/ejabberd_oauth.erl38
-rw-r--r--src/ejabberd_oauth_mnesia.erl2
-rw-r--r--src/ejabberd_oauth_rest.erl2
-rw-r--r--src/ejabberd_oauth_sql.erl2
-rw-r--r--src/ejabberd_piefxis.erl256
-rw-r--r--src/ejabberd_riak.erl6
-rw-r--r--src/ejabberd_router.erl133
-rw-r--r--src/ejabberd_router_multicast.erl14
-rw-r--r--src/ejabberd_s2s.erl82
-rw-r--r--src/ejabberd_s2s_in.erl673
-rw-r--r--src/ejabberd_s2s_out.erl935
-rw-r--r--src/ejabberd_service.erl519
-rw-r--r--src/ejabberd_sm.erl381
-rw-r--r--src/ejabberd_sm_mnesia.erl5
-rw-r--r--src/ejabberd_sm_redis.erl1
-rw-r--r--src/ejabberd_sm_sql.erl5
-rw-r--r--src/ejabberd_socket.erl35
-rw-r--r--src/ejabberd_sql.erl10
-rw-r--r--src/ejabberd_sql_sup.erl24
-rw-r--r--src/ejabberd_system_monitor.erl44
-rw-r--r--src/ejabberd_web.erl2
-rw-r--r--src/ejabberd_web_admin.erl111
-rw-r--r--src/ejabberd_websocket.erl2
-rw-r--r--src/ejabberd_xmlrpc.erl10
-rw-r--r--src/ejd2sql.erl294
-rw-r--r--src/ext_mod.erl22
-rw-r--r--src/gen_iq_handler.erl54
-rw-r--r--src/gen_pubsub_node.erl21
-rw-r--r--src/gen_pubsub_nodetree.erl27
-rw-r--r--src/http_p1.erl358
-rw-r--r--src/jd2ejd.erl71
-rw-r--r--src/jid.erl256
-rw-r--r--src/jlib.erl69
-rw-r--r--src/mod_adhoc.erl150
-rw-r--r--src/mod_admin_extra.erl402
-rw-r--r--src/mod_announce.erl464
-rw-r--r--src/mod_announce_mnesia.erl17
-rw-r--r--src/mod_announce_riak.erl14
-rw-r--r--src/mod_announce_sql.erl21
-rw-r--r--src/mod_blocking.erl167
-rw-r--r--src/mod_blocking_mnesia.erl1
-rw-r--r--src/mod_blocking_riak.erl1
-rw-r--r--src/mod_blocking_sql.erl1
-rw-r--r--src/mod_bosh.erl296
-rw-r--r--src/mod_caps.erl336
-rw-r--r--src/mod_caps_mnesia.erl11
-rw-r--r--src/mod_caps_riak.erl11
-rw-r--r--src/mod_caps_sql.erl9
-rw-r--r--src/mod_carboncopy.erl200
-rw-r--r--src/mod_carboncopy_mnesia.erl2
-rw-r--r--src/mod_client_state.erl139
-rw-r--r--src/mod_configure.erl1901
-rw-r--r--src/mod_configure2.erl223
-rw-r--r--src/mod_delegation.erl854
-rw-r--r--src/mod_disco.erl398
-rw-r--r--src/mod_echo.erl50
-rw-r--r--src/mod_fail2ban.erl8
-rw-r--r--src/mod_http_api.erl76
-rw-r--r--src/mod_http_bind.erl4
-rw-r--r--src/mod_http_fileserver.erl91
-rw-r--r--src/mod_http_upload.erl210
-rw-r--r--src/mod_http_upload_quota.erl6
-rw-r--r--src/mod_ip_blacklist.erl8
-rw-r--r--src/mod_irc.erl1229
-rw-r--r--src/mod_irc_connection.erl1384
-rw-r--r--src/mod_irc_mnesia.erl4
-rw-r--r--src/mod_irc_riak.erl2
-rw-r--r--src/mod_irc_sql.erl2
-rw-r--r--src/mod_last.erl158
-rw-r--r--src/mod_last_mnesia.erl2
-rw-r--r--src/mod_last_sql.erl19
-rw-r--r--src/mod_mam.erl835
-rw-r--r--src/mod_mam_mnesia.erl46
-rw-r--r--src/mod_mam_sql.erl88
-rw-r--r--src/mod_metrics.erl44
-rw-r--r--src/mod_mix.erl151
-rw-r--r--src/mod_muc.erl851
-rw-r--r--src/mod_muc_admin.erl16
-rw-r--r--src/mod_muc_log.erl46
-rw-r--r--src/mod_muc_mnesia.erl41
-rw-r--r--src/mod_muc_riak.erl36
-rw-r--r--src/mod_muc_room.erl4814
-rw-r--r--src/mod_muc_sql.erl39
-rw-r--r--src/mod_multicast.erl501
-rw-r--r--src/mod_offline.erl537
-rw-r--r--src/mod_offline_mnesia.erl34
-rw-r--r--src/mod_offline_riak.erl15
-rw-r--r--src/mod_offline_sql.erl75
-rw-r--r--src/mod_ping.erl35
-rw-r--r--src/mod_pres_counter.erl43
-rw-r--r--src/mod_privacy.erl720
-rw-r--r--src/mod_privacy_mnesia.erl32
-rw-r--r--src/mod_privacy_riak.erl31
-rw-r--r--src/mod_privacy_sql.erl69
-rw-r--r--src/mod_private.erl148
-rw-r--r--src/mod_private_mnesia.erl11
-rw-r--r--src/mod_private_riak.erl9
-rw-r--r--src/mod_private_sql.erl16
-rw-r--r--src/mod_privilege.erl684
-rw-r--r--src/mod_proxy65.erl6
-rw-r--r--src/mod_proxy65_service.erl345
-rw-r--r--src/mod_proxy65_sm.erl6
-rw-r--r--src/mod_pubsub.erl3198
-rw-r--r--src/mod_register.erl545
-rw-r--r--src/mod_register_web.erl4
-rw-r--r--src/mod_roster.erl769
-rw-r--r--src/mod_roster_mnesia.erl15
-rw-r--r--src/mod_roster_riak.erl18
-rw-r--r--src/mod_roster_sql.erl26
-rw-r--r--src/mod_service_log.erl26
-rw-r--r--src/mod_shared_roster.erl176
-rw-r--r--src/mod_shared_roster_ldap.erl14
-rw-r--r--src/mod_shared_roster_mnesia.erl18
-rw-r--r--src/mod_shared_roster_riak.erl20
-rw-r--r--src/mod_shared_roster_sql.erl22
-rw-r--r--src/mod_sic.erl83
-rw-r--r--src/mod_sip_proxy.erl4
-rw-r--r--src/mod_sip_registrar.erl4
-rw-r--r--src/mod_stats.erl128
-rw-r--r--src/mod_time.erl51
-rw-r--r--src/mod_vcard.erl599
-rw-r--r--src/mod_vcard_ldap.erl882
-rw-r--r--src/mod_vcard_mnesia.erl90
-rw-r--r--src/mod_vcard_riak.erl23
-rw-r--r--src/mod_vcard_sql.erl93
-rw-r--r--src/mod_vcard_xupdate.erl70
-rw-r--r--src/mod_vcard_xupdate_mnesia.erl9
-rw-r--r--src/mod_vcard_xupdate_riak.erl8
-rw-r--r--src/mod_vcard_xupdate_sql.erl14
-rw-r--r--src/mod_version.erl53
-rw-r--r--src/node_buddy.erl1
-rw-r--r--src/node_club.erl1
-rw-r--r--src/node_dag.erl7
-rw-r--r--src/node_dispatch.erl14
-rw-r--r--src/node_flat.erl83
-rw-r--r--src/node_flat_sql.erl538
-rw-r--r--src/node_hometree.erl1
-rw-r--r--src/node_hometree_sql.erl1
-rw-r--r--src/node_mb.erl1
-rw-r--r--src/node_mb_sql.erl1
-rw-r--r--src/node_online.erl3
-rw-r--r--src/node_pep.erl1
-rw-r--r--src/node_pep_sql.erl1
-rw-r--r--src/node_private.erl1
-rw-r--r--src/node_public.erl1
-rw-r--r--src/nodetree_dag.erl18
-rw-r--r--src/nodetree_tree.erl12
-rw-r--r--src/nodetree_tree_sql.erl18
-rw-r--r--src/nodetree_virtual.erl1
-rw-r--r--src/prosody2ejabberd.erl10
-rw-r--r--src/pubsub_db_sql.erl24
-rw-r--r--src/pubsub_index.erl2
-rw-r--r--src/pubsub_migrate.erl4
-rw-r--r--src/pubsub_subscription.erl117
-rw-r--r--src/pubsub_subscription_sql.erl134
-rw-r--r--src/randoms.erl2
-rw-r--r--src/rest.erl39
-rw-r--r--src/shaper.erl2
-rw-r--r--src/str.erl17
187 files changed, 15825 insertions, 20636 deletions
diff --git a/src/acl.erl b/src/acl.erl
index 1476081dd..e00aaa5d3 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -41,7 +41,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("jid.hrl").
-record(acl, {aclname, aclspec}).
-record(access, {name :: aclname(),
@@ -76,11 +76,11 @@
-export_type([acl/0]).
start() ->
- mnesia:create_table(acl,
+ ejabberd_mnesia:create(?MODULE, acl,
[{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, acl)}]),
- mnesia:create_table(access,
+ ejabberd_mnesia:create(?MODULE, access,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, access)}]),
@@ -342,7 +342,7 @@ acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
acl_rule_verify(_Spec) ->
false.
invalid_syntax(Msg, Data) ->
- throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
+ throw({invalid_syntax, (str:format(Msg, Data))}).
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
acl_rules_verify(Rest, true);
@@ -446,8 +446,8 @@ resolve_access(Name, Host) when is_atom(Name) ->
GAccess = mnesia:dirty_read(access, {Name, global}),
LAccess =
if Host /= global -> mnesia:dirty_read(access, {Name, Host});
- true -> []
- end,
+ true -> []
+ end,
case GAccess ++ LAccess of
[] ->
[];
@@ -542,7 +542,7 @@ parse_ip_netmask(S) ->
_ -> error
end;
[IPStr, MaskStr] ->
- case catch jlib:binary_to_integer(MaskStr) of
+ case catch binary_to_integer(MaskStr) of
Mask when is_integer(Mask), Mask >= 0 ->
case inet_parse:address(binary_to_list(IPStr)) of
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
diff --git a/src/adhoc.erl b/src/adhoc.erl
deleted file mode 100644
index 23ffd8dd8..000000000
--- a/src/adhoc.erl
+++ /dev/null
@@ -1,157 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : adhoc.erl
-%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
-%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
-%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
-%%%
-%%%
-%%% 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(adhoc).
-
--author('henoch@dtek.chalmers.se').
-
--export([
- parse_request/1,
- produce_response/2,
- produce_response/1
-]).
-
--include("ejabberd.hrl").
--include("logger.hrl").
--include("jlib.hrl").
--include("adhoc.hrl").
-
-%% Parse an ad-hoc request. Return either an adhoc_request record or
-%% an {error, ErrorType} tuple.
-%%
--spec parse_request(IQ :: iq_request()) -> adhoc_response() | {error, _}.
-
-parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
- ?DEBUG("entering parse_request...", []),
- 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
- false -> AllEls;
- _ -> lists:delete(XData, AllEls)
- end,
- #adhoc_request{
- lang = Lang,
- node = Node,
- sessionid = SessionID,
- action = Action,
- xdata = XData,
- others = Others
- };
-parse_request(#iq{lang = Lang}) ->
- Text = <<"Failed to parse ad-hoc command request">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Text)}.
-
-%% Borrowed from mod_vcard.erl
-find_xdata_el(#xmlel{children = SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
- case fxml:get_tag_attr_s(<<"xmlns">>, El) of
- ?NS_XDATA -> El;
- _ -> find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
-
-%% Produce a <command/> node to use as response from an adhoc_response
-%% record, filling in values for language, node and session id from
-%% the request.
-%%
--spec produce_response(Adhoc_Request :: adhoc_request(),
- Adhoc_Response :: adhoc_response()) ->
- Xmlel::xmlel().
-
-%% Produce a <command/> node to use as response from an adhoc_response
-%% record.
-produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
- Adhoc_Response) ->
- produce_response(Adhoc_Response#adhoc_response{
- lang = Lang, node = Node, sessionid = SessionID
- }).
-
-%%
--spec produce_response(Adhoc_Response::adhoc_response()) -> Xmlel::xmlel().
-
-produce_response(
- #adhoc_response{
- %lang = _Lang,
- node = Node,
- sessionid = ProvidedSessionID,
- status = Status,
- defaultaction = DefaultAction,
- actions = Actions,
- notes = Notes,
- elements = Elements
- }) ->
- SessionID = if is_binary(ProvidedSessionID),
- ProvidedSessionID /= <<"">> -> ProvidedSessionID;
- true -> jlib:now_to_utc_string(p1_time_compat:timestamp())
- end,
- case {Actions, Status} of
- {[], completed} ->
- ActionsEls = [];
- {[], _} ->
- ActionsEls = [
- #xmlel{
- name = <<"actions">>,
- attrs = [{<<"execute">>, <<"complete">>}],
- children = [#xmlel{name = <<"complete">>}]
- }
- ];
- _ ->
- case DefaultAction of
- <<"">> -> ActionsElAttrs = [];
- _ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
- end,
- ActionsEls = [
- #xmlel{
- name = <<"actions">>,
- attrs = ActionsElAttrs,
- children = [
- #xmlel{name = Action, attrs = [], children = []}
- || Action <- Actions]
- }
- ]
- end,
- NotesEls = lists:map(fun({Type, Text}) ->
- #xmlel{
- name = <<"note">>,
- attrs = [{<<"type">>, Type}],
- children = [{xmlcdata, Text}]
- }
- end, Notes),
- #xmlel{
- name = <<"command">>,
- attrs = [
- {<<"xmlns">>, ?NS_COMMANDS},
- {<<"sessionid">>, SessionID},
- {<<"node">>, Node},
- {<<"status">>, iolist_to_binary(atom_to_list(Status))}
- ],
- children = ActionsEls ++ NotesEls ++ Elements
- }.
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index a101e1380..4b0f5a26b 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -73,8 +73,8 @@
-callback mech_step(any(), binary()) -> {ok, props()} |
{ok, props(), binary()} |
{continue, binary(), any()} |
- {error, binary()} |
- {error, binary(), binary()}.
+ {error, atom()} |
+ {error, atom(), binary()}.
start() ->
ets:new(sasl_mechanism,
@@ -102,35 +102,11 @@ register_mechanism(Mechanism, Module, PasswordType) ->
true
end.
-%%% TODO: use callbacks
-%%-include("ejabberd.hrl").
-%%-include("jlib.hrl").
-%%check_authzid(_State, Props) ->
-%% AuthzId = fxml:get_attr_s(authzid, Props),
-%% case jid:from_string(AuthzId) of
-%% error ->
-%% {error, "invalid-authzid"};
-%% JID ->
-%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
-%% {U, S, R} = jid:tolower(JID),
-%% case R of
-%% "" ->
-%% {error, "invalid-authzid"};
-%% _ ->
-%% case {LUser, ?MYNAME} of
-%% {U, S} ->
-%% ok;
-%% _ ->
-%% {error, "invalid-authzid"}
-%% end
-%% end
-%% end.
-
check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of
- error -> {error, <<"not-authorized">>};
- <<"">> -> {error, <<"not-authorized">>};
+ error -> {error, 'not-authorized'};
+ <<"">> -> {error, 'not-authorized'};
_LUser -> ok
end.
@@ -159,6 +135,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
check_password = CheckPassword,
check_password_digest = CheckPasswordDigest}.
+server_start(State, Mech, undefined) ->
+ server_start(State, Mech, <<"">>);
server_start(State, Mech, ClientIn) ->
case lists:member(Mech,
listmech(State#sasl_state.myname))
@@ -174,11 +152,13 @@ server_start(State, Mech, ClientIn) ->
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
- _ -> {error, <<"no-mechanism">>}
+ _ -> {error, 'no-mechanism'}
end;
- false -> {error, <<"no-mechanism">>}
+ false -> {error, 'no-mechanism'}
end.
+server_step(State, undefined) ->
+ server_step(State, <<"">>);
server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod,
MechState = State#sasl_state.mech_state,
diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl
index 802e1cd7b..15980afc5 100644
--- a/src/cyrsasl_anonymous.erl
+++ b/src/cyrsasl_anonymous.erl
@@ -45,7 +45,7 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
- jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
+ integer_to_binary(p1_time_compat:unique_integer([positive]))]),
case ejabberd_auth:is_user_exists(User, Server) of
true -> mech_step(S, ClientIn);
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 0d408fc48..150aa854c 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -80,7 +80,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
mech_step(#state{step = 3, nonce = Nonce} = State,
ClientIn) ->
case parse(ClientIn) of
- bad -> {error, <<"bad-protocol">>};
+ bad -> {error, 'bad-protocol'};
KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
@@ -92,11 +92,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
"seems invalid: ~p (checking for Host "
"~p, FQDN ~p)",
[DigestURI, State#state.host, State#state.hostfqdn]),
- {error, <<"not-authorized">>, UserName};
+ {error, 'not-authorized', UserName};
true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
case (State#state.get_password)(UserName) of
- {false, _} -> {error, <<"not-authorized">>, UserName};
+ {false, _} -> {error, 'not-authorized', UserName};
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>),
@@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
State#state{step = 5, auth_module = AuthModule,
username = UserName,
authzid = AuthzId}};
- false -> {error, <<"not-authorized">>, UserName};
- {false, _} -> {error, <<"not-authorized">>, UserName}
+ false -> {error, 'not-authorized', UserName};
+ {false, _} -> {error, 'not-authorized', UserName}
end
end
end
@@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
- {error, <<"bad-protocol">>}.
+ {error, 'bad-protocol'}.
parse(S) -> parse1(binary_to_list(S), "", []).
diff --git a/src/cyrsasl_oauth.erl b/src/cyrsasl_oauth.erl
index 80ba315ed..21dedc6db 100644
--- a/src/cyrsasl_oauth.erl
+++ b/src/cyrsasl_oauth.erl
@@ -52,9 +52,9 @@ mech_step(State, ClientIn) ->
[{username, User}, {authzid, AuthzId},
{auth_module, ejabberd_oauth}]};
_ ->
- {error, <<"not-authorized">>, User}
+ {error, 'not-authorized', User}
end;
- _ -> {error, <<"bad-protocol">>}
+ _ -> {error, 'bad-protocol'}
end.
prepare(ClientIn) ->
diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl
index 82d68f87f..8e9b32b99 100644
--- a/src/cyrsasl_plain.erl
+++ b/src/cyrsasl_plain.erl
@@ -50,9 +50,9 @@ mech_step(State, ClientIn) ->
{ok,
[{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]};
- _ -> {error, <<"not-authorized">>, User}
+ _ -> {error, 'not-authorized', User}
end;
- _ -> {error, <<"bad-protocol">>}
+ _ -> {error, 'bad-protocol'}
end.
prepare(ClientIn) ->
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index 1c464e121..1e2a5c681 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -34,8 +34,6 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
-
-behaviour(cyrsasl).
-record(state,
@@ -67,21 +65,21 @@ mech_step(#state{step = 2} = State, ClientIn) ->
case re:split(ClientIn, <<",">>, [{return, binary}]) of
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
when ExtensionAttribute /= [] ->
- {error, <<"protocol-error-extension-not-supported">>};
+ {error, 'protocol-error-extension-not-supported'};
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
case parse_attribute(UserNameAttribute) of
{error, Reason} -> {error, Reason};
{_, EscapedUserName} ->
case unescape_username(EscapedUserName) of
- error -> {error, <<"protocol-error-bad-username">>};
+ error -> {error, 'protocol-error-bad-username'};
UserName ->
case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} ->
{Ret, _AuthModule} = (State#state.get_password)(UserName),
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};
+ {false, _} -> {error, 'not-authorized', UserName};
+ {_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, 'not-authorized', UserName};
{Ret, _} ->
{StoredKey, ServerKey, Salt, IterationCount} =
if is_tuple(Ret) -> Ret;
@@ -121,11 +119,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
server_nonce = ServerNonce,
username = UserName}}
end;
- _Else -> {error, <<"not-supported">>}
+ _Else -> {error, 'not-supported'}
end
end
end;
- _Else -> {error, <<"bad-protocol">>}
+ _Else -> {error, 'bad-protocol'}
end;
mech_step(#state{step = 4} = State, ClientIn) ->
case str:tokens(ClientIn, <<",">>) of
@@ -163,18 +161,18 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{authzid, State#state.username}],
<<"v=",
(jlib:encode_base64(ServerSignature))/binary>>};
- true -> {error, <<"bad-auth">>, State#state.username}
+ true -> {error, 'bad-auth', State#state.username}
end;
- _Else -> {error, <<"bad-protocol">>}
+ _Else -> {error, 'bad-protocol'}
end;
- {$r, _} -> {error, <<"bad-nonce">>};
- _Else -> {error, <<"bad-protocol">>}
+ {$r, _} -> {error, 'bad-nonce'};
+ _Else -> {error, 'bad-protocol'}
end;
- true -> {error, <<"bad-channel-binding">>}
+ true -> {error, 'bad-channel-binding'}
end;
- _Else -> {error, <<"bad-protocol">>}
+ _Else -> {error, 'bad-protocol'}
end;
- _Else -> {error, <<"bad-protocol">>}
+ _Else -> {error, 'bad-protocol'}
end.
parse_attribute(Attribute) ->
@@ -187,11 +185,11 @@ parse_attribute(Attribute) ->
if SecondChar == $= ->
String = str:substr(Attribute, 3),
{lists:nth(1, AttributeS), String};
- true -> {error, <<"bad-format second char not equal sign">>}
+ true -> {error, 'bad-format-second-char-not-equal-sign'}
end;
- _Else -> {error, <<"bad-format first char not a letter">>}
+ _Else -> {error, 'bad-format-first-char-not-a-letter'}
end;
- true -> {error, <<"bad-format attribute too short">>}
+ true -> {error, 'bad-format-attribute-too-short'}
end.
unescape_username(<<"">>) -> <<"">>;
diff --git a/src/ejabberd_access_permissions.erl b/src/ejabberd_access_permissions.erl
index 7ce75aa9c..60ad68a29 100644
--- a/src/ejabberd_access_permissions.erl
+++ b/src/ejabberd_access_permissions.erl
@@ -532,10 +532,10 @@ key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) ->
end.
report_error(Format, Args) ->
- throw({invalid_syntax, iolist_to_binary(io_lib:format(Format, Args))}).
+ throw({invalid_syntax, (str:format(Format, Args))}).
parse_error(Format, Args) ->
- {error, iolist_to_binary(io_lib:format(Format, Args))}.
+ {error, (str:format(Format, Args))}.
opt_type(api_permissions) ->
fun parse_api_permissions/1;
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 412e2bbd0..765d23810 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -303,8 +303,8 @@ set_loglevel(LogLevel) ->
%%%
stop_kindly(DelaySeconds, AnnouncementTextString) ->
- Subject = list_to_binary(io_lib:format("Server stop in ~p seconds!", [DelaySeconds])),
- WaitingDesc = list_to_binary(io_lib:format("Waiting ~p seconds", [DelaySeconds])),
+ Subject = (str:format("Server stop in ~p seconds!", [DelaySeconds])),
+ WaitingDesc = (str:format("Waiting ~p seconds", [DelaySeconds])),
AnnouncementText = list_to_binary(AnnouncementTextString),
Steps = [
{"Stopping ejabberd port listeners",
@@ -338,8 +338,7 @@ stop_kindly(DelaySeconds, AnnouncementTextString) ->
ok.
send_service_message_all_mucs(Subject, AnnouncementText) ->
- Message = list_to_binary(
- io_lib:format("~s~n~s", [Subject, AnnouncementText])),
+ Message = str:format("~s~n~s", [Subject, AnnouncementText]),
lists:foreach(
fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host(
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index e4087142b..e4333c816 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -43,7 +43,6 @@
start(normal, _Args) ->
ejabberd_logger:start(),
write_pid_file(),
- jid:start(),
start_apps(),
start_elixir_application(),
ejabberd:check_app(ejabberd),
@@ -77,7 +76,6 @@ start(normal, _Args) ->
ejabberd_oauth:start(),
gen_mod:start_modules(),
ejabberd_listener:start_listeners(),
- ejabberd_service:start(),
register_elixir_config_hooks(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
@@ -224,9 +222,7 @@ start_apps() ->
ejabberd:start_app(ssl),
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
- ejabberd:start_app(fast_xml),
- ejabberd:start_app(stringprep),
- http_p1:start(),
+ ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
opt_type(net_ticktime) ->
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 6b7f537c0..74c8009c2 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -36,8 +36,8 @@
check_password/6, check_password_with_authmodule/4,
check_password_with_authmodule/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2, export/1, import/1,
- get_vh_registered_users_number/1, import/3,
+ get_vh_registered_users/2, export/1, import_info/0,
+ get_vh_registered_users_number/1, import/5, import_start/2,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, get_password_with_authmodule/2,
is_user_exists/2, is_user_exists_in_other_modules/3,
@@ -438,15 +438,20 @@ auth_modules(Server) ->
export(Server) ->
ejabberd_auth_mnesia:export(Server).
-import(Server) ->
- ejabberd_auth_mnesia:import(Server).
+import_info() ->
+ [{<<"users">>, 3}].
-import(Server, mnesia, Passwd) ->
- ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
-import(Server, riak, Passwd) ->
- ejabberd_auth_riak:import(Server, riak, Passwd);
-import(_, _, _) ->
- pass.
+import_start(_LServer, mnesia) ->
+ ejabberd_auth_mnesia:init_db();
+import_start(_LServer, _) ->
+ ok.
+
+import(Server, {sql, _}, mnesia, <<"users">>, Fields) ->
+ ejabberd_auth_mnesia:import(Server, Fields);
+import(Server, {sql, _}, riak, <<"users">>, Fields) ->
+ ejabberd_auth_riak:import(Server, Fields);
+import(_LServer, {sql, _}, sql, <<"users">>, _) ->
+ ok.
opt_type(auth_method) ->
fun (V) when is_list(V) ->
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index 5a5b395bf..e0c4d471f 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -50,8 +50,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("jid.hrl").
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
@@ -60,7 +59,7 @@
start(Host) ->
%% TODO: Check cluster mode
- mnesia:create_table(anonymous, [{ram_copies, [node()]},
+ ejabberd_mnesia:create(?MODULE, anonymous, [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, anonymous)}]),
%% The hooks are needed to add / remove users from the anonymous tables
@@ -140,6 +139,7 @@ remove_connection(SID, LUser, LServer) ->
mnesia:transaction(F).
%% Register connection
+-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
register_connection(SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = proplists:get_value(auth_module, Info, undefined),
@@ -156,6 +156,7 @@ register_connection(SID,
end.
%% Remove an anonymous user from the anonymous users table
+-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
unregister_connection(SID,
#jid{luser = LUser, lserver = LServer}, _) ->
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl
index f36c9fbc7..eac19f024 100644
--- a/src/ejabberd_auth_mnesia.erl
+++ b/src/ejabberd_auth_mnesia.erl
@@ -36,12 +36,12 @@
-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/2, init_db/0,
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, export/1, import/1,
- import/3, plain_password_required/0, opt_type/1]).
+ remove_user/3, store_type/0, export/1, import/2,
+ plain_password_required/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -59,17 +59,20 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- mnesia:create_table(passwd,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, passwd)}]),
- mnesia:create_table(reg_users_counter,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, reg_users_counter)}]),
+ init_db(),
update_table(),
update_reg_users_counter_table(Host),
maybe_alert_password_scrammed_without_option(),
ok.
+init_db() ->
+ ejabberd_mnesia:create(?MODULE, passwd,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, passwd)}]),
+ ejabberd_mnesia:create(?MODULE, reg_users_counter,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, reg_users_counter)}]).
+
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
@@ -493,16 +496,9 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, password from users;">>,
- fun([LUser, Password]) ->
- #passwd{us = {LUser, LServer}, password = Password}
- end}].
-
-import(_LServer, mnesia, #passwd{} = P) ->
- mnesia:dirty_write(P);
-import(_, _, _) ->
- pass.
+import(LServer, [LUser, Password, _TimeStamp]) ->
+ mnesia:dirty_write(
+ #passwd{us = {LUser, LServer}, password = Password}).
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index 05add262e..51571c4e2 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -27,6 +27,8 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
+-behaviour(ejabberd_config).
+
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
@@ -39,8 +41,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, export/1, import/3,
- plain_password_required/0]).
+ remove_user/3, store_type/0, export/1, import/2,
+ plain_password_required/0, opt_type/1]).
-export([passwd_schema/0]).
-include("ejabberd.hrl").
@@ -301,7 +303,9 @@ export(_Server) ->
[]
end}].
-import(LServer, riak, #passwd{} = Passwd) ->
- ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]);
-import(_, _, _) ->
- pass.
+import(LServer, [LUser, Password, _TimeStamp]) ->
+ Passwd = #passwd{us = {LUser, LServer}, password = Password},
+ ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]).
+
+opt_type(auth_password_format) -> fun (V) -> V end;
+opt_type(_) -> [auth_password_format].
diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl
new file mode 100644
index 000000000..b94184167
--- /dev/null
+++ b/src/ejabberd_bosh.erl
@@ -0,0 +1,1095 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_bosh.erl
+%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Purpose : Manage BOSH sockets
+%%% Created : 20 Jul 2011 by Evgeniy 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(ejabberd_bosh).
+
+-protocol({xep, 124, '1.11'}).
+-protocol({xep, 206, '1.4'}).
+
+-define(GEN_FSM, p1_fsm).
+
+-behaviour(?GEN_FSM).
+
+%% API
+-export([start/2, start/3, start_link/3]).
+
+-export([send_xml/2, setopts/2, controlling_process/2,
+ migrate/3, custom_receiver/1, become_controller/2,
+ reset_stream/1, change_shaper/2, monitor/1, close/1,
+ sockname/1, peername/1, process_request/3, send/2,
+ change_controller/2]).
+
+%% gen_fsm callbacks
+-export([init/1, wait_for_session/2, wait_for_session/3,
+ active/2, active/3, handle_event/3, print_state/1,
+ handle_sync_event/4, handle_info/3, terminate/3,
+ code_change/4]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-include("jlib.hrl").
+
+-include("ejabberd_http.hrl").
+
+-include("bosh.hrl").
+
+%%-define(DBGFSM, true).
+-ifdef(DBGFSM).
+
+-define(FSMOPTS, [{debug, [trace]}]).
+
+-else.
+
+-define(FSMOPTS, []).
+
+-endif.
+
+-define(BOSH_VERSION, <<"1.11">>).
+
+-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
+
+-define(NS_HTTP_BIND,
+ <<"http://jabber.org/protocol/httpbind">>).
+
+-define(DEFAULT_MAXPAUSE, 120).
+
+-define(DEFAULT_WAIT, 300).
+
+-define(DEFAULT_HOLD, 1).
+
+-define(DEFAULT_POLLING, 2).
+
+-define(DEFAULT_INACTIVITY, 30).
+
+-define(MAX_SHAPED_REQUESTS_QUEUE_LEN, 1000).
+
+-define(SEND_TIMEOUT, 15000).
+
+-type bosh_socket() :: {http_bind, pid(),
+ {inet:ip_address(),
+ inet:port_number()}}.
+
+-export_type([bosh_socket/0]).
+
+-record(state,
+ {host = <<"">> :: binary(),
+ sid = <<"">> :: binary(),
+ el_ibuf = buf_new() :: ?TQUEUE,
+ el_obuf = buf_new() :: ?TQUEUE,
+ shaper_state = none :: shaper:shaper(),
+ c2s_pid :: pid(),
+ xmpp_ver = <<"">> :: binary(),
+ inactivity_timer :: reference(),
+ wait_timer :: reference(),
+ wait_timeout = ?DEFAULT_WAIT :: timeout(),
+ inactivity_timeout = ?DEFAULT_INACTIVITY :: timeout(),
+ prev_rid = 0 :: non_neg_integer(),
+ prev_key = <<"">> :: binary(),
+ prev_poll :: erlang:timestamp(),
+ max_concat = unlimited :: unlimited | non_neg_integer(),
+ responses = gb_trees:empty() :: ?TGB_TREE,
+ receivers = gb_trees:empty() :: ?TGB_TREE,
+ shaped_receivers = queue:new() :: ?TQUEUE,
+ ip :: inet:ip_address(),
+ max_requests = 1 :: non_neg_integer()}).
+
+-record(body,
+ {http_reason = <<"">> :: binary(),
+ attrs = [] :: [{any(), any()}],
+ els = [] :: [fxml_stream:xml_stream_el()],
+ size = 0 :: non_neg_integer()}).
+
+start(#body{attrs = Attrs} = Body, IP, SID) ->
+ XMPPDomain = get_attr(to, Attrs),
+ SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME),
+ case catch supervisor:start_child(SupervisorProc,
+ [Body, IP, SID])
+ of
+ {ok, Pid} -> {ok, Pid};
+ {'EXIT', {noproc, _}} ->
+ check_bosh_module(XMPPDomain),
+ {error, module_not_loaded};
+ Err ->
+ ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]),
+ {error, Err}
+ end.
+
+start(StateName, State) ->
+ (?GEN_FSM):start_link(?MODULE, [StateName, State],
+ ?FSMOPTS).
+
+start_link(Body, IP, SID) ->
+ (?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
+ ?FSMOPTS).
+
+send({http_bind, FsmRef, IP}, Packet) ->
+ send_xml({http_bind, FsmRef, IP}, Packet).
+
+send_xml({http_bind, FsmRef, _IP}, Packet) ->
+ case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ {send_xml, Packet},
+ ?SEND_TIMEOUT)
+ of
+ {'EXIT', {timeout, _}} -> {error, timeout};
+ {'EXIT', _} -> {error, einval};
+ Res -> Res
+ end.
+
+setopts({http_bind, FsmRef, _IP}, Opts) ->
+ case lists:member({active, once}, Opts) of
+ true ->
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {activate, self()});
+ _ ->
+ case lists:member({active, false}, Opts) of
+ true ->
+ case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ deactivate_socket)
+ of
+ {'EXIT', _} -> {error, einval};
+ Res -> Res
+ end;
+ _ -> ok
+ end
+ end.
+
+controlling_process(_Socket, _Pid) -> ok.
+
+custom_receiver({http_bind, FsmRef, _IP}) ->
+ {receiver, ?MODULE, FsmRef}.
+
+become_controller(FsmRef, C2SPid) ->
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {become_controller, C2SPid}).
+
+change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
+ become_controller(FsmRef, C2SPid).
+
+reset_stream({http_bind, _FsmRef, _IP}) -> ok.
+
+change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
+ (?GEN_FSM):send_all_state_event(FsmRef,
+ {change_shaper, Shaper}).
+
+monitor({http_bind, FsmRef, _IP}) ->
+ erlang:monitor(process, FsmRef).
+
+close({http_bind, FsmRef, _IP}) ->
+ catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ close).
+
+sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
+
+peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
+
+migrate(FsmRef, Node, After) when node(FsmRef) == node() ->
+ catch erlang:send_after(After, FsmRef, {migrate, Node});
+migrate(_FsmRef, _Node, _After) ->
+ ok.
+
+process_request(Data, IP, Type) ->
+ Opts1 = ejabberd_c2s_config:get_c2s_limits(),
+ Opts = case Type of
+ xml ->
+ [{xml_socket, true} | Opts1];
+ json ->
+ Opts1
+ end,
+ MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
+ Opts)
+ of
+ {value, {_, Size}} -> Size;
+ _ -> infinity
+ end,
+ PayloadSize = iolist_size(Data),
+ if PayloadSize > MaxStanzaSize ->
+ http_error(403, <<"Request Too Large">>, Type);
+ true ->
+ case decode_body(Data, PayloadSize, Type) of
+ {ok, #body{attrs = Attrs} = Body} ->
+ SID = get_attr(sid, Attrs),
+ To = get_attr(to, Attrs),
+ if SID == <<"">>, To == <<"">> ->
+ bosh_response_with_msg(#body{http_reason =
+ <<"Missing 'to' attribute">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"improper-addressing">>}]},
+ Type, Body);
+ SID == <<"">> ->
+ case start(Body, IP, make_sid()) of
+ {ok, Pid} -> process_request(Pid, Body, IP, Type);
+ _Err ->
+ bosh_response_with_msg(#body{http_reason =
+ <<"Failed to start BOSH session">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"internal-server-error">>}]},
+ Type, Body)
+ end;
+ true ->
+ case mod_bosh:find_session(SID) of
+ {ok, Pid} -> process_request(Pid, Body, IP, Type);
+ error ->
+ bosh_response_with_msg(#body{http_reason =
+ <<"Session ID mismatch">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"item-not-found">>}]},
+ Type, Body)
+ end
+ end;
+ {error, Reason} -> http_error(400, Reason, Type)
+ end
+ end.
+
+process_request(Pid, Req, _IP, Type) ->
+ case catch (?GEN_FSM):sync_send_event(Pid, Req,
+ infinity)
+ of
+ #body{} = Resp -> bosh_response(Resp, Type);
+ {'EXIT', {Reason, _}}
+ when Reason == noproc; Reason == normal ->
+ bosh_response(#body{http_reason =
+ <<"BOSH session not found">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition, <<"item-not-found">>}]},
+ Type);
+ {'EXIT', _} ->
+ bosh_response(#body{http_reason =
+ <<"Unexpected error">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition, <<"internal-server-error">>}]},
+ Type)
+ end.
+
+init([#body{attrs = Attrs}, IP, SID]) ->
+ Opts1 = ejabberd_c2s_config:get_c2s_limits(),
+ Opts2 = [{xml_socket, true} | Opts1],
+ Shaper = none,
+ ShaperState = shaper:new(Shaper),
+ Socket = make_socket(self(), IP),
+ XMPPVer = get_attr('xmpp:version', Attrs),
+ XMPPDomain = get_attr(to, Attrs),
+ {InBuf, Opts} = case gen_mod:get_module_opt(
+ XMPPDomain,
+ mod_bosh, prebind,
+ fun(B) when is_boolean(B) -> B end,
+ false) of
+ true ->
+ JID = make_random_jid(XMPPDomain),
+ {buf_new(), [{jid, JID} | Opts2]};
+ false ->
+ {buf_in([make_xmlstreamstart(XMPPDomain, XMPPVer)],
+ buf_new()),
+ Opts2}
+ end,
+ ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket,
+ Opts),
+ Inactivity = gen_mod:get_module_opt(XMPPDomain,
+ mod_bosh, max_inactivity,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_INACTIVITY),
+ MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat,
+ fun(unlimited) -> unlimited;
+ (N) when is_integer(N), N>0 -> N
+ end, unlimited),
+ State = #state{host = XMPPDomain, sid = SID, ip = IP,
+ xmpp_ver = XMPPVer, el_ibuf = InBuf,
+ max_concat = MaxConcat, el_obuf = buf_new(),
+ inactivity_timeout = Inactivity,
+ shaper_state = ShaperState},
+ NewState = restart_inactivity_timer(State),
+ mod_bosh:open_session(SID, self()),
+ {ok, wait_for_session, NewState};
+init([StateName, State]) ->
+ mod_bosh:open_session(State#state.sid, self()),
+ case State#state.c2s_pid of
+ C2SPid when is_pid(C2SPid) ->
+ NewSocket = make_socket(self(), State#state.ip),
+ C2SPid ! {change_socket, NewSocket},
+ NewState = restart_inactivity_timer(State),
+ {ok, StateName, NewState};
+ _ -> {stop, normal}
+ end.
+
+wait_for_session(_Event, State) ->
+ ?ERROR_MSG("unexpected event in 'wait_for_session': ~p",
+ [_Event]),
+ {next_state, wait_for_session, State}.
+
+wait_for_session(#body{attrs = Attrs} = Req, From,
+ State) ->
+ RID = get_attr(rid, Attrs),
+ ?DEBUG("got request:~n** RequestID: ~p~n** Request: "
+ "~p~n** From: ~p~n** State: ~p",
+ [RID, Req, From, State]),
+ Wait = min(get_attr(wait, Attrs, undefined),
+ ?DEFAULT_WAIT),
+ Hold = min(get_attr(hold, Attrs, undefined),
+ ?DEFAULT_HOLD),
+ NewKey = get_attr(newkey, Attrs),
+ Type = get_attr(type, Attrs),
+ Requests = Hold + 1,
+ {PollTime, Polling} = if Wait == 0, Hold == 0 ->
+ {p1_time_compat:timestamp(), [{polling, ?DEFAULT_POLLING}]};
+ true -> {undefined, []}
+ end,
+ MaxPause = gen_mod:get_module_opt(State#state.host,
+ mod_bosh, max_pause,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?DEFAULT_MAXPAUSE),
+ Resp = #body{attrs =
+ [{sid, State#state.sid}, {wait, Wait},
+ {ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING},
+ {inactivity, State#state.inactivity_timeout},
+ {hold, Hold}, {'xmpp:restartlogic', true},
+ {requests, Requests}, {secure, true},
+ {maxpause, MaxPause}, {'xmlns:xmpp', ?NS_BOSH},
+ {'xmlns:stream', ?NS_STREAM}, {from, State#state.host}
+ | Polling]},
+ {ShaperState, _} =
+ shaper:update(State#state.shaper_state, Req#body.size),
+ State1 = State#state{wait_timeout = Wait,
+ prev_rid = RID, prev_key = NewKey,
+ prev_poll = PollTime, shaper_state = ShaperState,
+ max_requests = Requests},
+ Els = maybe_add_xmlstreamend(Req#body.els, Type),
+ State2 = route_els(State1, Els),
+ {State3, RespEls} = get_response_els(State2),
+ State4 = stop_inactivity_timer(State3),
+ case RespEls of
+ [] ->
+ State5 = restart_wait_timer(State4),
+ Receivers = gb_trees:insert(RID, {From, Resp},
+ State5#state.receivers),
+ {next_state, active,
+ State5#state{receivers = Receivers}};
+ _ ->
+ reply_next_state(State4, Resp#body{els = RespEls}, RID,
+ From)
+ end;
+wait_for_session(_Event, _From, State) ->
+ ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p",
+ [_Event]),
+ {reply, {error, badarg}, wait_for_session, State}.
+
+active({#body{} = Body, From}, State) ->
+ active1(Body, From, State);
+active(_Event, State) ->
+ ?ERROR_MSG("unexpected event in 'active': ~p",
+ [_Event]),
+ {next_state, active, State}.
+
+active(#body{attrs = Attrs, size = Size} = Req, From,
+ State) ->
+ ?DEBUG("got request:~n** Request: ~p~n** From: "
+ "~p~n** State: ~p",
+ [Req, From, State]),
+ {ShaperState, Pause} =
+ shaper:update(State#state.shaper_state, Size),
+ State1 = State#state{shaper_state = ShaperState},
+ if Pause > 0 ->
+ QLen = queue:len(State1#state.shaped_receivers),
+ if QLen < (?MAX_SHAPED_REQUESTS_QUEUE_LEN) ->
+ TRef = start_shaper_timer(Pause),
+ Q = queue:in({TRef, From, Req},
+ State1#state.shaped_receivers),
+ State2 = stop_inactivity_timer(State1),
+ {next_state, active,
+ State2#state{shaped_receivers = Q}};
+ true ->
+ RID = get_attr(rid, Attrs),
+ reply_stop(State1,
+ #body{http_reason = <<"Too many requests">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"policy-violation">>}]},
+ From, RID)
+ end;
+ true -> active1(Req, From, State1)
+ end;
+active(_Event, _From, State) ->
+ ?ERROR_MSG("unexpected sync event in 'active': ~p",
+ [_Event]),
+ {reply, {error, badarg}, active, State}.
+
+active1(#body{attrs = Attrs} = Req, From, State) ->
+ RID = get_attr(rid, Attrs),
+ Key = get_attr(key, Attrs),
+ IsValidKey = is_valid_key(State#state.prev_key, Key),
+ IsOveractivity = is_overactivity(State#state.prev_poll),
+ Type = get_attr(type, Attrs),
+ if RID >
+ State#state.prev_rid + State#state.max_requests ->
+ reply_stop(State,
+ #body{http_reason = <<"Request ID is out of range">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"item-not-found">>}]},
+ From, RID);
+ RID > State#state.prev_rid + 1 ->
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:insert(RID, {From, Req},
+ State1#state.receivers),
+ {next_state, active,
+ State1#state{receivers = Receivers}};
+ RID =< State#state.prev_rid ->
+ %% TODO: do we need to check 'key' here? It seems so...
+ case gb_trees:lookup(RID, State#state.responses) of
+ {value, PrevBody} ->
+ {next_state, active,
+ do_reply(State, From, PrevBody, RID)};
+ none ->
+ State1 = drop_holding_receiver(State),
+ State2 = stop_inactivity_timer(State1),
+ State3 = restart_wait_timer(State2),
+ Receivers = gb_trees:insert(RID, {From, Req},
+ State3#state.receivers),
+ {next_state, active, State3#state{receivers = Receivers}}
+ end;
+ not IsValidKey ->
+ reply_stop(State,
+ #body{http_reason = <<"Session key mismatch">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"item-not-found">>}]},
+ From, RID);
+ IsOveractivity ->
+ reply_stop(State,
+ #body{http_reason = <<"Too many requests">>,
+ attrs =
+ [{<<"type">>, <<"terminate">>},
+ {<<"condition">>, <<"policy-violation">>}]},
+ From, RID);
+ true ->
+ State1 = stop_inactivity_timer(State),
+ State2 = stop_wait_timer(State1),
+ Els = case get_attr('xmpp:restart', Attrs, false) of
+ true ->
+ XMPPDomain = get_attr(to, Attrs, State#state.host),
+ XMPPVer = get_attr('xmpp:version', Attrs,
+ State#state.xmpp_ver),
+ [make_xmlstreamstart(XMPPDomain, XMPPVer)];
+ false -> Req#body.els
+ end,
+ State3 = route_els(State2,
+ maybe_add_xmlstreamend(Els, Type)),
+ {State4, RespEls} = get_response_els(State3),
+ NewKey = get_attr(newkey, Attrs, Key),
+ Pause = get_attr(pause, Attrs, undefined),
+ NewPoll = case State#state.prev_poll of
+ undefined -> undefined;
+ _ -> p1_time_compat:timestamp()
+ end,
+ State5 = State4#state{prev_poll = NewPoll,
+ prev_key = NewKey},
+ if Type == <<"terminate">> ->
+ reply_stop(State5,
+ #body{http_reason = <<"Session close">>,
+ attrs = [{<<"type">>, <<"terminate">>}],
+ els = RespEls},
+ From, RID);
+ Pause /= undefined ->
+ State6 = drop_holding_receiver(State5),
+ State7 = restart_inactivity_timer(State6, Pause),
+ InBuf = buf_in(RespEls, State7#state.el_ibuf),
+ {next_state, active,
+ State7#state{prev_rid = RID, el_ibuf = InBuf}};
+ RespEls == [] ->
+ State6 = drop_holding_receiver(State5),
+ State7 = stop_inactivity_timer(State6),
+ State8 = restart_wait_timer(State7),
+ Receivers = gb_trees:insert(RID, {From, #body{}},
+ State8#state.receivers),
+ {next_state, active,
+ State8#state{prev_rid = RID, receivers = Receivers}};
+ true ->
+ State6 = drop_holding_receiver(State5),
+ reply_next_state(State6#state{prev_rid = RID},
+ #body{els = RespEls}, RID, From)
+ end
+ end.
+
+handle_event({become_controller, C2SPid}, StateName,
+ State) ->
+ State1 = route_els(State#state{c2s_pid = C2SPid}),
+ {next_state, StateName, State1};
+handle_event({change_shaper, Shaper}, StateName,
+ State) ->
+ NewShaperState = shaper:new(Shaper),
+ {next_state, StateName,
+ State#state{shaper_state = NewShaperState}};
+handle_event(_Event, StateName, State) ->
+ ?ERROR_MSG("unexpected event in '~s': ~p",
+ [StateName, _Event]),
+ {next_state, StateName, State}.
+
+handle_sync_event({send_xml,
+ {xmlstreamstart, _, _} = El},
+ _From, StateName, State)
+ when State#state.xmpp_ver >= <<"1.0">> ->
+ OutBuf = buf_in([El], State#state.el_obuf),
+ {reply, ok, StateName, State#state{el_obuf = OutBuf}};
+handle_sync_event({send_xml, El}, _From, StateName,
+ State) ->
+ OutBuf = buf_in([El], State#state.el_obuf),
+ State1 = State#state{el_obuf = OutBuf},
+ case gb_trees:lookup(State1#state.prev_rid,
+ State1#state.receivers)
+ of
+ {value, {From, Body}} ->
+ {State2, Els} = get_response_els(State1),
+ {reply, ok, StateName,
+ reply(State2, Body#body{els = Els},
+ State2#state.prev_rid, From)};
+ none ->
+ State2 = case queue:out(State1#state.shaped_receivers)
+ of
+ {{value, {TRef, From, Body}}, Q} ->
+ cancel_timer(TRef),
+ (?GEN_FSM):send_event(self(), {Body, From}),
+ State1#state{shaped_receivers = Q};
+ _ -> State1
+ end,
+ {reply, ok, StateName, State2}
+ end;
+handle_sync_event(close, _From, _StateName, State) ->
+ {stop, normal, State};
+handle_sync_event(deactivate_socket, _From, StateName,
+ StateData) ->
+ {reply, ok, StateName,
+ StateData#state{c2s_pid = undefined}};
+handle_sync_event(_Event, _From, StateName, State) ->
+ ?ERROR_MSG("unexpected sync event in '~s': ~p",
+ [StateName, _Event]),
+ {reply, {error, badarg}, StateName, State}.
+
+handle_info({timeout, TRef, wait_timeout}, StateName,
+ #state{wait_timer = TRef} = State) ->
+ {next_state, StateName, drop_holding_receiver(State)};
+handle_info({timeout, TRef, inactive}, _StateName,
+ #state{inactivity_timer = TRef} = State) ->
+ {stop, normal, State};
+handle_info({timeout, TRef, shaper_timeout}, StateName,
+ State) ->
+ case queue:out(State#state.shaped_receivers) of
+ {{value, {TRef, From, Req}}, Q} ->
+ (?GEN_FSM):send_event(self(), {Req, From}),
+ {next_state, StateName,
+ State#state{shaped_receivers = Q}};
+ {{value, _}, _} ->
+ ?ERROR_MSG("shaper_timeout mismatch:~n** TRef: ~p~n** "
+ "State: ~p",
+ [TRef, State]),
+ {stop, normal, State};
+ _ -> {next_state, StateName, State}
+ end;
+handle_info({migrate, Node}, StateName, State) ->
+ if Node /= node() ->
+ NewState = bounce_receivers(State, migrated),
+ {migrate, NewState,
+ {Node, ?MODULE, start, [StateName, NewState]}, 0};
+ true -> {next_state, StateName, State}
+ end;
+handle_info(_Info, StateName, State) ->
+ ?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p",
+ [_Info, StateName]),
+ {next_state, StateName, State}.
+
+terminate({migrated, ClonePid}, _StateName, State) ->
+ ?INFO_MSG("Migrating session \"~s\" (c2s_pid = "
+ "~p) to ~p on node ~p",
+ [State#state.sid, State#state.c2s_pid, ClonePid,
+ node(ClonePid)]),
+ mod_bosh:close_session(State#state.sid);
+terminate(_Reason, _StateName, State) ->
+ mod_bosh:close_session(State#state.sid),
+ case State#state.c2s_pid of
+ C2SPid when is_pid(C2SPid) ->
+ (?GEN_FSM):send_event(C2SPid, closed);
+ _ -> ok
+ end,
+ bounce_receivers(State, closed),
+ bounce_els_from_obuf(State).
+
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+print_state(State) -> State.
+
+route_els(#state{el_ibuf = Buf} = State) ->
+ route_els(State#state{el_ibuf = buf_new()},
+ buf_to_list(Buf)).
+
+route_els(State, Els) ->
+ case State#state.c2s_pid of
+ C2SPid when is_pid(C2SPid) ->
+ lists:foreach(fun (El) ->
+ (?GEN_FSM):send_event(C2SPid, El)
+ end,
+ Els),
+ State;
+ _ ->
+ InBuf = buf_in(Els, State#state.el_ibuf),
+ State#state{el_ibuf = InBuf}
+ end.
+
+get_response_els(#state{el_obuf = OutBuf,
+ max_concat = MaxConcat} =
+ State) ->
+ {Els, NewOutBuf} = buf_out(OutBuf, MaxConcat),
+ {State#state{el_obuf = NewOutBuf}, Els}.
+
+reply(State, Body, RID, From) ->
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
+ State2 = do_reply(State1, From, Body, RID),
+ case catch gb_trees:take_smallest(Receivers) of
+ {NextRID, {From1, Req}, Receivers1}
+ when NextRID == RID + 1 ->
+ (?GEN_FSM):send_event(self(), {Req, From1}),
+ State2#state{receivers = Receivers1};
+ _ -> State2#state{receivers = Receivers}
+ end.
+
+reply_next_state(State, Body, RID, From) ->
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
+ State2 = do_reply(State1, From, Body, RID),
+ case catch gb_trees:take_smallest(Receivers) of
+ {NextRID, {From1, Req}, Receivers1}
+ when NextRID == RID + 1 ->
+ active(Req, From1,
+ State2#state{receivers = Receivers1});
+ _ ->
+ {next_state, active,
+ State2#state{receivers = Receivers}}
+ end.
+
+reply_stop(State, Body, From, RID) ->
+ {stop, normal, do_reply(State, From, Body, RID)}.
+
+drop_holding_receiver(State) ->
+ RID = State#state.prev_rid,
+ case gb_trees:lookup(RID, State#state.receivers) of
+ {value, {From, Body}} ->
+ State1 = restart_inactivity_timer(State),
+ Receivers = gb_trees:delete_any(RID,
+ State1#state.receivers),
+ State2 = State1#state{receivers = Receivers},
+ do_reply(State2, From, Body, RID);
+ none -> State
+ end.
+
+do_reply(State, From, Body, RID) ->
+ ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
+ "~p~n** To: ~p~n** State: ~p",
+ [RID, Body, From, State]),
+ (?GEN_FSM):reply(From, Body),
+ Responses = gb_trees:delete_any(RID,
+ State#state.responses),
+ Responses1 = case gb_trees:size(Responses) of
+ N when N < State#state.max_requests; N == 0 ->
+ Responses;
+ _ -> element(3, gb_trees:take_smallest(Responses))
+ end,
+ Responses2 = gb_trees:insert(RID, Body, Responses1),
+ State#state{responses = Responses2}.
+
+bounce_receivers(State, Reason) ->
+ Receivers = gb_trees:to_list(State#state.receivers),
+ ShapedReceivers = lists:map(fun ({_, From,
+ #body{attrs = Attrs} = Body}) ->
+ RID = get_attr(rid, Attrs),
+ {RID, {From, Body}}
+ end,
+ queue:to_list(State#state.shaped_receivers)),
+ lists:foldl(fun ({RID, {From, Body}}, AccState) ->
+ NewBody = if Reason == closed ->
+ #body{http_reason =
+ <<"Session closed">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"other-request">>}]};
+ Reason == migrated ->
+ Body#body{http_reason =
+ <<"Session migrated">>}
+ end,
+ do_reply(AccState, From, NewBody, RID)
+ end,
+ State, Receivers ++ ShapedReceivers).
+
+bounce_els_from_obuf(State) ->
+ lists:foreach(fun ({xmlstreamelement, El}) ->
+ case El of
+ #xmlel{name = Name, attrs = Attrs}
+ when Name == <<"presence">>;
+ Name == <<"message">>;
+ Name == <<"iq">> ->
+ FromS = fxml:get_attr_s(<<"from">>, Attrs),
+ ToS = fxml:get_attr_s(<<"to">>, Attrs),
+ case {jid:from_string(FromS),
+ jid:from_string(ToS)}
+ of
+ {#jid{} = From, #jid{} = To} ->
+ ejabberd_router:route(From, To, El);
+ _ -> ok
+ end;
+ _ -> ok
+ end;
+ (_) -> ok
+ end,
+ buf_to_list(State#state.el_obuf)).
+
+is_valid_key(<<"">>, <<"">>) -> true;
+is_valid_key(PrevKey, Key) ->
+ p1_sha:sha(Key) == PrevKey.
+
+is_overactivity(undefined) -> false;
+is_overactivity(PrevPoll) ->
+ PollPeriod = timer:now_diff(p1_time_compat:timestamp(), PrevPoll) div
+ 1000000,
+ if PollPeriod < (?DEFAULT_POLLING) -> true;
+ true -> false
+ end.
+
+make_xmlstreamstart(XMPPDomain, Version) ->
+ VersionEl = case Version of
+ <<"">> -> [];
+ _ -> [{<<"version">>, Version}]
+ end,
+ {xmlstreamstart, <<"stream:stream">>,
+ [{<<"to">>, XMPPDomain}, {<<"xmlns">>, ?NS_CLIENT},
+ {<<"xmlns:xmpp">>, ?NS_BOSH},
+ {<<"xmlns:stream">>, ?NS_STREAM}
+ | VersionEl]}.
+
+maybe_add_xmlstreamend(Els, <<"terminate">>) ->
+ Els ++ [{xmlstreamend, <<"stream:stream">>}];
+maybe_add_xmlstreamend(Els, _) -> Els.
+
+encode_body(#body{attrs = Attrs, els = Els}, Type) ->
+ Attrs1 = lists:map(fun ({K, V}) when is_atom(K) ->
+ AmK = iolist_to_binary(atom_to_list(K)),
+ case V of
+ true -> {AmK, <<"true">>};
+ false -> {AmK, <<"false">>};
+ I when is_integer(I), I >= 0 ->
+ {AmK, integer_to_binary(I)};
+ _ -> {AmK, V}
+ end;
+ ({K, V}) -> {K, V}
+ end,
+ Attrs),
+ Attrs2 = [{<<"xmlns">>, ?NS_HTTP_BIND} | Attrs1],
+ {Attrs3, XMLs} = lists:foldr(fun ({xmlstreamraw, XML},
+ {AttrsAcc, XMLBuf}) ->
+ {AttrsAcc, [XML | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = <<"stream:error">>} = El},
+ {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>},
+ {<<"xmlns:stream">>, ?NS_STREAM}
+ | AttrsAcc],
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = <<"stream:features">>} =
+ El},
+ {AttrsAcc, XMLBuf}) ->
+ {lists:keystore(<<"xmlns:stream">>, 1,
+ AttrsAcc,
+ {<<"xmlns:stream">>,
+ ?NS_STREAM}),
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamelement,
+ #xmlel{name = Name, attrs = EAttrs} = El},
+ {AttrsAcc, XMLBuf})
+ when Name == <<"message">>;
+ Name == <<"presence">>;
+ Name == <<"iq">> ->
+ NewAttrs = lists:keystore(
+ <<"xmlns">>, 1, EAttrs,
+ {<<"xmlns">>, ?NS_CLIENT}),
+ NewEl = El#xmlel{attrs = NewAttrs},
+ {AttrsAcc,
+ [encode_element(NewEl, Type) | XMLBuf]};
+ ({xmlstreamelement, El},
+ {AttrsAcc, XMLBuf}) ->
+ {AttrsAcc,
+ [encode_element(El, Type) | XMLBuf]};
+ ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>}
+ | AttrsAcc],
+ XMLBuf};
+ ({xmlstreamstart, <<"stream:stream">>,
+ SAttrs},
+ {AttrsAcc, XMLBuf}) ->
+ StreamID = fxml:get_attr_s(<<"id">>,
+ SAttrs),
+ NewAttrs = case
+ fxml:get_attr_s(<<"version">>,
+ SAttrs)
+ of
+ <<"">> ->
+ [{<<"authid">>,
+ StreamID}
+ | AttrsAcc];
+ V ->
+ lists:keystore(<<"xmlns:xmpp">>,
+ 1,
+ [{<<"xmpp:version">>,
+ V},
+ {<<"authid">>,
+ StreamID}
+ | AttrsAcc],
+ {<<"xmlns:xmpp">>,
+ ?NS_BOSH})
+ end,
+ {NewAttrs, XMLBuf};
+ ({xmlstreamerror, _},
+ {AttrsAcc, XMLBuf}) ->
+ {[{<<"type">>, <<"terminate">>},
+ {<<"condition">>,
+ <<"remote-stream-error">>}
+ | AttrsAcc],
+ XMLBuf};
+ (_, Acc) -> Acc
+ end,
+ {Attrs2, []}, Els),
+ case XMLs of
+ [] when Type == xml ->
+ [<<"<body">>, attrs_to_list(Attrs3), <<"/>">>];
+ _ when Type == xml ->
+ [<<"<body">>, attrs_to_list(Attrs3), $>, XMLs,
+ <<"</body>">>]
+ end.
+
+encode_element(El, xml) ->
+ fxml:element_to_binary(El);
+encode_element(El, json) ->
+ El.
+
+decode_body(Data, Size, Type) ->
+ case decode(Data, Type) of
+ #xmlel{name = <<"body">>, attrs = Attrs,
+ children = Els} ->
+ case attrs_to_body_attrs(Attrs) of
+ {error, _} = Err -> Err;
+ BodyAttrs ->
+ case get_attr(rid, BodyAttrs) of
+ <<"">> -> {error, <<"Missing \"rid\" attribute">>};
+ _ ->
+ Els1 = lists:flatmap(fun (#xmlel{} = El) ->
+ [{xmlstreamelement, El}];
+ (_) -> []
+ end,
+ Els),
+ {ok, #body{attrs = BodyAttrs, size = Size, els = Els1}}
+ end
+ end;
+ #xmlel{} -> {error, <<"Unexpected payload">>};
+ _ when Type == xml ->
+ {error, <<"XML is not well-formed">>};
+ _ when Type == json ->
+ {error, <<"JSON is not well-formed">>}
+ end.
+
+decode(Data, xml) ->
+ fxml_stream:parse_element(Data).
+
+attrs_to_body_attrs(Attrs) ->
+ lists:foldl(fun (_, {error, Reason}) -> {error, Reason};
+ ({Attr, Val}, Acc) ->
+ try case Attr of
+ <<"ver">> -> [{ver, Val} | Acc];
+ <<"xmpp:version">> ->
+ [{'xmpp:version', Val} | Acc];
+ <<"type">> -> [{type, Val} | Acc];
+ <<"key">> -> [{key, Val} | Acc];
+ <<"newkey">> -> [{newkey, Val} | Acc];
+ <<"xmlns">> -> Val = (?NS_HTTP_BIND), Acc;
+ <<"secure">> -> [{secure, to_bool(Val)} | Acc];
+ <<"xmpp:restart">> ->
+ [{'xmpp:restart', to_bool(Val)} | Acc];
+ <<"to">> ->
+ [{to, jid:nameprep(Val)} | Acc];
+ <<"wait">> -> [{wait, to_int(Val, 0)} | Acc];
+ <<"ack">> -> [{ack, to_int(Val, 0)} | Acc];
+ <<"sid">> -> [{sid, Val} | Acc];
+ <<"hold">> -> [{hold, to_int(Val, 0)} | Acc];
+ <<"rid">> -> [{rid, to_int(Val, 0)} | Acc];
+ <<"pause">> -> [{pause, to_int(Val, 0)} | Acc];
+ _ -> [{Attr, Val} | Acc]
+ end
+ catch
+ _:_ ->
+ {error,
+ <<"Invalid \"", Attr/binary, "\" attribute">>}
+ end
+ end,
+ [], Attrs).
+
+to_int(S, Min) ->
+ case binary_to_integer(S) of
+ I when I >= Min -> I;
+ _ -> erlang:error(badarg)
+ end.
+
+to_bool(<<"true">>) -> true;
+to_bool(<<"1">>) -> true;
+to_bool(<<"false">>) -> false;
+to_bool(<<"0">>) -> false.
+
+attrs_to_list(Attrs) -> [attr_to_list(A) || A <- Attrs].
+
+attr_to_list({Name, Value}) ->
+ [$\s, Name, $=, $', fxml:crypt(Value), $'].
+
+bosh_response(Body, Type) ->
+ CType = case Type of
+ xml -> ?CT_XML;
+ json -> ?CT_JSON
+ end,
+ {200, Body#body.http_reason, ?HEADER(CType),
+ encode_body(Body, Type)}.
+
+bosh_response_with_msg(Body, Type, RcvBody) ->
+ ?DEBUG("send error reply:~p~n** Receiced body: ~p",
+ [Body, RcvBody]),
+ bosh_response(Body, Type).
+
+http_error(Status, Reason, Type) ->
+ CType = case Type of
+ xml -> ?CT_XML;
+ json -> ?CT_JSON
+ end,
+ {Status, Reason, ?HEADER(CType), <<"">>}.
+
+make_sid() -> p1_sha:sha(randoms:get_string()).
+
+-compile({no_auto_import, [{min, 2}]}).
+
+min(undefined, B) -> B;
+min(A, B) -> erlang:min(A, B).
+
+check_bosh_module(XmppDomain) ->
+ case gen_mod:is_loaded(XmppDomain, mod_bosh) of
+ true -> ok;
+ false ->
+ ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) "
+ "in host ~p, but the module mod_bosh "
+ "is not started in that host. Configure "
+ "your BOSH client to connect to the correct "
+ "host, or add your desired host to the "
+ "configuration, or check your 'modules' "
+ "section in your ejabberd configuration "
+ "file.",
+ [XmppDomain])
+ end.
+
+get_attr(Attr, Attrs) -> get_attr(Attr, Attrs, <<"">>).
+
+get_attr(Attr, Attrs, Default) ->
+ case lists:keysearch(Attr, 1, Attrs) of
+ {value, {_, Val}} -> Val;
+ _ -> Default
+ end.
+
+buf_new() -> queue:new().
+
+buf_in(Xs, Buf) ->
+ lists:foldl(fun (X, Acc) -> queue:in(X, Acc) end, Buf,
+ Xs).
+
+buf_out(Buf, Num) when is_integer(Num), Num > 0 ->
+ buf_out(Buf, Num, []);
+buf_out(Buf, _) -> {queue:to_list(Buf), buf_new()}.
+
+buf_out(Buf, 0, Els) -> {lists:reverse(Els), Buf};
+buf_out(Buf, I, Els) ->
+ case queue:out(Buf) of
+ {{value, El}, NewBuf} ->
+ buf_out(NewBuf, I - 1, [El | Els]);
+ {empty, _} -> buf_out(Buf, 0, Els)
+ end.
+
+buf_to_list(Buf) -> queue:to_list(Buf).
+
+cancel_timer(TRef) when is_reference(TRef) ->
+ (?GEN_FSM):cancel_timer(TRef);
+cancel_timer(_) -> false.
+
+restart_timer(TRef, Timeout, Msg) ->
+ cancel_timer(TRef),
+ erlang:start_timer(timer:seconds(Timeout), self(), Msg).
+
+restart_inactivity_timer(#state{inactivity_timeout =
+ Timeout} =
+ State) ->
+ restart_inactivity_timer(State, Timeout).
+
+restart_inactivity_timer(#state{inactivity_timer =
+ TRef} =
+ State,
+ Timeout) ->
+ NewTRef = restart_timer(TRef, Timeout, inactive),
+ State#state{inactivity_timer = NewTRef}.
+
+stop_inactivity_timer(#state{inactivity_timer = TRef} =
+ State) ->
+ cancel_timer(TRef),
+ State#state{inactivity_timer = undefined}.
+
+restart_wait_timer(#state{wait_timer = TRef,
+ wait_timeout = Timeout} =
+ State) ->
+ NewTRef = restart_timer(TRef, Timeout, wait_timeout),
+ State#state{wait_timer = NewTRef}.
+
+stop_wait_timer(#state{wait_timer = TRef} = State) ->
+ cancel_timer(TRef), State#state{wait_timer = undefined}.
+
+start_shaper_timer(Timeout) ->
+ erlang:start_timer(Timeout, self(), shaper_timeout).
+
+make_random_jid(Host) ->
+ User = randoms:get_string(),
+ jid:make(User, Host, randoms:get_string()).
+
+make_socket(Pid, IP) -> {http_bind, Pid, IP}.
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 6068c85ef..6d84d8d93 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -74,7 +74,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
+%%-include("legacy.hrl").
-include("mod_privacy.hrl").
@@ -127,6 +128,17 @@
ask_offline = true,
lang = <<"">>}).
+-type state_name() :: wait_for_stream | wait_for_auth |
+ wait_for_feature_request | wait_for_bind |
+ wait_for_sasl_response | wait_for_resume |
+ session_established.
+-type state() :: #state{}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()}.
+-type fsm_reply() :: {reply, any(), state_name(), state(), non_neg_integer()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
+-export_type([state/0]).
+
%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -153,55 +165,13 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
--define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
-
--define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED).
-
--define(HOST_UNKNOWN_ERR, ?SERR_HOST_UNKNOWN).
-
--define(POLICY_VIOLATION_ERR(Lang, Text),
- ?SERRT_POLICY_VIOLATION(Lang, Text)).
-
--define(INVALID_FROM, ?SERR_INVALID_FROM).
-
%% XEP-0198:
--define(IS_STREAM_MGMT_TAG(Name),
- (Name == <<"enable">>) or
- (Name == <<"resume">>) or
- (Name == <<"a">>) or
- (Name == <<"r">>)).
-
--define(IS_SUPPORTED_MGMT_XMLNS(Xmlns),
- (Xmlns == ?NS_STREAM_MGMT_2) or
- (Xmlns == ?NS_STREAM_MGMT_3)).
-
--define(MGMT_FAILED(Condition, Attrs),
- #xmlel{name = <<"failed">>,
- attrs = Attrs,
- children = [#xmlel{name = Condition,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}],
- children = []}]}).
-
--define(MGMT_BAD_REQUEST(Xmlns),
- ?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])).
-
--define(MGMT_SERVICE_UNAVAILABLE(Xmlns),
- ?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])).
-
--define(MGMT_UNEXPECTED_REQUEST(Xmlns),
- ?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])).
-
--define(MGMT_UNSUPPORTED_VERSION(Xmlns),
- ?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])).
-
--define(MGMT_ITEM_NOT_FOUND(Xmlns),
- ?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])).
-
--define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn),
- ?MGMT_FAILED(<<"item-not-found">>,
- [{<<"xmlns">>, Xmlns},
- {<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])).
+-define(IS_STREAM_MGMT_PACKET(Pkt),
+ is_record(Pkt, sm_enable) or
+ is_record(Pkt, sm_resume) or
+ is_record(Pkt, sm_a) or
+ is_record(Pkt, sm_r)).
%%%----------------------------------------------------------------------
%%% API
@@ -226,21 +196,25 @@ get_last_presence(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{get_last_presence}, 1000).
+-spec get_aux_field(any(), state()) -> {ok, any()} | error.
get_aux_field(Key, #state{aux_fields = Opts}) ->
- case lists:keysearch(Key, 1, Opts) of
- {value, {_, Val}} -> {ok, Val};
- _ -> error
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Val} -> {ok, Val};
+ false -> error
end.
+-spec set_aux_field(any(), any(), state()) -> state().
set_aux_field(Key, Val,
#state{aux_fields = Opts} = State) ->
Opts1 = lists:keydelete(Key, 1, Opts),
State#state{aux_fields = [{Key, Val} | Opts1]}.
+-spec del_aux_field(any(), state()) -> state().
del_aux_field(Key, #state{aux_fields = Opts} = State) ->
Opts1 = lists:keydelete(Key, 1, Opts),
State#state{aux_fields = Opts1}.
+-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none.
get_subscription(From = #jid{}, StateData) ->
get_subscription(jid:tolower(From), StateData);
get_subscription(LFrom, StateData) ->
@@ -278,14 +252,19 @@ set_resume_timeout(#state{} = StateData, Timeout) ->
set_resume_timeout(FsmRef, Timeout) ->
FsmRef ! {set_resume_timeout, Timeout}.
+-spec send_filtered(pid(), binary(), jid(), jid(), stanza()) -> any().
send_filtered(FsmRef, Feature, From, To, Packet) ->
FsmRef ! {send_filtered, Feature, From, To, Packet}.
+-spec broadcast(pid(), any(), jid(), stanza()) -> any().
broadcast(FsmRef, Type, From, Packet) ->
FsmRef ! {broadcast, Type, From, Packet}.
+-spec stop(pid()) -> any().
stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, stop).
+-spec close(pid()) -> any().
+%% What is the difference between stop and close???
close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
%%%----------------------------------------------------------------------
@@ -376,47 +355,53 @@ init([{SockMod, Socket}, Opts]) ->
mgmt_resend = ResendOnTimeout},
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}.
+-spec get_subscribed(pid()) -> [ljid()].
%% Return list of all available resources of contacts,
get_subscribed(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
get_subscribed, 1000).
-wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
- DefaultLang = ?MYLANG,
- case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of
- ?NS_STREAM ->
- 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,
- StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of
- <<"1.0">> ->
- <<"1.0">>;
- _ ->
- <<"">>
- end,
+wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
+ try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
+ #stream_start{xmlns = NS_CLIENT, stream_xmlns = NS_STREAM,
+ version = Version, lang = Lang}
+ when NS_CLIENT /= ?NS_CLIENT; NS_STREAM /= ?NS_STREAM ->
+ send_header(StateData, ?MYNAME, Version, Lang),
+ send_element(StateData, xmpp:serr_invalid_namespace()),
+ {stop, normal, StateData};
+ #stream_start{lang = Lang, version = Version} when byte_size(Lang) > 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.
+ %% Do not store long language tag to avoid possible DoS/flood attacks
+ send_header(StateData, ?MYNAME, Version, ?MYLANG),
+ Txt = <<"Too long value of 'xml:lang' attribute">>,
+ send_element(StateData,
+ xmpp:serr_policy_violation(Txt, ?MYLANG)),
+ {stop, normal, StateData};
+ #stream_start{to = undefined, lang = Lang, version = Version} ->
+ Txt = <<"Missing 'to' attribute">>,
+ send_header(StateData, ?MYNAME, Version, Lang),
+ send_element(StateData,
+ xmpp:serr_improper_addressing(Txt, Lang)),
+ {stop, normal, StateData};
+ #stream_start{to = #jid{lserver = To}, lang = Lang,
+ version = Version} ->
+ Server = case StateData#state.server of
+ <<"">> -> To;
+ S -> S
+ end,
+ StreamVersion = case Version of
+ {1,0} -> {1,0};
+ _ -> undefined
+ end,
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
case lists:member(Server, ?MYHOSTS) of
true when IsBlacklistedIP == false ->
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
case StreamVersion of
- <<"1.0">> ->
- send_header(StateData, Server, <<"1.0">>, DefaultLang),
+ {1,0} ->
+ send_header(StateData, Server, {1,0}, ?MYLANG),
case StateData#state.authenticated of
false ->
TLS = StateData#state.tls,
@@ -439,15 +424,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
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}];
+ [#sasl_mechanisms{list = cyrsasl:listmech(Server)}];
false ->
[]
end,
@@ -457,11 +434,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
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">>}]}]}];
+ [#compression{methods = [<<"zlib">>]}];
_ ->
[]
end,
@@ -470,18 +443,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
(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;
+ [#starttls{required = TLSRequired}];
false ->
[]
end,
@@ -489,9 +451,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
Server, StreamFeatures1, [Server]),
send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = StreamFeatures}),
+ #stream_features{sub_els = StreamFeatures}),
fsm_next_state(wait_for_feature_request,
StateData#state{server = Server,
sasl_state = SASLState,
@@ -506,12 +466,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
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 = []}];
+ [#feature_sm{xmlns = ?NS_STREAM_MGMT_2},
+ #feature_sm{xmlns = ?NS_STREAM_MGMT_3}];
false ->
[]
end,
@@ -523,21 +479,12 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
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">>}]}]}];
+ [#compression{methods = [<<"zlib">>]}];
_ ->
[]
end,
- StreamFeatures1 = [#xmlel{name = <<"bind">>,
- attrs = [{<<"xmlns">>, ?NS_BIND}],
- children = []},
- #xmlel{name = <<"session">>,
- attrs = [{<<"xmlns">>, ?NS_SESSION}],
- children =
- [#xmlel{name = <<"optional">>}]}]
+ StreamFeatures1 =
+ [#bind{}, #xmpp_session{optional = true}]
++
RosterVersioningFeature ++
StreamManagementFeature ++
@@ -547,27 +494,23 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
Server, StreamFeatures1, [Server]),
send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = StreamFeatures}),
+ #stream_features{sub_els = StreamFeatures}),
fsm_next_state(wait_for_bind,
StateData#state{server = Server, lang = Lang});
_ ->
- send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = []}),
+ send_element(StateData, #stream_features{}),
fsm_next_state(session_established,
StateData#state{server = Server, lang = Lang})
end
end;
_ ->
- send_header(StateData, Server, <<"">>, DefaultLang),
+ send_header(StateData, Server, StreamVersion, ?MYLANG),
if not StateData#state.tls_enabled and
StateData#state.tls_required ->
- send_element(StateData,
- ?POLICY_VIOLATION_ERR(Lang,
- <<"Use of STARTTLS required">>)),
+ send_element(
+ StateData,
+ xmpp:serr_policy_violation(
+ <<"Use of STARTTLS required">>, Lang)),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
@@ -580,184 +523,152 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
{true, LogReason, ReasonT} = IsBlacklistedIP,
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
[jlib:ip_to_list(IP), LogReason]),
- send_header(StateData, Server, StreamVersion, DefaultLang),
- send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
+ send_header(StateData, Server, StreamVersion, ?MYLANG),
+ send_element(StateData, xmpp:serr_policy_violation(ReasonT, Lang)),
{stop, normal, StateData};
_ ->
- send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
- send_element(StateData, ?HOST_UNKNOWN_ERR),
+ send_header(StateData, ?MYNAME, StreamVersion, ?MYLANG),
+ send_element(StateData, xmpp:serr_host_unknown()),
{stop, normal, StateData}
end;
_ ->
- send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
- send_element(StateData, ?INVALID_NS_ERR),
+ send_header(StateData, ?MYNAME, {1,0}, ?MYLANG),
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {stop, normal, StateData}
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ send_header(StateData, ?MYNAME, {1,0}, ?MYLANG),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
- send_element(StateData, ?INVALID_XML_ERR),
+ send_header(StateData, ?MYNAME, {1,0}, <<"">>),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{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) ->
- 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),
+wait_for_auth({xmlstreamelement, #xmlel{} = El}, StateData) ->
+ decode_element(El, wait_for_auth, StateData);
+wait_for_auth(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) ->
+ fsm_next_state(wait_for_auth, dispatch_stream_mgmt(Pkt, StateData));
+wait_for_auth(#iq{type = get, sub_els = [#legacy_auth{}]} = IQ, StateData) ->
+ Auth = #legacy_auth{username = <<>>, password = <<>>, resource = <<>>},
+ Res = case ejabberd_auth:plain_password_required(StateData#state.server) of
+ false ->
+ xmpp:make_iq_result(IQ, Auth#legacy_auth{digest = <<>>});
+ true ->
+ xmpp:make_iq_result(IQ, Auth)
+ end,
+ send_element(StateData, Res),
+ fsm_next_state(wait_for_auth, StateData);
+wait_for_auth(#iq{type = set, sub_els = [#legacy_auth{resource = <<"">>}]} = IQ,
+ StateData) ->
+ Lang = StateData#state.lang,
+ Txt = <<"No resource provided">>,
+ Err = xmpp:make_error(IQ, xmpp:err_not_acceptable(Txt, Lang)),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+wait_for_auth(#iq{type = set, sub_els = [#legacy_auth{username = U,
+ password = P0,
+ digest = D0,
+ resource = R}]} = IQ,
+ StateData) when is_binary(U), is_binary(R) ->
+ JID = jid:make(U, StateData#state.server, R),
+ case (JID /= error) andalso
+ acl:access_matches(StateData#state.access,
+ #{usr => jid:split(JID), ip => StateData#state.ip},
+ StateData#state.server) == allow of
+ true ->
+ DGen = fun (PW) ->
+ p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
+ end,
+ P = if is_binary(P0) -> P0; true -> <<>> end,
+ D = if is_binary(D0) -> D0; true -> <<>> end,
+ case ejabberd_auth:check_password_with_authmodule(
+ U, U, StateData#state.server, P, D, DGen) of
+ {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 = xmpp:make_iq_result(IQ),
+ 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]),
+ Lang = StateData#state.lang,
+ Txt = <<"Legacy authentication failed">>,
+ Err = xmpp:make_error(IQ, xmpp:err_not_authorized(Txt, Lang)),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end;
+ false when JID == error ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for username '~s' with resource '~s'",
+ [StateData#state.socket, U, R]),
+ Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()),
+ send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
+ false ->
+ ?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]),
Lang = StateData#state.lang,
- Txt = <<"No resource provided">>,
- Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
+ Txt = <<"Legacy authentication forbidden">>,
+ Err = xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, 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:access_matches(StateData#state.access,
- #{usr => jid:split(JID), ip => StateData#state.ip},
- StateData#state.server) == 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)
- of
- {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 = 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]),
- Lang = StateData#state.lang,
- Txt = <<"Legacy authentication failed">>,
- Err = jlib:make_error_reply(
- El, ?ERRT_NOT_AUTHORIZED(Lang, Txt)),
- 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]),
- Lang = StateData#state.lang,
- Txt = <<"Legacy authentication forbidden">>,
- Err = jlib:make_error_reply(El, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- 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) ->
@@ -765,127 +676,97 @@ wait_for_auth(timeout, StateData) ->
wait_for_auth({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData};
wait_for_auth(stop, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+wait_for_auth(Pkt, StateData) ->
+ process_unauthenticated_stanza(StateData, Pkt),
+ fsm_next_state(wait_for_auth, StateData).
-wait_for_feature_request({xmlstreamelement, #xmlel{name = Name} = El},
- StateData)
- when ?IS_STREAM_MGMT_TAG(Name) ->
+wait_for_feature_request({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_feature_request, StateData);
+wait_for_feature_request(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) ->
fsm_next_state(wait_for_feature_request,
- dispatch_stream_mgmt(El, StateData));
-wait_for_feature_request({xmlstreamelement, El},
- StateData) ->
- #xmlel{name = Name, attrs = Attrs, children = Els} = El,
+ dispatch_stream_mgmt(Pkt, StateData));
+wait_for_feature_request(#sasl_auth{mechanism = Mech,
+ text = ClientIn},
+ #state{tls_enabled = TLSEnabled,
+ tls_required = TLSRequired} = StateData)
+ when TLSEnabled or not TLSRequired ->
+ case cyrsasl:server_start(StateData#state.sasl_state, Mech, ClientIn) of
+ {ok, Props} ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ U = identity(Props),
+ AuthModule = proplists:get_value(auth_module, Props, undefined),
+ ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s",
+ [StateData#state.socket, U, 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]),
+ send_element(StateData, #sasl_success{}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ sasl_state = undefined,
+ user = U});
+ {continue, ServerOut, NewSASLState} ->
+ send_element(StateData, #sasl_challenge{text = ServerOut}),
+ 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,
+ 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]),
+ send_element(StateData, #sasl_failure{reason = Error}),
+ fsm_next_state(wait_for_feature_request, StateData);
+ {error, Error} ->
+ send_element(StateData, #sasl_failure{reason = Error}),
+ fsm_next_state(wait_for_feature_request, StateData)
+ end;
+wait_for_feature_request(#starttls{},
+ #state{tls = true, tls_enabled = false} = StateData) ->
+ case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
+ gen_tcp ->
+ TLSOpts = case ejabberd_config:get_option(
+ {domain_certfile, StateData#state.server},
+ fun iolist_to_binary/1) of
+ undefined ->
+ StateData#state.tls_options;
+ CertFile ->
+ lists:keystore(certfile, 1,
+ StateData#state.tls_options,
+ {certfile, CertFile})
+ end,
+ Socket = StateData#state.socket,
+ BProceed = fxml:element_to_binary(xmpp:encode(#starttls_proceed{})),
+ TLSSocket = (StateData#state.sockmod):starttls(Socket, TLSOpts, BProceed),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = TLSSocket,
+ streamid = new_id(),
+ tls_enabled = true});
+ _ ->
+ Lang = StateData#state.lang,
+ Txt = <<"Unsupported TLS transport">>,
+ send_element(StateData, xmpp:serr_policy_violation(Txt, Lang)),
+ {stop, normal, StateData}
+ end;
+wait_for_feature_request(#compress{} = Comp, StateData) ->
Zlib = StateData#state.zlib,
- TLS = StateData#state.tls,
- TLSEnabled = StateData#state.tls_enabled,
- TLSRequired = StateData#state.tls_required,
- SockMod =
- (StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
- {?NS_SASL, <<"auth">>}
- when TLSEnabled or not TLSRequired ->
- 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
- {ok, Props} ->
- (StateData#state.sockmod):reset_stream(StateData#state.socket),
- U = identity(Props),
- AuthModule = proplists:get_value(auth_module, Props, undefined),
- ?INFO_MSG("(~w) Accepted authentication for ~s "
- "by ~p from ~s",
- [StateData#state.socket, U, 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]),
- send_element(StateData,
- #xmlel{name = <<"success">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = []}),
- fsm_next_state(wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- sasl_state = undefined,
- user = U});
- {continue, ServerOut, NewSASLState} ->
- send_element(StateData,
- #xmlel{name = <<"challenge">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- 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,
- 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]),
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = Error, attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- {error, Error} ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = Error, attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end;
- {?NS_TLS, <<"starttls">>}
- when TLS == true, TLSEnabled == false,
- SockMod == gen_tcp ->
- TLSOpts = case
- ejabberd_config:get_option(
- {domain_certfile, StateData#state.server},
- fun iolist_to_binary/1)
- of
- undefined -> StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile} | lists:keydelete(certfile, 1,
- StateData#state.tls_options)]
- end,
- Socket = StateData#state.socket,
- BProceed = fxml:element_to_binary(#xmlel{name = <<"proceed">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}]}),
- TLSSocket = (StateData#state.sockmod):starttls(Socket,
- TLSOpts,
- BProceed),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true});
- {?NS_COMPRESS, <<"compress">>}
- when Zlib == true,
- (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,
- send_element(StateData,
- ?POLICY_VIOLATION_ERR(Lang,
- <<"Use of STARTTLS required">>)),
- {stop, normal, StateData};
- true ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_feature_request, StateData)
- end
+ SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ if Zlib == true, (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(Comp, wait_for_feature_request, StateData);
+ true ->
+ send_element(StateData, #compress_failure{reason = 'setup-failed'}),
+ fsm_next_state(wait_for_feature_request, StateData)
end;
wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
@@ -894,106 +775,82 @@ wait_for_feature_request({xmlstreamend, _Name},
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{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)
- when ?IS_STREAM_MGMT_TAG(Name) ->
- fsm_next_state(wait_for_sasl_response, dispatch_stream_mgmt(El, StateData));
-wait_for_sasl_response({xmlstreamelement, El},
- StateData) ->
- #xmlel{name = Name, attrs = Attrs, children = Els} = El,
- case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
- {?NS_SASL, <<"response">>} ->
- ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
- case cyrsasl:server_step(StateData#state.sasl_state,
- ClientIn)
- of
- {ok, Props} ->
- catch
- (StateData#state.sockmod):reset_stream(StateData#state.socket),
- U = identity(Props),
- AuthModule = proplists:get_value(auth_module, Props, <<>>),
- ?INFO_MSG("(~w) Accepted authentication for ~s "
- "by ~p from ~s",
- [StateData#state.socket, U, 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]),
- send_element(StateData,
- #xmlel{name = <<"success">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = []}),
- fsm_next_state(wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- sasl_state = undefined,
- user = U});
- {ok, Props, ServerOut} ->
- (StateData#state.sockmod):reset_stream(StateData#state.socket),
- U = identity(Props),
- AuthModule = proplists:get_value(auth_module, Props, undefined),
- ?INFO_MSG("(~w) Accepted authentication for ~s "
- "by ~p from ~s",
- [StateData#state.socket, U, 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]),
- send_element(StateData,
- #xmlel{name = <<"success">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- fsm_next_state(wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true,
- auth_module = AuthModule,
- sasl_state = undefined,
- user = U});
- {continue, ServerOut, NewSASLState} ->
- send_element(StateData,
- #xmlel{name = <<"challenge">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [{xmlcdata,
- jlib:encode_base64(ServerOut)}]}),
- 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,
- 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]),
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = Error, attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- {error, Error} ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = Error, attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end;
- _ ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_feature_request, StateData)
+ {stop, normal, StateData};
+wait_for_feature_request(_Pkt,
+ #state{tls_required = TLSRequired,
+ tls_enabled = TLSEnabled} = StateData)
+ when TLSRequired and not TLSEnabled ->
+ Lang = StateData#state.lang,
+ Txt = <<"Use of STARTTLS required">>,
+ send_element(StateData, xmpp:serr_policy_violation(Txt, Lang)),
+ {stop, normal, StateData};
+wait_for_feature_request(Pkt, StateData) ->
+ process_unauthenticated_stanza(StateData, Pkt),
+ fsm_next_state(wait_for_feature_request, StateData).
+
+wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_sasl_response, StateData);
+wait_for_sasl_response(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) ->
+ fsm_next_state(wait_for_sasl_response,
+ dispatch_stream_mgmt(Pkt, StateData));
+wait_for_sasl_response(#sasl_response{text = ClientIn}, StateData) ->
+ case cyrsasl:server_step(StateData#state.sasl_state, ClientIn) of
+ {ok, Props} ->
+ catch (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ U = identity(Props),
+ AuthModule = proplists:get_value(auth_module, Props, <<>>),
+ ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s",
+ [StateData#state.socket, U, 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]),
+ send_element(StateData, #sasl_success{}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ sasl_state = undefined,
+ user = U});
+ {ok, Props, ServerOut} ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ U = identity(Props),
+ AuthModule = proplists:get_value(auth_module, Props, undefined),
+ ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s",
+ [StateData#state.socket, U, 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]),
+ send_element(StateData, #sasl_success{text = ServerOut}),
+ fsm_next_state(wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true,
+ auth_module = AuthModule,
+ sasl_state = undefined,
+ user = U});
+ {continue, ServerOut, NewSASLState} ->
+ send_element(StateData, #sasl_challenge{text = ServerOut}),
+ 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,
+ 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]),
+ send_element(StateData, #sasl_failure{reason = Error}),
+ fsm_next_state(wait_for_feature_request, StateData);
+ {error, Error} ->
+ send_element(StateData, #sasl_failure{reason = Error}),
+ fsm_next_state(wait_for_feature_request, StateData)
end;
wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
@@ -1002,13 +859,18 @@ wait_for_sasl_response({xmlstreamend, _Name},
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _},
StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response(stop, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+wait_for_sasl_response(Pkt, StateData) ->
+ process_unauthenticated_stanza(StateData, Pkt),
+ fsm_next_state(wait_for_feature_request, StateData).
+-spec resource_conflict_action(binary(), binary(), binary()) ->
+ {accept_resource, binary()} | closenew.
resource_conflict_action(U, S, R) ->
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
true ->
@@ -1038,108 +900,115 @@ resource_conflict_action(U, S, R) ->
{accept_resource, Rnew}
end.
-wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El},
- StateData)
- when ?IS_STREAM_MGMT_TAG(Name) ->
- case Name of
- <<"resume">> ->
- case handle_resume(StateData, Attrs) of
- {ok, ResumedState} ->
- fsm_next_state(session_established, ResumedState);
- error ->
- fsm_next_state(wait_for_bind, StateData)
- end;
- _ ->
- fsm_next_state(wait_for_bind, dispatch_stream_mgmt(El, StateData))
- end;
-wait_for_bind({xmlstreamelement, El}, StateData) ->
- case jlib:iq_query_info(El) of
- #iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} =
- IQ ->
- U = StateData#state.user,
- R1 = fxml:get_path_s(SubEl,
- [{elem, <<"resource">>}, cdata]),
- R = case jid:resourceprep(R1) of
- error -> error;
- <<"">> -> new_uniq_id();
- Resource -> Resource
- end,
- case R of
- error ->
- Txt = <<"Malformed resource">>,
- Err = jlib:make_error_reply(El, ?ERRT_BAD_REQUEST(Lang, Txt)),
- send_element(StateData, Err),
- fsm_next_state(wait_for_bind, StateData);
- _ ->
- case resource_conflict_action(U, StateData#state.server,
- R)
- of
- closenew ->
- Err = jlib:make_error_reply(El,
- ?STANZA_ERROR(<<"409">>,
- <<"modify">>,
- <<"conflict">>)),
+-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
+decode_element(#xmlel{} = El, StateName, StateData) ->
+ try case xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
+ #iq{sub_els = [_], type = T} = Pkt when T == set; T == get ->
+ NewPkt = xmpp:decode_els(
+ Pkt, ?NS_CLIENT,
+ fun(SubEl) when StateName == session_established ->
+ case xmpp:get_ns(SubEl) of
+ ?NS_PRIVACY -> true;
+ ?NS_BLOCKING -> true;
+ _ -> false
+ end;
+ (SubEl) ->
+ xmpp:is_known_tag(SubEl)
+ end),
+ ?MODULE:StateName(NewPkt, StateData);
+ Pkt ->
+ ?MODULE:StateName(Pkt, StateData)
+ end
+ catch error:{xmpp_codec, Why} ->
+ NS = xmpp:get_ns(El),
+ fsm_next_state(
+ StateName,
+ case xmpp:is_stanza(El) of
+ true ->
+ Lang = xmpp:get_lang(El),
+ Txt = xmpp:format_error(Why),
+ send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+ false when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 ->
+ Err = #sm_failed{reason = 'bad-request', xmlns = NS},
send_element(StateData, Err),
- fsm_next_state(wait_for_bind, StateData);
- {accept_resource, R2} ->
- 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)}]}]}]},
- try
- send_element(StateData3, jlib:iq_to_xml(Res))
- catch exit:normal ->
- close(self())
- end,
- 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;
- _ ->
- #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);
- _ ->
+ StateData;
+ false ->
+ StateData
+ end)
+ end.
+
+wait_for_bind({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_bind, StateData);
+wait_for_bind(#sm_resume{} = Pkt, StateData) ->
+ case handle_resume(StateData, Pkt) of
+ {ok, ResumedState} ->
+ fsm_next_state(session_established, ResumedState);
+ error ->
+ fsm_next_state(wait_for_bind, StateData)
+ end;
+wait_for_bind(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) ->
+ fsm_next_state(wait_for_bind, dispatch_stream_mgmt(Pkt, StateData));
+wait_for_bind(#iq{type = set,
+ sub_els = [#bind{resource = R0}]} = IQ, StateData) ->
+ U = StateData#state.user,
+ R = case R0 of
+ <<>> -> new_uniq_id();
+ _ -> R0
+ end,
+ case resource_conflict_action(U, StateData#state.server, R) of
+ closenew ->
+ Err = xmpp:make_error(IQ, xmpp:err_conflict()),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_bind, StateData);
+ {accept_resource, R2} ->
+ JID = jid:make(U, StateData#state.server, R2),
+ StateData2 = StateData#state{resource = R2, jid = JID},
+ case open_session(StateData2) of
+ {ok, StateData3} ->
+ Res = xmpp:make_iq_result(IQ, #bind{jid = JID}),
+ try
+ send_element(StateData3, Res)
+ catch
+ exit:normal -> close(self())
+ end,
+ fsm_next_state_pack(session_established,StateData3);
+ {error, Error} ->
+ Err = xmpp:make_error(IQ, Error),
+ send_element(StateData, Err),
fsm_next_state(wait_for_bind, StateData)
end
end;
+wait_for_bind(#compress{} = Comp, StateData) ->
+ Zlib = StateData#state.zlib,
+ SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ if Zlib == true, (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(Comp, wait_for_bind, StateData);
+ true ->
+ send_element(StateData, #compress_failure{reason = 'setup-failed'}),
+ fsm_next_state(wait_for_bind, StateData)
+ end;
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData};
wait_for_bind(stop, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+wait_for_bind(Pkt, StateData) ->
+ fsm_next_state(
+ wait_for_bind,
+ case xmpp:is_stanza(Pkt) of
+ true ->
+ send_error(StateData, Pkt, xmpp:err_not_acceptable());
+ false ->
+ StateData
+ end).
+-spec open_session(state()) -> {ok, state()} | {error, stanza_error()}.
open_session(StateData) ->
U = StateData#state.user,
R = StateData#state.resource,
@@ -1185,33 +1054,18 @@ open_session(StateData) ->
?INFO_MSG("(~w) Forbidden session for ~s",
[StateData#state.socket, jid:to_string(JID)]),
Txt = <<"Denied by ACL">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+ {error, xmpp:err_not_allowed(Txt, Lang)}
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));
-session_established({xmlstreamelement,
- #xmlel{name = <<"active">>,
- attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
- StateData) ->
+session_established({xmlstreamelement, El}, StateData) ->
+ decode_element(El, session_established, StateData);
+session_established(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) ->
+ fsm_next_state(session_established, dispatch_stream_mgmt(Pkt, StateData));
+session_established(#csi{type = active}, StateData) ->
NewStateData = csi_flush_queue(StateData),
fsm_next_state(session_established, NewStateData#state{csi_state = active});
-session_established({xmlstreamelement,
- #xmlel{name = <<"inactive">>,
- attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
- StateData) ->
+session_established(#csi{type = inactive}, StateData) ->
fsm_next_state(session_established, StateData#state{csi_state = inactive});
-session_established({xmlstreamelement, El},
- StateData) ->
- FromJID = StateData#state.jid,
- case check_from(El, FromJID) of
- 'invalid-from' ->
- send_element(StateData, ?INVALID_FROM),
- {stop, normal, StateData};
- _NewEl ->
- session_established2(El, StateData)
- end;
%% We hibernate the process to reduce memory consumption after a
%% configurable activity timeout
session_established(timeout, StateData) ->
@@ -1225,10 +1079,10 @@ session_established({xmlstreamerror,
<<"XML stanza is too big">> = E},
StateData) ->
send_element(StateData,
- ?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
+ xmpp:serr_policy_violation(E, StateData#state.lang)),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
- send_element(StateData, ?INVALID_XML_ERR),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
catch (StateData#state.sockmod):close(StateData#state.socket),
@@ -1236,91 +1090,78 @@ session_established(closed, #state{mgmt_state = active} = StateData) ->
session_established(closed, StateData) ->
{stop, normal, StateData};
session_established(stop, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+session_established(Pkt, StateData) when ?is_stanza(Pkt) ->
+ FromJID = StateData#state.jid,
+ case check_from(Pkt, FromJID) of
+ 'invalid-from' ->
+ send_element(StateData, xmpp:serr_invalid_from()),
+ {stop, normal, StateData};
+ _ ->
+ NewStateData = update_num_stanzas_in(StateData, Pkt),
+ session_established2(Pkt, NewStateData)
+ end;
+session_established(_Pkt, StateData) ->
+ fsm_next_state(session_established, StateData).
+-spec session_established2(xmpp_element(), state()) -> fsm_next().
%% 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 = fxml:get_attr_s(<<"to">>, Attrs),
- ToJID = case To of
- <<"">> -> jid:make(User, Server, <<"">>);
- _ -> jid:from_string(To)
- end,
- NewEl1 = jlib:remove_attr(<<"xmlns">>, El),
- NewEl = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
- <<"">> ->
- case NewStateData#state.lang of
- <<"">> -> NewEl1;
- Lang ->
- fxml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
- end;
- _ -> NewEl1
+session_established2(Pkt, StateData) ->
+ User = StateData#state.user,
+ Server = StateData#state.server,
+ FromJID = StateData#state.jid,
+ ToJID = case xmpp:get_to(Pkt) of
+ undefined -> jid:make(User, Server, <<"">>);
+ J -> J
end,
- NewState = case ToJID of
- error ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> NewStateData;
- <<"result">> -> NewStateData;
- _ ->
- Err = jlib:make_error_reply(NewEl,
- ?ERR_JID_MALFORMED),
- send_packet(NewStateData, Err)
- end;
- _ ->
- case Name of
- <<"presence">> ->
- PresenceEl0 =
- ejabberd_hooks:run_fold(c2s_update_presence,
- Server, NewEl,
- [User, Server]),
- PresenceEl =
- ejabberd_hooks:run_fold(
- user_send_packet, Server, PresenceEl0,
- [NewStateData, FromJID, ToJID]),
- case ToJID of
- #jid{user = User, server = Server,
- resource = <<"">>} ->
- ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
- [FromJID, PresenceEl, NewStateData]),
- presence_update(FromJID, PresenceEl,
- NewStateData);
- _ ->
- presence_track(FromJID, ToJID, PresenceEl,
- NewStateData)
- end;
- <<"iq">> ->
- case jlib:iq_query_info(NewEl) of
- #iq{xmlns = Xmlns} = IQ
- when Xmlns == (?NS_PRIVACY);
- 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);
- _ ->
- NewEl0 = ejabberd_hooks:run_fold(
- user_send_packet, Server, NewEl,
- [NewStateData, FromJID, ToJID]),
- check_privacy_route(FromJID, NewStateData,
- FromJID, ToJID, NewEl0)
- end;
- <<"message">> ->
- NewEl0 = ejabberd_hooks:run_fold(
- user_send_packet, Server, NewEl,
- [NewStateData, FromJID, ToJID]),
- check_privacy_route(FromJID, NewStateData, FromJID,
- ToJID, NewEl0);
- _ -> NewStateData
- end
- end,
+ Lang = case xmpp:get_lang(Pkt) of
+ <<"">> -> StateData#state.lang;
+ L -> L
+ end,
+ NewPkt = xmpp:set_lang(Pkt, Lang),
+ NewState =
+ case NewPkt of
+ #presence{} ->
+ Presence0 = ejabberd_hooks:run_fold(
+ c2s_update_presence, Server, NewPkt,
+ [User, Server]),
+ Presence = ejabberd_hooks:run_fold(
+ user_send_packet, Server, Presence0,
+ [StateData, FromJID, ToJID]),
+ case ToJID of
+ #jid{user = User, server = Server, resource = <<"">>} ->
+ ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
+ [FromJID, Presence, StateData]),
+ presence_update(FromJID, Presence,
+ StateData);
+ _ ->
+ presence_track(FromJID, ToJID, Presence,
+ StateData)
+ end;
+ #iq{type = T, sub_els = [El]} when T == set; T == get ->
+ NS = xmpp:get_ns(El),
+ if NS == ?NS_BLOCKING; NS == ?NS_PRIVACY ->
+ IQ = xmpp:set_from_to(Pkt, FromJID, ToJID),
+ process_privacy_iq(IQ, StateData);
+ NS == ?NS_SESSION ->
+ Res = xmpp:make_iq_result(Pkt),
+ send_stanza(StateData, Res);
+ true ->
+ NewPkt0 = ejabberd_hooks:run_fold(
+ user_send_packet, Server, NewPkt,
+ [StateData, FromJID, ToJID]),
+ check_privacy_route(FromJID, StateData, FromJID,
+ ToJID, NewPkt0)
+ end;
+ _ ->
+ NewPkt0 = ejabberd_hooks:run_fold(
+ user_send_packet, Server, NewPkt,
+ [StateData, FromJID, ToJID]),
+ check_privacy_route(FromJID, StateData, FromJID,
+ ToJID, NewPkt0)
+ end,
ejabberd_hooks:run(c2s_loop_debug,
- [{xmlstreamelement, El}]),
+ [{xmlstreamelement, Pkt}]),
fsm_next_state(session_established, NewState).
wait_for_resume({xmlstreamelement, _El} = Event, StateData) ->
@@ -1384,14 +1225,14 @@ handle_info({send_text, Text}, StateName, StateData) ->
fsm_next_state(StateName, StateData);
handle_info(replaced, StateName, StateData) ->
Lang = StateData#state.lang,
- Xmlelement = ?SERRT_CONFLICT(Lang, <<"Replaced by new connection">>),
- handle_info({kick, replaced, Xmlelement}, StateName, StateData);
+ Pkt = xmpp:serr_conflict(<<"Replaced by new connection">>, Lang),
+ handle_info({kick, replaced, Pkt}, StateName, StateData);
handle_info(kick, StateName, StateData) ->
Lang = StateData#state.lang,
- Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
- handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
-handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
- send_element(StateData, Xmlelement),
+ Pkt = xmpp:serr_policy_violation(<<"has been kicked">>, Lang),
+ handle_info({kick, kicked_by_admin, Pkt}, StateName, StateData);
+handle_info({kick, Reason, Pkt}, _StateName, StateData) ->
+ send_element(StateData, Pkt),
{stop, normal,
StateData#state{authenticated = Reason}};
handle_info({route, _From, _To, {broadcast, Data}},
@@ -1403,7 +1244,7 @@ handle_info({route, _From, _To, {broadcast, Data}},
roster_change(IJID, ISubscription, StateData));
{exit, Reason} ->
Lang = StateData#state.lang,
- send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
+ send_element(StateData, xmpp:serr_conflict(Reason, Lang)),
{stop, normal, StateData};
{privacy_list, PrivList, PrivListName} ->
case ejabberd_hooks:run_fold(privacy_updated_list,
@@ -1414,24 +1255,15 @@ handle_info({route, _From, _To, {broadcast, Data}},
false ->
fsm_next_state(StateName, StateData);
NewPL ->
- PrivPushIQ = #iq{type = set,
- id = <<"push",
- (randoms:get_string())/binary>>,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>,
- ?NS_PRIVACY}],
- children =
- [#xmlel{name = <<"list">>,
- attrs = [{<<"name">>,
- PrivListName}],
- children = []}]}]},
- PrivPushEl = jlib:replace_from_to(
- jid:remove_resource(StateData#state.jid),
- StateData#state.jid,
- jlib:iq_to_xml(PrivPushIQ)),
- NewState = send_stanza(
- StateData, PrivPushEl),
+ PrivPushIQ =
+ #iq{type = set,
+ from = jid:remove_resource(StateData#state.jid),
+ to = StateData#state.jid,
+ id = <<"push", (randoms:get_string())/binary>>,
+ sub_els = [#privacy_query{
+ lists = [#privacy_list{
+ name = PrivListName}]}]},
+ NewState = send_stanza(StateData, PrivPushIQ),
fsm_next_state(StateName,
NewState#state{privacy_list = NewPL})
end;
@@ -1442,272 +1274,146 @@ handle_info({route, _From, _To, {broadcast, Data}},
fsm_next_state(StateName, StateData)
end;
%% Process Packets that are to be send to the user
-handle_info({route, From, To,
- #xmlel{name = Name, attrs = Attrs, children = Els} = Packet},
- StateName, StateData) ->
- {Pass, NewAttrs, NewState} = case Name of
- <<"presence">> ->
- State =
- ejabberd_hooks:run_fold(c2s_presence_in,
- StateData#state.server,
- StateData,
- [{From, To,
- Packet}]),
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"probe">> ->
- LFrom = jid:tolower(From),
- LBFrom =
- jid:remove_resource(LFrom),
- NewStateData = case
- (?SETS):is_element(LFrom,
- State#state.pres_a)
- orelse
- (?SETS):is_element(LBFrom,
- State#state.pres_a)
- of
- true -> State;
- false ->
- case
- (?SETS):is_element(LFrom,
- State#state.pres_f)
- of
- true ->
- A =
- (?SETS):add_element(LFrom,
- State#state.pres_a),
- State#state{pres_a
- =
- A};
- false ->
- case
- (?SETS):is_element(LBFrom,
- State#state.pres_f)
- of
- true ->
- A =
- (?SETS):add_element(LBFrom,
- State#state.pres_a),
- State#state{pres_a
- =
- A};
- false ->
- State
- end
- end
- end,
- process_presence_probe(From, To,
- NewStateData),
- {false, Attrs, NewStateData};
- <<"error">> ->
- NewA =
- remove_element(jid:tolower(From),
- State#state.pres_a),
- {true, Attrs,
- State#state{pres_a = NewA}};
- <<"subscribe">> ->
- SRes = is_privacy_allow(State,
- From, To,
- Packet,
- in),
- {SRes, Attrs, State};
- <<"subscribed">> ->
- SRes = is_privacy_allow(State,
- From, To,
- Packet,
- in),
- {SRes, Attrs, State};
- <<"unsubscribe">> ->
- SRes = is_privacy_allow(State,
- From, To,
- Packet,
- in),
- {SRes, Attrs, State};
- <<"unsubscribed">> ->
- SRes = is_privacy_allow(State,
- From, To,
- Packet,
- in),
- {SRes, Attrs, State};
- _ ->
- case privacy_check_packet(State,
- From, To,
- Packet,
- in)
- of
- allow ->
- LFrom =
- jid:tolower(From),
- LBFrom =
- jid:remove_resource(LFrom),
- case
- (?SETS):is_element(LFrom,
- State#state.pres_a)
- orelse
- (?SETS):is_element(LBFrom,
- State#state.pres_a)
- of
- true ->
- {true, Attrs, State};
- false ->
- case
- (?SETS):is_element(LFrom,
- State#state.pres_f)
- of
- true ->
- A =
- (?SETS):add_element(LFrom,
- State#state.pres_a),
- {true, Attrs,
- State#state{pres_a
- =
- A}};
- false ->
- case
- (?SETS):is_element(LBFrom,
- State#state.pres_f)
- of
- true ->
- A =
- (?SETS):add_element(LBFrom,
- State#state.pres_a),
- {true,
- Attrs,
- State#state{pres_a
- =
- A}};
- false ->
- {true,
- Attrs,
- State}
- end
- end
- end;
- deny -> {false, Attrs, State}
- end
- end;
- <<"iq">> ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{xmlns = ?NS_LAST} ->
- LFrom = jid:tolower(From),
- LBFrom =
- jid:remove_resource(LFrom),
- HasFromSub =
- ((?SETS):is_element(LFrom,
- StateData#state.pres_f)
- orelse
- (?SETS):is_element(LBFrom,
- StateData#state.pres_f))
- andalso
- is_privacy_allow(StateData,
- To, From,
- #xmlel{name
- =
- <<"presence">>,
- attrs
- =
- [],
- children
- =
- []},
- out),
- case HasFromSub of
- true ->
- case
- privacy_check_packet(StateData,
- From,
- To,
- Packet,
- in)
- of
- allow ->
- {true, Attrs,
- StateData};
- deny ->
- Err =
- jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To,
- From,
- Err),
- {false, Attrs,
- StateData}
- end;
- _ ->
- Err =
- jlib:make_error_reply(Packet,
- ?ERR_FORBIDDEN),
- ejabberd_router:route(To,
- From,
- Err),
- {false, Attrs, StateData}
- end;
- IQ
- when is_record(IQ, iq) or
- (IQ == reply) ->
- case
- privacy_check_packet(StateData,
- From, To,
- Packet, in)
- of
- allow ->
- {true, Attrs, StateData};
- deny when is_record(IQ, iq) ->
- Err =
- jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To,
- From,
- Err),
- {false, Attrs, StateData};
- deny when IQ == reply ->
- {false, Attrs, StateData}
- end;
- IQ
- when (IQ == invalid) or
- (IQ == not_iq) ->
- {false, Attrs, StateData}
- end;
- <<"message">> ->
- case privacy_check_packet(StateData,
- From, To,
- Packet, in)
- of
- allow ->
- {true, Attrs, StateData};
- deny ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- <<"groupchat">> -> ok;
- <<"headline">> -> ok;
- _ ->
- case fxml:get_subtag_with_xmlns(Packet,
- <<"x">>,
- ?NS_MUC_USER)
- of
- false ->
- Err =
- jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From,
- Err);
- _ -> ok
- end
- end,
- {false, Attrs, StateData}
- end;
- _ -> {true, Attrs, StateData}
- end,
+handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Packet) ->
+ {Pass, NewState} =
+ case Packet of
+ #presence{type = T} ->
+ State = ejabberd_hooks:run_fold(c2s_presence_in,
+ StateData#state.server,
+ StateData,
+ [{From, To, Packet}]),
+ case T of
+ probe ->
+ LFrom = jid:tolower(From),
+ LBFrom = jid:remove_resource(LFrom),
+ NewStateData =
+ case (?SETS):is_element(LFrom, State#state.pres_a)
+ orelse (?SETS):is_element(LBFrom, State#state.pres_a) of
+ true -> State;
+ false ->
+ case (?SETS):is_element(LFrom, State#state.pres_f) of
+ true ->
+ A = (?SETS):add_element(LFrom, State#state.pres_a),
+ State#state{pres_a = A};
+ false ->
+ case (?SETS):is_element(LBFrom, State#state.pres_f) of
+ true ->
+ A = (?SETS):add_element(LBFrom, State#state.pres_a),
+ State#state{pres_a = A};
+ false ->
+ State
+ end
+ end
+ end,
+ process_presence_probe(From, To, NewStateData),
+ {false, NewStateData};
+ error ->
+ NewA = ?SETS:del_element(jid:tolower(From), State#state.pres_a),
+ {true, State#state{pres_a = NewA}};
+ subscribe ->
+ SRes = is_privacy_allow(State, From, To, Packet, in),
+ {SRes, State};
+ subscribed ->
+ SRes = is_privacy_allow(State, From, To, Packet, in),
+ {SRes, State};
+ unsubscribe ->
+ SRes = is_privacy_allow(State, From, To, Packet, in),
+ {SRes, State};
+ unsubscribed ->
+ SRes = is_privacy_allow(State, From, To, Packet, in),
+ {SRes, State};
+ _ ->
+ case privacy_check_packet(State, From, To, Packet, in) of
+ allow ->
+ LFrom = jid:tolower(From),
+ LBFrom = jid:remove_resource(LFrom),
+ case (?SETS):is_element(LFrom, State#state.pres_a)
+ orelse (?SETS):is_element(LBFrom, State#state.pres_a) of
+ true ->
+ {true, State};
+ false ->
+ case (?SETS):is_element(LFrom, State#state.pres_f) of
+ true ->
+ A = (?SETS):add_element(LFrom, State#state.pres_a),
+ {true, State#state{pres_a = A}};
+ false ->
+ case (?SETS):is_element(LBFrom,
+ State#state.pres_f) of
+ true ->
+ A = (?SETS):add_element(
+ LBFrom,
+ State#state.pres_a),
+ {true, State#state{pres_a = A}};
+ false ->
+ {true, State}
+ end
+ end
+ end;
+ deny -> {false, State}
+ end
+ end;
+ #iq{type = T} ->
+ case xmpp:has_subtag(Packet, #last{}) of
+ true when T == get; T == set ->
+ LFrom = jid:tolower(From),
+ LBFrom = jid:remove_resource(LFrom),
+ HasFromSub = ((?SETS):is_element(LFrom, StateData#state.pres_f)
+ orelse (?SETS):is_element(LBFrom, StateData#state.pres_f))
+ andalso is_privacy_allow(StateData, To, From, #presence{}, out),
+ case HasFromSub of
+ true ->
+ case privacy_check_packet(
+ StateData, From, To, Packet, in) of
+ allow ->
+ {true, StateData};
+ deny ->
+ ejabberd_router:route_error(
+ To, From, Packet,
+ xmpp:err_service_unavailable()),
+ {false, StateData}
+ end;
+ _ ->
+ ejabberd_router:route_error(
+ To, From, Packet, xmpp:err_forbidden()),
+ {false, StateData}
+ end;
+ _ ->
+ case privacy_check_packet(StateData, From, To, Packet, in) of
+ allow ->
+ {true, StateData};
+ deny ->
+ ejabberd_router:route_error(
+ To, From, Packet, xmpp:err_service_unavailable()),
+ {false, StateData}
+ end
+ end;
+ #message{type = T} ->
+ case privacy_check_packet(StateData, From, To, Packet, in) of
+ allow ->
+ {true, StateData};
+ deny ->
+ case T of
+ groupchat -> ok;
+ headline -> ok;
+ _ ->
+ case xmpp:has_subtag(Packet, #muc_user{}) of
+ true ->
+ ok;
+ false ->
+ ejabberd_router:route_error(
+ To, From, Packet, xmpp:err_service_unavailable())
+ end
+ end,
+ {false, StateData}
+ end
+ end,
if Pass ->
- Attrs2 =
- jlib:replace_from_to_attrs(jid:to_string(From),
- jid:to_string(To), NewAttrs),
- FixedPacket0 = #xmlel{name = Name, attrs = Attrs2, children = Els},
+ FixedPacket0 = xmpp:set_from_to(Packet, From, To),
FixedPacket = ejabberd_hooks:run_fold(
- user_receive_packet,
- NewState#state.server,
- FixedPacket0,
- [NewState, NewState#state.jid, From, To]),
+ user_receive_packet,
+ NewState#state.server,
+ FixedPacket0,
+ [NewState, NewState#state.jid, From, To]),
SentStateData = send_packet(NewState, FixedPacket),
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, SentStateData);
@@ -1727,11 +1433,11 @@ handle_info({'DOWN', Monitor, _Type, _Object, _Info},
handle_info(system_shutdown, StateName, StateData) ->
case StateName of
wait_for_stream ->
- send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
- send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
+ send_header(StateData, ?MYNAME, {1,0}, <<"en">>),
+ send_element(StateData, xmpp:serr_system_shutdown()),
ok;
_ ->
- send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
+ send_element(StateData, xmpp:serr_system_shutdown()),
ok
end,
{stop, normal, StateData};
@@ -1742,17 +1448,17 @@ handle_info({route_xmlstreamelement, El}, _StateName, 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 =
- ejabberd_hooks:run_fold(c2s_update_presence,
- LServer,
- StateData#state.pres_last,
- [LUser, LServer]),
- StateData2 = StateData#state{pres_last = PresenceEl},
- presence_update(StateData2#state.jid, PresenceEl,
- StateData2),
- StateData2;
- _ -> StateData
+ #presence{} ->
+ Presence =
+ ejabberd_hooks:run_fold(c2s_update_presence,
+ LServer,
+ StateData#state.pres_last,
+ [LUser, LServer]),
+ StateData2 = StateData#state{pres_last = Presence},
+ presence_update(StateData2#state.jid, Presence,
+ StateData2),
+ StateData2;
+ undefined -> StateData
end,
fsm_next_state(StateName, NewStateData);
handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) ->
@@ -1765,7 +1471,7 @@ handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) ->
jid:to_string(To)]),
StateData;
true ->
- FinalPacket = jlib:replace_from_to(From, To, Packet),
+ FinalPacket = xmpp:set_from_to(Packet, From, To),
case StateData#state.jid of
To ->
case privacy_check_packet(StateData, From, To,
@@ -1814,6 +1520,7 @@ handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
+-spec print_state(state()) -> state().
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)},
@@ -1833,18 +1540,16 @@ terminate(_Reason, StateName, StateData) ->
[StateData#state.socket,
jid:to_string(StateData#state.jid)]),
From = StateData#state.jid,
- Packet = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"status">>, attrs = [],
- children =
- [{xmlcdata,
- <<"Replaced by new connection">>}]}]},
+ Lang = StateData#state.lang,
+ Status = <<"Replaced by new connection">>,
+ Packet = #presence{
+ type = unavailable,
+ status = xmpp:mk_text(Status, Lang)},
ejabberd_sm:close_session_unset_presence(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
- <<"Replaced by new connection">>),
+ Status),
presence_broadcast(StateData, From,
StateData#state.pres_a, Packet);
_ ->
@@ -1860,9 +1565,7 @@ terminate(_Reason, StateName, StateData) ->
StateData#state.resource);
_ ->
From = StateData#state.jid,
- Packet = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children = []},
+ Packet = #presence{type = unavailable},
ejabberd_sm:close_session_unset_presence(StateData#state.sid,
StateData#state.user,
StateData#state.server,
@@ -1897,7 +1600,7 @@ terminate(_Reason, StateName, StateData) ->
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-
+-spec change_shaper(state(), jid()) -> ok.
change_shaper(StateData, JID) ->
Shaper = acl:access_matches(StateData#state.shaper,
#{usr => jid:split(JID), ip => StateData#state.ip},
@@ -1905,6 +1608,7 @@ change_shaper(StateData, JID) ->
(StateData#state.sockmod):change_shaper(StateData#state.socket,
Shaper).
+-spec send_text(state(), iodata()) -> ok | {error, any()}.
send_text(StateData, Text) when StateData#state.mgmt_state == pending ->
?DEBUG("Cannot send text while waiting for resumption: ~p", [Text]);
send_text(StateData, Text) when StateData#state.xml_socket ->
@@ -1924,15 +1628,29 @@ send_text(StateData, Text) ->
?DEBUG("Send XML on stream = ~p", [Text]),
(StateData#state.sockmod):send(StateData#state.socket, Text).
+-spec send_element(state(), xmlel() | xmpp_element()) -> ok | {error, any()}.
send_element(StateData, El) when StateData#state.mgmt_state == pending ->
?DEBUG("Cannot send element while waiting for resumption: ~p", [El]);
-send_element(StateData, El) when StateData#state.xml_socket ->
+send_element(StateData, #xmlel{} = El) when StateData#state.xml_socket ->
?DEBUG("Send XML on stream = ~p", [fxml:element_to_binary(El)]),
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
-send_element(StateData, El) ->
- send_text(StateData, fxml:element_to_binary(El)).
+send_element(StateData, #xmlel{} = El) ->
+ send_text(StateData, fxml:element_to_binary(El));
+send_element(StateData, Pkt) ->
+ send_element(StateData, xmpp:encode(Pkt, ?NS_CLIENT)).
+
+-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> state().
+send_error(StateData, Stanza, Error) ->
+ Type = xmpp:get_type(Stanza),
+ if Type == error; Type == result;
+ Type == <<"error">>; Type == <<"result">> ->
+ StateData;
+ true ->
+ send_stanza(StateData, xmpp:make_error(Stanza, Error))
+ end.
+-spec send_stanza(state(), xmpp_element()) -> state().
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 ->
@@ -1944,8 +1662,9 @@ send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
+-spec send_packet(state(), xmpp_element()) -> state().
send_packet(StateData, Packet) ->
- case is_stanza(Packet) of
+ case xmpp:is_stanza(Packet) of
true ->
send_stanza(StateData, Packet);
false ->
@@ -1953,40 +1672,23 @@ send_packet(StateData, Packet) ->
StateData
end.
-send_header(StateData, Server, Version, Lang)
- when StateData#state.xml_socket ->
- VersionAttr = case Version of
- <<"">> -> [];
- _ -> [{<<"version">>, Version}]
- end,
- LangAttr = case Lang of
- <<"">> -> [];
- _ -> [{<<"xml:lang">>, Lang}]
- end,
- Header = {xmlstreamstart, <<"stream:stream">>,
- VersionAttr ++
- LangAttr ++
- [{<<"xmlns">>, <<"jabber:client">>},
- {<<"xmlns:stream">>,
- <<"http://etherx.jabber.org/streams">>},
- {<<"id">>, StateData#state.streamid},
- {<<"from">>, Server}]},
- (StateData#state.sockmod):send_xml(StateData#state.socket,
- Header);
+-spec send_header(state(), binary(), binary(), binary()) -> ok | {error, any()}.
send_header(StateData, Server, Version, Lang) ->
- VersionStr = case Version of
- <<"">> -> <<"">>;
- _ -> [<<" version='">>, Version, <<"'">>]
- end,
- LangStr = case Lang of
- <<"">> -> <<"">>;
- _ -> [<<" xml:lang='">>, Lang, <<"'">>]
- end,
- Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, Server, VersionStr,
- LangStr]),
- send_text(StateData, iolist_to_binary(Header)).
+ Header = #xmlel{name = Name, attrs = Attrs} =
+ xmpp:encode(#stream_start{version = Version,
+ lang = Lang,
+ xmlns = ?NS_CLIENT,
+ stream_xmlns = ?NS_STREAM,
+ id = StateData#state.streamid,
+ from = jid:make(Server)}),
+ if StateData#state.xml_socket ->
+ (StateData#state.sockmod):send_xml(StateData#state.socket,
+ {xmlstreamstart, Name, Attrs});
+ true ->
+ send_text(StateData, fxml:element_to_header(Header))
+ end.
+-spec send_trailer(state()) -> ok | {error, any()}.
send_trailer(StateData)
when StateData#state.mgmt_state == pending ->
?DEBUG("Cannot send stream trailer while waiting for resumption", []);
@@ -1997,66 +1699,27 @@ send_trailer(StateData)
send_trailer(StateData) ->
send_text(StateData, ?STREAM_TRAILER).
+-spec new_id() -> binary().
new_id() -> randoms:get_string().
+-spec new_uniq_id() -> binary().
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, <<"">>, <<"">>, <<"">>, <<"">>)};
- _ -> false
- end.
-
-is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>;
- Name == <<"presence">>;
- Name == <<"iq">> ->
- case fxml:get_attr(<<"xmlns">>, Attrs) of
- {value, NS} when NS /= <<"jabber:client">>,
- NS /= <<"jabber:server">> ->
- false;
- _ ->
- true
- end;
-is_stanza(_El) ->
- false.
-
-get_auth_tags([#xmlel{name = Name, children = Els} | L],
- U, P, D, R) ->
- 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);
- <<"digest">> -> get_auth_tags(L, U, P, CData, R);
- <<"resource">> -> get_auth_tags(L, U, P, D, CData);
- _ -> get_auth_tags(L, U, P, D, R)
- end;
-get_auth_tags([_ | L], U, P, D, R) ->
- get_auth_tags(L, U, P, D, R);
-get_auth_tags([], U, P, D, R) ->
- {U, P, D, R}.
-
-%% Copied from ejabberd_socket.erl
--record(socket_state, {sockmod, socket, receiver}).
+ integer_to_binary(p1_time_compat:unique_integer([positive]))]).
+-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket |
+ c2s_compressed_tls | http_bind.
get_conn_type(StateData) ->
- case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
- gen_tcp -> c2s;
- fast_tls -> c2s_tls;
- ezlib ->
- case ezlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
- gen_tcp -> c2s_compressed;
- fast_tls -> c2s_compressed_tls
- end;
- ejabberd_http_bind -> http_bind;
- ejabberd_http_ws -> websocket;
- _ -> unknown
+ case (StateData#state.sockmod):get_transport(StateData#state.socket) of
+ tcp -> c2s;
+ tls -> c2s_tls;
+ tcp_zlib -> c2s_compressed;
+ tls_zlib -> c2s_compressed_tls;
+ http_bind -> http_bind;
+ websocket -> websocket
end.
+-spec process_presence_probe(jid(), jid(), state()) -> ok.
process_presence_probe(From, To, StateData) ->
LFrom = jid:tolower(From),
LBFrom = setelement(3, LFrom, <<"">>),
@@ -2070,8 +1733,9 @@ process_presence_probe(From, To, StateData) ->
(?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),
+ Packet = xmpp_util:add_delay_info(
+ StateData#state.pres_last, To,
+ StateData#state.pres_timestamp),
case privacy_check_packet(StateData, To, From, Packet, out) of
deny ->
ok;
@@ -2092,14 +1756,12 @@ process_presence_probe(From, To, StateData) ->
end.
%% User updates his presence (non-directed presence packet)
+-spec presence_update(jid(), presence(), state()) -> state().
presence_update(From, Packet, StateData) ->
- #xmlel{attrs = Attrs} = Packet,
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"unavailable">> ->
- Status = case fxml:get_subtag(Packet, <<"status">>) of
- false -> <<"">>;
- StatusTag -> fxml:get_tag_cdata(StatusTag)
- end,
+ #presence{type = Type} = Packet,
+ case Type of
+ unavailable ->
+ Status = xmpp:get_text(Packet#presence.status),
Info = [{ip, StateData#state.ip},
{conn, StateData#state.conn},
{auth_module, StateData#state.auth_module}],
@@ -2111,12 +1773,12 @@ presence_update(From, Packet, StateData) ->
StateData#state.pres_a, Packet),
StateData#state{pres_last = undefined,
pres_timestamp = undefined, pres_a = (?SETS):new()};
- <<"error">> -> StateData;
- <<"probe">> -> StateData;
- <<"subscribe">> -> StateData;
- <<"subscribed">> -> StateData;
- <<"unsubscribe">> -> StateData;
- <<"unsubscribed">> -> StateData;
+ error -> StateData;
+ probe -> StateData;
+ subscribe -> StateData;
+ subscribed -> StateData;
+ unsubscribe -> StateData;
+ unsubscribed -> StateData;
_ ->
OldPriority = case StateData#state.pres_last of
undefined -> 0;
@@ -2154,57 +1816,69 @@ presence_update(From, Packet, StateData) ->
end.
%% User sends a directed presence packet
+-spec presence_track(jid(), jid(), presence(), state()) -> state().
presence_track(From, To, Packet, StateData) ->
- #xmlel{attrs = Attrs} = Packet,
+ #presence{type = Type} = Packet,
LTo = jid:tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
- 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);
- <<"subscribe">> ->
- try_roster_subscribe(subscribe, User, Server, From, To, Packet, StateData);
- <<"subscribed">> ->
- ejabberd_hooks:run(roster_out_subscription, Server,
- [User, Server, To, subscribed]),
- check_privacy_route(From, StateData,
- 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,
- jid:remove_resource(From), To, Packet);
- <<"error">> ->
- check_privacy_route(From, StateData, From, To, Packet);
- <<"probe">> ->
- check_privacy_route(From, StateData, From, To, Packet);
- _ ->
- A = (?SETS):add_element(LTo, StateData#state.pres_a),
- check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet)
+ Lang = StateData#state.lang,
+ case privacy_check_packet(StateData, From, To, Packet, out) of
+ deny ->
+ ErrText = <<"Your active privacy list has denied "
+ "the routing of this stanza.">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
+ allow when Type == subscribe; Type == subscribed;
+ Type == unsubscribe; Type == unsubscribed ->
+ Access = gen_mod:get_module_opt(Server, mod_roster, access,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ MyBareJID = jid:make(User, Server, <<"">>),
+ case acl:match_rule(Server, Access, MyBareJID) of
+ deny ->
+ ErrText = <<"Denied by ACL">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
+ allow ->
+ ejabberd_hooks:run(roster_out_subscription,
+ Server,
+ [User, Server, To, Type]),
+ ejabberd_router:route(jid:remove_resource(From), To, Packet),
+ StateData
+ end;
+ allow when Type == error; Type == probe ->
+ ejabberd_router:route(From, To, Packet),
+ StateData;
+ allow ->
+ ejabberd_router:route(From, To, Packet),
+ A = case Type of
+ available ->
+ ?SETS:add_element(LTo, StateData#state.pres_a);
+ unavailable ->
+ ?SETS:del_element(LTo, StateData#state.pres_a)
+ end,
+ StateData#state{pres_a = A}
end.
+-spec check_privacy_route(jid(), state(), jid(), jid(), stanza()) -> state().
check_privacy_route(From, StateData, FromRoute, To,
Packet) ->
case privacy_check_packet(StateData, From, To, Packet,
- out)
- of
+ out) of
deny ->
Lang = StateData#state.lang,
ErrText = <<"Your active privacy list has denied "
- "the routing of this stanza.">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- Err2 = jlib:replace_from_to(To, From, Err),
- send_stanza(StateData, Err2);
+ "the routing of this stanza.">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
allow ->
- ejabberd_router:route(FromRoute, To, Packet),
+ ejabberd_router:route(FromRoute, To, Packet),
StateData
end.
%% Check if privacy rules allow this delivery
+-spec privacy_check_packet(state(), jid(), jid(), stanza(), in | out) -> allow | deny.
privacy_check_packet(StateData, From, To, Packet,
Dir) ->
ejabberd_hooks:run_fold(privacy_check_packet,
@@ -2213,49 +1887,37 @@ privacy_check_packet(StateData, From, To, Packet,
StateData#state.privacy_list, {From, To, Packet},
Dir]).
+-spec is_privacy_allow(state(), jid(), jid(), stanza(), in | out) -> boolean().
is_privacy_allow(StateData, From, To, Packet, Dir) ->
allow ==
privacy_check_packet(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 = 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 ->
- %% Silently drop this (un)subscription request
- StateData;
- allow ->
- ejabberd_hooks:run(roster_out_subscription,
- Server,
- [User, Server, To, Type]),
- check_privacy_route(From, StateData, jid:remove_resource(From),
- To, Packet)
- end.
-
%% Send presence when disconnecting
+-spec presence_broadcast(state(), jid(), ?SETS:set(), presence()) -> ok.
presence_broadcast(StateData, From, JIDSet, Packet) ->
JIDs = ?SETS:to_list(JIDSet),
JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2, Packet).
+-spec presence_broadcast_to_trusted(
+ state(), jid(), ?SETS:set(), ?SETS:set(), presence()) -> ok.
%% Send presence when updating presence
presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) ->
- JIDs = ?SETS:to_list(JIDSet),
- JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)],
- JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted, out),
+ JIDs = ?SETS:to_list(?SETS:intersection(Trusted, JIDSet)),
+ JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2, Packet).
%% Send presence when connecting
+-spec presence_broadcast_first(jid(), state(), presence()) -> state().
presence_broadcast_first(From, StateData, Packet) ->
JIDsProbe =
?SETS:fold(
fun(JID, L) -> [JID | L] end,
[],
StateData#state.pres_t),
- PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []},
+ PacketProbe = #presence{type = probe},
JIDs2Probe = format_and_check_privacy(From, StateData, PacketProbe, JIDsProbe, out),
Server = StateData#state.server,
send_multiple(From, Server, JIDs2Probe, PacketProbe),
@@ -2270,6 +1932,8 @@ presence_broadcast_first(From, StateData, Packet) ->
send_multiple(From, Server, JIDs2, Packet),
StateData#state{pres_a = As}.
+-spec format_and_check_privacy(
+ jid(), state(), stanza(), [ljid()], in | out) -> [jid()].
format_and_check_privacy(From, StateData, Packet, JIDs, Dir) ->
FJIDs = [jid:make(JID) || JID <- JIDs],
lists:filter(
@@ -2288,15 +1952,11 @@ format_and_check_privacy(From, StateData, Packet, JIDs, Dir) ->
end,
FJIDs).
+-spec send_multiple(jid(), binary(), [jid()], stanza()) -> ok.
send_multiple(From, Server, JIDs, Packet) ->
ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
-remove_element(E, Set) ->
- case (?SETS):is_element(E, Set) of
- true -> (?SETS):del_element(E, Set);
- _ -> Set
- end.
-
+-spec roster_change(jid(), both | from | none | remove | to, state()) -> state().
roster_change(IJID, ISubscription, StateData) ->
LIJID = jid:tolower(IJID),
IsFrom = (ISubscription == both) or (ISubscription == from),
@@ -2304,11 +1964,11 @@ roster_change(IJID, ISubscription, StateData) ->
OldIsFrom = (?SETS):is_element(LIJID, StateData#state.pres_f),
FSet = if
IsFrom -> (?SETS):add_element(LIJID, StateData#state.pres_f);
- true -> remove_element(LIJID, StateData#state.pres_f)
+ true -> ?SETS:del_element(LIJID, StateData#state.pres_f)
end,
TSet = if
IsTo -> (?SETS):add_element(LIJID, StateData#state.pres_t);
- true -> remove_element(LIJID, StateData#state.pres_t)
+ true -> ?SETS:del_element(LIJID, StateData#state.pres_t)
end,
case StateData#state.pres_last of
undefined ->
@@ -2333,21 +1993,20 @@ roster_change(IJID, ISubscription, StateData) ->
pres_t = TSet};
Cond2 ->
?DEBUG("C2: ~p~n", [LIJID]),
- PU = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children = []},
+ PU = #presence{type = unavailable},
case privacy_check_packet(StateData, From, To, PU, out)
of
deny -> ok;
allow -> ejabberd_router:route(From, To, PU)
end,
- A = remove_element(LIJID, StateData#state.pres_a),
+ A = ?SETS:del_element(LIJID, StateData#state.pres_a),
StateData#state{pres_a = A, pres_f = FSet,
pres_t = TSet};
true -> StateData#state{pres_f = FSet, pres_t = TSet}
end
end.
+-spec update_priority(integer(), presence(), state()) -> ok.
update_priority(Priority, Packet, StateData) ->
Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn},
{auth_module, StateData#state.auth_module}],
@@ -2355,20 +2014,16 @@ update_priority(Priority, Packet, StateData) ->
StateData#state.user, StateData#state.server,
StateData#state.resource, Priority, Packet, Info).
-get_priority_from_presence(PresencePacket) ->
- case fxml:get_subtag(PresencePacket, <<"priority">>) of
- false -> 0;
- SubEl ->
- case catch
- jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
- of
- P when is_integer(P) -> P;
- _ -> 0
- end
+-spec get_priority_from_presence(presence()) -> integer().
+get_priority_from_presence(#presence{priority = Prio}) ->
+ case Prio of
+ undefined -> 0;
+ _ -> Prio
end.
-process_privacy_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) ->
+-spec process_privacy_iq(iq(), state()) -> state().
+process_privacy_iq(#iq{from = From, to = To,
+ type = Type, lang = Lang} = IQ, StateData) ->
Txt = <<"No module is handling this query">>,
{Res, NewStateData} =
case Type of
@@ -2376,16 +2031,15 @@ process_privacy_iq(From, To,
R = ejabberd_hooks:run_fold(
privacy_iq_get,
StateData#state.server,
- {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
- [From, To, IQ,
- StateData#state.privacy_list]),
+ {error, xmpp:err_feature_not_implemented(Txt, Lang)},
+ [IQ, StateData#state.privacy_list]),
{R, StateData};
set ->
case ejabberd_hooks:run_fold(
privacy_iq_set,
StateData#state.server,
- {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
- [From, To, IQ])
+ {error, xmpp:err_feature_not_implemented(Txt, Lang)},
+ [IQ, StateData#state.privacy_list])
of
{result, R, NewPrivList} ->
{{result, R},
@@ -2396,21 +2050,21 @@ process_privacy_iq(From, To,
end,
IQRes = case Res of
{result, Result} ->
- IQ#iq{type = result, sub_el = Result};
+ xmpp:make_iq_result(IQ, Result);
{error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
+ xmpp:make_error(IQ, Error)
end,
- ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)),
+ ejabberd_router:route(To, From, IQRes),
NewStateData.
+-spec resend_offline_messages(state()) -> ok.
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])
of
Rs -> %%when is_list(Rs) ->
- lists:foreach(fun ({route, From, To,
- #xmlel{} = Packet}) ->
+ lists:foreach(fun ({route, From, To, Packet}) ->
Pass = case privacy_check_packet(StateData,
From, To,
Packet, in)
@@ -2428,6 +2082,7 @@ resend_offline_messages(#state{ask_offline = true} = StateData) ->
resend_offline_messages(_StateData) ->
ok.
+-spec resend_subscription_requests(state()) -> state().
resend_subscription_requests(#state{user = User,
server = Server} = StateData) ->
PendingSubscriptions =
@@ -2439,52 +2094,43 @@ resend_subscription_requests(#state{user = User,
StateData,
PendingSubscriptions).
+-spec get_showtag(undefined | presence()) -> binary().
get_showtag(undefined) -> <<"unavailable">>;
-get_showtag(Presence) ->
- case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
- <<"">> -> <<"available">>;
- ShowTag -> ShowTag
- end.
-
-get_statustag(undefined) -> <<"">>;
-get_statustag(Presence) ->
- fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
-
-process_unauthenticated_stanza(StateData, El) ->
- NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of
- <<"">> ->
- case StateData#state.lang of
- <<"">> -> El;
- Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El)
- end;
- _ -> El
- end,
- case jlib:iq_query_info(NewEl) of
- #iq{lang = L} = IQ ->
- Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
- StateData#state.server, empty,
- [StateData#state.server, IQ,
- StateData#state.ip]),
- case Res of
- empty ->
- Txt = <<"Authentication required">>,
- ResIQ = IQ#iq{type = error,
- sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]},
- Res1 = jlib:replace_from_to(jid:make(<<"">>,
- StateData#state.server,
- <<"">>),
- jid:make(<<"">>, <<"">>,
- <<"">>),
- jlib:iq_to_xml(ResIQ)),
- send_element(StateData,
- jlib:remove_attr(<<"to">>, Res1));
- _ -> send_element(StateData, Res)
- end;
- _ ->
- % Drop any stanza, which isn't IQ stanza
- ok
- end.
+get_showtag(#presence{show = undefined}) -> <<"available">>;
+get_showtag(#presence{show = Show}) -> atom_to_binary(Show, utf8).
+
+-spec get_statustag(undefined | presence()) -> binary().
+get_statustag(#presence{status = Status}) -> xmpp:get_text(Status);
+get_statustag(undefined) -> <<"">>.
+
+-spec process_unauthenticated_stanza(state(), iq()) -> ok | {error, any()}.
+process_unauthenticated_stanza(StateData, #iq{type = T, lang = L} = IQ)
+ when T == set; T == get ->
+ Lang = if L == undefined; L == <<"">> -> StateData#state.lang;
+ true -> L
+ end,
+ NewIQ = IQ#iq{lang = Lang},
+ Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
+ StateData#state.server, empty,
+ [StateData#state.server, NewIQ,
+ StateData#state.ip]),
+ case Res of
+ empty ->
+ Txt = <<"Authentication required">>,
+ Err0 = xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)),
+ Err1 = Err0#iq{from = jid:make(<<>>, StateData#state.server, <<>>),
+ to = undefined},
+ send_element(StateData, Err1);
+ _ ->
+ send_element(StateData, Res)
+ end;
+process_unauthenticated_stanza(_StateData, _) ->
+ %% Drop any stanza, which isn't IQ stanza
+ ok.
+-spec peerip(ejabberd_socket:sockmod(),
+ ejabberd_socket:socket()) ->
+ {inet:ip_address(), non_neg_integer()} | undefined.
peerip(SockMod, Socket) ->
IP = case SockMod of
gen_tcp -> inet:peername(Socket);
@@ -2497,9 +2143,11 @@ peerip(SockMod, Socket) ->
%% fsm_next_state_pack: Pack the StateData structure to improve
%% sharing.
+-spec fsm_next_state_pack(state_name(), state()) -> fsm_transition().
fsm_next_state_pack(StateName, StateData) ->
fsm_next_state_gc(StateName, pack(StateData)).
+-spec fsm_next_state_gc(state_name(), state()) -> fsm_transition().
%% fsm_next_state_gc: Garbage collect the process heap to make use of
%% the newly packed StateData structure.
fsm_next_state_gc(StateName, PackedStateData) ->
@@ -2508,12 +2156,13 @@ fsm_next_state_gc(StateName, PackedStateData) ->
%% fsm_next_state: Generate the next_state FSM tuple with different
%% timeout, depending on the future state
+-spec fsm_next_state(state_name(), state()) -> fsm_transition().
fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
StateData) ->
?WARNING_MSG("ACK queue too long, terminating session for ~s",
[jid:to_string(StateData#state.jid)]),
- Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
- <<"Too many unacked stanzas">>),
+ Err = xmpp:serr_policy_violation(<<"Too many unacked stanzas">>,
+ StateData#state.lang),
send_element(StateData, Err),
{stop, normal, StateData#state{mgmt_resend = false}};
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
@@ -2551,6 +2200,7 @@ fsm_next_state(StateName, StateData) ->
%% fsm_reply: Generate the reply FSM tuple with different timeout,
%% depending on the future state
+-spec fsm_reply(_, state_name(), state()) -> fsm_reply().
fsm_reply(Reply, session_established, StateData) ->
{reply, Reply, session_established, StateData,
?C2S_HIBERNATE_TIMEOUT};
@@ -2562,34 +2212,32 @@ fsm_reply(Reply, StateName, StateData) ->
{reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
%% Used by c2s blacklist plugins
+-spec is_ip_blacklisted(undefined | {inet:ip_address(), non_neg_integer()},
+ binary()) -> false | {true, binary(), binary()}.
is_ip_blacklisted(undefined, _Lang) -> false;
is_ip_blacklisted({IP, _Port}, Lang) ->
ejabberd_hooks:run_fold(check_bl_c2s, false, [IP, Lang]).
%% Check from attributes
%% returns invalid-from|NewElement
-check_from(El, FromJID) ->
- case fxml:get_tag_attr(<<"from">>, El) of
- false ->
- El;
- {value, 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) ->
- El;
- (JID#jid.luser == FromJID#jid.luser) and
- (JID#jid.lserver == FromJID#jid.lserver) and
- (JID#jid.lresource == <<"">>) ->
- El;
- true ->
- 'invalid-from'
- end
+-spec check_from(stanza(), jid()) -> 'invalid-from' | stanza().
+check_from(Pkt, FromJID) ->
+ JID = xmpp:get_from(Pkt),
+ case JID of
+ undefined ->
+ Pkt;
+ #jid{} ->
+ if
+ (JID#jid.luser == FromJID#jid.luser) and
+ (JID#jid.lserver == FromJID#jid.lserver) and
+ (JID#jid.lresource == FromJID#jid.lresource) ->
+ Pkt;
+ (JID#jid.luser == FromJID#jid.luser) and
+ (JID#jid.lserver == FromJID#jid.lserver) and
+ (JID#jid.lresource == <<"">>) ->
+ Pkt;
+ true ->
+ 'invalid-from'
end
end.
@@ -2605,6 +2253,7 @@ fsm_limit_opts(Opts) ->
end
end.
+-spec bounce_messages() -> ok.
bounce_messages() ->
receive
{route, From, To, El} ->
@@ -2612,93 +2261,58 @@ bounce_messages() ->
after 0 -> ok
end.
-process_compression_request(El, StateName, StateData) ->
- case fxml:get_subtag(El, <<"method">>) of
+-spec process_compression_request(compress(), state_name(), state()) -> fsm_next().
+process_compression_request(#compress{methods = []}, StateName, StateData) ->
+ send_element(StateData, #compress_failure{reason = 'setup-failed'}),
+ fsm_next_state(StateName, StateData);
+process_compression_request(#compress{methods = Ms}, StateName, StateData) ->
+ case lists:member(<<"zlib">>, Ms) of
+ true ->
+ Socket = StateData#state.socket,
+ BCompressed = fxml:element_to_binary(xmpp:encode(#compressed{})),
+ ZlibSocket = (StateData#state.sockmod):compress(Socket, BCompressed),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = ZlibSocket,
+ streamid = new_id()});
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
+ #compress_failure{reason = 'unsupported-method'}),
+ fsm_next_state(StateName, StateData)
end.
%%%----------------------------------------------------------------------
%%% XEP-0191
%%%----------------------------------------------------------------------
+-spec route_blocking(
+ {block, [jid()]} | {unblock, [jid()]} | unblock_all, state()) -> state().
route_blocking(What, StateData) ->
SubEl = case What of
- {block, JIDs} ->
- #xmlel{name = <<"block">>,
- attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
- children =
- lists:map(fun (JID) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string(JID)}],
- children = []}
- end,
- JIDs)};
- {unblock, JIDs} ->
- #xmlel{name = <<"unblock">>,
- attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
- children =
- lists:map(fun (JID) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string(JID)}],
- children = []}
- end,
- JIDs)};
- unblock_all ->
- #xmlel{name = <<"unblock">>,
- attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []}
+ {block, JIDs} ->
+ #block{items = JIDs};
+ {unblock, JIDs} ->
+ #unblock{items = JIDs};
+ unblock_all ->
+ #unblock{}
end,
- PrivPushIQ = #iq{type = set, id = <<"push">>, sub_el = [SubEl]},
- PrivPushEl =
- jlib:replace_from_to(jid:remove_resource(StateData#state.jid),
- StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)),
+ PrivPushIQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
+ from = jid:remove_resource(StateData#state.jid),
+ to = StateData#state.jid},
%% No need to replace active privacy list here,
%% blocking pushes are always accompanied by
%% Privacy List pushes
- send_stanza(StateData, PrivPushEl).
+ send_stanza(StateData, PrivPushIQ).
%%%----------------------------------------------------------------------
%%% XEP-0198
%%%----------------------------------------------------------------------
-
+-spec stream_mgmt_enabled(state()) -> boolean().
stream_mgmt_enabled(#state{mgmt_state = disabled}) ->
false;
stream_mgmt_enabled(_StateData) ->
true.
+-spec dispatch_stream_mgmt(xmpp_element(), state()) -> state().
dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData)
when MgmtState == active;
MgmtState == pending ->
@@ -2706,171 +2320,144 @@ dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData)
dispatch_stream_mgmt(El, StateData) ->
negotiate_stream_mgmt(El, StateData).
+-spec negotiate_stream_mgmt(xmpp_element(), state()) -> state().
negotiate_stream_mgmt(_El, #state{resource = <<"">>} = StateData) ->
%% XEP-0198 says: "For client-to-server connections, the client MUST NOT
%% attempt to enable stream management until after it has completed Resource
%% Binding unless it is resuming a previous session". However, it also
%% says: "Stream management errors SHOULD be considered recoverable", so we
%% won't bail out.
- send_element(StateData, ?MGMT_UNEXPECTED_REQUEST(?NS_STREAM_MGMT_3)),
+ send_element(StateData, #sm_failed{reason = 'unexpected-request',
+ xmlns = ?NS_STREAM_MGMT_3}),
StateData;
-negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
- case stream_mgmt_enabled(StateData) of
- true ->
- case Name of
- <<"enable">> ->
- handle_enable(StateData#state{mgmt_xmlns = Xmlns}, Attrs);
- _ ->
- Res = if Name == <<"a">>;
- Name == <<"r">>;
- Name == <<"resume">> ->
- ?MGMT_UNEXPECTED_REQUEST(Xmlns);
- true ->
- ?MGMT_BAD_REQUEST(Xmlns)
- end,
- send_element(StateData, Res),
- StateData
- end;
- false ->
- send_element(StateData, ?MGMT_SERVICE_UNAVAILABLE(Xmlns)),
- StateData
- end;
- _ ->
- send_element(StateData, ?MGMT_UNSUPPORTED_VERSION(?NS_STREAM_MGMT_3)),
- StateData
+negotiate_stream_mgmt(Pkt, StateData) ->
+ Xmlns = xmpp:get_ns(Pkt),
+ case stream_mgmt_enabled(StateData) of
+ true ->
+ case Pkt of
+ #sm_enable{} ->
+ handle_enable(StateData#state{mgmt_xmlns = Xmlns}, Pkt);
+ _ ->
+ Res = if is_record(Pkt, sm_a);
+ is_record(Pkt, sm_r);
+ is_record(Pkt, sm_resume) ->
+ #sm_failed{reason = 'unexpected-request',
+ xmlns = Xmlns};
+ true ->
+ #sm_failed{reason = 'bad-request',
+ xmlns = Xmlns}
+ end,
+ send_element(StateData, Res),
+ StateData
+ end;
+ false ->
+ send_element(StateData,
+ #sm_failed{reason = 'service-unavailable',
+ xmlns = Xmlns}),
+ StateData
end.
-perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- Xmlns when Xmlns == StateData#state.mgmt_xmlns ->
- case Name of
- <<"r">> ->
- handle_r(StateData);
- <<"a">> ->
- handle_a(StateData, Attrs);
- _ ->
- Res = if Name == <<"enable">>;
- Name == <<"resume">> ->
- ?MGMT_UNEXPECTED_REQUEST(Xmlns);
- true ->
- ?MGMT_BAD_REQUEST(Xmlns)
- end,
- send_element(StateData, Res),
- StateData
- end;
- _ ->
- send_element(StateData,
- ?MGMT_UNSUPPORTED_VERSION(StateData#state.mgmt_xmlns)),
- StateData
+-spec perform_stream_mgmt(xmpp_element(), state()) -> state().
+perform_stream_mgmt(Pkt, StateData) ->
+ case xmpp:get_ns(Pkt) of
+ Xmlns when Xmlns == StateData#state.mgmt_xmlns ->
+ case Pkt of
+ #sm_r{} ->
+ handle_r(StateData);
+ #sm_a{} ->
+ handle_a(StateData, Pkt);
+ _ ->
+ Res = if is_record(Pkt, sm_enable);
+ is_record(Pkt, sm_resume) ->
+ #sm_failed{reason = 'unexpected-request',
+ xmlns = Xmlns};
+ true ->
+ #sm_failed{reason = 'bad-request',
+ xmlns = Xmlns}
+ end,
+ send_element(StateData, Res),
+ StateData
+ end;
+ _ ->
+ send_element(StateData,
+ #sm_failed{reason = 'unsupported-version',
+ xmlns = StateData#state.mgmt_xmlns})
end.
+-spec handle_enable(state(), sm_enable()) -> state().
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 = fxml:get_attr_s(<<"max">>, Attrs),
- case catch jlib:binary_to_integer(MaxAttr) of
- Max when is_integer(Max), Max > 0, Max =< MaxTimeout ->
- Max;
- _ ->
- DefaultTimeout
- end;
- _ ->
- 0
+ mgmt_max_timeout = MaxTimeout} = StateData,
+ #sm_enable{resume = Resume, max = Max}) ->
+ Timeout = if Resume == false ->
+ 0;
+ Max /= undefined, Max > 0, Max =< MaxTimeout ->
+ Max;
+ true ->
+ DefaultTimeout
end,
- ResAttrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}] ++
- if Timeout > 0 ->
- ?INFO_MSG("Stream management with resumption enabled for ~s",
- [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",
- [jid:to_string(StateData#state.jid)]),
- []
- end,
- Res = #xmlel{name = <<"enabled">>,
- attrs = ResAttrs,
- children = []},
+ Res = if Timeout > 0 ->
+ ?INFO_MSG("Stream management with resumption enabled for ~s",
+ [jid:to_string(StateData#state.jid)]),
+ #sm_enabled{xmlns = StateData#state.mgmt_xmlns,
+ id = make_resume_id(StateData),
+ resume = true,
+ max = Timeout};
+ true ->
+ ?INFO_MSG("Stream management without resumption enabled for ~s",
+ [jid:to_string(StateData#state.jid)]),
+ #sm_enabled{xmlns = StateData#state.mgmt_xmlns}
+ end,
send_element(StateData, Res),
StateData#state{mgmt_state = active,
mgmt_queue = queue:new(),
mgmt_timeout = Timeout * 1000}.
+-spec handle_r(state()) -> state().
handle_r(StateData) ->
- H = jlib:integer_to_binary(StateData#state.mgmt_stanzas_in),
- Res = #xmlel{name = <<"a">>,
- attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns},
- {<<"h">>, H}],
- children = []},
+ Res = #sm_a{xmlns = StateData#state.mgmt_xmlns,
+ h = StateData#state.mgmt_stanzas_in},
send_element(StateData, Res),
StateData.
-handle_a(StateData, Attrs) ->
- case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
- H when is_integer(H), H >= 0 ->
- NewStateData = check_h_attribute(StateData, H),
- maybe_renew_ack_request(NewStateData);
- _ ->
- ?DEBUG("Ignoring invalid ACK element from ~s",
- [jid:to_string(StateData#state.jid)]),
- StateData
- end.
+-spec handle_a(state(), sm_a()) -> state().
+handle_a(StateData, #sm_a{h = H}) ->
+ NewStateData = check_h_attribute(StateData, H),
+ maybe_renew_ack_request(NewStateData).
-handle_resume(StateData, Attrs) ->
- R = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
- case stream_mgmt_enabled(StateData) of
- true ->
- 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
+-spec handle_resume(state(), sm_resume()) -> {ok, state()} | error.
+handle_resume(StateData, #sm_resume{h = H, previd = PrevID, xmlns = Xmlns}) ->
+ R = case stream_mgmt_enabled(StateData) of
+ true ->
+ case inherit_session_state(StateData, PrevID) of
{ok, InheritedState, Info} ->
{ok, InheritedState, Info, H};
- {error, Err, InH} ->
- {error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err};
- {error, Err} ->
- {error, ?MGMT_ITEM_NOT_FOUND(Xmlns), Err}
- end;
- _ ->
- {error, ?MGMT_BAD_REQUEST(Xmlns),
- <<"Invalid request">>}
- end;
- false ->
- {error, ?MGMT_SERVICE_UNAVAILABLE(Xmlns),
- <<"XEP-0198 disabled">>}
- end;
- _ ->
- {error, ?MGMT_UNSUPPORTED_VERSION(?NS_STREAM_MGMT_3),
- <<"Invalid XMLNS">>}
+ {error, Err, InH} ->
+ {error, #sm_failed{reason = 'item-not-found',
+ h = InH, xmlns = Xmlns}, Err};
+ {error, Err} ->
+ {error, #sm_failed{reason = 'item-not-found',
+ xmlns = Xmlns}, Err}
+ end;
+ false ->
+ {error, #sm_failed{reason = 'service-unavailable',
+ xmlns = Xmlns},
+ <<"XEP-0198 disabled">>}
end,
case R of
{ok, ResumedState, ResumedInfo, NumHandled} ->
NewState = check_h_attribute(ResumedState, NumHandled),
AttrXmlns = NewState#state.mgmt_xmlns,
AttrId = make_resume_id(NewState),
- AttrH = jlib:integer_to_binary(NewState#state.mgmt_stanzas_in),
- send_element(NewState,
- #xmlel{name = <<"resumed">>,
- attrs = [{<<"xmlns">>, AttrXmlns},
- {<<"h">>, AttrH},
- {<<"previd">>, AttrId}],
- children = []}),
+ AttrH = NewState#state.mgmt_stanzas_in,
+ send_element(NewState, #sm_resumed{xmlns = AttrXmlns,
+ h = AttrH,
+ previd = AttrId}),
SendFun = fun(_F, _T, El, Time) ->
NewEl = add_resent_delay_info(NewState, El, Time),
send_element(NewState, NewEl)
end,
handle_unacked_stanzas(NewState, SendFun),
- send_element(NewState,
- #xmlel{name = <<"r">>,
- attrs = [{<<"xmlns">>, AttrXmlns}],
- children = []}),
+ send_element(NewState, #sm_r{xmlns = AttrXmlns}),
NewState1 = csi_flush_queue(NewState),
NewState2 = ejabberd_hooks:run_fold(c2s_session_resumed,
StateData#state.server,
@@ -2888,6 +2475,7 @@ handle_resume(StateData, Attrs) ->
error
end.
+-spec check_h_attribute(state(), non_neg_integer()) -> state().
check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H)
when H > NumStanzasOut ->
?DEBUG("~s acknowledged ~B stanzas, but only ~B were sent",
@@ -2898,10 +2486,11 @@ check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) ->
[jid:to_string(StateData#state.jid), H, NumStanzasOut]),
mgmt_queue_drop(StateData, H).
+-spec update_num_stanzas_in(state(), xmpp_element()) -> state().
update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
when MgmtState == active;
MgmtState == pending ->
- NewNum = case {is_stanza(El), StateData#state.mgmt_stanzas_in} of
+ NewNum = case {xmpp:is_stanza(El), StateData#state.mgmt_stanzas_in} of
{true, 4294967295} ->
0;
{true, Num} ->
@@ -2928,7 +2517,7 @@ maybe_request_ack(StateData) ->
request_ack(#state{mgmt_xmlns = Xmlns,
mgmt_ack_timeout = AckTimeout} = StateData) ->
- AckReq = #xmlel{name = <<"r">>, attrs = [{<<"xmlns">>, Xmlns}]},
+ AckReq = #sm_r{xmlns = Xmlns},
case {send_element(StateData, AckReq), AckTimeout} of
{ok, undefined} ->
ok;
@@ -2954,6 +2543,7 @@ maybe_renew_ack_request(#state{mgmt_ack_timer = Timer,
StateData#state{mgmt_ack_timer = undefined}
end.
+-spec mgmt_queue_add(state(), xmpp_element()) -> state().
mgmt_queue_add(StateData, El) ->
NewNum = case StateData#state.mgmt_stanzas_out of
4294967295 ->
@@ -2966,11 +2556,13 @@ mgmt_queue_add(StateData, El) ->
mgmt_stanzas_out = NewNum},
check_queue_length(NewState).
+-spec mgmt_queue_drop(state(), non_neg_integer()) -> state().
mgmt_queue_drop(StateData, NumHandled) ->
NewQueue = jlib:queue_drop_while(fun({N, _T, _E}) -> N =< NumHandled end,
StateData#state.mgmt_queue),
StateData#state{mgmt_queue = NewQueue}.
+-spec check_queue_length(state()) -> state().
check_queue_length(#state{mgmt_max_queue = Limit} = StateData)
when Limit == infinity;
Limit == exceeded ->
@@ -2984,6 +2576,7 @@ check_queue_length(#state{mgmt_queue = Queue,
StateData
end.
+-spec handle_unacked_stanzas(state(), fun((_, _, _, _) -> _)) -> ok.
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
when MgmtState == active;
MgmtState == pending;
@@ -2996,20 +2589,21 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
?DEBUG("~B stanza(s) were not acknowledged by ~s",
[N, jid:to_string(StateData#state.jid)]),
lists:foreach(
- fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
- From_s = fxml:get_attr_s(<<"from">>, Attrs),
- To_s = fxml:get_attr_s(<<"to">>, Attrs),
- case {jid:from_string(From_s), jid:from_string(To_s)} of
- {#jid{} = From, #jid{} = To} ->
- F(From, To, El, Time);
- {_, _} ->
- ?DEBUG("Dropping stanza due to invalid JID(s)", [])
+ fun({_, Time, Pkt}) ->
+ From = xmpp:get_from(Pkt),
+ To = xmpp:get_to(Pkt),
+ case {From, To} of
+ {#jid{}, #jid{}} ->
+ F(From, To, Pkt, Time);
+ {_, _} ->
+ ?DEBUG("Dropping stanza due to invalid JID(s)", [])
end
end, queue:to_list(Queue))
end;
handle_unacked_stanzas(_StateData, _F) ->
ok.
+-spec handle_unacked_stanzas(state()) -> ok.
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
when MgmtState == active;
MgmtState == pending;
@@ -3040,79 +2634,44 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
false ->
fun(From, To, El, _Time) ->
Txt = <<"User session terminated">>,
- Err =
- jlib:make_error_reply(
- El,
- ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
+ ejabberd_router:route_error(
+ To, From, El, xmpp:err_service_unavailable(Txt, Lang))
end
end,
- F = fun(From, _To, #xmlel{name = <<"presence">>}, _Time) ->
+ F = fun(From, _To, #presence{}, _Time) ->
?DEBUG("Dropping presence stanza from ~s",
[jid:to_string(From)]);
- (From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
+ (From, To, #iq{} = El, _Time) ->
Txt = <<"User session terminated">>,
- Err = jlib:make_error_reply(
- El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ ejabberd_router:route_error(
+ To, From, El, xmpp:err_service_unavailable(Txt, Lang));
+ (From, _To, #message{meta = #{carbon_copy := true}}, _Time) ->
+ %% XEP-0280 says: "When a receiving server attempts to deliver a
+ %% forked message, and that message bounces with an error for
+ %% any reason, the receiving server MUST NOT forward that error
+ %% back to the original sender." Resending such a stanza could
+ %% easily lead to unexpected results as well.
+ ?DEBUG("Dropping forwarded message stanza from ~s",
+ [jid:to_string(From)]);
(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
- %% deliver a forked message, and that message bounces with an
- %% error for any reason, the receiving server MUST NOT forward
- %% that error back to the original sender." Resending such a
- %% stanza could easily lead to unexpected results as well.
- case is_encapsulated_forward(El) of
+ case ejabberd_hooks:run_fold(message_is_archived,
+ StateData#state.server, false,
+ [StateData, From,
+ StateData#state.jid, El]) of
true ->
- ?DEBUG("Dropping forwarded message stanza from ~s",
- [fxml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
+ ?DEBUG("Dropping archived message stanza from ~p",
+ [jid:to_string(xmpp:get_from(El))]);
false ->
- case ejabberd_hooks:run_fold(message_is_archived,
- StateData#state.server,
- false,
- [StateData, From,
- StateData#state.jid, El]) of
- true ->
- ?DEBUG("Dropping archived message stanza from ~s",
- [fxml:get_attr_s(<<"from">>,
- El#xmlel.attrs)]),
- ok;
- false ->
- ReRoute(From, To, El, Time)
- end
+ ReRoute(From, To, El, Time)
end
end,
handle_unacked_stanzas(StateData, F);
handle_unacked_stanzas(_StateData) ->
ok.
-is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
- 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} ->
- Tag;
- {false, Tag, false} ->
- Tag;
- {_, _, Tag} ->
- Tag
- end,
- if SubTag == false ->
- false;
- true ->
- case fxml:get_subtag(SubTag, <<"forwarded">>) of
- false ->
- false;
- _ ->
- true
- end
- end;
-is_encapsulated_forward(_El) ->
- false.
-
+-spec inherit_session_state(state(), binary()) -> {ok, state()} |
+ {error, binary()} |
+ {error, binary(), non_neg_integer()}.
inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
case jlib:base64_to_term(ResumeID) of
{term, {R, Time}} ->
@@ -3173,22 +2732,25 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
{error, <<"Invalid 'previd' value">>}
end.
+-spec resume_session({integer(), pid()}) -> any().
resume_session({Time, PID}) ->
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 15000).
+-spec make_resume_id(state()) -> binary().
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) ->
+-spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza().
+add_resent_delay_info(_State, #iq{} = El, _Time) ->
El;
add_resent_delay_info(#state{server = From}, El, Time) ->
- jlib:add_delay_info(El, From, Time, <<"Resent">>).
+ xmpp_util:add_delay_info(El, jid:make(From), Time, <<"Resent">>).
%%%----------------------------------------------------------------------
%%% XEP-0352
%%%----------------------------------------------------------------------
-
+-spec csi_filter_stanza(state(), stanza()) -> state().
csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData, Stanza) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server,
@@ -3200,6 +2762,7 @@ csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} =
Stanzas),
StateData2#state{csi_state = CsiState}.
+-spec csi_flush_queue(state()) -> state().
csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server,
@@ -3217,6 +2780,7 @@ csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} =
%% Try to reduce the heap footprint of the four presence sets
%% by ensuring that we re-use strings and Jids wherever possible.
+-spec pack(state()) -> state().
pack(S = #state{pres_a = A, pres_f = F,
pres_t = T}) ->
{NewA, Pack2} = pack_jid_set(A, gb_trees:empty()),
@@ -3253,6 +2817,7 @@ pack_string(String, Pack) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
+-spec identity([{atom(), binary()}]) -> binary().
identity(Props) ->
case proplists:get_value(authzid, Props, <<>>) of
<<>> -> proplists:get_value(username, Props, <<>>);
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 157700c47..85d7595a2 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -41,31 +41,17 @@
-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, opt_type/1]).
-
--include("jlib.hrl").
+ opt_type/1]).
+-include("xmpp.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-
-include("ejabberd_http.hrl").
--define(VFIELD(Type, Var, Value),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [Value]}]}).
-
--define(CAPTCHA_TEXT(Lang),
- translate:translate(Lang,
- <<"Enter the text you see">>)).
-
-define(CAPTCHA_LIFETIME, 120000).
-
-define(LIMIT_PERIOD, 60*1000*1000).
--type error() :: efbig | enodata | limit | malformed_image | timeout.
+-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
-record(state, {limits = treap:empty() :: treap:treap()}).
@@ -79,188 +65,80 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
+-spec captcha_text(undefined | binary()) -> binary().
+captcha_text(Lang) ->
+ translate:translate(Lang, <<"Enter the text you see">>).
+
+-spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field().
+mk_ocr_field(Lang, CID, Type) ->
+ URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
+ #xdata_field{var = <<"ocr">>,
+ type = 'text-single',
+ label = captcha_text(Lang),
+ required = true,
+ sub_els = [#media{uri = [URI]}]}.
+
+mk_field(Type, Var, Value) ->
+ #xdata_field{type = Type, var = Var, values = [Value]}.
+
-spec create_captcha(binary(), jid(), jid(),
- binary(), any(), any()) -> {error, error()} |
- {ok, binary(), [xmlel()]}.
+ binary(), any(), any()) -> {error, image_error()} |
+ {ok, binary(), [text()], [xmlel()]}.
create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
- B64Image = jlib:encode_base64((Image)),
JID = jid:to_string(From),
- CID = <<"sha1+", (p1_sha:sha(Image))/binary,
- "@bob.xmpp.org">>,
- Data = #xmlel{name = <<"data">>,
- attrs =
- [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
- {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
- children = [{xmlcdata, B64Image}]},
- Captcha = #xmlel{name = <<"captcha">>,
- attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- [?VFIELD(<<"hidden">>,
- <<"FORM_TYPE">>,
- {xmlcdata, ?NS_CAPTCHA}),
- ?VFIELD(<<"hidden">>, <<"from">>,
- {xmlcdata,
- jid:to_string(To)}),
- ?VFIELD(<<"hidden">>,
- <<"challenge">>,
- {xmlcdata, Id}),
- ?VFIELD(<<"hidden">>, <<"sid">>,
- {xmlcdata, SID}),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"ocr">>},
- {<<"label">>,
- ?CAPTCHA_TEXT(Lang)}],
- children =
- [#xmlel{name =
- <<"required">>,
- attrs = [],
- children = []},
- #xmlel{name =
- <<"media">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MEDIA}],
- children =
- [#xmlel{name
- =
- <<"uri">>,
- attrs
- =
- [{<<"type">>,
- Type}],
- children
- =
- [{xmlcdata,
- <<"cid:",
- CID/binary>>}]}]}]}]}]},
- BodyString1 = translate:translate(Lang,
- <<"Your messages to ~s are being blocked. "
- "To unblock them, visit ~s">>),
- BodyString = iolist_to_binary(io_lib:format(BodyString1,
- [JID, get_url(Id)])),
- Body = #xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, BodyString}]},
- OOB = #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_OOB}],
- children =
- [#xmlel{name = <<"url">>, attrs = [],
- children = [{xmlcdata, get_url(Id)}]}]},
+ CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
+ Data = #bob_data{cid = CID, 'max-age' = 0, type = Type,
+ data = Image},
+ Fs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA),
+ mk_field(hidden, <<"from">>, jid:to_string(To)),
+ mk_field(hidden, <<"challenge">>, Id),
+ mk_field(hidden, <<"sid">>, SID),
+ mk_ocr_field(Lang, CID, Type)],
+ X = #xdata{type = form, fields = Fs},
+ Captcha = #xcaptcha{xdata = X},
+ BodyString = {<<"Your messages to ~s are being blocked. "
+ "To unblock them, visit ~s">>, [JID, get_url(Id)]},
+ Body = xmpp:mk_text(BodyString, Lang),
+ OOB = #oob_x{url = get_url(Id)},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
args = Args}),
- {ok, Id, [Body, OOB, Captcha, Data]};
+ {ok, Id, Body, [OOB, Captcha, Data]};
Err -> Err
end.
--spec create_captcha_x(binary(), jid(), binary(),
- any(), [xmlel()]) -> {ok, [xmlel()]} |
- {error, error()}.
-
-create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
- create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
+-spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) ->
+ {ok, xdata()} | {error, image_error()}.
--spec create_captcha_x(binary(), jid(), binary(),
- any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
- {error, error()}.
-
-create_captcha_x(SID, To, Lang, Limiter, HeadEls,
- TailEls) ->
+create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
- B64Image = jlib:encode_base64((Image)),
- CID = <<"sha1+", (p1_sha:sha(Image))/binary,
- "@bob.xmpp.org">>,
- Data = #xmlel{name = <<"data">>,
- attrs =
- [{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
- {<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
- children = [{xmlcdata, B64Image}]},
+ CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
+ Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image},
HelpTxt = translate:translate(Lang,
<<"If you don't see the CAPTCHA image here, "
"visit the web page.">>),
Imageurl = get_url(<<Id/binary, "/image">>),
- Captcha = #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- [?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
- {xmlcdata, ?NS_CAPTCHA})
- | HeadEls]
- ++
- [#xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"fixed">>}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- HelpTxt}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"hidden">>},
- {<<"var">>, <<"captchahidden">>}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- <<"workaround-for-psi">>}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"CAPTCHA web page">>)},
- {<<"var">>, <<"url">>}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- Imageurl}]}]},
- ?VFIELD(<<"hidden">>, <<"from">>,
- {xmlcdata, jid:to_string(To)}),
- ?VFIELD(<<"hidden">>, <<"challenge">>,
- {xmlcdata, Id}),
- ?VFIELD(<<"hidden">>, <<"sid">>,
- {xmlcdata, SID}),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"ocr">>},
- {<<"label">>,
- ?CAPTCHA_TEXT(Lang)}],
- children =
- [#xmlel{name = <<"required">>,
- attrs = [], children = []},
- #xmlel{name = <<"media">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MEDIA}],
- children =
- [#xmlel{name =
- <<"uri">>,
- attrs =
- [{<<"type">>,
- Type}],
- children =
- [{xmlcdata,
- <<"cid:",
- CID/binary>>}]}]}]}]
- ++ TailEls},
+ NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++
+ [#xdata_field{type = fixed, values = [HelpTxt]},
+ #xdata_field{type = hidden, var = <<"captchahidden">>,
+ values = [<<"workaround-for-psi">>]},
+ #xdata_field{type = 'text-single', var = <<"url">>,
+ label = translate:translate(
+ Lang, <<"CAPTCHA web page">>),
+ values = [Imageurl]},
+ mk_field(hidden, <<"from">>, jid:to_string(To)),
+ mk_field(hidden, <<"challenge">>, Id),
+ mk_field(hidden, <<"sid">>, SID),
+ mk_ocr_field(Lang, CID, Type)],
+ Captcha = X#xdata{type = form, fields = NewFs},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
@@ -281,7 +159,7 @@ build_captcha_html(Id, Lang) ->
attrs =
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
children = []},
- TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
+ TextEl = {xmlcdata, captcha_text(Lang)},
IdEl = #xmlel{name = <<"input">>,
attrs =
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
@@ -317,27 +195,24 @@ build_captcha_html(Id, Lang) ->
_ -> captcha_not_found
end.
--spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
-
-process_reply(#xmlel{} = El) ->
- case fxml:get_subtag(El, <<"x">>) of
- false -> {error, malformed};
- Xdata ->
- Fields = jlib:parse_xdata_submit(Xdata),
- case catch {proplists:get_value(<<"challenge">>,
- Fields),
- proplists:get_value(<<"ocr">>, Fields)}
- of
- {[Id | _], [OCR | _]} ->
- case check_captcha(Id, OCR) of
- captcha_valid -> ok;
- captcha_non_valid -> {error, bad_match};
- captcha_not_found -> {error, not_found}
- end;
- _ -> {error, malformed}
- end
+-spec process_reply(xmpp_element()) -> ok | {error, bad_match | not_found | malformed}.
+
+process_reply(#xdata{} = X) ->
+ case {xmpp_util:get_xdata_values(<<"challenge">>, X),
+ xmpp_util:get_xdata_values(<<"ocr">>, X)} of
+ {[Id], [OCR]} ->
+ case check_captcha(Id, OCR) of
+ captcha_valid -> ok;
+ captcha_non_valid -> {error, bad_match};
+ captcha_not_found -> {error, not_found}
+ end;
+ _ ->
+ {error, malformed}
end;
-process_reply(_) -> {error, malformed}.
+process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
+ process_reply(X);
+process_reply(_) ->
+ {error, malformed}.
process(_Handlers,
#request{method = 'GET', lang = Lang,
@@ -515,7 +390,7 @@ get_url(Str) ->
end.
get_transfer_protocol(PortString) ->
- PortNumber = jlib:binary_to_integer(PortString),
+ PortNumber = binary_to_integer(PortString),
PortListeners = get_port_listeners(PortNumber),
get_captcha_transfer_protocol(PortListeners).
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index 8d74ad5a2..223163a2b 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -277,7 +277,11 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
- mnesia:create_table(ejabberd_commands,
+ try mnesia:transform_table(ejabberd_commands, ignore,
+ record_info(fields, ejabberd_commands))
+ catch exit:{aborted, {no_exists, _}} -> ok
+ end,
+ ejabberd_mnesia:create(?MODULE, ejabberd_commands,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, ejabberd_commands)},
diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl
index dc00c5d2a..bb519a600 100644
--- a/src/ejabberd_commands_doc.erl
+++ b/src/ejabberd_commands_doc.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
@@ -41,7 +41,7 @@
-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(NUM(A), ?SPAN(num,integer_to_binary(A))).
-define(FIELD(A), ?SPAN(field,A)).
-define(ID(A), ?SPAN(id,A)).
-define(OP(A), ?SPAN(op,A)).
@@ -171,7 +171,7 @@ 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_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index af26767f8..e930e36b1 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -104,7 +104,7 @@ mnesia_init() ->
_ ->
ok
end,
- mnesia:create_table(local_config,
+ ejabberd_mnesia:create(?MODULE, local_config,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, local_config)}]),
@@ -1300,7 +1300,7 @@ convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
catch mnesia:delete_table(TmpTab),
- case mnesia:create_table(TmpTab,
+ case ejabberd_mnesia:create(?MODULE, TmpTab,
[{disc_only_copies, [node()]},
{type, Type},
{local_content, true},
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index a96a28016..63adcdf69 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -311,7 +311,7 @@ try_call_command(Args, Auth, AccessCommands, Version) ->
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
-call_command([CmdString | Args], Auth, AccessCommands, Version) ->
+call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index b8e706f23..ab5b6a701 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -42,6 +42,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
+ get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod).
+get_transport(FsmRef) ->
+ gen_server:call(FsmRef, get_transport).
+
get_peer_certificate(FsmRef) ->
gen_server:call(FsmRef, get_peer_certificate).
@@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
+handle_call(get_transport, _From, State) ->
+ Reply = case State#state.sockmod of
+ gen_tcp -> tcp;
+ fast_tls -> tls;
+ ezlib ->
+ case ezlib:get_sockmod(State#state.socket) of
+ tcp -> tcp_zlib;
+ tls -> tls_zlib
+ end;
+ ejabberd_http_bind -> http_bind;
+ ejabberd_http_ws -> websocket
+ end,
+ {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index e6e49d9b2..c0c7bbbd6 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -39,7 +39,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -257,7 +257,7 @@ process_header(State, Data) ->
request_headers = add_header(Name, Auth, State)};
{ok,
{http_header, _, 'Content-Length' = Name, _, SLen}} ->
- case catch jlib:binary_to_integer(SLen) of
+ case catch binary_to_integer(SLen) of
Len when is_integer(Len) ->
State#state{request_content_length = Len,
request_headers = add_header(Name, SLen, State)};
@@ -332,10 +332,10 @@ get_transfer_protocol(SockMod, HostPort) ->
case {SockMod, PortList} of
{gen_tcp, []} -> {Host, 80, http};
{gen_tcp, [Port]} ->
- {Host, jlib:binary_to_integer(Port), http};
+ {Host, binary_to_integer(Port), http};
{fast_tls, []} -> {Host, 443, https};
{fast_tls, [Port]} ->
- {Host, jlib:binary_to_integer(Port), https}
+ {Host, binary_to_integer(Port), https}
end.
%% XXX bard: search through request handlers looking for one that
@@ -399,17 +399,17 @@ extract_path_query(#state{request_method = Method,
case recv_data(State, Len) of
error -> {State, false};
{NewState, Data} ->
- ?DEBUG("client data: ~p~n", [Data]),
- case catch url_decode_q_split(Path) of
- {'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}}
+ ?DEBUG("client data: ~p~n", [Data]),
+ case catch url_decode_q_split(Path) of
+ {'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
end;
extract_path_query(State) ->
@@ -550,12 +550,12 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
of
{value, _} ->
[{<<"Content-Length">>,
- iolist_to_binary(integer_to_list(byte_size(Data)))}
+ integer_to_binary(byte_size(Data))}
| Headers];
_ ->
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
{<<"Content-Length">>,
- iolist_to_binary(integer_to_list(byte_size(Data)))}
+ integer_to_binary(byte_size(Data))}
| Headers]
end,
HeadersOut = case {State#state.request_version,
@@ -577,7 +577,7 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
end,
HeadersOut),
SL = [Version,
- iolist_to_binary(integer_to_list(Status)), <<" ">>,
+ integer_to_binary(Status), <<" ">>,
code_to_phrase(Status), <<"\r\n">>],
Data2 = case State#state.request_method of
'HEAD' -> <<"">>;
@@ -595,12 +595,12 @@ make_text_output(State, Status, Reason, Headers, Text) ->
of
{value, _} ->
[{<<"Content-Length">>,
- jlib:integer_to_binary(byte_size(Data))}
+ integer_to_binary(byte_size(Data))}
| Headers];
_ ->
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
{<<"Content-Length">>,
- jlib:integer_to_binary(byte_size(Data))}
+ integer_to_binary(byte_size(Data))}
| Headers]
end,
HeadersOut = case {State#state.request_version,
@@ -625,7 +625,7 @@ make_text_output(State, Status, Reason, Headers, Text) ->
_ -> Reason
end,
SL = [Version,
- jlib:integer_to_binary(Status), <<" ">>,
+ integer_to_binary(Status), <<" ">>,
NewReason, <<"\r\n">>],
Data2 = case State#state.request_method of
'HEAD' -> <<"">>;
diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl
index 628119e6f..ea64b3cdf 100644
--- a/src/ejabberd_http_bind.erl
+++ b/src/ejabberd_http_bind.erl
@@ -62,7 +62,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -125,8 +125,6 @@
%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
-define(BOSH_VERSION, <<"1.8">>).
--define(NS_CLIENT, <<"jabber:client">>).
-
-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
-define(NS_HTTP_BIND,
@@ -856,7 +854,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(fxml:get_attr_s(<<"pause">>,
+ binary_to_integer(fxml:get_attr_s(<<"pause">>,
Attrs))
of
{'EXIT', _} -> {true, 0};
@@ -974,21 +972,17 @@ prepare_outpacket_response(#http_bind{id = Sid,
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"sid">>, Sid},
{<<"wait">>,
- iolist_to_binary(integer_to_list(Wait))},
+ integer_to_binary(Wait)},
{<<"requests">>,
- iolist_to_binary(integer_to_list(Hold
- +
- 1))},
+ integer_to_binary(Hold + 1)},
{<<"inactivity">>,
- iolist_to_binary(integer_to_list(trunc(MaxInactivity
- /
- 1000)))},
+ integer_to_binary(
+ trunc(MaxInactivity / 1000))},
{<<"maxpause">>,
- iolist_to_binary(integer_to_list(MaxPause))},
+ integer_to_binary(MaxPause)},
{<<"polling">>,
- iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
- /
- 1000000)))},
+ integer_to_binary(
+ trunc((?MIN_POLLING) / 1000000))},
{<<"ver">>, ?BOSH_VERSION},
{<<"from">>, From},
{<<"secure">>, <<"true">>}]
@@ -1122,7 +1116,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
true ->
case catch
- jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
+ binary_to_integer(fxml:get_attr_s(<<"rid">>,
Attrs))
of
{'EXIT', _} -> {error, bad_request};
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index e76e8689a..b92345dd4 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -39,7 +39,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index 2ba943693..a5ee6a242 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -46,7 +46,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state, {}).
@@ -60,6 +60,8 @@
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
+-type ping_timeout() :: non_neg_integer() | undefined.
+
%%====================================================================
%% API
%%====================================================================
@@ -71,37 +73,30 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-process_iq(From, To, Packet) ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{xmlns = XMLNS, lang = Lang} ->
- Host = To#jid.lserver,
- case ets:lookup(?IQTABLE, {XMLNS, Host}) of
- [{_, Module, Function}] ->
- ResIQ = Module:Function(From, To, IQ),
- if ResIQ /= ignore ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- true -> ok
- end;
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:handle(Host, Module, Function, Opts,
- From, To, IQ);
- [] ->
- Txt = <<"No module is handling this query">>,
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
- end;
- reply ->
- IQReply = jlib:iq_query_or_response_info(Packet),
- process_iq_reply(From, To, IQReply);
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err),
- ok
- end.
-
+-spec process_iq(jid(), jid(), iq()) -> any().
+process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
+ when T == get; T == set ->
+ XMLNS = xmpp:get_ns(El),
+ Host = To#jid.lserver,
+ case ets:lookup(?IQTABLE, {XMLNS, Host}) of
+ [{_, Module, Function}] ->
+ gen_iq_handler:handle(Host, Module, Function, no_queue,
+ From, To, Packet);
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:handle(Host, Module, Function, Opts,
+ From, To, Packet);
+ [] ->
+ Txt = <<"No module is handling this query">>,
+ Err = xmpp:err_service_unavailable(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end;
+process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
+ Err = xmpp:err_bad_request(),
+ ejabberd_router:route_error(To, From, Packet, Err);
+process_iq(From, To, #iq{type = T} = Packet) when T == result; T == error ->
+ process_iq_reply(From, To, Packet).
+
+-spec process_iq_reply(jid(), jid(), iq()) -> any().
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of
{ok, undefined, Function} -> Function(IQ), ok;
@@ -110,6 +105,7 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
_ -> nothing
end.
+-spec route(jid(), jid(), stanza()) -> any().
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@@ -118,26 +114,32 @@ route(From, To, Packet) ->
_ -> ok
end.
+-spec route_iq(jid(), jid(), iq(), function()) -> any().
route_iq(From, To, IQ, F) ->
route_iq(From, To, IQ, F, undefined).
+-spec route_iq(jid(), jid(), iq(), function(), ping_timeout()) -> any().
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F, Timeout),
- jlib:iq_to_xml(IQ#iq{id = ID});
+ IQ#iq{id = ID};
true ->
- jlib:iq_to_xml(IQ)
+ IQ
end,
ejabberd_router:route(From, To, Packet).
+-spec register_iq_response_handler(binary(), binary(), module(),
+ atom() | function()) -> any().
register_iq_response_handler(Host, ID, Module,
Function) ->
register_iq_response_handler(Host, ID, Module, Function,
undefined).
+-spec register_iq_response_handler(binary(), binary(), module(),
+ atom() | function(), ping_timeout()) -> any().
register_iq_response_handler(_Host, ID, Module,
Function, Timeout0) ->
Timeout = case Timeout0 of
@@ -150,29 +152,40 @@ register_iq_response_handler(_Host, ID, Module,
function = Function,
timer = TRef}).
+-spec register_iq_handler(binary(), binary(), module(), function()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun}.
+-spec register_iq_handler(binary(), binary(), module(), function(),
+ gen_iq_handler:opts()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
+-spec unregister_iq_response_handler(binary(), binary()) -> ok.
unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID), ok.
+-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
+-spec refresh_iq_handlers() -> any().
refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
+-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
+bounce_resource_packet(_From, #jid{lresource = <<"">>}, #presence{}) ->
+ ok;
+bounce_resource_packet(_From, #jid{lresource = <<"">>},
+ #message{type = headline}) ->
+ ok;
bounce_resource_packet(From, To, Packet) ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = xmpp:get_lang(Packet),
Txt = <<"No available resource found">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
- ejabberd_router:route(To, From, Err),
+ Err = xmpp:err_item_not_found(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err),
stop.
%%====================================================================
@@ -192,7 +205,7 @@ init([]) ->
?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
update_table(),
- mnesia:create_table(iq_response,
+ ejabberd_mnesia:create(?MODULE, iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
mnesia:add_table_copy(iq_response, node(), ram_copies),
@@ -261,50 +274,36 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+-spec do_route(jid(), jid(), stanza()) -> any().
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]),
+ Type = xmpp:get_type(Packet),
if To#jid.luser /= <<"">> ->
- ejabberd_sm:route(From, To, Packet);
- To#jid.lresource == <<"">> ->
- #xmlel{name = Name} = Packet,
- case Name of
- <<"iq">> -> process_iq(From, To, Packet);
- <<"message">> ->
- #xmlel{attrs = Attrs} = Packet,
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"headline">> -> ok;
- <<"error">> -> ok;
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end;
- <<"presence">> -> ok;
- _ -> ok
- end;
+ ejabberd_sm:route(From, To, Packet);
+ is_record(Packet, iq), To#jid.lresource == <<"">> ->
+ process_iq(From, To, Packet);
+ Type == result; Type == error ->
+ ok;
true ->
- #xmlel{attrs = Attrs} = Packet,
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- <<"result">> -> ok;
- _ ->
- ejabberd_hooks:run(local_send_to_resource_hook,
- To#jid.lserver, [From, To, Packet])
- end
+ ejabberd_hooks:run(local_send_to_resource_hook,
+ To#jid.lserver, [From, To, Packet])
end.
+-spec update_table() -> ok.
update_table() ->
case catch mnesia:table_info(iq_response, attributes) of
[id, module, function] ->
- mnesia:delete_table(iq_response);
+ mnesia:delete_table(iq_response),
+ ok;
[id, module, function, timer] ->
ok;
{'EXIT', _} ->
ok
end.
+-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of
[#iq_response{module = Module, timer = TRef,
@@ -316,9 +315,11 @@ get_iq_callback(ID) ->
error
end.
+-spec process_iq_timeout(binary()) -> any().
process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID.
+-spec process_iq_timeout() -> any().
process_iq_timeout() ->
receive
ID ->
@@ -332,6 +333,7 @@ process_iq_timeout() ->
ok
end.
+-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
diff --git a/src/ejabberd_mnesia.erl b/src/ejabberd_mnesia.erl
new file mode 100644
index 000000000..0e067ad65
--- /dev/null
+++ b/src/ejabberd_mnesia.erl
@@ -0,0 +1,169 @@
+%%%----------------------------------------------------------------------
+%%% File : mnesia_mnesia.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Handle configurable mnesia schema
+%%% Created : 17 Nov 2016 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%%% This module should be used everywhere ejabberd creates a mnesia table
+%%% to make the schema customizable without code change
+%%% Just apply this change in ejabberd modules
+%%% s/ejabberd_mnesia:create(?MODULE, /ejabberd_mnesia:create(?MODULE, /
+
+-module(ejabberd_mnesia).
+-author('christophe.romain@process-one.net').
+-export([create/3, reset/2, update/2]).
+
+-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
+-define(NEED_RESET, [local_content, type]).
+
+create(Module, Name, TabDef) ->
+ Schema = schema(Module, Name, TabDef),
+ {attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
+ case catch mnesia:table_info(Name, attributes) of
+ {'EXIT', _} ->
+ mnesia:create_table(Name, Schema);
+ Attrs ->
+ case need_reset(TabDef, Schema) of
+ true -> reset(Name, Schema);
+ false -> update(Name, Schema)
+ end;
+ OldAttrs ->
+ Fun = case lists:member({transform,1}, Module:module_info(exports)) of
+ true -> fun(Old) -> Module:transform(Old) end;
+ false -> fun(Old) -> transform(OldAttrs, Attrs, Old) end
+ end,
+ mnesia:transform_table(Name, Fun, Attrs)
+ end.
+
+reset(Name, TabDef) ->
+ mnesia:delete_table(Name),
+ ejabberd_mnesia:create(?MODULE, Name, TabDef).
+
+update(Name, TabDef) ->
+ Storage = mnesia:table_info(Name, storage_type),
+ NewStorage = lists:foldl(
+ fun({Key, _}, Acc) ->
+ case lists:member(Key, ?STORAGE_TYPES) of
+ true -> Key;
+ false -> Acc
+ end
+ end, Storage, TabDef),
+ R1 = if Storage=/=NewStorage ->
+ mnesia:change_table_copy_type(Name, node(), NewStorage);
+ true ->
+ {atomic, ok}
+ end,
+ Indexes = mnesia:table_info(Name, index),
+ NewIndexes = proplists:get_value(index, TabDef, []),
+ [mnesia:del_table_index(Name, Attr)
+ || Attr <- Indexes--NewIndexes],
+ R2 = [mnesia:add_table_index(Name, Attr)
+ || Attr <- NewIndexes--Indexes],
+ lists:foldl(
+ fun({atomic, ok}, Acc) -> Acc;
+ (Error, _Acc) -> Error
+ end, {atomic, ok}, [R1|R2]).
+
+%
+% utilities
+%
+
+schema(Module, Name, TabDef) ->
+ case parse(Module) of
+ {ok, CustomDefs} ->
+ case lists:keyfind(Name, 1, CustomDefs) of
+ {Name, CustomDef} -> merge(TabDef, CustomDef);
+ _ -> TabDef
+ end;
+ _ ->
+ TabDef
+ end.
+
+merge(TabDef, CustomDef) ->
+ {CustomKeys, _} = lists:unzip(CustomDef),
+ CleanDef = lists:foldl(
+ fun(Elem, Acc) ->
+ case lists:member(Elem, ?STORAGE_TYPES) of
+ true ->
+ lists:foldl(
+ fun(Key, CleanAcc) ->
+ lists:keydelete(Key, 1, CleanAcc)
+ end, Acc, ?STORAGE_TYPES);
+ false ->
+ Acc
+ end
+ end, TabDef, CustomKeys),
+ lists:ukeymerge(1,
+ lists:ukeysort(1, CustomDef),
+ lists:ukeysort(1, CleanDef)).
+
+parse(Module) ->
+ Path = case os:getenv("EJABBERD_SCHEMA_PATH") of
+ false ->
+ case code:priv_dir(ejabberd) of
+ {error, _} -> "schema"; % $SPOOL_DIR/schema
+ Priv -> filename:join(Priv, "schema")
+ end;
+ CustomDir ->
+ CustomDir
+ end,
+ File = filename:join(Path, atom_to_list(Module)++".mnesia"),
+ case file:consult(File) of
+ {ok, Terms} -> parse(Terms, []);
+ Error -> Error
+ end.
+
+parse([], Acc) ->
+ {ok, lists:reverse(Acc)};
+parse([{Name, Storage, TabDef}|Tail], Acc)
+ when is_atom(Name),
+ is_atom(Storage),
+ is_list(TabDef) ->
+ NewDef = case lists:member(Storage, ?STORAGE_TYPES) of
+ true -> [{Storage, [node()]} | TabDef];
+ false -> TabDef
+ end,
+ parse(Tail, [{Name, NewDef} | Acc]);
+parse([Other|_], _) ->
+ {error, {invalid, Other}}.
+
+need_reset(FromDef, ToDef) ->
+ ValuesF = [lists:keyfind(Key, 1, FromDef) || Key <- ?NEED_RESET],
+ ValuesT = [lists:keyfind(Key, 1, ToDef) || Key <- ?NEED_RESET],
+ lists:foldl(
+ fun({Val, Val}, Acc) -> Acc;
+ ({_, false}, Acc) -> Acc;
+ ({_, _}, _) -> true
+ end, false, lists:zip(ValuesF, ValuesT)).
+
+transform(OldAttrs, Attrs, Old) ->
+ [Name|OldValues] = tuple_to_list(Old),
+ Before = lists:zip(OldAttrs, OldValues),
+ After = lists:foldl(
+ fun(Attr, Acc) ->
+ case lists:keyfind(Attr, 1, Before) of
+ false -> [{Attr, undefined}|Acc];
+ Value -> [Value|Acc]
+ end
+ end, [], lists:reverse(Attrs)),
+ {Attrs, NewRecord} = lists:unzip(After),
+ list_to_tuple([Name|NewRecord]).
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index d11548c22..74e26e8da 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -51,7 +51,7 @@
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -140,11 +140,11 @@ oauth_issue_token(Jid, TTLSeconds, ScopesString) ->
case oauth2:authorize_password({Username, Server}, Scopes, admin_generated) of
{ok, {_Ctx,Authorization}} ->
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]),
- {ok, AccessToken} = oauth2_response:access_token(Response),
- {ok, VerifiedScope} = oauth2_response:scope(Response),
+ {ok, AccessToken} = oauth2_response:access_token(Response),
+ {ok, VerifiedScope} = oauth2_response:scope(Response),
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
- {error, Error} ->
- {error, Error}
+ {error, Error} ->
+ {error, Error}
end;
error ->
{error, "Invalid JID: " ++ Jid}
@@ -212,12 +212,12 @@ authenticate_user({User, Server}, Ctx) ->
allow ->
case Ctx of
{password, Password} ->
- case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
- true ->
- {ok, {Ctx, {user, User, Server}}};
- false ->
- {error, badpass}
- end;
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
+ true ->
+ {ok, {Ctx, {user, User, Server}}};
+ false ->
+ {error, badpass}
+ end;
admin_generated ->
{ok, {Ctx, {user, User, Server}}}
end;
@@ -291,7 +291,7 @@ associate_access_token(AccessToken, Context, AppContext) ->
%% It always pass the global configured value. Here we use the app context to pass the per-case
%% ttl if we want to override it.
seconds_since_epoch(ExpiresIn)
- end,
+ end,
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Scope = proplists:get_value(<<"scope">>, Context, []),
R = #oauth_token{
@@ -335,7 +335,7 @@ check_token(User, Server, ScopeList, Token) ->
LServer = jid:nameprep(Server),
case lookup(Token) of
{ok, #oauth_token{us = {LUser, LServer},
- scope = TokenScope,
+ scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
@@ -355,7 +355,7 @@ check_token(User, Server, ScopeList, Token) ->
check_token(ScopeList, Token) ->
case lookup(Token) of
{ok, #oauth_token{us = US,
- scope = TokenScope,
+ scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
@@ -367,7 +367,7 @@ check_token(ScopeList, Token) ->
ScopeList) of
true -> {ok, user, US};
false -> {false, no_matching_scope}
- end;
+ end;
true ->
{false, expired}
end;
@@ -471,7 +471,7 @@ process(_Handlers,
[{<<"href">>, <<"https://www.ejabberd.im">>},
{<<"title">>, <<"ejabberd XMPP server">>}],
<<"ejabberd">>),
- ?C(" is maintained by "),
+ ?C(<<" is maintained by ">>),
?XAC(<<"a">>,
[{<<"href">>, <<"https://www.process-one.net">>},
{<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
@@ -494,7 +494,7 @@ process(_Handlers,
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
- _ -> jlib:binary_to_integer(TTL)
+ _ -> binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
ClientId,
@@ -556,7 +556,7 @@ process(_Handlers,
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
- _ -> jlib:binary_to_integer(TTL)
+ _ -> binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
Scope,
@@ -732,7 +732,7 @@ css() ->
text-decoration: underline;
}
- .container > .section {
+ .container > .section {
background: #424A55;
}
diff --git a/src/ejabberd_oauth_mnesia.erl b/src/ejabberd_oauth_mnesia.erl
index a23f443ed..bdd2d0edd 100644
--- a/src/ejabberd_oauth_mnesia.erl
+++ b/src/ejabberd_oauth_mnesia.erl
@@ -34,7 +34,7 @@
-include("ejabberd_oauth.hrl").
init() ->
- mnesia:create_table(oauth_token,
+ ejabberd_mnesia:create(?MODULE, oauth_token,
[{disc_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
diff --git a/src/ejabberd_oauth_rest.erl b/src/ejabberd_oauth_rest.erl
index aadb97084..c932d16f5 100644
--- a/src/ejabberd_oauth_rest.erl
+++ b/src/ejabberd_oauth_rest.erl
@@ -35,7 +35,7 @@
-include("ejabberd.hrl").
-include("ejabberd_oauth.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("jid.hrl").
init() ->
rest:start(?MYNAME),
diff --git a/src/ejabberd_oauth_sql.erl b/src/ejabberd_oauth_sql.erl
index 9253335ff..3c09362c2 100644
--- a/src/ejabberd_oauth_sql.erl
+++ b/src/ejabberd_oauth_sql.erl
@@ -36,7 +36,7 @@
-include("ejabberd_oauth.hrl").
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
--include("jlib.hrl").
+-include("jid.hrl").
init() ->
ok.
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 758001239..b6f90ccf8 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -48,7 +48,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
-include("mod_roster.hrl").
@@ -196,7 +196,7 @@ format_scram_password({StoredKey, ServerKey, Salt, IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
- IterationCountBin = list_to_binary(integer_to_list(IterationCount)),
+ IterationCountBin = (integer_to_binary(IterationCount)),
<<"scram:", StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
parse_scram_password(PassData) ->
@@ -206,34 +206,31 @@ parse_scram_password(PassData) ->
storedkey = StoredKeyB64,
serverkey = ServerKeyB64,
salt = SaltB64,
- iterationcount = list_to_integer(binary_to_list(IterationCountBin))
+ iterationcount = (binary_to_integer(IterationCountBin))
}.
+-spec get_vcard(binary(), binary()) -> [xmlel()].
get_vcard(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;
- _ ->
- []
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case mod_vcard:get_vcard(LUser, LServer) of
+ error -> [];
+ Els -> Els
end.
+-spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) ->
- case mod_offline:get_offline_els(User, Server) of
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case mod_offline:get_offline_els(LUser, LServer) of
[] ->
[];
Els ->
- NewEls = lists:map(
- fun(#xmlel{attrs = Attrs} = El) ->
- NewAttrs = lists:keystore(<<"xmlns">>, 1,
- Attrs,
- {<<"xmlns">>,
- <<"jabber:client">>}),
- El#xmlel{attrs = NewAttrs}
- end, Els),
+ NewEls = lists:map(fun xmpp:encode/1, Els),
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
end.
+-spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
@@ -241,25 +238,16 @@ get_privacy(User, Server) ->
XLists = lists:map(
fun({Name, Items}) ->
XItems = lists:map(
- fun mod_privacy:item_to_xml/1, Items),
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, Name}],
- children = XItems}
+ fun mod_privacy:encode_list_item/1,
+ Items),
+ #privacy_list{name = Name, items = XItems}
end, Lists),
- DefaultEl = case Default of
- none ->
- [];
- _ ->
- [#xmlel{name = <<"default">>,
- attrs = [{<<"name">>, Default}]}]
- end,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
- children = DefaultEl ++ XLists}];
+ [xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ ->
[]
end.
+-spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) ->
JID = jid:make(User, Server, <<>>),
case mod_roster:get_roster(User, Server) of
@@ -272,18 +260,11 @@ get_roster(User, Server) ->
Status = if is_binary(Msg) -> (Msg);
true -> <<"">>
end,
- [#xmlel{name = <<"presence">>,
- attrs =
- [{<<"from">>,
- jid:to_string(R#roster.jid)},
- {<<"to">>, jid:to_string(JID)},
- {<<"xmlns">>, <<"jabber:client">>},
- {<<"type">>, <<"subscribe">>}],
- children =
- [#xmlel{name = <<"status">>,
- attrs = [],
- children =
- [{xmlcdata, Status}]}]}];
+ [xmpp:encode(
+ #presence{from = jid:make(R#roster.jid),
+ to = JID,
+ type = subscribe,
+ status = xmpp:mk_text(Status)})];
(_) ->
[]
end, Items),
@@ -291,21 +272,18 @@ get_roster(User, Server) ->
fun(#roster{ask = in, subscription = none}) ->
[];
(R) ->
- [mod_roster:item_to_xml(R)]
+ [mod_roster:encode_item(R)]
end, Items),
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER}],
- children = Rs} | Subs];
+ [xmpp:encode(#roster_query{items = Rs}) | Subs];
_ ->
[]
end.
+-spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) ->
case mod_private:get_data(User, Server) of
[_|_] = Els ->
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
- children = Els}];
+ [xmpp:encode(#private{xml_els = Els})];
_ ->
[]
end.
@@ -451,129 +429,124 @@ process_user_els([], State) ->
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
- case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
- {<<"query">>, ?NS_ROSTER} ->
- process_roster(El, State);
- {<<"query">>, ?NS_PRIVACY} ->
- %% Make sure <list/> elements go before <active/> and <default/>
- NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)),
- process_privacy_el(El#xmlel{children = NewEls}, State);
- {<<"query">>, ?NS_PRIVATE} ->
- process_private(El, State);
- {<<"vCard">>, ?NS_VCARD} ->
- process_vcard(El, State);
- {<<"offline-messages">>, _} ->
- process_offline_msgs(Els, State);
- {<<"presence">>, <<"jabber:client">>} ->
- process_presence(El, State);
- _ ->
- {ok, State}
+ try
+ case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
+ {<<"query">>, ?NS_ROSTER} ->
+ process_roster(xmpp:decode(El), State);
+ {<<"query">>, ?NS_PRIVACY} ->
+ %% Make sure <list/> elements go before <active/> and <default/>
+ process_privacy(xmpp:decode(El), State);
+ {<<"query">>, ?NS_PRIVATE} ->
+ process_private(xmpp:decode(El), State);
+ {<<"vCard">>, ?NS_VCARD} ->
+ process_vcard(El, State);
+ {<<"offline-messages">>, NS} ->
+ Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els],
+ process_offline_msgs(Msgs, State);
+ {<<"presence">>, ?NS_CLIENT} ->
+ process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
+ _ ->
+ {ok, State}
+ end
+ catch _:{xmpp_codec, Why} ->
+ ErrTxt = xmpp:format_error(Why),
+ stop("failed to decode XML '~s': ~s",
+ [fxml:element_to_binary(El), ErrTxt])
end.
-process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) ->
- case process_privacy(#xmlel{children = [SubEl]}, State) of
+-spec process_offline_msgs([stanza()], state()) -> {ok, state()} | {error, _}.
+process_offline_msgs([#message{} = Msg|Msgs], State) ->
+ case process_offline_msg(Msg, State) of
{ok, NewState} ->
- process_privacy_el(El#xmlel{children = SubEls}, NewState);
+ process_offline_msgs(Msgs, NewState);
Err ->
Err
end;
-process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) ->
- process_privacy_el(El#xmlel{children = SubEls}, State);
-process_privacy_el(#xmlel{children = []}, State) ->
- {ok, State}.
-
-process_offline_msgs([#xmlel{} = El|Els], State) ->
- case process_offline_msg(El, State) of
- {ok, NewState} ->
- process_offline_msgs(Els, NewState);
- Err ->
- Err
- end;
-process_offline_msgs([_|Els], State) ->
- process_offline_msgs(Els, State);
+process_offline_msgs([_|Msgs], State) ->
+ process_offline_msgs(Msgs, State);
process_offline_msgs([], State) ->
{ok, State}.
-process_roster(El, State = #state{user = U, server = S}) ->
- case mod_roster:set_items(U, S, El) of
+-spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}.
+process_roster(RosterQuery, State = #state{user = U, server = S}) ->
+ case mod_roster:set_items(U, S, RosterQuery) of
{atomic, _} ->
{ok, State};
Err ->
stop("Failed to write roster: ~p", [Err])
end.
-process_privacy(El, State = #state{user = U, server = 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 fxml:remove_cdata(Els) of
- [#xmlel{name = N}] -> N;
- _ -> undefined
- end,
- #xmlel{attrs = Attrs} = Error,
- ErrorCode = case lists:keysearch(<<"code">>, 1, Attrs) of
- {value, {_, V}} -> V;
- false -> undefined
- end,
- if
- ErrorCode == <<"404">>, Name == <<"default">> ->
- {ok, State};
- true ->
- stop("Failed to write privacy: ~p", [Err])
+-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
+process_privacy(#privacy_query{lists = Lists,
+ default = Default,
+ active = Active} = PrivacyQuery,
+ State = #state{user = U, server = S}) ->
+ JID = jid:make(U, S),
+ IQ = #iq{type = set, id = randoms:get_string(),
+ from = JID, to = JID, sub_els = [PrivacyQuery]},
+ Txt = <<"No module is handling this query">>,
+ Error = {error, xmpp:err_feature_not_implemented(Txt, ?MYLANG)},
+ case mod_privacy:process_iq_set(Error, IQ, #userlist{}) of
+ {error, #stanza_error{reason = Reason}} = Err ->
+ if Reason == 'item-not-found', Lists == [],
+ Active == undefined, Default /= undefined ->
+ %% Failed to set default list because there is no
+ %% list with such name. We shouldn't stop here.
+ {ok, State};
+ true ->
+ stop("Failed to write privacy: ~p", [Err])
end;
_ ->
{ok, State}
end.
-process_private(El, State = #state{user = U, server = S}) ->
- JID = jid:make(U, S, <<"">>),
- case mod_private:process_sm_iq(
- JID, JID, #iq{type = set, sub_el = El}) of
+-spec process_private(private(), state()) -> {ok, state()} | {error, _}.
+process_private(Private, State = #state{user = U, server = S}) ->
+ JID = jid:make(U, S),
+ IQ = #iq{type = set, id = randoms:get_string(),
+ from = JID, to = JID, sub_els = [Private]},
+ case mod_private:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
Err ->
stop("Failed to write private: ~p", [Err])
end.
+-spec process_vcard(xmlel(), state()) -> {ok, state()} | {error, _}.
process_vcard(El, State = #state{user = U, server = S}) ->
- JID = jid:make(U, S, <<"">>),
- case mod_vcard:process_sm_iq(
- JID, JID, #iq{type = set, sub_el = El}) of
+ JID = jid:make(U, S),
+ IQ = #iq{type = set, id = randoms:get_string(),
+ from = JID, to = JID, sub_els = [El]},
+ case mod_vcard:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
Err ->
stop("Failed to write vcard: ~p", [Err])
end.
-process_offline_msg(El, State = #state{user = U, server = S}) ->
- FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
- case jid:from_string(FromS) of
- #jid{} = From ->
- 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 ->
- stop("Failed to store offline message: ~p", [Err]);
- _ ->
- {ok, State}
- end;
- _ ->
- stop("Invalid 'from' = ~s", [FromS])
+-spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}.
+process_offline_msg(#message{from = undefined}, _State) ->
+ stop("No 'from' attribute found", []);
+process_offline_msg(Msg, State = #state{user = U, server = S}) ->
+ From = xmpp:get_from(Msg),
+ To = jid:make(U, S, <<>>),
+ NewMsg = xmpp:set_from_to(Msg, From, To),
+ case catch mod_offline:store_packet(From, To, NewMsg) of
+ {'EXIT', _} = Err ->
+ stop("Failed to store offline message: ~p", [Err]);
+ _ ->
+ {ok, State}
end.
-process_presence(El, #state{user = U, server = S} = State) ->
- FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
- case jid:from_string(FromS) of
- #jid{} = From ->
- To = jid:make(U, S, <<>>),
- NewEl = jlib:replace_from_to(From, To, El),
- ejabberd_router:route(From, To, NewEl),
- {ok, State};
- _ ->
- stop("Invalid 'from' = ~s", [FromS])
- end.
+-spec process_presence(presence(), state()) -> {ok, state()} | {error, _}.
+process_presence(#presence{from = undefined}, _State) ->
+ stop("No 'from' attribute found", []);
+process_presence(Pres, #state{user = U, server = S} = State) ->
+ From = xmpp:get_from(Pres),
+ To = jid:make(U, S, <<>>),
+ NewPres = xmpp:set_from_to(Pres, From, To),
+ ejabberd_router:route(From, To, NewPres),
+ {ok, State}.
stop(Fmt, Args) ->
?ERROR_MSG(Fmt, Args),
@@ -581,9 +554,8 @@ stop(Fmt, Args) ->
make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
- list_to_binary(
- io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
- [Year, Month, Day, Hour, Minute, Second])).
+ str:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
+ [Year, Month, Day, Hour, Minute, Second]).
make_main_basefilename(Dir, FnT) ->
Filename2 = <<FnT/binary, ".xml">>,
diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl
index 575810acc..44628d1c2 100644
--- a/src/ejabberd_riak.erl
+++ b/src/ejabberd_riak.erl
@@ -428,7 +428,7 @@ map_key(Obj, _, _) ->
<<"b_", B/binary>> ->
B;
<<"i_", B/binary>> ->
- list_to_integer(binary_to_list(B));
+ (binary_to_integer(B));
B ->
erlang:binary_to_term(B)
end].
@@ -483,7 +483,7 @@ encode_index_key(Idx, Key) ->
encode_key(Bin) when is_binary(Bin) ->
<<"b_", Bin/binary>>;
encode_key(Int) when is_integer(Int) ->
- <<"i_", (list_to_binary(integer_to_list(Int)))/binary>>;
+ <<"i_", ((integer_to_binary(Int)))/binary>>;
encode_key(Term) ->
erlang:term_to_binary(Term).
@@ -519,7 +519,7 @@ log_error(_, _, _) ->
ok.
make_invalid_object(Val) ->
- list_to_binary(io_lib:fwrite("Invalid object: ~p", [Val])).
+ (str:format("Invalid object: ~p", [Val])).
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index e29d6acfb..33093abb0 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -39,6 +39,7 @@
register_route/3,
register_routes/1,
host_of_route/1,
+ process_iq/3,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@@ -53,7 +54,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
@@ -71,7 +72,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
--spec route(jid(), jid(), xmlel()) -> ok.
+-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -84,13 +85,21 @@ route(From, To, Packet) ->
%% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1
--spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
+-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
+ (jid(), jid(), stanza(), stanza_error()) -> ok.
-route_error(From, To, ErrPacket, OrigPacket) ->
+route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
+ end;
+route_error(From, To, Packet, #stanza_error{} = Err) ->
+ Type = xmpp:get_type(Packet),
+ if Type == error; Type == result ->
+ ok;
+ true ->
+ ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
end.
-spec register_route(binary()) -> term().
@@ -236,6 +245,28 @@ host_of_route(Domain) ->
end
end.
+-spec process_iq(jid(), jid(), iq() | xmlel()) -> any().
+process_iq(From, To, #iq{} = IQ) ->
+ if To#jid.luser == <<"">> ->
+ ejabberd_local:process_iq(From, To, IQ);
+ true ->
+ ejabberd_sm:process_iq(From, To, IQ)
+ end;
+process_iq(From, To, El) ->
+ try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
+ IQ -> process_iq(From, To, IQ)
+ catch _:{xmpp_codec, Why} ->
+ Type = xmpp:get_type(El),
+ if Type == <<"get">>; Type == <<"set">> ->
+ Txt = xmpp:format_error(Why),
+ Lang = xmpp:get_lang(El),
+ Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
+ ejabberd_router:route(To, From, Err);
+ true ->
+ ok
+ end
+ end.
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -249,7 +280,7 @@ host_of_route(Domain) ->
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
- mnesia:create_table(route,
+ ejabberd_mnesia:create(?MODULE, route,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, route)}]),
@@ -347,6 +378,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~p~n",
@@ -357,69 +389,80 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
{From, To, Packet} ->
LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of
- [] -> ejabberd_s2s:route(From, To, Packet);
+ [] ->
+ try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
+ Pkt ->
+ ejabberd_s2s:route(From, To, Pkt)
+ catch _:{xmpp_codec, Why} ->
+ log_decoding_error(From, To, Packet, Why)
+ end;
[R] ->
- Pid = R#route.pid,
- if node(Pid) == node() ->
- case R#route.local_hint of
- {apply, Module, Function} ->
- Module:Function(From, To, Packet);
- _ -> Pid ! {route, From, To, Packet}
- end;
- is_pid(Pid) -> Pid ! {route, From, To, Packet};
- true -> drop
- end;
+ do_route(From, To, Packet, R);
Rs ->
- Value = case
- ejabberd_config:get_option({domain_balancing,
- LDstDomain}, fun(D) when is_atom(D) -> D end)
- of
- undefined -> p1_time_compat:monotonic_time();
- random -> p1_time_compat:monotonic_time();
- source -> jid:tolower(From);
- destination -> jid:tolower(To);
- bare_source ->
- jid:remove_resource(jid:tolower(From));
- bare_destination ->
- jid:remove_resource(jid:tolower(To))
- end,
+ Value = get_domain_balancing(From, To, LDstDomain),
case get_component_number(LDstDomain) of
undefined ->
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
- Pid = R#route.pid,
- if is_pid(Pid) -> Pid ! {route, From, To, Packet};
- true -> drop
- end;
+ do_route(From, To, Packet, R);
LRs ->
- R = lists:nth(erlang:phash(Value, length(LRs)),
- LRs),
- Pid = R#route.pid,
- case R#route.local_hint of
- {apply, Module, Function} ->
- Module:Function(From, To, Packet);
- _ -> Pid ! {route, From, To, Packet}
- end
+ R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
+ do_route(From, To, Packet, R)
end;
_ ->
SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
- Pid = R#route.pid,
- if is_pid(Pid) -> Pid ! {route, From, To, Packet};
- true -> drop
- end
+ do_route(From, To, Packet, R)
end
end;
drop -> ok
end.
+-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
+do_route(From, To, Packet, #route{local_hint = LocalHint,
+ pid = Pid}) when is_pid(Pid) ->
+ try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
+ Pkt ->
+ case LocalHint of
+ {apply, Module, Function} when node(Pid) == node() ->
+ Module:Function(From, To, Pkt);
+ _ ->
+ Pid ! {route, From, To, Pkt}
+ end
+ catch error:{xmpp_codec, Why} ->
+ log_decoding_error(From, To, Packet, Why)
+ end;
+do_route(_From, _To, _Packet, _Route) ->
+ drop.
+
+-spec log_decoding_error(jid(), jid(), xmlel() | xmpp_element(), term()) -> ok.
+log_decoding_error(From, To, Packet, Reason) ->
+ ?ERROR_MSG("failed to decode xml element ~p when "
+ "routing from ~s to ~s: ~s",
+ [Packet, jid:to_string(From), jid:to_string(To),
+ xmpp:format_error(Reason)]).
+
+-spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) ->
ejabberd_config:get_option(
{domain_balancing_component_number, LDomain},
fun(N) when is_integer(N), N > 1 -> N end,
undefined).
+-spec get_domain_balancing(jid(), jid(), binary()) -> any().
+get_domain_balancing(From, To, LDomain) ->
+ case ejabberd_config:get_option(
+ {domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
+ undefined -> p1_time_compat:monotonic_time();
+ random -> p1_time_compat:monotonic_time();
+ source -> jid:tolower(From);
+ destination -> jid:tolower(To);
+ bare_source -> jid:remove_resource(jid:tolower(From));
+ bare_destination -> jid:remove_resource(jid:tolower(To))
+ end.
+
+-spec update_tables() -> ok.
update_tables() ->
try
mnesia:transform_table(route, ignore, record_info(fields, route))
diff --git a/src/ejabberd_router_multicast.erl b/src/ejabberd_router_multicast.erl
index fa32c8ed7..c7a190670 100644
--- a/src/ejabberd_router_multicast.erl
+++ b/src/ejabberd_router_multicast.erl
@@ -43,9 +43,10 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
--record(route_multicast, {domain, pid}).
+-record(route_multicast, {domain = <<"">> :: binary() | '_',
+ pid = self() :: pid()}).
-record(state, {}).
%%====================================================================
@@ -58,7 +59,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
+-spec route_multicast(jid(), binary(), [jid()], stanza()) -> ok.
route_multicast(From, Domain, Destinations, Packet) ->
case catch do_route(From, Domain, Destinations, Packet) of
{'EXIT', Reason} ->
@@ -68,6 +69,7 @@ route_multicast(From, Domain, Destinations, Packet) ->
ok
end.
+-spec register_route(binary()) -> any().
register_route(Domain) ->
case jid:nameprep(Domain) of
error ->
@@ -81,6 +83,7 @@ register_route(Domain) ->
mnesia:transaction(F)
end.
+-spec unregister_route(binary()) -> any().
unregister_route(Domain) ->
case jid:nameprep(Domain) of
error ->
@@ -112,7 +115,7 @@ unregister_route(Domain) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
- mnesia:create_table(route_multicast,
+ ejabberd_mnesia:create(?MODULE, route_multicast,
[{ram_copies, [node()]},
{type, bag},
{attributes,
@@ -206,6 +209,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%% From = #jid
%% Destinations = [#jid]
+-spec do_route(jid(), binary(), [jid()], stanza()) -> any().
do_route(From, Domain, Destinations, Packet) ->
?DEBUG("route_multicast~n\tfrom ~s~n\tdomain ~s~n\tdestinations ~p~n\tpacket ~p~n",
@@ -226,6 +230,7 @@ do_route(From, Domain, Destinations, Packet) ->
Pid ! {route_trusted, From, Destinations, Packet}
end.
+-spec pick_multicast_pid([#route_multicast{}]) -> pid().
pick_multicast_pid(Rs) ->
List = case [R || R <- Rs, node(R#route_multicast.pid) == node()] of
[] -> Rs;
@@ -233,5 +238,6 @@ pick_multicast_pid(Rs) ->
end,
(hd(List))#route_multicast.pid.
+-spec do_route_normal(jid(), [jid()], stanza()) -> any().
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 2a17c75cb..4df1761cb 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -39,6 +39,7 @@
remove_connection/2, find_connection/2,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
+ stop_all_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
@@ -55,7 +56,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_commands.hrl").
@@ -89,7 +90,7 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
--spec route(jid(), jid(), xmlel()) -> ok.
+-spec route(jid(), jid(), xmpp_element()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -222,6 +223,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
{error, <<"Cannot get peer certificate">>}
end.
+-spec make_key({binary(), binary()}, binary()) -> binary().
make_key({From, To}, StreamID) ->
Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end),
p1_sha:to_hexlist(
@@ -234,14 +236,14 @@ make_key({From, To}, StreamID) ->
init([]) ->
update_tables(),
- mnesia:create_table(s2s,
+ ejabberd_mnesia:create(?MODULE, 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(get_commands_spec()),
- mnesia:create_table(temporarily_blocked,
+ ejabberd_mnesia:create(?MODULE, temporarily_blocked,
[{ram_copies, [node()]},
{attributes, record_info(fields, temporarily_blocked)}]),
{ok, #state{}}.
@@ -275,7 +277,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-
+-spec clean_table_from_bad_node(node()) -> any().
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
@@ -289,6 +291,7 @@ clean_table_from_bad_node(Node) ->
end,
mnesia:async_dirty(F).
+-spec do_route(jid(), jid(), stanza()) -> ok | false.
do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
@@ -296,28 +299,16 @@ do_route(From, To, Packet) ->
case find_connection(From, To) of
{atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
- #xmlel{name = Name, attrs = Attrs, children = Els} =
- Packet,
- NewAttrs =
- 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]),
- send_element(Pid,
- #xmlel{name = Name, attrs = NewAttrs, children = Els}),
+ send_element(Pid, xmpp:set_from_to(Packet, From, To)),
ok;
{aborted, _Reason} ->
- case fxml:get_tag_attr_s(<<"type">>, Packet) of
- <<"error">> -> ok;
- <<"result">> -> ok;
- _ ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"No s2s connection found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
- end,
+ Lang = xmpp:get_lang(Packet),
+ Txt = <<"No s2s connection found">>,
+ Err = xmpp:err_service_unavailable(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err),
false
end.
@@ -367,9 +358,11 @@ find_connection(From, To) ->
end
end.
+-spec choose_connection(jid(), [#s2s{}]) -> pid().
choose_connection(From, Connections) ->
choose_pid(From, [C#s2s.pid || C <- Connections]).
+-spec choose_pid(jid(), [pid()]) -> pid().
choose_pid(From, Pids) ->
Pids1 = case [P || P <- Pids, node(P) == node()] of
[] -> Pids;
@@ -417,22 +410,21 @@ new_connection(MyServer, Server, From, FromTo,
end,
TRes.
+-spec max_s2s_connections_number({binary(), binary()}) -> integer().
max_s2s_connections_number({From, To}) ->
- case acl:match_rule(From, max_s2s_connections,
- jid:make(<<"">>, To, <<"">>))
- of
+ case acl:match_rule(From, max_s2s_connections, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
+-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer().
max_s2s_connections_number_per_node({From, To}) ->
- case acl:match_rule(From, max_s2s_connections_per_node,
- jid:make(<<"">>, To, <<"">>))
- of
+ case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
+-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer().
needed_connections_number(Ls, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode) ->
LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()],
@@ -444,6 +436,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
%% Description: Return true if the destination must be considered as a
%% service.
%% --------------------------------------------------------------------
+-spec is_service(jid(), jid()) -> boolean().
is_service(From, To) ->
LFromDomain = From#jid.lserver,
case ejabberd_config:get_option(
@@ -475,18 +468,24 @@ send_element(Pid, El) ->
get_commands_spec() ->
[#ejabberd_commands{
name = incoming_s2s_number,
- tags = [stats, s2s],
+ tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
- policy = admin,
- module = ?MODULE, function = incoming_s2s_number,
- args = [], result = {s2s_incoming, integer}},
+ policy = admin,
+ module = ?MODULE, function = incoming_s2s_number,
+ args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
name = outgoing_s2s_number,
- tags = [stats, s2s],
+ tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
- policy = admin,
- module = ?MODULE, function = outgoing_s2s_number,
- args = [], result = {s2s_outgoing, integer}}].
+ policy = admin,
+ module = ?MODULE, function = outgoing_s2s_number,
+ args = [], result = {s2s_outgoing, integer}},
+ #ejabberd_commands{name = stop_all_connections,
+ tags = [s2s],
+ desc = "Stop all outgoing and incoming connections",
+ policy = admin,
+ module = ?MODULE, function = stop_all_connections,
+ args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
@@ -502,6 +501,15 @@ supervisor_count(Supervisor) ->
length(Result)
end.
+stop_all_connections() ->
+ lists:foreach(
+ fun({_Id, Pid, _Type, _Module}) ->
+ exit(Pid, kill)
+ end,
+ supervisor:which_children(ejabberd_s2s_in_sup) ++
+ supervisor:which_children(ejabberd_s2s_out_sup)),
+ mnesia:clear_table(s2s).
+
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
@@ -547,7 +555,7 @@ allow_host1(MyHost, S2SHost) ->
s2s_access,
fun(A) -> A end,
all),
- JID = jid:make(<<"">>, S2SHost, <<"">>),
+ JID = jid:make(S2SHost),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
allow ->
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index d8d0a400a..395a0fce7 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -42,7 +42,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(DICT, dict).
@@ -62,40 +62,19 @@
connections = (?DICT):new() :: ?TDICT,
timer = make_ref() :: reference()}).
-%-define(DBGFSM, true).
+-type state_name() :: wait_for_stream | wait_for_feature_request | stream_established.
+-type state() :: #state{}.
+-type fsm_next() :: {next_state, state_name(), state()}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
+%%-define(DBGFSM, true).
-ifdef(DBGFSM).
-
-define(FSMOPTS, [{debug, [trace]}]).
-
-else.
-
-define(FSMOPTS, []).
-
-endif.
--define(STREAM_HEADER(Version),
- <<"<?xml version='1.0'?><stream:stream "
- "xmlns:stream='http://etherx.jabber.org/stream"
- "s' xmlns='jabber:server' xmlns:db='jabber:ser"
- "ver:dialback' id='",
- (StateData#state.streamid)/binary, "'", Version/binary,
- ">">>).
-
--define(STREAM_TRAILER, <<"</stream:stream>">>).
-
--define(INVALID_NAMESPACE_ERR,
- fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-
--define(HOST_UNKNOWN_ERR,
- fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-
--define(INVALID_FROM_ERR,
- fxml:element_to_binary(?SERR_INVALID_FROM)).
-
--define(INVALID_XML_ERR,
- fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-
start(SockData, Opts) ->
supervisor:start_child(ejabberd_s2s_in_sup,
[SockData, Opts]).
@@ -185,351 +164,294 @@ init([{SockMod, Socket}, Opts]) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-
-wait_for_stream({xmlstreamstart, _Name, Attrs},
- StateData) ->
- 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
- not StateData#state.authenticated ->
- send_text(StateData,
- ?STREAM_HEADER(<<" version='1.0'">>)),
- Auth = if StateData#state.tls_enabled ->
- 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,
- StateData#state.socket,
- From),
- {Result, From, Message};
- _ ->
- {error, <<"(unknown)">>,
- <<"Got no valid 'from' attribute">>}
- end;
- true ->
- {no_verify, <<"(unknown)">>,
- <<"TLS not (yet) enabled">>}
- end,
- StartTLS = if StateData#state.tls_enabled -> [];
- not StateData#state.tls_enabled and
+wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
+ try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
+ #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
+ when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_invalid_namespace()),
+ {stop, normal, StateData};
+ #stream_start{to = #jid{lserver = Server},
+ from = From, version = {1,0}}
+ when StateData#state.tls and not StateData#state.authenticated ->
+ send_header(StateData, {1,0}),
+ Auth = if StateData#state.tls_enabled ->
+ case From of
+ #jid{} ->
+ {Result, Message} =
+ ejabberd_s2s:check_peer_certificate(
+ StateData#state.sockmod,
+ StateData#state.socket,
+ From#jid.lserver),
+ {Result, From#jid.lserver, Message};
+ undefined ->
+ {error, <<"(unknown)">>,
+ <<"Got no valid 'from' attribute">>}
+ end;
+ true ->
+ {no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
+ end,
+ StartTLS = if StateData#state.tls_enabled -> [];
+ not StateData#state.tls_enabled and
not StateData#state.tls_required ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = []}];
- not StateData#state.tls_enabled and
+ [#starttls{required = false}];
+ not StateData#state.tls_enabled and
StateData#state.tls_required ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children =
- [#xmlel{name = <<"required">>,
- attrs = [], children = []}]}]
- end,
- case Auth of
- {error, RemoteServer, CertError}
- when StateData#state.tls_certverify ->
- ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
- [StateData#state.server, RemoteServer, CertError]),
- send_text(StateData,
- <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
- CertError)))/binary,
- (?STREAM_TRAILER)/binary>>),
- {stop, normal, StateData};
- {VerifyResult, RemoteServer, Msg} ->
- {SASL, NewStateData} = case VerifyResult of
- ok ->
- {[#xmlel{name = <<"mechanisms">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = <<"mechanism">>,
- attrs = [],
- children =
- [{xmlcdata,
- <<"EXTERNAL">>}]}]}],
- StateData#state{auth_domain = RemoteServer}};
- error ->
- ?DEBUG("Won't accept certificate of ~s: ~s",
- [RemoteServer, Msg]),
- {[], StateData};
- no_verify ->
- {[], StateData}
- end,
- send_element(NewStateData,
- #xmlel{name = <<"stream:features">>, attrs = [],
- children =
- SASL ++
- StartTLS ++
- ejabberd_hooks:run_fold(s2s_stream_features,
- Server, [],
- [Server])}),
- {next_state, wait_for_feature_request,
- NewStateData#state{server = Server}}
- end;
- {<<"jabber:server">>, _, Server, true}
- when StateData#state.authenticated ->
- send_text(StateData,
- ?STREAM_HEADER(<<" version='1.0'">>)),
- send_element(StateData,
- #xmlel{name = <<"stream:features">>, attrs = [],
- children =
- ejabberd_hooks:run_fold(s2s_stream_features,
- Server, [],
- [Server])}),
- {next_state, stream_established, StateData};
- {<<"jabber:server">>, <<"jabber:server:dialback">>,
- _Server, _} when
- (StateData#state.tls_required and StateData#state.tls_enabled)
- or (not StateData#state.tls_required) ->
- send_text(StateData, ?STREAM_HEADER(<<"">>)),
- {next_state, stream_established, StateData};
- _ ->
- send_text(StateData, ?INVALID_NAMESPACE_ERR),
- {stop, normal, StateData}
+ [#starttls{required = true}]
+ end,
+ case Auth of
+ {error, RemoteServer, CertError}
+ when StateData#state.tls_certverify ->
+ ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
+ [StateData#state.server, RemoteServer, CertError]),
+ send_element(StateData,
+ xmpp:serr_policy_violation(CertError, ?MYLANG)),
+ {stop, normal, StateData};
+ {VerifyResult, RemoteServer, Msg} ->
+ {SASL, NewStateData} =
+ case VerifyResult of
+ ok ->
+ {[#sasl_mechanisms{list = [<<"EXTERNAL">>]}],
+ StateData#state{auth_domain = RemoteServer}};
+ error ->
+ ?DEBUG("Won't accept certificate of ~s: ~s",
+ [RemoteServer, Msg]),
+ {[], StateData};
+ no_verify ->
+ {[], StateData}
+ end,
+ send_element(NewStateData,
+ #stream_features{
+ sub_els = SASL ++ StartTLS ++
+ ejabberd_hooks:run_fold(
+ s2s_stream_features, Server, [],
+ [Server])}),
+ {next_state, wait_for_feature_request,
+ NewStateData#state{server = Server}}
+ end;
+ #stream_start{to = #jid{lserver = Server},
+ version = {1,0}} when StateData#state.authenticated ->
+ send_header(StateData, {1,0}),
+ send_element(StateData,
+ #stream_features{
+ sub_els = ejabberd_hooks:run_fold(
+ s2s_stream_features, Server, [],
+ [Server])}),
+ {next_state, stream_established, StateData};
+ #stream_start{db_xmlns = ?NS_SERVER_DIALBACK}
+ when (StateData#state.tls_required and StateData#state.tls_enabled)
+ or (not StateData#state.tls_required) ->
+ send_header(StateData, undefined),
+ {next_state, stream_established, StateData};
+ #stream_start{} ->
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_undefined_condition()),
+ {stop, normal, StateData};
+ _ ->
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {stop, normal, StateData}
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
+ {stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?STREAM_HEADER(<<"">>))/binary,
- (?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>),
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
+ send_header(StateData, {1,0}),
+ send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
-wait_for_feature_request({xmlstreamelement, El},
- StateData) ->
- #xmlel{name = Name, attrs = Attrs} = El,
- TLS = StateData#state.tls,
- TLSEnabled = StateData#state.tls_enabled,
- SockMod =
- (StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
- {?NS_TLS, <<"starttls">>}
- when TLS == true, TLSEnabled == false,
- SockMod == gen_tcp ->
- ?DEBUG("starttls", []),
- Socket = StateData#state.socket,
- TLSOpts1 = case
- ejabberd_config:get_option(
- {domain_certfile, StateData#state.server},
- fun iolist_to_binary/1) of
- undefined -> StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile} | lists:keydelete(certfile, 1,
- StateData#state.tls_options)]
- end,
- TLSOpts = case ejabberd_config:get_option(
- {s2s_tls_compression, StateData#state.server},
- fun(true) -> true;
- (false) -> false
- end, false) of
- true -> lists:delete(compression_none, TLSOpts1);
- false -> [compression_none | TLSOpts1]
- end,
- TLSSocket = (StateData#state.sockmod):starttls(Socket,
- TLSOpts,
- fxml:element_to_binary(#xmlel{name
- =
- <<"proceed">>,
- attrs
- =
- [{<<"xmlns">>,
- ?NS_TLS}],
- children
- =
- []})),
- {next_state, wait_for_stream,
- StateData#state{socket = TLSSocket, streamid = new_id(),
- tls_enabled = true, tls_options = TLSOpts}};
- {?NS_SASL, <<"auth">>} when TLSEnabled ->
- Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
- case Mech of
- <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
- AuthDomain = StateData#state.auth_domain,
- AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
- AuthDomain),
- if AllowRemoteHost ->
- (StateData#state.sockmod):reset_stream(StateData#state.socket),
- send_element(StateData,
- #xmlel{name = <<"success">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = []}),
- ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
- [AuthDomain, StateData#state.tls_enabled]),
- change_shaper(StateData, <<"">>,
- jid:make(<<"">>, AuthDomain, <<"">>)),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true}};
- true ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = []}),
- send_text(StateData, ?STREAM_TRAILER),
- {stop, normal, StateData}
- end;
- _ ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children =
- [#xmlel{name = <<"invalid-mechanism">>,
- attrs = [], children = []}]}),
- {stop, normal, StateData}
- end;
- _ ->
- stream_established({xmlstreamelement, El}, StateData)
+wait_for_feature_request({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_feature_request, StateData);
+wait_for_feature_request(#starttls{},
+ #state{tls = true, tls_enabled = false} = StateData) ->
+ case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
+ gen_tcp ->
+ ?DEBUG("starttls", []),
+ Socket = StateData#state.socket,
+ TLSOpts1 = case
+ ejabberd_config:get_option(
+ {domain_certfile, StateData#state.server},
+ fun iolist_to_binary/1) of
+ undefined -> StateData#state.tls_options;
+ CertFile ->
+ lists:keystore(certfile, 1,
+ StateData#state.tls_options,
+ {certfile, CertFile})
+ end,
+ TLSOpts2 = case ejabberd_config:get_option(
+ {s2s_cafile, StateData#state.server},
+ fun iolist_to_binary/1) of
+ undefined -> TLSOpts1;
+ CAFile ->
+ lists:keystore(cafile, 1, TLSOpts1,
+ {cafile, CAFile})
+ end,
+ TLSOpts = case ejabberd_config:get_option(
+ {s2s_tls_compression, StateData#state.server},
+ fun(true) -> true;
+ (false) -> false
+ end, false) of
+ true -> lists:delete(compression_none, TLSOpts2);
+ false -> [compression_none | TLSOpts2]
+ end,
+ TLSSocket = (StateData#state.sockmod):starttls(
+ Socket, TLSOpts,
+ fxml:element_to_binary(
+ xmpp:encode(#starttls_proceed{}))),
+ {next_state, wait_for_stream,
+ StateData#state{socket = TLSSocket, streamid = new_id(),
+ tls_enabled = true, tls_options = TLSOpts}};
+ _ ->
+ send_element(StateData, #starttls_failure{}),
+ {stop, normal, StateData}
end;
-wait_for_feature_request({xmlstreamend, _Name},
- StateData) ->
- send_text(StateData, ?STREAM_TRAILER),
+wait_for_feature_request(#sasl_auth{mechanism = Mech},
+ #state{tls_enabled = true} = StateData) ->
+ case Mech of
+ <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
+ AuthDomain = StateData#state.auth_domain,
+ AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain),
+ if AllowRemoteHost ->
+ (StateData#state.sockmod):reset_stream(StateData#state.socket),
+ send_element(StateData, #sasl_success{}),
+ ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
+ [AuthDomain, StateData#state.tls_enabled]),
+ change_shaper(StateData, <<"">>, jid:make(AuthDomain)),
+ {next_state, wait_for_stream,
+ StateData#state{streamid = new_id(),
+ authenticated = true}};
+ true ->
+ Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
+ send_element(StateData,
+ #sasl_failure{reason = 'not-authorized',
+ text = Txt}),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}),
+ {stop, normal, StateData}
+ end;
+wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
-wait_for_feature_request({xmlstreamerror, _},
- StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
+wait_for_feature_request({xmlstreamerror, _}, StateData) ->
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired,
+ tls_enabled = TLSEnabled} = StateData)
+ when TLSRequired and not TLSEnabled ->
+ Txt = <<"Use of STARTTLS required">>,
+ send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)),
+ {stop, normal, StateData};
+wait_for_feature_request(El, StateData) ->
+ stream_established({xmlstreamelement, El}, StateData).
stream_established({xmlstreamelement, El}, StateData) ->
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- case is_key_packet(El) of
- {key, To, From, Id, Key} ->
- ?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
- LTo = jid:nameprep(To),
- LFrom = jid:nameprep(From),
- case {ejabberd_s2s:allow_host(LTo, LFrom),
- lists:member(LTo,
- ejabberd_router:dirty_get_all_domains())}
- of
- {true, true} ->
- ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
- ejabberd_s2s_out:start(LTo, LFrom,
- {verify, self(), Key,
- StateData#state.streamid}),
- Conns = (?DICT):store({LFrom, LTo},
- wait_for_verification,
- StateData#state.connections),
- change_shaper(StateData, LTo,
- jid:make(<<"">>, LFrom, <<"">>)),
- {next_state, stream_established,
- StateData#state{connections = Conns, timer = Timer}};
- {_, false} ->
- send_text(StateData, ?HOST_UNKNOWN_ERR),
- {stop, normal, StateData};
- {false, _} ->
- send_text(StateData, ?INVALID_FROM_ERR),
- {stop, normal, StateData}
- end;
- {verify, To, From, Id, Key} ->
- ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
- 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">>,
- attrs =
- [{<<"from">>, To}, {<<"to">>, From},
- {<<"id">>, Id}, {<<"type">>, Type}],
- children = []}),
- {next_state, stream_established,
- StateData#state{timer = Timer}};
- _ ->
- NewEl = jlib:remove_attr(<<"xmlns">>, El),
- #xmlel{name = Name, attrs = Attrs} = NewEl,
- 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,
- if StateData#state.authenticated ->
- case LFrom == StateData#state.auth_domain andalso
- lists:member(LTo,
- ejabberd_router:dirty_get_all_domains())
- of
- true ->
- if (Name == <<"iq">>) or (Name == <<"message">>)
- or (Name == <<"presence">>) ->
- ejabberd_hooks:run(s2s_receive_packet, LTo,
- [From, To, NewEl]),
- ejabberd_router:route(From, To, NewEl);
- true -> error
- end;
- false -> error
- end;
- true ->
- case (?DICT):find({LFrom, LTo},
- StateData#state.connections)
- of
- {ok, established} ->
- if (Name == <<"iq">>) or (Name == <<"message">>)
- or (Name == <<"presence">>) ->
- ejabberd_hooks:run(s2s_receive_packet, LTo,
- [From, To, NewEl]),
- ejabberd_router:route(From, To, NewEl);
- true -> error
- end;
- _ -> error
- end
- end;
- true -> error
- end,
- ejabberd_hooks:run(s2s_loop_debug,
- [{xmlstreamelement, El}]),
- {next_state, stream_established,
- StateData#state{timer = Timer}}
+ decode_element(El, stream_established, StateData#state{timer = Timer});
+stream_established(#db_result{to = To, from = From, key = Key},
+ StateData) ->
+ ?DEBUG("GET KEY: ~p", [{To, From, Key}]),
+ case {ejabberd_s2s:allow_host(To, From),
+ lists:member(To, ejabberd_router:dirty_get_all_domains())} of
+ {true, true} ->
+ ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
+ ejabberd_s2s_out:start(To, From,
+ {verify, self(), Key,
+ StateData#state.streamid}),
+ Conns = (?DICT):store({From, To},
+ wait_for_verification,
+ StateData#state.connections),
+ change_shaper(StateData, To, jid:make(From)),
+ {next_state, stream_established,
+ StateData#state{connections = Conns}};
+ {_, false} ->
+ send_element(StateData, xmpp:serr_host_unknown()),
+ {stop, normal, StateData};
+ {false, _} ->
+ send_element(StateData, xmpp:serr_invalid_from()),
+ {stop, normal, StateData}
end;
+stream_established(#db_verify{to = To, from = From, id = Id, key = Key},
+ StateData) ->
+ ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
+ Type = case ejabberd_s2s:make_key({To, From}, Id) of
+ Key -> valid;
+ _ -> invalid
+ end,
+ send_element(StateData,
+ #db_verify{from = To, to = From, id = Id, type = Type}),
+ {next_state, stream_established, StateData};
+stream_established(Pkt, StateData) when ?is_stanza(Pkt) ->
+ From = xmpp:get_from(Pkt),
+ To = xmpp:get_to(Pkt),
+ if To /= undefined, From /= undefined ->
+ LFrom = From#jid.lserver,
+ LTo = To#jid.lserver,
+ if StateData#state.authenticated ->
+ case LFrom == StateData#state.auth_domain andalso
+ lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of
+ true ->
+ ejabberd_hooks:run(s2s_receive_packet, LTo,
+ [From, To, Pkt]),
+ ejabberd_router:route(From, To, Pkt);
+ false ->
+ send_error(StateData, Pkt, xmpp:err_not_authorized())
+ end;
+ true ->
+ case (?DICT):find({LFrom, LTo}, StateData#state.connections) of
+ {ok, established} ->
+ ejabberd_hooks:run(s2s_receive_packet, LTo,
+ [From, To, Pkt]),
+ ejabberd_router:route(From, To, Pkt);
+ _ ->
+ send_error(StateData, Pkt, xmpp:err_not_authorized())
+ end
+ end;
+ true ->
+ send_error(StateData, Pkt, xmpp:err_jid_malformed())
+ end,
+ ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
+ {next_state, stream_established, StateData};
stream_established({valid, From, To}, StateData) ->
send_element(StateData,
- #xmlel{name = <<"db:result">>,
- attrs =
- [{<<"from">>, To}, {<<"to">>, From},
- {<<"type">>, <<"valid">>}],
- children = []}),
+ #db_result{from = To, to = From, type = valid}),
?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
[From, StateData#state.tls_enabled]),
- LFrom = jid:nameprep(From),
- LTo = jid:nameprep(To),
NSD = StateData#state{connections =
- (?DICT):store({LFrom, LTo}, established,
+ (?DICT):store({From, To}, established,
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({invalid, From, To}, StateData) ->
send_element(StateData,
- #xmlel{name = <<"db:result">>,
- attrs =
- [{<<"from">>, To}, {<<"to">>, From},
- {<<"type">>, <<"invalid">>}],
- children = []}),
- LFrom = jid:nameprep(From),
- LTo = jid:nameprep(To),
+ #db_result{from = To, to = From, type = invalid}),
NSD = StateData#state{connections =
- (?DICT):erase({LFrom, LTo},
+ (?DICT):erase({From, To},
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
stream_established(timeout, StateData) ->
+ send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
stream_established(closed, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+stream_established(Pkt, StateData) ->
+ ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
+ {next_state, stream_established, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
@@ -589,8 +511,14 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
-handle_info({timeout, Timer, _}, _StateName,
+handle_info({timeout, Timer, _}, StateName,
#state{timer = Timer} = StateData) ->
+ if StateName == wait_for_stream ->
+ send_header(StateData, undefined);
+ true ->
+ ok
+ end,
+ send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -603,6 +531,7 @@ terminate(Reason, _StateName, StateData) ->
|| Host <- get_external_hosts(StateData)];
_ -> ok
end,
+ catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -621,39 +550,55 @@ print_state(State) -> State.
%%% Internal functions
%%%----------------------------------------------------------------------
+-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,
Text).
+-spec send_element(state(), xmpp_element()) -> ok.
send_element(StateData, El) ->
- send_text(StateData, fxml:element_to_binary(El)).
+ El1 = xmpp:encode(El, ?NS_SERVER),
+ send_text(StateData, fxml:element_to_binary(El1)).
+
+-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
+send_error(StateData, Stanza, Error) ->
+ Type = xmpp:get_type(Stanza),
+ if Type == error; Type == result;
+ Type == <<"error">>; Type == <<"result">> ->
+ ok;
+ true ->
+ send_element(StateData, xmpp:make_error(Stanza, Error))
+ end.
+-spec send_trailer(state()) -> ok.
+send_trailer(StateData) ->
+ send_text(StateData, <<"</stream:stream>">>).
+
+-spec send_header(state(), undefined | {integer(), integer()}) -> ok.
+send_header(StateData, Version) ->
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_SERVER,
+ stream_xmlns = ?NS_STREAM,
+ db_xmlns = ?NS_SERVER_DIALBACK,
+ id = StateData#state.streamid,
+ version = Version}),
+ send_text(StateData, fxml:element_to_header(Header)).
+
+-spec change_shaper(state(), binary(), jid()) -> ok.
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper,
JID),
(StateData#state.sockmod):change_shaper(StateData#state.socket,
Shaper).
+-spec new_id() -> binary().
new_id() -> randoms:get_string().
+-spec cancel_timer(reference()) -> ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive {timeout, Timer, _} -> ok after 0 -> ok end.
-is_key_packet(#xmlel{name = Name, attrs = Attrs,
- children = Els})
- when Name == <<"db:result">> ->
- {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, 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) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
@@ -666,10 +611,34 @@ fsm_limit_opts(Opts) ->
end
end.
+-spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
+decode_element(#xmlel{} = El, StateName, StateData) ->
+ Opts = if StateName == stream_established ->
+ [ignore_els];
+ true ->
+ []
+ end,
+ try xmpp:decode(El, ?NS_SERVER, Opts) of
+ Pkt -> ?MODULE:StateName(Pkt, StateData)
+ catch error:{xmpp_codec, Why} ->
+ case xmpp:is_stanza(El) of
+ true ->
+ Lang = xmpp:get_lang(El),
+ Txt = xmpp:format_error(Why),
+ send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+ false ->
+ ok
+ end,
+ {next_state, StateName, StateData}
+ end;
+decode_element(Pkt, StateName, StateData) ->
+ ?MODULE:StateName(Pkt, StateData).
+
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_cafile) -> 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) ->
@@ -691,6 +660,6 @@ opt_type(s2s_use_starttls) ->
(required_trusted) -> required_trusted
end;
opt_type(_) ->
- [domain_certfile, max_fsm_queue, s2s_certfile,
+ [domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
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 ae3433a6a..b9ce47830 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -50,8 +50,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state,
{socket :: ejabberd_socket:socket_state(),
@@ -75,6 +74,17 @@
bridge :: {atom(), atom()},
timer = make_ref() :: reference()}).
+-type state_name() :: open_socket | wait_for_stream |
+ wait_for_validation | wait_for_features |
+ wait_for_auth_result | wait_for_starttls_proceed |
+ relay_to_bridge | reopen_socket | wait_before_retry |
+ stream_established.
+-type state() :: #state{}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()} |
+ {next_state, state_name(), state()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
+
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -96,23 +106,6 @@
%% Specified in miliseconds. Default value is 5 minutes.
-define(MAX_RETRY_DELAY, 300000).
--define(STREAM_HEADER,
- <<"<?xml version='1.0'?><stream:stream "
- "xmlns:stream='http://etherx.jabber.org/stream"
- "s' xmlns='jabber:server' xmlns:db='jabber:ser"
- "ver:dialback' from='~s' to='~s'~s>">>).
-
--define(STREAM_TRAILER, <<"</stream:stream>">>).
-
--define(INVALID_NAMESPACE_ERR,
- fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-
--define(HOST_UNKNOWN_ERR,
- fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-
--define(INVALID_XML_ERR,
- fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
%%%----------------------------------------------------------------------
@@ -229,23 +222,19 @@ open_socket(init, StateData) ->
?SOCKET_DEFAULT_RESULT, AddrList)
of
{ok, Socket} ->
- Version = if StateData#state.use_v10 ->
- <<" version='1.0'">>;
- true -> <<"">>
+ Version = if StateData#state.use_v10 -> {1,0};
+ true -> undefined
end,
NewStateData = StateData#state{socket = Socket,
tls_enabled = false,
streamid = new_id()},
- send_text(NewStateData,
- io_lib:format(?STREAM_HEADER,
- [StateData#state.myname,
- StateData#state.server, Version])),
+ send_header(NewStateData, Version),
{next_state, wait_for_stream, NewStateData,
?FSMTIMEOUT};
- {error, _Reason} ->
+ {error, Reason} ->
?INFO_MSG("s2s connection: ~s -> ~s (remote server "
- "not found)",
- [StateData#state.myname, StateData#state.server]),
+ "not found: ~p)",
+ [StateData#state.myname, StateData#state.server, Reason]),
case ejabberd_hooks:run_fold(find_s2s_bridge, undefined,
[StateData#state.myname,
StateData#state.server])
@@ -259,18 +248,8 @@ open_socket(init, StateData) ->
_ -> wait_before_reconnect(StateData)
end
end;
-open_socket(closed, StateData) ->
- ?INFO_MSG("s2s connection: ~s -> ~s (stopped in "
- "open socket)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-open_socket(timeout, StateData) ->
- ?INFO_MSG("s2s connection: ~s -> ~s (timeout in "
- "open socket)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-open_socket(_, StateData) ->
- {next_state, open_socket, StateData}.
+open_socket(Event, StateData) ->
+ handle_unexpected_event(Event, open_socket, StateData).
open_socket1({_, _, _, _} = Addr, Port) ->
open_socket2(inet, Addr, Port);
@@ -309,466 +288,215 @@ open_socket2(Type, Addr, Port) ->
%%----------------------------------------------------------------------
-wait_for_stream({xmlstreamstart, _Name, Attrs},
- StateData) ->
- {CertCheckRes, CertCheckMsg, StateData0} =
- if StateData#state.tls_certverify, StateData#state.tls_enabled ->
- {Res, Msg} =
- ejabberd_s2s:check_peer_certificate(ejabberd_socket,
- StateData#state.socket,
- StateData#state.server),
- ?DEBUG("Certificate verification result for ~s: ~s",
- [StateData#state.server, Msg]),
- {Res, Msg, StateData#state{tls_certverify = false}};
+wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) ->
+ {CertCheckRes, CertCheckMsg, StateData} =
+ if StateData0#state.tls_certverify, StateData0#state.tls_enabled ->
+ {Res, Msg} =
+ ejabberd_s2s:check_peer_certificate(ejabberd_socket,
+ StateData0#state.socket,
+ StateData0#state.server),
+ ?DEBUG("Certificate verification result for ~s: ~s",
+ [StateData0#state.server, Msg]),
+ {Res, Msg, StateData0#state{tls_certverify = false}};
true ->
- {no_verify, <<"Not verified">>, StateData}
+ {no_verify, <<"Not verified">>, StateData0}
end,
- 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,
- <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
- CertCheckMsg)))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
- [NewStateData#state.myname,
- NewStateData#state.server,
- CertCheckMsg]),
- {stop, normal, NewStateData};
- {<<"jabber:server">>, <<"jabber:server:dialback">>,
- false} ->
- send_db_request(NewStateData);
- {<<"jabber:server">>, <<"jabber:server:dialback">>,
- true}
- when NewStateData#state.use_v10 ->
- {next_state, wait_for_features, NewStateData, ?FSMTIMEOUT};
- %% Clause added to handle Tigase's workaround for an old ejabberd bug:
- {<<"jabber:server">>, <<"jabber:server:dialback">>,
- true}
- when not NewStateData#state.use_v10 ->
- send_db_request(NewStateData);
- {<<"jabber:server">>, <<"">>, true}
- when NewStateData#state.use_v10 ->
- {next_state, wait_for_features,
- NewStateData#state{db_enabled = false}, ?FSMTIMEOUT};
- {NSProvided, DB, _} ->
- send_text(NewStateData, ?INVALID_NAMESPACE_ERR),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
- "namespace).~nNamespace provided: ~p~nNamespac"
- "e expected: \"jabber:server\"~nxmlns:db "
- "provided: ~p~nAll attributes: ~p",
- [NewStateData#state.myname, NewStateData#state.server,
- NSProvided, DB, Attrs]),
- {stop, normal, NewStateData}
+ try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
+ _ when CertCheckRes == error ->
+ send_element(StateData,
+ xmpp:serr_policy_violation(CertCheckMsg, ?MYLANG)),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
+ [StateData#state.myname, StateData#state.server,
+ CertCheckMsg]),
+ {stop, normal, StateData};
+ #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
+ when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
+ send_element(StateData, xmpp:serr_invalid_namespace()),
+ {stop, normal, StateData};
+ #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID,
+ version = V} when V /= {1,0} ->
+ send_db_request(StateData#state{remote_streamid = ID});
+ #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
+ when StateData#state.use_v10 ->
+ {next_state, wait_for_features,
+ StateData#state{remote_streamid = ID}, ?FSMTIMEOUT};
+ #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
+ when not StateData#state.use_v10 ->
+ %% Handle Tigase's workaround for an old ejabberd bug:
+ send_db_request(StateData#state{remote_streamid = ID});
+ #stream_start{id = ID} when StateData#state.use_v10 ->
+ {next_state, wait_for_features,
+ StateData#state{db_enabled = false, remote_streamid = ID},
+ ?FSMTIMEOUT};
+ #stream_start{} ->
+ send_element(StateData, xmpp:serr_invalid_namespace()),
+ {stop, normal, StateData};
+ _ ->
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {stop, normal, StateData}
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
+ {stop, normal, StateData}
end;
-wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
- "xml)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-wait_for_stream({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-wait_for_stream(timeout, StateData) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout "
- "in wait_for_stream)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-wait_for_stream(closed, StateData) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (close "
- "in wait_for_stream)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}.
-
-wait_for_validation({xmlstreamelement, El},
+wait_for_stream(Event, StateData) ->
+ handle_unexpected_event(Event, wait_for_stream, StateData).
+
+wait_for_validation({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_validation, StateData);
+wait_for_validation(#db_result{to = To, from = From, type = Type}, StateData) ->
+ ?DEBUG("recv result: ~p", [{From, To, Type}]),
+ case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of
+ {valid, Enabled, Required} when (Enabled == true) or (Required == false) ->
+ send_queue(StateData, StateData#state.queue),
+ ?INFO_MSG("Connection established: ~s -> ~s with "
+ "TLS=~p",
+ [StateData#state.myname, StateData#state.server,
+ StateData#state.tls_enabled]),
+ ejabberd_hooks:run(s2s_connect_hook,
+ [StateData#state.myname,
+ StateData#state.server]),
+ {next_state, stream_established, StateData#state{queue = queue:new()}};
+ {valid, Enabled, Required} when (Enabled == false) and (Required == true) ->
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS "
+ "is required but unavailable)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData};
+ _ ->
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
+ "dialback key result)",
+ [StateData#state.myname, StateData#state.server]),
+ {stop, normal, StateData}
+ end;
+wait_for_validation(#db_verify{to = To, from = From, id = Id, type = Type},
StateData) ->
- case is_verify_res(El) of
- {result, To, From, Id, Type} ->
- ?DEBUG("recv result: ~p", [{From, To, Id, Type}]),
- case {Type, StateData#state.tls_enabled,
- StateData#state.tls_required}
- of
- {<<"valid">>, Enabled, Required}
- when (Enabled == true) or (Required == false) ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s with "
- "TLS=~p",
- [StateData#state.myname, StateData#state.server,
- StateData#state.tls_enabled]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established,
- StateData#state{queue = queue:new()}};
- {<<"valid">>, Enabled, Required}
- when (Enabled == false) and (Required == true) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS "
- "is required but unavailable)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
- _ ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
- "dialback key)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- {verify, To, From, Id, Type} ->
- ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
- case StateData#state.verify of
- false ->
- NextState = wait_for_validation,
- {next_state, NextState, StateData,
- get_timeout_interval(NextState)};
- {Pid, _Key, _SID} ->
- case Type of
- <<"valid">> ->
- p1_fsm:send_event(Pid,
- {valid, StateData#state.server,
- StateData#state.myname});
- _ ->
- p1_fsm:send_event(Pid,
- {invalid, StateData#state.server,
- StateData#state.myname})
- end,
- if StateData#state.verify == false ->
- {stop, normal, StateData};
- true ->
- NextState = wait_for_validation,
- {next_state, NextState, StateData,
- get_timeout_interval(NextState)}
- end
- end;
- _ ->
- {next_state, wait_for_validation, StateData,
- (?FSMTIMEOUT) * 3}
+ ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
+ case StateData#state.verify of
+ false ->
+ NextState = wait_for_validation,
+ {next_state, NextState, StateData, get_timeout_interval(NextState)};
+ {Pid, _Key, _SID} ->
+ case Type of
+ valid ->
+ p1_fsm:send_event(Pid,
+ {valid, StateData#state.server,
+ StateData#state.myname});
+ _ ->
+ p1_fsm:send_event(Pid,
+ {invalid, StateData#state.server,
+ StateData#state.myname})
+ end,
+ if StateData#state.verify == false ->
+ {stop, normal, StateData};
+ true ->
+ NextState = wait_for_validation,
+ {next_state, NextState, StateData, get_timeout_interval(NextState)}
+ end
end;
-wait_for_validation({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamend)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-wait_for_validation({xmlstreamerror, _}, StateData) ->
- ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)",
- [StateData#state.myname, StateData#state.server]),
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- {stop, normal, StateData};
wait_for_validation(timeout,
#state{verify = {VPid, VKey, SID}} = StateData)
- when is_pid(VPid) and is_binary(VKey) and
- is_binary(SID) ->
- ?DEBUG("wait_for_validation: ~s -> ~s (timeout "
- "in verify connection)",
+ when is_pid(VPid) and is_binary(VKey) and is_binary(SID) ->
+ ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
-wait_for_validation(timeout, StateData) ->
- ?INFO_MSG("wait_for_validation: ~s -> ~s (connect "
- "timeout)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-wait_for_validation(closed, StateData) ->
- ?INFO_MSG("wait for validation: ~s -> ~s (closed)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}.
+wait_for_validation(Event, StateData) ->
+ handle_unexpected_event(Event, wait_for_validation, StateData).
wait_for_features({xmlstreamelement, El}, StateData) ->
- case El of
- #xmlel{name = <<"stream:features">>, children = Els} ->
- {SASLEXT, StartTLS, StartTLSRequired} = lists:foldl(fun
- (#xmlel{name =
- <<"mechanisms">>,
- attrs =
- Attrs1,
- children
- =
- Els1} =
- _El1,
- {_SEXT, STLS,
- STLSReq} =
- Acc) ->
- case
- fxml:get_attr_s(<<"xmlns">>,
- Attrs1)
- of
- ?NS_SASL ->
- NewSEXT =
- lists:any(fun
- (#xmlel{name
- =
- <<"mechanism">>,
- children
- =
- Els2}) ->
- case
- fxml:get_cdata(Els2)
- of
- <<"EXTERNAL">> ->
- true;
- _ ->
- false
- end;
- (_) ->
- false
- end,
- Els1),
- {NewSEXT,
- STLS,
- STLSReq};
- _ -> Acc
- end;
- (#xmlel{name =
- <<"starttls">>,
- attrs =
- Attrs1} =
- El1,
- {SEXT, _STLS,
- _STLSReq} =
- Acc) ->
- case
- fxml:get_attr_s(<<"xmlns">>,
- Attrs1)
- of
- ?NS_TLS ->
- Req =
- case
- fxml:get_subtag(El1,
- <<"required">>)
- of
- #xmlel{} ->
- true;
- false ->
- false
- end,
- {SEXT,
- true,
- Req};
- _ -> Acc
- end;
- (_, Acc) -> Acc
- end,
- {false, false,
- false},
- Els),
- if not SASLEXT and not StartTLS and
- StateData#state.authenticated ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s with "
- "SASL EXTERNAL and TLS=~p",
- [StateData#state.myname, StateData#state.server,
- StateData#state.tls_enabled]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established,
- StateData#state{queue = queue:new()}};
- SASLEXT and StateData#state.try_auth and
- (StateData#state.new /= false) and
- (StateData#state.tls_enabled or
- not StateData#state.tls_required) ->
- send_element(StateData,
- #xmlel{name = <<"auth">>,
- attrs =
- [{<<"xmlns">>, ?NS_SASL},
- {<<"mechanism">>, <<"EXTERNAL">>}],
- children =
- [{xmlcdata,
- jlib:encode_base64(StateData#state.myname)}]}),
- {next_state, wait_for_auth_result,
- StateData#state{try_auth = false}, ?FSMTIMEOUT};
- StartTLS and StateData#state.tls and
- not StateData#state.tls_enabled ->
- send_element(StateData,
- #xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = []}),
- {next_state, wait_for_starttls_proceed, StateData,
- ?FSMTIMEOUT};
- StartTLSRequired and not StateData#state.tls ->
- ?DEBUG("restarted: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined, use_v10 = false},
- ?FSMTIMEOUT};
- StateData#state.db_enabled ->
- send_db_request(StateData);
- true ->
- ?DEBUG("restarted: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined, use_v10 = false},
- ?FSMTIMEOUT}
- end;
- _ ->
- send_text(StateData,
- <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
-wait_for_features({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("wait_for_features: xmlstreamend", []),
- {stop, normal, StateData};
-wait_for_features({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("wait for features: xmlstreamerror", []),
- {stop, normal, StateData};
-wait_for_features(timeout, StateData) ->
- ?INFO_MSG("wait for features: timeout", []),
- {stop, normal, StateData};
-wait_for_features(closed, StateData) ->
- ?INFO_MSG("wait for features: closed", []),
- {stop, normal, StateData}.
-
-wait_for_auth_result({xmlstreamelement, El},
- StateData) ->
- case El of
- #xmlel{name = <<"success">>, attrs = Attrs} ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_SASL ->
- ?DEBUG("auth: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:reset_stream(StateData#state.socket),
- send_text(StateData,
- io_lib:format(?STREAM_HEADER,
- [StateData#state.myname,
- StateData#state.server,
- <<" version='1.0'">>])),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true},
- ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- #xmlel{name = <<"failure">>, attrs = Attrs} ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_SASL ->
- ?DEBUG("restarted: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined}, ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- _ ->
- send_text(StateData,
- <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
+ decode_element(El, wait_for_features, StateData);
+wait_for_features(#stream_features{sub_els = Els}, StateData) ->
+ {SASLEXT, StartTLS, StartTLSRequired} =
+ lists:foldl(
+ fun(#sasl_mechanisms{list = Mechs}, {_, STLS, STLSReq}) ->
+ {lists:member(<<"EXTERNAL">>, Mechs), STLS, STLSReq};
+ (#starttls{required = Required}, {SEXT, _, _}) ->
+ {SEXT, true, Required};
+ (_, Acc) ->
+ Acc
+ end, {false, false, false}, Els),
+ if not SASLEXT and not StartTLS and StateData#state.authenticated ->
+ send_queue(StateData, StateData#state.queue),
+ ?INFO_MSG("Connection established: ~s -> ~s with "
+ "SASL EXTERNAL and TLS=~p",
+ [StateData#state.myname, StateData#state.server,
+ StateData#state.tls_enabled]),
+ ejabberd_hooks:run(s2s_connect_hook,
+ [StateData#state.myname,
+ StateData#state.server]),
+ {next_state, stream_established,
+ StateData#state{queue = queue:new()}};
+ SASLEXT and StateData#state.try_auth and
+ (StateData#state.new /= false) and
+ (StateData#state.tls_enabled or
+ not StateData#state.tls_required) ->
+ send_element(StateData,
+ #sasl_auth{mechanism = <<"EXTERNAL">>,
+ text = StateData#state.myname}),
+ {next_state, wait_for_auth_result,
+ StateData#state{try_auth = false}, ?FSMTIMEOUT};
+ StartTLS and StateData#state.tls and
+ not StateData#state.tls_enabled ->
+ send_element(StateData, #starttls{}),
+ {next_state, wait_for_starttls_proceed, StateData, ?FSMTIMEOUT};
+ StartTLSRequired and not StateData#state.tls ->
+ ?DEBUG("restarted: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined, use_v10 = false},
+ ?FSMTIMEOUT};
+ StateData#state.db_enabled ->
+ send_db_request(StateData);
+ true ->
+ ?DEBUG("restarted: ~p",
+ [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined, use_v10 = false}, ?FSMTIMEOUT}
end;
-wait_for_auth_result({xmlstreamend, _Name},
- StateData) ->
- ?INFO_MSG("wait for auth result: xmlstreamend", []),
- {stop, normal, StateData};
-wait_for_auth_result({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("wait for auth result: xmlstreamerror", []),
- {stop, normal, StateData};
-wait_for_auth_result(timeout, StateData) ->
- ?INFO_MSG("wait for auth result: timeout", []),
- {stop, normal, StateData};
-wait_for_auth_result(closed, StateData) ->
- ?INFO_MSG("wait for auth result: closed", []),
- {stop, normal, StateData}.
-
-wait_for_starttls_proceed({xmlstreamelement, El},
- StateData) ->
- case El of
- #xmlel{name = <<"proceed">>, attrs = Attrs} ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_TLS ->
- ?DEBUG("starttls: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- Socket = StateData#state.socket,
- TLSOpts = case
- ejabberd_config:get_option(
- {domain_certfile, StateData#state.myname},
- fun iolist_to_binary/1)
- of
- undefined -> StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile}
- | lists:keydelete(certfile, 1,
- StateData#state.tls_options)]
- end,
- TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
- NewStateData = StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true,
- tls_options = TLSOpts},
- send_text(NewStateData,
- io_lib:format(?STREAM_HEADER,
- [NewStateData#state.myname,
- NewStateData#state.server,
- <<" version='1.0'">>])),
- {next_state, wait_for_stream, NewStateData,
- ?FSMTIMEOUT};
- _ ->
- send_text(StateData,
- <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- _ ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
- "format)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
-wait_for_starttls_proceed({xmlstreamend, _Name},
- StateData) ->
- ?INFO_MSG("wait for starttls proceed: xmlstreamend",
- []),
- {stop, normal, StateData};
-wait_for_starttls_proceed({xmlstreamerror, _},
- StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("wait for starttls proceed: xmlstreamerror",
- []),
- {stop, normal, StateData};
-wait_for_starttls_proceed(timeout, StateData) ->
- ?INFO_MSG("wait for starttls proceed: timeout", []),
- {stop, normal, StateData};
-wait_for_starttls_proceed(closed, StateData) ->
- ?INFO_MSG("wait for starttls proceed: closed", []),
- {stop, normal, StateData}.
+wait_for_features(Event, StateData) ->
+ handle_unexpected_event(Event, wait_for_features, StateData).
+
+wait_for_auth_result({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_auth_result, StateData);
+wait_for_auth_result(#sasl_success{}, StateData) ->
+ ?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:reset_stream(StateData#state.socket),
+ send_header(StateData, {1,0}),
+ {next_state, wait_for_stream,
+ StateData#state{streamid = new_id(), authenticated = true},
+ ?FSMTIMEOUT};
+wait_for_auth_result(#sasl_failure{}, StateData) ->
+ ?DEBUG("restarted: ~p", [{StateData#state.myname, StateData#state.server}]),
+ ejabberd_socket:close(StateData#state.socket),
+ {next_state, reopen_socket,
+ StateData#state{socket = undefined}, ?FSMTIMEOUT};
+wait_for_auth_result(Event, StateData) ->
+ handle_unexpected_event(Event, wait_for_auth_result, StateData).
+
+wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
+ decode_element(El, wait_for_starttls_proceed, StateData);
+wait_for_starttls_proceed(#starttls_proceed{}, StateData) ->
+ ?DEBUG("starttls: ~p", [{StateData#state.myname, StateData#state.server}]),
+ Socket = StateData#state.socket,
+ TLSOpts = case ejabberd_config:get_option(
+ {domain_certfile, StateData#state.myname},
+ fun iolist_to_binary/1) of
+ undefined -> StateData#state.tls_options;
+ CertFile ->
+ [{certfile, CertFile}
+ | lists:keydelete(certfile, 1,
+ StateData#state.tls_options)]
+ end,
+ TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
+ NewStateData = StateData#state{socket = TLSSocket,
+ streamid = new_id(),
+ tls_enabled = true,
+ tls_options = TLSOpts},
+ send_header(NewStateData, {1,0}),
+ {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
+wait_for_starttls_proceed(Event, StateData) ->
+ handle_unexpected_event(Event, wait_for_starttls_proceed, StateData).
reopen_socket({xmlstreamelement, _El}, StateData) ->
{next_state, reopen_socket, StateData, ?FSMTIMEOUT};
@@ -797,47 +525,70 @@ relay_to_bridge(_Event, StateData) ->
{next_state, relay_to_bridge, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
- ?DEBUG("s2S stream established", []),
- case is_verify_res(El) of
- {verify, VTo, VFrom, VId, VType} ->
- ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
- case StateData#state.verify of
- {VPid, _VKey, _SID} ->
- case VType of
- <<"valid">> ->
- p1_fsm:send_event(VPid,
- {valid, StateData#state.server,
- StateData#state.myname});
- _ ->
- p1_fsm:send_event(VPid,
- {invalid, StateData#state.server,
- StateData#state.myname})
- end;
- _ -> ok
- end;
- _ -> ok
+ decode_element(El, stream_established, StateData);
+stream_established(#db_verify{to = VTo, from = VFrom, id = VId, type = VType},
+ StateData) ->
+ ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
+ case StateData#state.verify of
+ {VPid, _VKey, _SID} ->
+ case VType of
+ valid ->
+ p1_fsm:send_event(VPid,
+ {valid, StateData#state.server,
+ StateData#state.myname});
+ _ ->
+ p1_fsm:send_event(VPid,
+ {invalid, StateData#state.server,
+ StateData#state.myname})
+ end;
+ _ -> ok
end,
{next_state, stream_established, StateData};
-stream_established({xmlstreamend, _Name}, StateData) ->
- ?INFO_MSG("Connection closed in stream established: "
- "~s -> ~s (xmlstreamend)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-stream_established({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
- ?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-stream_established(timeout, StateData) ->
- ?INFO_MSG("stream established: ~s -> ~s (timeout)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
-stream_established(closed, StateData) ->
- ?INFO_MSG("stream established: ~s -> ~s (closed)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}.
+stream_established(Event, StateData) ->
+ handle_unexpected_event(Event, stream_established, StateData).
+
+-spec handle_unexpected_event(term(), state_name(), state()) -> fsm_transition().
+handle_unexpected_event(Event, StateName, StateData) ->
+ case Event of
+ {xmlstreamerror, _} ->
+ send_element(StateData, xmpp:serr_not_well_formed()),
+ ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
+ "got invalid XML from peer",
+ [StateData#state.myname, StateData#state.server,
+ StateName]),
+ {stop, normal, StateData};
+ {xmlstreamend, _} ->
+ ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
+ "XML stream closed by peer",
+ [StateData#state.myname, StateData#state.server,
+ StateName]),
+ {stop, normal, StateData};
+ timeout ->
+ send_element(StateData, xmpp:serr_connection_timeout()),
+ ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
+ "timed out during establishing an XML stream",
+ [StateData#state.myname, StateData#state.server,
+ StateName]),
+ {stop, normal, StateData};
+ closed ->
+ ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
+ "connection socket closed",
+ [StateData#state.myname, StateData#state.server,
+ StateName]),
+ {stop, normal, StateData};
+ Pkt when StateName == wait_for_stream;
+ StateName == wait_for_features;
+ StateName == wait_for_auth_result;
+ StateName == wait_for_starttls_proceed ->
+ send_element(StateData, xmpp:serr_bad_format()),
+ ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
+ "got unexpected event ~p",
+ [StateData#state.myname, StateData#state.server,
+ StateName, Pkt]),
+ {stop, normal, StateData};
+ _ ->
+ {next_state, StateName, StateData, get_timeout_interval(StateName)}
+ end.
%%----------------------------------------------------------------------
%% Func: StateName/3
@@ -917,7 +668,7 @@ handle_info({send_element, El}, StateName, StateData) ->
%% In this state we bounce all message: We are waiting before
%% trying to reconnect
wait_before_retry ->
- bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
+ bounce_element(El, xmpp:err_remote_server_not_found()),
{next_state, StateName, StateData};
relay_to_bridge ->
{Mod, Fun} = StateData#state.bridge,
@@ -926,7 +677,7 @@ handle_info({send_element, El}, StateName, StateData) ->
{'EXIT', Reason} ->
?ERROR_MSG("Error while relaying to bridge: ~p",
[Reason]),
- bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR),
+ bounce_element(El, xmpp:err_internal_server_error()),
wait_before_reconnect(StateData);
_ -> {next_state, StateName, StateData}
end;
@@ -966,12 +717,13 @@ terminate(Reason, StateName, StateData) ->
StateData#state.server},
self())
end,
- bounce_queue(StateData#state.queue,
- ?ERR_REMOTE_SERVER_NOT_FOUND),
- bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
+ bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()),
+ bounce_messages(xmpp:err_remote_server_not_found()),
case StateData#state.socket of
undefined -> ok;
- _Socket -> ejabberd_socket:close(StateData#state.socket)
+ _Socket ->
+ catch send_trailer(StateData),
+ ejabberd_socket:close(StateData#state.socket)
end,
ok.
@@ -981,12 +733,32 @@ print_state(State) -> State.
%%% Internal functions
%%%----------------------------------------------------------------------
+-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
+ ?DEBUG("Send Text on stream = ~s", [Text]),
ejabberd_socket:send(StateData#state.socket, Text).
+-spec send_element(state(), xmpp_element()) -> ok.
send_element(StateData, El) ->
- send_text(StateData, fxml:element_to_binary(El)).
-
+ El1 = xmpp:encode(El, ?NS_SERVER),
+ send_text(StateData, fxml:element_to_binary(El1)).
+
+-spec send_header(state(), undefined | {integer(), integer()}) -> ok.
+send_header(StateData, Version) ->
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_SERVER,
+ stream_xmlns = ?NS_STREAM,
+ db_xmlns = ?NS_SERVER_DIALBACK,
+ from = jid:make(StateData#state.myname),
+ to = jid:make(StateData#state.server),
+ version = Version}),
+ send_text(StateData, fxml:element_to_header(Header)).
+
+-spec send_trailer(state()) -> ok.
+send_trailer(StateData) ->
+ send_text(StateData, <<"</stream:stream>">>).
+
+-spec send_queue(state(), queue:queue()) -> ok.
send_queue(StateData, Q) ->
case queue:out(Q) of
{{value, El}, Q1} ->
@@ -995,20 +767,13 @@ send_queue(StateData, Q) ->
end.
%% Bounce a single message (xmlelement)
+-spec bounce_element(stanza(), stanza_error()) -> ok.
bounce_element(El, Error) ->
- #xmlel{attrs = Attrs} = El,
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- <<"result">> -> ok;
- _ ->
- Err = jlib:make_error_reply(El, Error),
- From = jid:from_string(fxml:get_tag_attr_s(<<"from">>,
- El)),
- To = jid:from_string(fxml:get_tag_attr_s(<<"to">>,
- El)),
- ejabberd_router:route(To, From, Err)
- end.
+ From = xmpp:get_from(El),
+ To = xmpp:get_to(El),
+ ejabberd_router:route_error(To, From, El, Error).
+-spec bounce_queue(queue:queue(), stanza_error()) -> ok.
bounce_queue(Q, Error) ->
case queue:out(Q) of
{{value, El}, Q1} ->
@@ -1016,12 +781,15 @@ bounce_queue(Q, Error) ->
{empty, _} -> ok
end.
+-spec new_id() -> binary().
new_id() -> randoms:get_string().
+-spec cancel_timer(reference()) -> ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive {timeout, Timer, _} -> ok after 0 -> ok end.
+-spec bounce_messages(stanza_error()) -> ok.
bounce_messages(Error) ->
receive
{send_element, El} ->
@@ -1029,6 +797,7 @@ bounce_messages(Error) ->
after 0 -> ok
end.
+-spec send_db_request(state()) -> fsm_transition().
send_db_request(StateData) ->
Server = StateData#state.server,
New = case StateData#state.new of
@@ -1045,22 +814,18 @@ send_db_request(StateData) ->
{StateData#state.myname, Server},
StateData#state.remote_streamid),
send_element(StateData,
- #xmlel{name = <<"db:result">>,
- attrs =
- [{<<"from">>, StateData#state.myname},
- {<<"to">>, Server}],
- children = [{xmlcdata, Key1}]})
+ #db_result{from = StateData#state.myname,
+ to = Server,
+ key = Key1})
end,
case StateData#state.verify of
false -> ok;
{_Pid, Key2, SID} ->
send_element(StateData,
- #xmlel{name = <<"db:verify">>,
- attrs =
- [{<<"from">>, StateData#state.myname},
- {<<"to">>, StateData#state.server},
- {<<"id">>, SID}],
- children = [{xmlcdata, Key2}]})
+ #db_verify{from = StateData#state.myname,
+ to = StateData#state.server,
+ id = SID,
+ key = Key2})
end,
{next_state, wait_for_validation, NewStateData,
(?FSMTIMEOUT) * 6}
@@ -1068,20 +833,6 @@ send_db_request(StateData) ->
_:_ -> {stop, normal, NewStateData}
end.
-is_verify_res(#xmlel{name = Name, attrs = Attrs})
- when Name == <<"db:result">> ->
- {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, 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.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% SRV support
@@ -1189,12 +940,14 @@ get_addrs(Host, Family) ->
[]
end.
+-spec outgoing_s2s_port() -> pos_integer().
outgoing_s2s_port() ->
ejabberd_config:get_option(
outgoing_s2s_port,
fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
5269).
+-spec outgoing_s2s_families() -> [ipv4 | ipv6].
outgoing_s2s_families() ->
ejabberd_config:get_option(
outgoing_s2s_families,
@@ -1206,6 +959,7 @@ outgoing_s2s_families() ->
Families
end, [ipv4, ipv6]).
+-spec outgoing_s2s_timeout() -> pos_integer().
outgoing_s2s_timeout() ->
ejabberd_config:get_option(
outgoing_s2s_timeout,
@@ -1255,21 +1009,24 @@ log_s2s_out(_, Myname, Server, Tls) ->
%% Calculate timeout depending on which state we are in:
%% Can return integer > 0 | infinity
+-spec get_timeout_interval(state_name()) -> pos_integer() | infinity.
get_timeout_interval(StateName) ->
case StateName of
%% Validation implies dialback: Networking can take longer:
wait_for_validation -> (?FSMTIMEOUT) * 6;
%% When stream is established, we only rely on S2S Timeout timer:
stream_established -> infinity;
+ relay_to_bridge -> infinity;
+ open_socket -> infinity;
_ -> ?FSMTIMEOUT
end.
%% This function is intended to be called at the end of a state
%% function that want to wait for a reconnect delay before stopping.
+-spec wait_before_reconnect(state()) -> fsm_next().
wait_before_reconnect(StateData) ->
- bounce_queue(StateData#state.queue,
- ?ERR_REMOTE_SERVER_NOT_FOUND),
- bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
+ bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()),
+ bounce_messages(xmpp:err_remote_server_not_found()),
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
undefined_delay ->
@@ -1281,6 +1038,7 @@ wait_before_reconnect(StateData) ->
StateData#state{timer = Timer, delay_to_retry = Delay,
queue = queue:new()}}.
+-spec get_max_retry_delay() -> pos_integer().
get_max_retry_delay() ->
case ejabberd_config:get_option(
s2s_max_retry_delay,
@@ -1290,6 +1048,7 @@ get_max_retry_delay() ->
end.
%% Terminate s2s_out connections that are in state wait_before_retry
+-spec terminate_if_waiting_delay(binary(), binary()) -> ok.
terminate_if_waiting_delay(From, To) ->
FromTo = {From, To},
Pids = ejabberd_s2s:get_connections_pids(FromTo),
@@ -1298,6 +1057,7 @@ terminate_if_waiting_delay(From, To) ->
end,
Pids).
+-spec fsm_limit_opts() -> [{max_queue, pos_integer()}].
fsm_limit_opts() ->
case ejabberd_config:get_option(
max_fsm_queue,
@@ -1306,6 +1066,29 @@ fsm_limit_opts() ->
N -> [{max_queue, N}]
end.
+-spec decode_element(xmlel(), state_name(), state()) -> fsm_next().
+decode_element(#xmlel{} = El, StateName, StateData) ->
+ Opts = if StateName == stream_established ->
+ [ignore_els];
+ true ->
+ []
+ end,
+ try xmpp:decode(El, ?NS_SERVER, Opts) of
+ Pkt -> ?MODULE:StateName(Pkt, StateData)
+ catch error:{xmpp_codec, Why} ->
+ Type = xmpp:get_type(El),
+ case xmpp:is_stanza(El) of
+ true when Type /= <<"result">>, Type /= <<"error">> ->
+ Lang = xmpp:get_lang(El),
+ Txt = xmpp:format_error(Why),
+ Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
+ send_element(StateData, Err);
+ false ->
+ ok
+ end,
+ {next_state, StateName, StateData, get_timeout_interval(StateName)}
+ 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;
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index 26374c1f1..35cfe15af 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -36,7 +36,7 @@
-behaviour(?GEN_FSM).
%% External exports
--export([start/0, start/2, start_link/2, send_text/2,
+-export([start/2, start_link/2, send_text/2,
send_element/2, socket_type/0, transform_listen_option/2]).
-export([init/1, wait_for_stream/2,
@@ -44,61 +44,35 @@
handle_event/3, handle_sync_event/4, code_change/4,
handle_info/3, terminate/3, print_state/1, opt_type/1]).
--include("ejabberd_service.hrl").
--include("mod_privacy.hrl").
-
--export([get_delegated_ns/1]).
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-record(state,
+ {socket :: ejabberd_socket:socket_state(),
+ sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
+ streamid = <<"">> :: binary(),
+ host_opts = dict:new() :: ?TDICT,
+ host = <<"">> :: binary(),
+ access :: atom(),
+ check_from = true :: boolean()}).
+
+-type state_name() :: wait_for_stream | wait_for_handshake | stream_established.
+-type state() :: #state{}.
+-type fsm_next() :: {next_state, state_name(), state()}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
%-define(DBGFSM, true).
-
-ifdef(DBGFSM).
-
-define(FSMOPTS, [{debug, [trace]}]).
-
-else.
-
-define(FSMOPTS, []).
-
-endif.
--define(STREAM_HEADER,
- <<"<?xml version='1.0'?><stream:stream "
- "xmlns:stream='http://etherx.jabber.org/stream"
- "s' xmlns='jabber:component:accept' id='~s' "
- "from='~s'>">>).
-
--define(STREAM_TRAILER, <<"</stream:stream>">>).
-
--define(INVALID_HEADER_ERR,
- <<"<stream:stream xmlns:stream='http://etherx.ja"
- "bber.org/streams'><stream:error>Invalid "
- "Stream Header</stream:error></stream:stream>">>).
-
--define(INVALID_HANDSHAKE_ERR,
- <<"<stream:error><not-authorized xmlns='urn:ietf"
- ":params:xml:ns:xmpp-streams'/><text "
- "xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
- "xml:lang='en'>Invalid Handshake</text></strea"
- "m:error></stream:stream>">>).
-
--define(INVALID_XML_ERR,
- fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-
--define(INVALID_NS_ERR,
- fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-
-%% for xep-0355
-%% table contans records like {namespace, fitering attributes, pid(),
-%% host, disco info for general case, bare jid disco info }
-
-start() ->
- ets:new(delegated_namespaces, [named_table, public]),
- ets:new(hooks_tmp, [named_table, public]).
-
start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup,
[SockData, Opts]).
@@ -109,20 +83,9 @@ start_link(SockData, Opts) ->
socket_type() -> xml_stream.
-get_delegated_ns(FsmRef) ->
- (?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}).
-
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
-
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("(~w) External service connected", [Socket]),
Access = case lists:keysearch(access, 1, Opts) of
@@ -144,21 +107,6 @@ init([{SockMod, Socket}, Opts]) ->
p1_sha:sha(randoms:bytes(20))),
dict:from_list([{global, Pass}])
end,
- %% privilege access to entities data
- PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of
- {value, {_, PrivAcc}} -> PrivAcc;
- _ -> []
- end,
- Delegations = case lists:keyfind(delegations, 1, Opts) of
- {delegations, Del} ->
- lists:foldl(
- fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION ->
- Attr = proplists:get_value(filtering, FiltAttr, []),
- D ++ [{Ns, Attr}];
- (_Deleg, D) -> D
- end, [], Del);
- false -> []
- end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
@@ -172,223 +120,127 @@ init([{SockMod, Socket}, Opts]) ->
SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream,
#state{socket = Socket, sockmod = SockMod,
- streamid = new_id(), host_opts = HostOpts, access = Access,
- check_from = CheckFrom, privilege_access = PrivAccess,
- delegations = Delegations}}.
-
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-
-wait_for_stream({xmlstreamstart, _Name, Attrs},
- StateData) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- <<"jabber:component:accept">> ->
- 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}
+ streamid = new_id(), host_opts = HostOpts,
+ access = Access, check_from = CheckFrom}}.
+
+wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
+ try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
+ #stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM}
+ when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM ->
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_invalid_namespace()),
+ {stop, normal, StateData};
+ #stream_start{to = To} when is_record(To, jid) ->
+ Host = To#jid.lserver,
+ send_header(StateData, Host),
+ 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}};
+ #stream_start{} ->
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_improper_addressing()),
+ {stop, normal, StateData};
+ _ ->
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_invalid_xml()),
+ {stop, normal, StateData}
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
+ {stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
- Header = io_lib:format(?STREAM_HEADER,
- [<<"none">>, ?MYNAME]),
- send_text(StateData,
- <<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
+ send_header(StateData, ?MYNAME),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_handshake({xmlstreamelement, El}, StateData) ->
- #xmlel{name = Name, children = Els} = El,
- case {Name, fxml:get_cdata(Els)} of
- {<<"handshake">>, Digest} ->
- 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]),
- ejabberd_hooks:run(component_connected,
- [H])
- end, dict:fetch_keys(StateData#state.host_opts)),
-
- mod_privilege:advertise_permissions(StateData),
- DelegatedNs = mod_delegation:advertise_delegations(StateData),
-
- RosterAccess = proplists:get_value(roster,
- StateData#state.privilege_access),
-
- case proplists:get_value(presence,
- StateData#state.privilege_access) of
- <<"managed_entity">> ->
- mod_privilege:initial_presences(StateData),
- Fun = mod_privilege:process_presence(self()),
- add_hooks(user_send_packet, Fun);
- <<"roster">> when (RosterAccess == <<"both">>) or
- (RosterAccess == <<"get">>) ->
- mod_privilege:initial_presences(StateData),
- Fun = mod_privilege:process_presence(self()),
- add_hooks(user_send_packet, Fun),
- Fun2 = mod_privilege:process_roster_presence(self()),
- add_hooks(s2s_receive_packet, Fun2);
- _ -> ok
- end,
- {next_state, stream_established,
- StateData#state{delegations = DelegatedNs}};
- _ ->
- 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}
+ decode_element(El, wait_for_handshake, StateData);
+wait_for_handshake(#handshake{data = Digest}, 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_element(StateData, #handshake{}),
+ lists:foreach(
+ fun (H) ->
+ ejabberd_router:register_route(H, ?MYNAME),
+ ?INFO_MSG("Route registered for service ~p~n",
+ [H]),
+ ejabberd_hooks:run(component_connected, [H])
+ end, dict:fetch_keys(StateData#state.host_opts)),
+ {next_state, stream_established, StateData};
+ _ ->
+ send_element(StateData, xmpp:serr_not_authorized()),
+ {stop, normal, StateData}
+ end;
+ _ ->
+ send_element(StateData, xmpp:serr_not_authorized()),
+ {stop, normal, StateData}
end;
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_handshake({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_handshake(closed, StateData) ->
- {stop, normal, StateData}.
+ {stop, normal, StateData};
+wait_for_handshake(_Pkt, StateData) ->
+ {next_state, wait_for_handshake, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
- NewEl = jlib:remove_attr(<<"xmlns">>, El),
- #xmlel{name = Name, attrs = Attrs} = NewEl,
- 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 -> jid:from_string(From);
- %% The default is the standard behaviour in XEP-0114
- _ ->
- FromJID1 = jid:from_string(From),
- case FromJID1 of
- #jid{lserver = Server} ->
- case dict:is_key(Server, StateData#state.host_opts) of
- true -> FromJID1;
- false -> error
- end;
- _ -> error
- end
- end,
- To = fxml:get_attr_s(<<"to">>, Attrs),
- ToJID = case To of
- <<"">> -> error;
- _ -> jid:from_string(To)
- end,
- if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) ->
- mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl);
- (Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) ->
- ejabberd_router:route(FromJID, ToJID, NewEl);
- (Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) ->
- mod_privilege:process_message(StateData, FromJID, ToJID, NewEl);
+ decode_element(El, stream_established, StateData);
+stream_established(El, StateData) when ?is_stanza(El) ->
+ From = xmpp:get_from(El),
+ To = xmpp:get_to(El),
+ Lang = xmpp:get_lang(El),
+ if From == undefined orelse To == undefined ->
+ Txt = <<"Missing 'from' or 'to' attribute">>,
+ send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang));
true ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
- Txt = <<"Incorrect stanza name or from/to JID">>,
- Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
- send_element(StateData, Err),
- error
+ case check_from(From, StateData) of
+ true ->
+ ejabberd_router:route(From, To, El);
+ false ->
+ Txt = <<"Improper domain part of 'from' attribute">>,
+ send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang))
+ end
end,
{next_state, stream_established, StateData};
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
- send_text(StateData,
- <<(?INVALID_XML_ERR)/binary,
- (?STREAM_TRAILER)/binary>>),
+ send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
stream_established(closed, StateData) ->
- {stop, normal, 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}.
+ {stop, normal, StateData};
+stream_established(_Event, StateData) ->
+ {next_state, stream_established, 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: {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_delegated_ns}, _From, StateName, StateData) ->
- Reply = {StateData#state.host, StateData#state.delegations},
- {reply, Reply, StateName, StateData};
-
-handle_sync_event(_Event, _From, StateName, StateData) ->
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
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};
@@ -397,64 +249,20 @@ handle_info({send_element, El}, StateName, StateData) ->
{next_state, StateName, StateData};
handle_info({route, From, To, Packet}, StateName,
StateData) ->
- case acl:match_rule(global, StateData#state.access,
- From)
- of
+ case acl:match_rule(global, StateData#state.access, From) of
allow ->
- #xmlel{name = Name, attrs = Attrs, children = Els} =
- Packet,
- Attrs2 =
- 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 ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route_error(To, From, Err, Packet)
+ Pkt = xmpp:set_from_to(Packet, From, To),
+ send_element(StateData, Pkt);
+ deny ->
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
end,
{next_state, StateName, StateData};
-
-handle_info({user_presence, Packet, From},
- stream_established, StateData) ->
- To = jid:from_string(StateData#state.host),
- PacketNew = jlib:replace_from_to(From, To, Packet),
- send_element(StateData, PacketNew),
- {next_state, stream_established, StateData};
-
-handle_info({roster_presence, Packet, From},
- stream_established, StateData) ->
- %% check that current presence stanza is equivalent to last
- PresenceNew = jlib:remove_attr(<<"to">>, Packet),
- Dict = StateData#state.last_pres,
- LastPresence =
- try dict:fetch(From, Dict)
- catch _:_ ->
- undefined
- end,
- case mod_privilege:compare_presences(LastPresence, PresenceNew) of
- false ->
- #xmlel{attrs = Attrs} = PresenceNew,
- Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]},
- send_element(StateData, Presence),
- DictNew = dict:store(From, PresenceNew, Dict),
- StateDataNew = StateData#state{last_pres = DictNew},
- {next_state, stream_established, StateDataNew};
- _ ->
- {next_state, stream_established, StateData}
- end;
-
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
case StateName of
@@ -462,30 +270,12 @@ terminate(Reason, StateName, StateData) ->
lists:foreach(fun (H) ->
ejabberd_router:unregister_route(H),
ejabberd_hooks:run(component_disconnected,
- [StateData#state.host, Reason])
+ [H, Reason])
end,
- dict:fetch_keys(StateData#state.host_opts)),
-
- lists:foreach(fun({Ns, _FilterAttr}) ->
- ets:delete(delegated_namespaces, Ns),
- remove_iq_handlers(Ns)
- end, StateData#state.delegations),
-
- RosterAccess = proplists:get_value(roster, StateData#state.privilege_access),
- case proplists:get_value(presence, StateData#state.privilege_access) of
- <<"managed_entity">> ->
- Fun = mod_privilege:process_presence(self()),
- remove_hooks(user_send_packet, Fun);
- <<"roster">> when (RosterAccess == <<"both">>) or
- (RosterAccess == <<"get">>) ->
- Fun = mod_privilege:process_presence(self()),
- remove_hooks(user_send_packet, Fun),
- Fun2 = mod_privilege:process_roster_presence(self()),
- remove_hooks(s2s_receive_packet, Fun2);
- _ -> ok
- end;
+ dict:fetch_keys(StateData#state.host_opts));
_ -> ok
end,
+ catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -500,13 +290,68 @@ print_state(State) -> State.
%%% Internal functions
%%%----------------------------------------------------------------------
+-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,
Text).
+-spec send_element(state(), xmpp_element()) -> ok.
send_element(StateData, El) ->
- send_text(StateData, fxml:element_to_binary(El)).
+ El1 = xmpp:encode(El, ?NS_COMPONENT),
+ send_text(StateData, fxml:element_to_binary(El1)).
+
+-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
+send_error(StateData, Stanza, Error) ->
+ Type = xmpp:get_type(Stanza),
+ if Type == error; Type == result;
+ Type == <<"error">>; Type == <<"result">> ->
+ ok;
+ true ->
+ send_element(StateData, xmpp:make_error(Stanza, Error))
+ end.
+-spec send_header(state(), binary()) -> ok.
+send_header(StateData, Host) ->
+ Header = xmpp:encode(
+ #stream_start{xmlns = ?NS_COMPONENT,
+ stream_xmlns = ?NS_STREAM,
+ from = jid:make(Host),
+ id = StateData#state.streamid}),
+ send_text(StateData, fxml:element_to_header(Header)).
+
+-spec send_trailer(state()) -> ok.
+send_trailer(StateData) ->
+ send_text(StateData, <<"</stream:stream>">>).
+
+-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
+decode_element(#xmlel{} = El, StateName, StateData) ->
+ try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of
+ Pkt -> ?MODULE:StateName(Pkt, StateData)
+ catch error:{xmpp_codec, Why} ->
+ case xmpp:is_stanza(El) of
+ true ->
+ Lang = xmpp:get_lang(El),
+ Txt = xmpp:format_error(Why),
+ send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+ false ->
+ ok
+ end,
+ {next_state, StateName, StateData}
+ end.
+
+-spec check_from(jid(), state()) -> boolean().
+check_from(_From, #state{check_from = false}) ->
+ %% 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.
+ true;
+check_from(From, StateData) ->
+ %% The default is the standard behaviour in XEP-0114
+ Server = From#jid.lserver,
+ dict:is_key(Server, StateData#state.host_opts).
+
+-spec new_id() -> binary().
new_id() -> randoms:get_string().
transform_listen_option({hosts, Hosts, O}, Opts) ->
@@ -543,19 +388,3 @@ fsm_limit_opts(Opts) ->
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) -> [max_fsm_queue].
-
-remove_iq_handlers(Ns) ->
- lists:foreach(fun(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns)
- end, ?MYHOSTS).
-
-add_hooks(Hook, Fun) ->
- lists:foreach(fun(Host) ->
- ejabberd_hooks:add(Hook, Host,Fun, 100)
- end, ?MYHOSTS).
-
-remove_hooks(Hook, Fun) ->
- lists:foreach(fun(Host) ->
- ejabberd_hooks:delete(Hook, Host, Fun, 100)
- end, ?MYHOSTS).
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 3369b7ca0..56dc3092e 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -78,7 +78,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_commands.hrl").
-include("mod_privacy.hrl").
@@ -98,10 +98,19 @@
%% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity).
+-type broadcast() :: {broadcast, broadcast_data()}.
+
+-type broadcast_data() ::
+ {rebind, pid(), binary()} | %% ejabberd_c2s
+ {item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
+ {exit, binary()} | %% mod_roster/mod_shared_roster
+ {privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
+ {blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
+
%%====================================================================
%% API
%%====================================================================
--export_type([sid/0]).
+-export_type([sid/0, info/0]).
start() ->
ChildSpec = {?MODULE, {?MODULE, start_link, []},
@@ -111,7 +120,7 @@ start() ->
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
--spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
+-spec route(jid(), jid(), stanza() | broadcast()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -150,23 +159,22 @@ close_session(SID, 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().
-
+-spec check_in_subscription(boolean(), binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> boolean() | {stop, false}.
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
true -> Acc;
false -> {stop, false}
end.
--spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
+-spec bounce_offline_message(jid(), jid(), message()) -> stop.
bounce_offline_message(From, To, Packet) ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = xmpp:get_lang(Packet),
Txt = <<"User session not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err),
+ Err = xmpp:err_service_unavailable(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err),
stop.
-spec disconnect_removed_user(binary(), binary()) -> ok.
@@ -225,7 +233,7 @@ get_user_info(User, Server, Resource) ->
end.
-spec set_presence(sid(), binary(), binary(), binary(),
- prio(), xmlel(), info()) -> ok.
+ prio(), presence(), info()) -> ok.
set_presence(SID, User, Server, Resource, Priority,
Presence, Info) ->
@@ -288,7 +296,7 @@ get_offline_info(Time, User, Server, Resource) ->
[#session{sid = {Time, _}, info = Info}] ->
case proplists:get_bool(offline, Info) of
true ->
- Info;
+ Info;
false ->
none
end;
@@ -436,164 +444,105 @@ is_online(#session{info = Info}) ->
not proplists:get_bool(offline, Info).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
+do_route(From, #jid{lresource = <<"">>} = To, {broadcast, _} = Packet) ->
+ ?DEBUG("processing broadcast to bare JID: ~p", [Packet]),
+ lists:foreach(
+ fun(R) ->
+ do_route(From, jid:replace_resource(To, R), Packet)
+ end, get_user_resources(To#jid.user, To#jid.server));
do_route(From, To, {broadcast, _} = Packet) ->
- case To#jid.lresource of
- <<"">> ->
- lists:foreach(fun(R) ->
- do_route(From,
- jid:replace_resource(To, R),
- Packet)
- end,
- get_user_resources(To#jid.user, To#jid.server));
- _ ->
- {U, S, R} = jid:tolower(To),
- Mod = get_sm_backend(S),
- case online(Mod:get_sessions(U, S, R)) of
- [] ->
- ?DEBUG("packet dropped~n", []);
- Ss ->
- Session = lists:max(Ss),
- Pid = element(2, Session#session.sid),
- ?DEBUG("sending to process ~p~n", [Pid]),
- Pid ! {route, From, To, Packet}
- end
+ ?DEBUG("processing broadcast to full JID: ~p", [Packet]),
+ {U, S, R} = jid:tolower(To),
+ Mod = get_sm_backend(S),
+ case online(Mod:get_sessions(U, S, R)) of
+ [] ->
+ ?DEBUG("dropping broadcast to unavailable resourse: ~p", [Packet]);
+ Ss ->
+ Session = lists:max(Ss),
+ Pid = element(2, Session#session.sid),
+ ?DEBUG("sending to process ~p: ~p", [Pid, Packet]),
+ Pid ! {route, From, To, Packet}
end;
-do_route(From, To, #xmlel{} = Packet) ->
- ?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
- "~P~n",
- [From, To, Packet, 8]),
+do_route(From, To, #presence{type = T, status = Status} = Packet)
+ when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
+ ?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
#jid{user = User, server = Server,
- luser = LUser, lserver = LServer, lresource = LResource} = To,
- #xmlel{name = Name, attrs = Attrs} = Packet,
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
- case LResource of
- <<"">> ->
- case Name of
- <<"presence">> ->
- {Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
- of
- <<"subscribe">> ->
- Reason = fxml:get_path_s(Packet,
- [{elem,
- <<"status">>},
- cdata]),
- {is_privacy_allow(From, To, Packet)
- andalso
- ejabberd_hooks:run_fold(roster_in_subscription,
- LServer,
- false,
- [User, Server,
- From,
- subscribe,
- Reason]),
- true};
- <<"subscribed">> ->
- {is_privacy_allow(From, To, Packet)
- andalso
- ejabberd_hooks:run_fold(roster_in_subscription,
- LServer,
- false,
- [User, Server,
- From,
- subscribed,
- <<"">>]),
- true};
- <<"unsubscribe">> ->
- {is_privacy_allow(From, To, Packet)
- andalso
- ejabberd_hooks:run_fold(roster_in_subscription,
- LServer,
- false,
- [User, Server,
- From,
- unsubscribe,
- <<"">>]),
- true};
- <<"unsubscribed">> ->
- {is_privacy_allow(From, To, Packet)
- andalso
- ejabberd_hooks:run_fold(roster_in_subscription,
- LServer,
- false,
- [User, Server,
- From,
- unsubscribed,
- <<"">>]),
- true};
- _ -> {true, false}
- end,
- if Pass ->
- PResources = get_user_present_resources(LUser, LServer),
- lists:foreach(fun ({_, R}) ->
- do_route(From,
- jid:replace_resource(To,
- R),
- Packet)
- end,
- PResources);
- true -> ok
- end;
- <<"message">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"chat">> -> route_message(From, To, Packet, chat);
- <<"headline">> -> route_message(From, To, Packet, headline);
- <<"error">> -> ok;
- <<"groupchat">> ->
- ErrTxt = <<"User session not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- route_message(From, To, Packet, normal)
- end;
- <<"iq">> -> process_iq(From, To, Packet);
- _ -> ok
- end;
- _ ->
- Mod = get_sm_backend(LServer),
- case online(Mod:get_sessions(LUser, LServer, LResource)) of
- [] ->
- case Name of
- <<"message">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"chat">> -> route_message(From, To, Packet, chat);
- <<"headline">> -> ok;
- <<"error">> -> ok;
- <<"groupchat">> ->
- ErrTxt = <<"User session not found">>,
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- route_message(From, To, Packet, normal)
- end;
- <<"iq">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- <<"result">> -> ok;
- _ ->
- ErrTxt = <<"User session not found">>,
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
- ejabberd_router:route(To, From, Err)
- end;
- _ -> ?DEBUG("packet dropped~n", [])
- end;
- Ss ->
- Session = lists:max(Ss),
- Pid = element(2, Session#session.sid),
- ?DEBUG("sending to process ~p~n", [Pid]),
- Pid ! {route, From, To, Packet}
- end
+ luser = LUser, lserver = LServer} = To,
+ Reason = if T == subscribe -> xmpp:get_text(Status);
+ true -> <<"">>
+ end,
+ case is_privacy_allow(From, To, Packet) andalso
+ ejabberd_hooks:run_fold(
+ roster_in_subscription,
+ LServer, false,
+ [User, Server, From, T, Reason]) of
+ true ->
+ Mod = get_sm_backend(LServer),
+ lists:foreach(
+ fun(#session{sid = SID, usr = {_, _, R},
+ priority = Prio}) when is_integer(Prio) ->
+ Pid = element(2, SID),
+ ?DEBUG("sending to process ~p:~n~s",
+ [Pid, xmpp:pp(Packet)]),
+ Pid ! {route, From, jid:replace_resource(To, R), Packet};
+ (_) ->
+ ok
+ end, online(Mod:get_sessions(LUser, LServer)));
+ false ->
+ ok
+ end;
+do_route(From, #jid{lresource = <<"">>} = To, #presence{} = Packet) ->
+ ?DEBUG("processing presence to bare JID:~n~s", [xmpp:pp(Packet)]),
+ {LUser, LServer, _} = jid:tolower(To),
+ lists:foreach(
+ fun({_, R}) ->
+ do_route(From, jid:replace_resource(To, R), Packet)
+ end, get_user_present_resources(LUser, LServer));
+do_route(From, #jid{lresource = <<"">>} = To, #message{type = T} = Packet) ->
+ ?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
+ if T == chat; T == headline; T == normal; T == groupchat ->
+ route_message(From, To, Packet, T);
+ true ->
+ Lang = xmpp:get_lang(Packet),
+ ErrTxt = <<"User session not found">>,
+ Err = xmpp:err_service_unavailable(ErrTxt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end;
+do_route(From, #jid{lresource = <<"">>} = To, #iq{} = Packet) ->
+ ?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
+ process_iq(From, To, Packet);
+do_route(From, To, Packet) ->
+ ?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
+ {LUser, LServer, LResource} = jid:tolower(To),
+ Mod = get_sm_backend(LServer),
+ case online(Mod:get_sessions(LUser, LServer, LResource)) of
+ [] ->
+ case Packet of
+ #message{type = T} when T == chat; T == normal;
+ T == headline; T == groupchat ->
+ route_message(From, To, Packet, T);
+ #presence{} ->
+ ?DEBUG("dropping presence to unavalable resource:~n~s",
+ [xmpp:pp(Packet)]);
+ _ ->
+ Lang = xmpp:get_lang(Packet),
+ ErrTxt = <<"User session not found">>,
+ Err = xmpp:err_service_unavailable(ErrTxt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end;
+ Ss ->
+ Session = lists:max(Ss),
+ Pid = element(2, Session#session.sid),
+ ?DEBUG("sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]),
+ Pid ! {route, From, To, Packet}
end.
%% The default list applies to the user as a whole,
%% and is processed if there is no active list set
%% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user.
+-spec is_privacy_allow(jid(), jid(), stanza()) -> boolean().
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
@@ -604,6 +553,7 @@ is_privacy_allow(From, To, Packet) ->
%% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl
+-spec is_privacy_allow(jid(), jid(), stanza(), #userlist{}) -> boolean().
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
@@ -613,14 +563,15 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
[User, Server, PrivacyList, {From, To, Packet},
in]).
+-spec route_message(jid(), jid(), message(), message_type()) -> any().
route_message(From, To, Packet, Type) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
- {Priority, _R}
- when is_integer(Priority), Priority >= 0 ->
- lists:foreach(fun ({P, R}) when P == Priority;
+ {MaxPrio, MaxRes}
+ when is_integer(MaxPrio), MaxPrio >= 0 ->
+ lists:foreach(fun ({P, R}) when P == MaxPrio;
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
@@ -632,34 +583,44 @@ route_message(From, To, Packet, Type) ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
- Pid ! {route, From, To, Packet}
+ LMaxRes = jid:resourceprep(MaxRes),
+ Packet1 = maybe_mark_as_copy(Packet,
+ LResource,
+ LMaxRes,
+ P, MaxPrio),
+ Pid ! {route, From, To, Packet1}
end;
%% Ignore other priority:
({_Prio, _Res}) -> ok
end,
PrioRes);
_ ->
- case Type of
- headline -> ok;
- _ ->
- case ejabberd_auth:is_user_exists(LUser, LServer) andalso
- is_privacy_allow(From, To, Packet) of
- true ->
- ejabberd_hooks:run(offline_message_hook, LServer,
- [From, To, Packet]);
- false ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
- end
+ case ejabberd_auth:is_user_exists(LUser, LServer) andalso
+ is_privacy_allow(From, To, Packet) of
+ true ->
+ ejabberd_hooks:run(offline_message_hook, LServer,
+ [From, To, Packet]);
+ false ->
+ Err = xmpp:err_service_unavailable(),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end
end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec maybe_mark_as_copy(message(), binary(), binary(), integer(), integer())
+ -> message().
+maybe_mark_as_copy(Packet, R, R, P, P) ->
+ Packet;
+maybe_mark_as_copy(Packet, _, _, P, P) ->
+ xmpp:put_meta(Packet, sm_copy, true);
+maybe_mark_as_copy(Packet, _, _, _, _) ->
+ Packet.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec clean_session_list([#session{}]) -> [#session{}].
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
+-spec clean_session_list([#session{}], [#session{}]) -> [#session{}].
clean_session_list([], Res) -> Res;
clean_session_list([S], Res) -> [S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
@@ -674,6 +635,7 @@ clean_session_list([S1, S2 | Rest], Res) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% On new session, check if some existing connections need to be replace
+-spec check_for_sessions_to_replace(binary(), binary(), binary()) -> ok | replaced.
check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -681,6 +643,7 @@ check_for_sessions_to_replace(User, Server, Resource) ->
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
+-spec check_existing_resources(binary(), binary(), binary()) -> ok.
check_existing_resources(LUser, LServer, LResource) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer, LResource),
@@ -704,6 +667,7 @@ check_existing_resources(LUser, LServer, LResource) ->
is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource).
+-spec get_resource_sessions(binary(), binary(), binary()) -> [sid()].
get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -711,6 +675,7 @@ get_resource_sessions(User, Server, Resource) ->
Mod = get_sm_backend(LServer),
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
+-spec check_max_sessions(binary(), binary()) -> ok | replaced.
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
@@ -731,6 +696,7 @@ check_max_sessions(LUser, LServer) ->
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
+-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions,
jid:make(LUser, Host, <<"">>))
@@ -742,36 +708,31 @@ get_max_user_sessions(LUser, Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-process_iq(From, To, Packet) ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{xmlns = XMLNS, lang = Lang} ->
- Host = To#jid.lserver,
- case ets:lookup(sm_iqtable, {XMLNS, Host}) of
- [{_, Module, Function}] ->
- ResIQ = Module:Function(From, To, IQ),
- if ResIQ /= ignore ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- true -> ok
- end;
- [{_, Module, Function, Opts}] ->
- gen_iq_handler:handle(Host, Module, Function, Opts,
- From, To, IQ);
- [] ->
- Txt = <<"No module is handling this query">>,
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
- end;
- reply -> ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err),
- ok
- end.
+-spec process_iq(jid(), jid(), iq()) -> any().
+process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
+ when T == get; T == set ->
+ XMLNS = xmpp:get_ns(El),
+ Host = To#jid.lserver,
+ case ets:lookup(sm_iqtable, {XMLNS, Host}) of
+ [{_, Module, Function}] ->
+ gen_iq_handler:handle(Host, Module, Function, no_queue,
+ From, To, Packet);
+ [{_, Module, Function, Opts}] ->
+ gen_iq_handler:handle(Host, Module, Function, Opts,
+ From, To, Packet);
+ [] ->
+ Txt = <<"No module is handling this query">>,
+ Err = xmpp:err_service_unavailable(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end;
+process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
+ Err = xmpp:err_bad_request(),
+ ejabberd_router:route_error(To, From, Packet, Err),
+ ok;
+process_iq(_From, _To, #iq{}) ->
+ ok.
--spec force_update_presence({binary(), binary()}) -> any().
+-spec force_update_presence({binary(), binary()}) -> ok.
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
diff --git a/src/ejabberd_sm_mnesia.erl b/src/ejabberd_sm_mnesia.erl
index b900da315..ed38ceee9 100644
--- a/src/ejabberd_sm_mnesia.erl
+++ b/src/ejabberd_sm_mnesia.erl
@@ -26,7 +26,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
--include("jlib.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-record(state, {}).
@@ -81,10 +80,10 @@ get_sessions(LUser, LServer, LResource) ->
%%%===================================================================
init([]) ->
update_tables(),
- mnesia:create_table(session,
+ ejabberd_mnesia:create(?MODULE, session,
[{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
- mnesia:create_table(session_counter,
+ ejabberd_mnesia:create(?MODULE, session_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, session_counter)}]),
mnesia:add_table_index(session, usr),
diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl
index 2bfd2d8d1..049f1de58 100644
--- a/src/ejabberd_sm_redis.erl
+++ b/src/ejabberd_sm_redis.erl
@@ -19,7 +19,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("logger.hrl").
--include("jlib.hrl").
%%%===================================================================
%%% API
diff --git a/src/ejabberd_sm_sql.erl b/src/ejabberd_sm_sql.erl
index 8871bbca4..2a7b80c19 100644
--- a/src/ejabberd_sm_sql.erl
+++ b/src/ejabberd_sm_sql.erl
@@ -24,7 +24,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("logger.hrl").
--include("jlib.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
@@ -148,7 +147,7 @@ timestamp_to_now(I) ->
{MSec, Sec, USec}.
dec_priority(Prio) ->
- case catch jlib:binary_to_integer(Prio) of
+ case catch binary_to_integer(Prio) of
{'EXIT', _} ->
undefined;
Int ->
@@ -158,7 +157,7 @@ dec_priority(Prio) ->
enc_priority(undefined) ->
<<"">>;
enc_priority(Int) when is_integer(Int) ->
- jlib:integer_to_binary(Int).
+ integer_to_binary(Int).
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
Now = timestamp_to_now(USec),
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 887b4a0f3..e26fc8652 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -31,6 +31,7 @@
-export([start/4,
connect/3,
connect/4,
+ connect/5,
starttls/2,
starttls/3,
compress/1,
@@ -41,6 +42,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
+ get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@@ -48,15 +50,16 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
-type sockmod() :: ejabberd_http_bind |
+ ejabberd_bosh |
ejabberd_http_ws |
gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
fast_tls:tls_socket() |
- ezlib:zlib_socket() |
+ ezlib:zlib_socket() |
+ ejabberd_bosh:bind_socket() |
ejabberd_http_bind:bind_socket().
-record(socket_state, {sockmod = gen_tcp :: sockmod(),
@@ -65,7 +68,7 @@
-type socket_state() :: #socket_state{}.
--export_type([socket_state/0, sockmod/0]).
+-export_type([socket/0, socket_state/0, sockmod/0]).
%%====================================================================
@@ -125,19 +128,21 @@ start(Module, SockMod, Socket, Opts) ->
end.
connect(Addr, Port, Opts) ->
- connect(Addr, Port, Opts, infinity).
+ connect(Addr, Port, Opts, infinity, self()).
connect(Addr, Port, Opts, Timeout) ->
+ connect(Addr, Port, Opts, Timeout, self()).
+
+connect(Addr, Port, Opts, Timeout, Owner) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
{ok, Socket} ->
Receiver = ejabberd_receiver:start(Socket, gen_tcp,
none),
SocketData = #socket_state{sockmod = gen_tcp,
socket = Socket, receiver = Receiver},
- Pid = self(),
case gen_tcp:controlling_process(Socket, Receiver) of
ok ->
- ejabberd_receiver:become_controller(Receiver, Pid),
+ ejabberd_receiver:become_controller(Receiver, Owner),
{ok, SocketData};
{error, _Reason} = Error -> gen_tcp:close(Socket), Error
end;
@@ -188,7 +193,7 @@ send(SocketData, Data) ->
%% Can only be called when in c2s StateData#state.xml_socket is true
%% This function is used for HTTP bind
%% sockmod=ejabberd_http_ws|ejabberd_http_bind or any custom module
--spec send_xml(socket_state(), xmlel()) -> any().
+-spec send_xml(socket_state(), fxml:xmlel()) -> any().
send_xml(SocketData, Data) ->
catch
@@ -215,6 +220,21 @@ monitor(SocketData)
get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
+get_transport(#socket_state{sockmod = SockMod,
+ socket = Socket}) ->
+ case SockMod of
+ gen_tcp -> tcp;
+ fast_tls -> tls;
+ ezlib ->
+ case ezlib:get_sockmod(Socket) of
+ tcp -> tcp_zlib;
+ tls -> tls_zlib
+ end;
+ ejabberd_bosh -> http_bind;
+ ejabberd_http_bind -> http_bind;
+ ejabberd_http_ws -> websocket
+ end.
+
get_peer_certificate(SocketData) ->
fast_tls:get_peer_certificate(SocketData#socket_state.socket).
@@ -237,4 +257,3 @@ peername(#socket_state{sockmod = SockMod,
gen_tcp -> inet:peername(Socket);
_ -> SockMod:peername(Socket)
end.
-
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index 27c2815ba..8db8b6c5f 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -768,7 +768,7 @@ sqlite_to_odbc(Host, {rowid, _}) ->
sqlite_to_odbc(_Host, [{columns, Columns}, {rows, TRows}]) ->
Rows = [lists:map(
fun(I) when is_integer(I) ->
- jlib:integer_to_binary(I);
+ integer_to_binary(I);
(B) ->
B
end, tuple_to_list(Row)) || Row <- TRows],
@@ -813,11 +813,11 @@ pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows,
{selected, [element(1, Row) || Row <- Rows], Recs};
pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) ->
[_OID, N] = str:tokens(OIDN, <<" ">>),
- {updated, jlib:binary_to_integer(N)};
+ {updated, binary_to_integer(N)};
pgsql_item_to_odbc(<<"DELETE ", N/binary>>) ->
- {updated, jlib:binary_to_integer(N)};
+ {updated, binary_to_integer(N)};
pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
- {updated, jlib:binary_to_integer(N)};
+ {updated, binary_to_integer(N)};
pgsql_item_to_odbc({error, Error}) -> {error, Error};
pgsql_item_to_odbc(_) -> {updated, undefined}.
@@ -875,7 +875,7 @@ mysql_item_to_odbc(Columns, Recs) ->
to_odbc({selected, Columns, Recs}) ->
Rows = [lists:map(
fun(I) when is_integer(I) ->
- jlib:integer_to_binary(I);
+ integer_to_binary(I);
(B) ->
B
end, Row) || Row <- Recs],
diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl
index 682414557..93bc10ac5 100644
--- a/src/ejabberd_sql_sup.erl
+++ b/src/ejabberd_sql_sup.erl
@@ -49,7 +49,7 @@
-record(sql_pool, {host, pid}).
start_link(Host) ->
- mnesia:create_table(sql_pool,
+ ejabberd_mnesia:create(?MODULE, sql_pool,
[{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, sql_pool)}]),
@@ -61,10 +61,6 @@ start_link(Host) ->
?MODULE, [Host]).
init([Host]) ->
- PoolSize = ejabberd_config:get_option(
- {sql_pool_size, Host},
- fun(I) when is_integer(I), I>0 -> I end,
- ?DEFAULT_POOL_SIZE),
StartInterval = ejabberd_config:get_option(
{sql_start_interval, Host},
fun(I) when is_integer(I), I>0 -> I end,
@@ -76,6 +72,7 @@ init([Host]) ->
(mssql) -> mssql;
(odbc) -> odbc
end, odbc),
+ PoolSize = get_pool_size(Type, Host),
case Type of
sqlite ->
check_sqlite_db(Host);
@@ -117,6 +114,23 @@ remove_pid(Host, Pid) ->
end,
mnesia:ets(F).
+-spec get_pool_size(atom(), binary()) -> pos_integer().
+get_pool_size(SQLType, Host) ->
+ PoolSize = ejabberd_config:get_option(
+ {sql_pool_size, Host},
+ fun(I) when is_integer(I), I>0 -> I end,
+ case SQLType of
+ sqlite -> 1;
+ _ -> ?DEFAULT_POOL_SIZE
+ end),
+ if PoolSize > 1 andalso SQLType == sqlite ->
+ ?WARNING_MSG("it's not recommended to set sql_pool_size > 1 for "
+ "sqlite, because it may cause race conditions", []);
+ true ->
+ ok
+ end,
+ PoolSize.
+
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl
index 3f6c05667..5d52a041d 100644
--- a/src/ejabberd_system_monitor.erl
+++ b/src/ejabberd_system_monitor.erl
@@ -41,7 +41,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state, {}).
@@ -61,23 +61,22 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
[]).
+-spec process_command(jid(), jid(), stanza()) -> ok.
process_command(From, To, Packet) ->
case To of
#jid{luser = <<"">>, lresource = <<"watchdog">>} ->
- #xmlel{name = Name} = Packet,
- case Name of
- <<"message">> ->
+ case Packet of
+ #message{body = Body} ->
LFrom =
jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
true ->
- Body = fxml:get_path_s(Packet,
- [{elem, <<"body">>}, cdata]),
+ BodyText = xmpp:get_text(Body),
spawn(fun () ->
process_flag(priority, high),
- process_command1(From, To, Body)
+ process_command1(From, To, BodyText)
end),
- stop;
+ ok;
false -> ok
end;
_ -> ok
@@ -181,29 +180,24 @@ process_large_heap(Pid, Info) ->
Host = (?MYNAME),
JIDs = get_admin_jids(),
DetailedInfo = detailed_info(Pid),
- Body = iolist_to_binary(
- io_lib:format("(~w) The process ~w is consuming too "
- "much memory:~n~p~n~s",
- [node(), Pid, Info, DetailedInfo])),
+ Body = str:format("(~w) The process ~w is consuming too "
+ "much memory:~n~p~n~s",
+ [node(), Pid, Info, DetailedInfo]),
From = jid:make(<<"">>, Host, <<"watchdog">>),
- Hint = [#xmlel{name = <<"no-permanent-store">>,
- attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
- lists:foreach(fun (JID) ->
- send_message(From, jid:make(JID), Body, Hint)
- end, JIDs).
+ Hint = [#hint{type = 'no-permanent-store'}],
+ lists:foreach(
+ fun(JID) ->
+ 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}]}
- | ExtraEls]}).
+ #message{type = chat,
+ body = xmpp:mk_text(Body),
+ sub_els = ExtraEls}).
get_admin_jids() ->
ejabberd_config:get_option(
@@ -305,7 +299,7 @@ process_command2([<<"showlh">>, SNode], From, To) ->
process_command2([<<"setlh">>, SNode, NewValueString],
From, To) ->
Node = jlib:binary_to_atom(SNode),
- NewValue = jlib:binary_to_integer(NewValueString),
+ NewValue = binary_to_integer(NewValueString),
remote_command(Node, [setlh, NewValue], From, To);
process_command2([<<"help">>], From, To) ->
send_message(To, From, help());
diff --git a/src/ejabberd_web.erl b/src/ejabberd_web.erl
index 459423aa4..523feb9c7 100644
--- a/src/ejabberd_web.erl
+++ b/src/ejabberd_web.erl
@@ -34,7 +34,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index fb57fa560..3836beda7 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -38,7 +38,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -274,7 +274,7 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case acl:any_rules_allowed(HostOfRule, AccessRule,
- jid:make(User, Server, <<"">>))
+ jid:make(User, Server, <<"">>))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
@@ -763,8 +763,8 @@ process_admin(Host,
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"acls">>,
- (iolist_to_binary(integer_to_list(lists:max([16,
- NumLines])))),
+ (integer_to_binary(lists:max([16,
+ NumLines]))),
<<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>),
?BR,
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
@@ -865,8 +865,8 @@ process_admin(Host,
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"access">>,
- (iolist_to_binary(integer_to_list(lists:max([16,
- NumLines])))),
+ (integer_to_binary(lists:max([16,
+ NumLines]))),
<<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>),
?BR,
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
@@ -926,7 +926,7 @@ process_admin(Host,
Rs1 -> Rs1
end,
make_xhtml([?XC(<<"h1">>,
- list_to_binary(io_lib:format(
+ (str:format(
?T(<<"~s access rule configuration">>),
[SName])))]
++
@@ -1052,17 +1052,21 @@ process_admin(Host,
process_admin(Host,
#request{lang = Lang, auth = {_, _Auth, AJID}} =
Request) ->
- {Hook, Opts} = case Host of
- global -> {webadmin_page_main, [Request]};
- Host -> {webadmin_page_host, [Host, Request]}
- end,
- case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
+ Res = case Host of
+ global ->
+ ejabberd_hooks:run_fold(
+ webadmin_page_main, Host, [], [Request]);
+ _ ->
+ ejabberd_hooks:run_fold(
+ webadmin_page_host, Host, [], [Host, Request])
+ end,
+ case Res of
[] ->
setelement(1,
make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
AJID),
404);
- Res -> make_xhtml(Res, Host, Lang, AJID)
+ _ -> make_xhtml(Res, Host, Lang, AJID)
end.
%%%==================================
@@ -1137,7 +1141,7 @@ acl_spec_select(ID, Opt) ->
%% @spec (T::any()) -> StringLine::string()
term_to_string(T) ->
StringParagraph =
- iolist_to_binary(io_lib:format("~1000000p", [T])),
+ (str:format("~1000000p", [T])),
ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>,
<<"">>).
@@ -1461,8 +1465,8 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
- N1 = jlib:binary_to_integer(S1),
- N2 = jlib:binary_to_integer(S2),
+ N1 = binary_to_integer(S1),
+ N2 = binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
[list_given_users(Host, Sub, <<"../../">>, Lang,
URLFunc)].
@@ -1502,7 +1506,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
{{Year, Month, Day},
{Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
- iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ (str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year,
Month,
Day,
@@ -1651,7 +1655,7 @@ user_info(User, Server, Query, Lang) ->
"://",
(jlib:ip_to_list(IP))/binary,
":",
- (jlib:integer_to_binary(Port))/binary,
+ (integer_to_binary(Port))/binary,
"#",
(jlib:atom_to_binary(Node))/binary>>
end,
@@ -1679,14 +1683,14 @@ user_info(User, Server, Query, Lang) ->
Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
- iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ (str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day,
Hour, Minute,
Second]))
end;
_ -> ?T(<<"Online">>)
end,
- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>),
+ [?XC(<<"h1">>, (str:format(?T(<<"User ~s">>),
[us_to_list(US)])))]
++
case Res of
@@ -1769,9 +1773,7 @@ list_last_activity(Host, Lang, Integral, Period) ->
[?XAE(<<"li">>,
[{<<"style">>,
<<"width:",
- (iolist_to_binary(integer_to_list(trunc(90 * V
- /
- Max))))/binary,
+ (integer_to_binary(trunc(90 * V / Max)))/binary,
"%;">>}],
[{xmlcdata, pretty_string_int(V)}])
|| V <- Hist ++ Tail])]
@@ -1846,7 +1848,7 @@ get_node(global, Node, [], Query, Lang) ->
Base = get_base_path(global, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
[?XC(<<"h1">>,
- list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))]
+ (str:format(?T(<<"Node ~p">>), [Node])))]
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -1871,7 +1873,7 @@ get_node(global, Node, [], Query, Lang) ->
get_node(Host, Node, [], _Query, Lang) ->
Base = get_base_path(Host, Node),
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))),
+ [?XC(<<"h1">>, (str:format(?T(<<"Node ~p">>), [Node]))),
?XE(<<"ul">>,
([?LI([?ACT(<<Base/binary, "modules/">>,
<<"Modules">>)])]
@@ -1930,7 +1932,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) ->
end,
STables),
[?XC(<<"h1">>,
- list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>),
+ (str:format(?T(<<"Database Tables at ~p">>),
[Node]))
)]
++
@@ -1966,9 +1968,9 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
ok -> [?XREST(<<"Submitted">>)];
{error, Error} ->
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
- (list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
+ ((str:format("~p", [Error])))/binary>>)]
end,
- [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
+ [?XC(<<"h1">>, (str:format(?T(<<"Backup of ~p">>), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
@@ -2120,7 +2122,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
{'EXIT', _Reason} -> error;
{is_added, ok} -> ok;
{is_added, {error, Reason}} ->
- {error, iolist_to_binary(io_lib:format("~p", [Reason]))};
+ {error, (str:format("~p", [Reason]))};
_ -> nothing
end,
NewPorts = lists:sort(ejabberd_cluster:call(Node, ejabberd_config,
@@ -2157,7 +2159,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
end,
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])),
+ H1String = (str:format(?T(<<"Modules at ~p">>), [Node])),
(?H1GL(H1String, <<"modulesoverview">>,
<<"Modules Overview">>))
++
@@ -2173,10 +2175,10 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
get_node(global, Node, [<<"stats">>], _Query, Lang) ->
UpTime = ejabberd_cluster:call(Node, erlang, statistics,
[wall_clock]),
- UpTimeS = list_to_binary(io_lib:format("~.3f",
+ UpTimeS = (str:format("~.3f",
[element(1, UpTime) / 1000])),
CPUTime = ejabberd_cluster:call(Node, erlang, statistics, [runtime]),
- CPUTimeS = list_to_binary(io_lib:format("~.3f",
+ CPUTimeS = (str:format("~.3f",
[element(1, CPUTime) / 1000])),
OnlineUsers = ejabberd_sm:connected_users_number(),
TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
@@ -2188,7 +2190,7 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) ->
TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_log_writes]),
[?XC(<<"h1">>,
- list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
+ (str:format(?T(<<"Statistics of ~p">>), [Node]))),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
@@ -2252,11 +2254,11 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
(BeamsLis ++ SelectButtons))
end,
FmtScript = (?XC(<<"pre">>,
- list_to_binary(io_lib:format("~p", [Script])))),
+ (str:format("~p", [Script])))),
FmtLowLevelScript = (?XC(<<"pre">>,
- list_to_binary(io_lib:format("~p", [LowLevelScript])))),
+ (str:format("~p", [LowLevelScript])))),
[?XC(<<"h1">>,
- list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))]
+ (str:format(?T(<<"Update ~p">>), [Node])))]
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2276,16 +2278,17 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
?BR,
?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])];
get_node(Host, Node, NPath, Query, Lang) ->
- {Hook, Opts} = case Host of
- global ->
- {webadmin_page_node, [Node, NPath, Query, Lang]};
- Host ->
- {webadmin_page_hostnode,
- [Host, Node, NPath, Query, Lang]}
- end,
- case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
+ Res = case Host of
+ global ->
+ ejabberd_hooks:run_fold(webadmin_page_node, Host, [],
+ [Node, NPath, Query, Lang]);
+ _ ->
+ ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [],
+ [Host, Node, NPath, Query, Lang])
+ end,
+ case Res of
[] -> [?XC(<<"h1">>, <<"Not Found">>)];
- Res -> Res
+ _ -> Res
end.
%%%==================================
@@ -2477,7 +2480,7 @@ node_ports_to_xhtml(Ports, Lang) ->
SModule, <<"15">>)]),
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SSPort/binary>>,
- (iolist_to_binary(integer_to_list(NumLines))),
+ (integer_to_binary(NumLines)),
<<"35">>, SOptsClean)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
@@ -2522,7 +2525,7 @@ make_netprot_html(NetProt) ->
get_port_data(PortIP, Opts) ->
{Port, IPT, IPS, _IPV, NetProt, OptsClean} =
ejabberd_listener:parse_listener_portip(PortIP, Opts),
- SPort = jlib:integer_to_binary(Port),
+ SPort = integer_to_binary(Port),
SSPort = list_to_binary(
lists:map(fun (N) ->
io_lib:format("~.16b", [N])
@@ -2620,7 +2623,7 @@ node_modules_to_xhtml(Modules, Lang) ->
[?XC(<<"td">>, SModule),
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SModule/binary>>,
- (iolist_to_binary(integer_to_list(NumLines))),
+ (integer_to_binary(NumLines)),
<<"40">>, SOpts)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
@@ -2702,11 +2705,11 @@ node_update_parse_query(Node, Query) ->
{ok, _} -> ok;
{error, Error} ->
?ERROR_MSG("~p~n", [Error]),
- {error, iolist_to_binary(io_lib:format("~p", [Error]))};
+ {error, (str:format("~p", [Error]))};
{badrpc, Error} ->
?ERROR_MSG("Bad RPC: ~p~n", [Error]),
{error,
- <<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>}
+ <<"Bad RPC: ", ((str:format("~p", [Error])))/binary>>}
end;
_ -> nothing
end.
@@ -2771,7 +2774,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
element_to_list(X) when is_atom(X) ->
iolist_to_binary(atom_to_list(X));
element_to_list(X) when is_integer(X) ->
- iolist_to_binary(integer_to_list(X)).
+ integer_to_binary(X).
list_to_element(Bin) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)),
@@ -2779,8 +2782,8 @@ list_to_element(Bin) ->
Element.
url_func({user_diapason, From, To}) ->
- <<(iolist_to_binary(integer_to_list(From)))/binary, "-",
- (iolist_to_binary(integer_to_list(To)))/binary, "/">>;
+ <<(integer_to_binary(From))/binary, "-",
+ (integer_to_binary(To))/binary, "/">>;
url_func({users_queue, Prefix, User, _Server}) ->
<<Prefix/binary, "user/", User/binary, "/queue/">>;
url_func({user, Prefix, User, _Server}) ->
@@ -2795,7 +2798,7 @@ cache_control_public() ->
%% Transform 1234567890 into "1,234,567,890"
pretty_string_int(Integer) when is_integer(Integer) ->
- pretty_string_int(iolist_to_binary(integer_to_list(Integer)));
+ pretty_string_int(integer_to_binary(Integer));
pretty_string_int(String) when is_binary(String) ->
{_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
{1, <<NewNumber, $,, Result/binary>>};
diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl
index 0cdd9bac5..76568aa2d 100644
--- a/src/ejabberd_websocket.erl
+++ b/src/ejabberd_websocket.erl
@@ -47,7 +47,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 1dd88f837..2792d08c1 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -42,7 +42,7 @@
-include("ejabberd_http.hrl").
-include("mod_roster.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state,
{access_commands = [] :: list(),
@@ -216,10 +216,10 @@ process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
L),
L
end, all),
- CommOpts = gen_mod:get_opt(
- options, AcOpts,
- fun(L) when is_list(L) -> L end,
- []),
+ %% CommOpts = gen_mod:get_opt(
+ %% options, AcOpts,
+ %% fun(L) when is_list(L) -> L end,
+ %% []),
[{<<"ejabberd_xmlrpc compatibility shim">>, {[?MODULE], [{access, Ac}], Commands}}];
(Wrong) ->
?WARNING_MSG("wrong options format for ~p: ~p",
diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl
index 7bace05dd..9b7fdbbef 100644
--- a/src/ejd2sql.erl
+++ b/src/ejd2sql.erl
@@ -30,12 +30,12 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
--export([export/2, export/3, import_file/2, import/2,
- import/3, delete/1]).
+-export([export/2, export/3, import/3, import/4, delete/1, import_info/1]).
+
-define(MAX_RECORDS_PER_TRANSACTION, 100).
--record(dump, {fd, cont = start}).
+-record(sql_dump, {fd, type}).
%%%----------------------------------------------------------------------
%%% API
@@ -50,13 +50,14 @@
modules() ->
[ejabberd_auth,
mod_announce,
+ mod_caps,
mod_irc,
mod_last,
mod_muc,
mod_offline,
mod_privacy,
mod_private,
- %% mod_pubsub,
+ mod_pubsub,
mod_roster,
mod_shared_roster,
mod_vcard,
@@ -100,49 +101,44 @@ delete(Server, Module) ->
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) ->
- case disk_log:open([{name, make_ref()},
- {file, FileName},
- {mode, read_only}]) of
- {ok, Fd} ->
- 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_mnesia,
- ejabberd_auth:auth_modules(LServer)) of
- true ->
- [{ejabberd_auth, mnesia}];
- false ->
- []
- end,
- import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd});
- Err ->
- exit(Err)
- end.
-
-import(Server, Output) ->
- import(Server, Output, [{fast, true}]).
-
-import(Server, Output, Opts) ->
- LServer = jid:nameprep(iolist_to_binary(Server)),
- Modules = modules(),
- IO = prepare_output(Output, disk_log),
+import(Server, Dir, ToType) ->
lists:foreach(
- fun(Module) ->
- import(LServer, IO, Opts, Module)
- end, Modules),
- close_output(Output, IO).
+ fun(Mod) ->
+ ?INFO_MSG("importing ~p...", [Mod]),
+ import(Mod, Server, Dir, ToType)
+ end, modules()).
-import(Server, Output, Opts, Module) ->
+import(Mod, Server, Dir, ToType) ->
LServer = jid:nameprep(iolist_to_binary(Server)),
- IO = prepare_output(Output, disk_log),
+ try Mod:import_start(LServer, ToType)
+ catch error:undef -> ok end,
lists:foreach(
- fun({SelectQuery, ConvertFun}) ->
- import(LServer, SelectQuery, IO, ConvertFun, Opts)
- end, Module:import(Server)),
- close_output(Output, IO).
+ fun({File, Tab, _Mod, FieldsNumber}) ->
+ FileName = filename:join([Dir, File]),
+ case open_sql_dump(FileName) of
+ {ok, #sql_dump{type = FromType} = Dump} ->
+ import_rows(LServer, {sql, FromType}, ToType,
+ Tab, Mod, Dump, FieldsNumber),
+ close_sql_dump(Dump);
+ {error, enoent} ->
+ ok;
+ eof ->
+ ?INFO_MSG("It seems like SQL dump ~s is empty", [FileName]);
+ Err ->
+ ?ERROR_MSG("Failed to open SQL dump ~s: ~s",
+ [FileName, format_error(Err)])
+ end
+ end, import_info(Mod)),
+ try Mod:import_stop(LServer, ToType)
+ catch error:undef -> ok end.
+
+import_info(Mod) ->
+ Info = Mod:import_info(),
+ lists:map(
+ fun({Tab, FieldsNum}) ->
+ FileName = <<Tab/binary, ".txt">>,
+ {FileName, Tab, Mod, FieldsNum}
+ end, Info).
%%%----------------------------------------------------------------------
%%% Internal functions
@@ -200,79 +196,6 @@ delete(LServer, Table, ConvertFun) ->
end,
mnesia:transaction(F).
-import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
- F = case proplists:get_bool(fast, Opts) of
- true ->
- fun() ->
- case ejabberd_sql:sql_query_t(SelectQuery) of
- {selected, _, Rows} ->
- lists:foldl(fun process_sql_row/2,
- {IO, ConvertFun, undefined}, Rows);
- Err ->
- erlang:error(Err)
- end
- end;
- false ->
- fun() ->
- ejabberd_sql:sql_query_t(
- [iolist_to_binary(
- [<<"declare c cursor for ">>, SelectQuery])]),
- fetch(IO, ConvertFun, undefined)
- end
- end,
- ejabberd_sql:sql_transaction(LServer, F).
-
-fetch(IO, ConvertFun, PrevRow) ->
- case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
- {selected, _, [Row]} ->
- process_sql_row(Row, {IO, ConvertFun, PrevRow}),
- fetch(IO, ConvertFun, Row);
- {selected, _, []} ->
- ok;
- Err ->
- erlang:error(Err)
- end.
-
-process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow ->
- %% Avoid calling ConvertFun with the same input
- {IO, ConvertFun, Row};
-process_sql_row(Row, {IO, ConvertFun, _PrevRow}) ->
- case catch ConvertFun(Row) of
- {'EXIT', _} = Err ->
- ?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]);
- Term ->
- ok = disk_log:log(IO#dump.fd, Term)
- end,
- {IO, ConvertFun, Row}.
-
-import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) ->
- case disk_log:chunk(Fd, Cont) of
- {NewCont, Terms} ->
- import_terms(LServer, Mods, Terms),
- import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont});
- eof ->
- ok;
- Err ->
- exit(Err)
- end.
-
-import_terms(LServer, Mods, [Term|Terms]) ->
- import_term(LServer, Mods, Term),
- import_terms(LServer, Mods, Terms);
-import_terms(_LServer, _Mods, []) ->
- ok.
-
-import_term(LServer, [{Mod, DBType}|Mods], Term) ->
- case catch Mod:import(LServer, DBType, Term) of
- pass -> import_term(LServer, Mods, Term);
- ok -> ok;
- Err ->
- ?ERROR_MSG("failed to import ~p for module ~p: ~p",
- [Term, Mod, Err])
- end;
-import_term(_LServer, [], _Term) ->
- ok.
-
prepare_output(FileName) ->
prepare_output(FileName, normal).
@@ -285,25 +208,11 @@ prepare_output(FileName, normal) when is_list(FileName) ->
Err ->
exit(Err)
end;
-prepare_output(FileName, disk_log) when is_list(FileName) ->
- case disk_log:open([{name, make_ref()},
- {repair, truncate},
- {file, FileName}]) of
- {ok, Fd} ->
- #dump{fd = Fd};
- Err ->
- exit(Err)
- end;
prepare_output(Output, _Type) ->
Output.
close_output(FileName, Fd) when FileName /= Fd ->
- case Fd of
- #dump{} ->
- disk_log:close(Fd#dump.fd);
- _ ->
- file:close(Fd)
- end,
+ file:close(Fd),
ok;
close_output(_, _) ->
ok.
@@ -321,6 +230,129 @@ flatten1([H|T], Acc) ->
flatten1([], Acc) ->
Acc.
+import_rows(LServer, FromType, ToType, Tab, Mod, Dump, FieldsNumber) ->
+ case read_row_from_sql_dump(Dump, FieldsNumber) of
+ {ok, Fields} ->
+ case catch Mod:import(LServer, FromType, ToType, Tab, Fields) of
+ ok ->
+ ok;
+ Err ->
+ ?ERROR_MSG("Failed to import fields ~p for tab ~p: ~p",
+ [Fields, Tab, Err])
+ end,
+ import_rows(LServer, FromType, ToType,
+ Tab, Mod, Dump, FieldsNumber);
+ eof ->
+ ok;
+ Err ->
+ ?ERROR_MSG("Failed to read row from SQL dump: ~s",
+ [format_error(Err)])
+ end.
+
+open_sql_dump(FileName) ->
+ case file:open(FileName, [raw, read, binary, read_ahead]) of
+ {ok, Fd} ->
+ case file:read(Fd, 11) of
+ {ok, <<"PGCOPY\n", 16#ff, "\r\n", 0>>} ->
+ case skip_pgcopy_header(Fd) of
+ ok ->
+ {ok, #sql_dump{fd = Fd, type = pgsql}};
+ Err ->
+ Err
+ end;
+ {ok, _} ->
+ file:position(Fd, 0),
+ {ok, #sql_dump{fd = Fd, type = mysql}};
+ Err ->
+ Err
+ end;
+ Err ->
+ Err
+ end.
+
+close_sql_dump(#sql_dump{fd = Fd}) ->
+ file:close(Fd).
+
+read_row_from_sql_dump(#sql_dump{fd = Fd, type = pgsql}, _) ->
+ case file:read(Fd, 2) of
+ {ok, <<(-1):16/signed>>} ->
+ eof;
+ {ok, <<FieldsNum:16>>} ->
+ read_fields(Fd, FieldsNum, []);
+ {ok, _} ->
+ {error, eof};
+ eof ->
+ {error, eof};
+ {error, _} = Err ->
+ Err
+ end;
+read_row_from_sql_dump(#sql_dump{fd = Fd, type = mysql}, FieldsNum) ->
+ read_lines(Fd, FieldsNum, <<"">>, []).
+
+skip_pgcopy_header(Fd) ->
+ try
+ {ok, <<_:4/binary, ExtSize:32>>} = file:read(Fd, 8),
+ {ok, <<_:ExtSize/binary>>} = file:read(Fd, ExtSize),
+ ok
+ catch error:{badmatch, {error, _} = Err} ->
+ Err;
+ error:{badmatch, _} ->
+ {error, eof}
+ end.
+
+read_fields(_Fd, 0, Acc) ->
+ {ok, lists:reverse(Acc)};
+read_fields(Fd, N, Acc) ->
+ case file:read(Fd, 4) of
+ {ok, <<(-1):32/signed>>} ->
+ read_fields(Fd, N-1, [null|Acc]);
+ {ok, <<ValSize:32>>} ->
+ case file:read(Fd, ValSize) of
+ {ok, <<Val:ValSize/binary>>} ->
+ read_fields(Fd, N-1, [Val|Acc]);
+ {ok, _} ->
+ {error, eof};
+ Err ->
+ Err
+ end;
+ {ok, _} ->
+ {error, eof};
+ eof ->
+ {error, eof};
+ {error, _} = Err ->
+ Err
+ end.
+
+read_lines(_Fd, 0, <<"">>, Acc) ->
+ {ok, lists:reverse(Acc)};
+read_lines(Fd, N, Buf, Acc) ->
+ case file:read_line(Fd) of
+ {ok, Data} when size(Data) >= 2 ->
+ Size = size(Data) - 2,
+ case Data of
+ <<Val:Size/binary, 0, $\n>> ->
+ NewBuf = <<Buf/binary, Val/binary>>,
+ read_lines(Fd, N-1, <<"">>, [NewBuf|Acc]);
+ _ ->
+ NewBuf = <<Buf/binary, Data/binary>>,
+ read_lines(Fd, N, NewBuf, Acc)
+ end;
+ {ok, Data} ->
+ NewBuf = <<Buf/binary, Data/binary>>,
+ read_lines(Fd, N, NewBuf, Acc);
+ eof when Buf == <<"">>, Acc == [] ->
+ eof;
+ eof ->
+ {error, eof};
+ {error, _} = Err ->
+ Err
+ end.
+
+format_error({error, eof}) ->
+ "unexpected end of file";
+format_error({error, Posix}) ->
+ file:format_error(Posix).
+
format_queries(SQLs) ->
lists:map(
fun(#sql_query{} = SQL) ->
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 842bb09fc..5b970623b 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -45,7 +45,7 @@
start() ->
[code:add_patha(module_ebin_dir(Module))
|| {Module, _} <- installed()],
- application:start(inets),
+ p1_http:start(),
ejabberd_commands:register_commands(get_commands_spec()).
stop() ->
@@ -170,7 +170,10 @@ install(Package) when is_binary(Package) ->
ok ->
code:add_patha(module_ebin_dir(Module)),
ejabberd_config:reload_file(),
- ok;
+ case erlang:function_exported(Module, post_install, 0) of
+ true -> Module:post_install();
+ _ -> ok
+ end;
Error ->
delete_path(module_lib_dir(Module)),
Error
@@ -183,6 +186,10 @@ uninstall(Package) when is_binary(Package) ->
case installed(Package) of
true ->
Module = jlib:binary_to_atom(Package),
+ case erlang:function_exported(Module, pre_uninstall, 0) of
+ true -> Module:pre_uninstall();
+ _ -> ok
+ end,
[catch gen_mod:stop_module(Host, Module)
|| Host <- ejabberd_config:get_myhosts()],
code:purge(Module),
@@ -271,10 +278,10 @@ geturl(Url, Hdrs, UsrOpts) ->
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
_ -> []
end,
- case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts++[{version, "HTTP/1.0"}], []) of
- {ok, {{_, 200, _}, Headers, Response}} ->
+ case p1_http:request(get, Url, Hdrs, [], Host++User++UsrOpts++[{version, "HTTP/1.0"}]) of
+ {ok, 200, Headers, Response} ->
{ok, Headers, Response};
- {ok, {{_, Code, _}, _Headers, Response}} ->
+ {ok, Code, _Headers, Response} ->
{error, {Code, Response}};
{error, Reason} ->
{error, Reason}
@@ -520,11 +527,8 @@ compile(_Module, _Spec, DestDir) ->
filelib:ensure_dir(filename:join(Ebin, ".")),
EjabBin = filename:dirname(code:which(ejabberd)),
EjabInc = filename:join(filename:dirname(EjabBin), "include"),
- 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]
- ++ ExtLib,
+ verbose, report_errors, report_warnings],
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
%% Compile erlang files
diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl
index c2b4252c9..4a7a03c27 100644
--- a/src/gen_iq_handler.erl
+++ b/src/gen_iq_handler.erl
@@ -40,13 +40,14 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state, {host, module, function}).
-type component() :: ejabberd_sm | ejabberd_local.
-type type() :: no_queue | one_queue | pos_integer() | parallel.
-type opts() :: no_queue | {one_queue, pid()} | {queues, [pid()]} | parallel.
+-export_type([opts/0]).
%%====================================================================
%% API
@@ -59,6 +60,8 @@ start_link(Host, Module, Function) ->
gen_server:start_link(?MODULE, [Host, Module, Function],
[]).
+-spec add_iq_handler(module(), binary(), binary(), module(), atom(), type()) -> any().
+
add_iq_handler(Component, Host, NS, Module, Function,
Type) ->
case Type of
@@ -124,14 +127,47 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
-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]);
- ResIQ ->
- if ResIQ /= ignore ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- true -> ok
- end
+process_iq(_Host, Module, Function, From, To, IQ0) ->
+ IQ = xmpp:set_from_to(IQ0, From, To),
+ try
+ ResIQ = case erlang:function_exported(Module, Function, 1) of
+ true ->
+ process_iq(Module, Function, IQ);
+ false ->
+ process_iq(Module, Function, From, To,
+ jlib:iq_query_info(xmpp:encode(IQ)))
+ end,
+ if ResIQ /= ignore ->
+ ejabberd_router:route(To, From, ResIQ);
+ true ->
+ ok
+ end
+ catch E:R ->
+ ?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
+ [xmpp:pp(IQ), {E, {R, erlang:get_stacktrace()}}]),
+ Txt = <<"Module failed to handle the query">>,
+ Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
+ ejabberd_router:route_error(To, From, IQ, Err)
+ end.
+
+-spec process_iq(module(), atom(), iq()) -> ignore | iq().
+process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) ->
+ try
+ Pkt = case erlang:function_exported(Module, decode_iq_subel, 1) of
+ true -> Module:decode_iq_subel(El);
+ false -> xmpp:decode(El)
+ end,
+ Module:Function(IQ#iq{sub_els = [Pkt]})
+ catch error:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end.
+
+-spec process_iq(module(), atom(), jid(), jid(), term()) -> iq().
+process_iq(Module, Function, From, To, IQ) ->
+ case Module:Function(From, To, IQ) of
+ ignore -> ignore;
+ ResIQ -> xmpp:decode(jlib:iq_to_xml(ResIQ), ?NS_CLIENT, [ignore_els])
end.
-spec check_type(type()) -> type().
diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl
index 27cb032bd..5690c8a1d 100644
--- a/src/gen_pubsub_node.erl
+++ b/src/gen_pubsub_node.erl
@@ -25,7 +25,7 @@
-module(gen_pubsub_node).
--include("jlib.hrl").
+-include("xmpp.hrl").
-type(host() :: mod_pubsub:host()).
-type(nodeId() :: mod_pubsub:nodeId()).
@@ -175,20 +175,13 @@
ok |
{error, xmlel()}.
--callback get_items(NodeIdx :: nodeIdx(),
- JID :: jid(),
- AccessModel :: accessModel(),
- Presence_Subscription :: boolean(),
- RosterGroup :: boolean(),
- SubId :: subId(),
- RSM :: none | rsm_in()) ->
- {result, {[pubsubItem()], none | rsm_out()}} |
- {error, xmlel()}.
+-callback get_items(nodeIdx(), jid(), accessModel(),
+ boolean(), boolean(), binary(),
+ undefined | rsm_set()) ->
+ {result, {[pubsubItem()], undefined | rsm_set()}} | {error, stanza_error()}.
--callback get_items(NodeIdx :: nodeIdx(),
- From :: jid(),
- RSM :: none | rsm_in()) ->
- {result, {[pubsubItem()], none | rsm_out()}}.
+-callback get_items(nodeIdx(), jid(), undefined | rsm_set()) ->
+ {result, {[pubsubItem()], undefined | rsm_set()}}.
-callback get_item(NodeIdx :: nodeIdx(),
ItemId :: itemId(),
diff --git a/src/gen_pubsub_nodetree.erl b/src/gen_pubsub_nodetree.erl
index 73583af02..a18bc8d39 100644
--- a/src/gen_pubsub_nodetree.erl
+++ b/src/gen_pubsub_nodetree.erl
@@ -25,7 +25,6 @@
-module(gen_pubsub_nodetree).
--include("jlib.hrl").
-type(host() :: mod_pubsub:host()).
-type(nodeId() :: mod_pubsub:nodeId()).
@@ -42,25 +41,25 @@
-callback options() -> nodeOptions().
-callback set_node(PubsubNode :: pubsubNode()) ->
- ok | {result, NodeIdx::nodeIdx()} | {error, xmlel()}.
+ ok | {result, NodeIdx::nodeIdx()} | {error, fxml:xmlel()}.
-callback get_node(Host :: host(),
NodeId :: nodeId(),
- From :: jid()) ->
+ From :: jid:jid()) ->
pubsubNode() |
- {error, xmlel()}.
+ {error, fxml:xmlel()}.
-callback get_node(Host :: host(),
NodeId :: nodeId()) ->
pubsubNode() |
- {error, xmlel()}.
+ {error, fxml:xmlel()}.
-callback get_node(NodeIdx :: nodeIdx()) ->
pubsubNode() |
- {error, xmlel()}.
+ {error, fxml:xmlel()}.
-callback get_nodes(Host :: host(),
- From :: jid())->
+ From :: jid:jid())->
[pubsubNode()].
-callback get_nodes(Host :: host())->
@@ -68,33 +67,33 @@
-callback get_parentnodes(Host :: host(),
NodeId :: nodeId(),
- From :: jid()) ->
+ From :: jid:jid()) ->
[pubsubNode()] |
- {error, xmlel()}.
+ {error, fxml:xmlel()}.
-callback get_parentnodes_tree(Host :: host(),
NodeId :: nodeId(),
- From :: jid()) ->
+ From :: jid:jid()) ->
[{0, [pubsubNode(),...]}].
-callback get_subnodes(Host :: host(),
NodeId :: nodeId(),
- From :: jid()) ->
+ From :: jid:jid()) ->
[pubsubNode()].
-callback get_subnodes_tree(Host :: host(),
NodeId :: nodeId(),
- From :: jid()) ->
+ From :: jid:jid()) ->
[pubsubNode()].
-callback create_node(Host :: host(),
NodeId :: nodeId(),
Type :: binary(),
- Owner :: jid(),
+ Owner :: jid:jid(),
Options :: nodeOptions(),
Parents :: [nodeId()]) ->
{ok, NodeIdx::nodeIdx()} |
- {error, xmlel()} |
+ {error, fxml:xmlel()} |
{error, {virtual, {host(), nodeId()}}}.
-callback delete_node(Host :: host(),
diff --git a/src/http_p1.erl b/src/http_p1.erl
deleted file mode 100644
index f430bbe11..000000000
--- a/src/http_p1.erl
+++ /dev/null
@@ -1,358 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : http_p1.erl
-%%% Author : Emilio Bustos <ebustos@process-one.net>
-%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
-%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@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(http_p1).
-
--author('ebustos@process-one.net').
-
--export([start/0, stop/0, get/1, get/2, post/2, post/3,
- request/3, request/4, request/5,
- get_pool_size/0, set_pool_size/1]).
-
--include("logger.hrl").
-
--define(USE_INETS, 1).
-% -define(USE_LHTTPC, 1).
-% -define(USE_IBROWSE, 1).
-% inets used as default if none specified
-
--ifdef(USE_IBROWSE).
-
-start() ->
- ejabberd:start_app(ibrowse).
-
-stop() ->
- application:stop(ibrowse).
-
-request(Method, URL, Hdrs, Body, Opts) ->
- TimeOut = proplists:get_value(timeout, Opts, infinity),
- Options = [{inactivity_timeout, TimeOut}
- | proplists:delete(timeout, Opts)],
- case ibrowse:send_req(URL, Hdrs, Method, Body, Options)
- of
- {ok, Status, Headers, Response} ->
- {ok, jlib:binary_to_integer(Status), Headers,
- Response};
- {error, Reason} -> {error, Reason}
- end.
-
-get_pool_size() ->
- application:get_env(ibrowse, default_max_sessions, 10).
-
-set_pool_size(Size) ->
- application:set_env(ibrowse, default_max_sessions, Size).
-
--else.
-
--ifdef(USE_LHTTPC).
-
-start() ->
- ejabberd:start_app(lhttpc).
-
-stop() ->
- application:stop(lhttpc).
-
-request(Method, URL, Hdrs, Body, Opts) ->
- {[TO, SO], Rest} = proplists:split(Opts, [timeout, socket_options]),
- TimeOut = proplists:get_value(timeout, TO, infinity),
- SockOpt = proplists:get_value(socket_options, SO, []),
- Options = [{connect_options, SockOpt} | Rest],
- Result = lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options),
- ?DEBUG("HTTP request -> response:~n"
- "** Method = ~p~n"
- "** URI = ~s~n"
- "** Body = ~s~n"
- "** Hdrs = ~p~n"
- "** Timeout = ~p~n"
- "** Options = ~p~n"
- "** Response = ~p",
- [Method, URL, Body, Hdrs, TimeOut, Options, Result]),
- case Result of
- {ok, {{Status, _Reason}, Headers, Response}} ->
- {ok, Status, Headers, (Response)};
- {error, Reason} -> {error, Reason}
- end.
-
-get_pool_size() ->
- Opts = proplists:get_value(lhttpc_manager, lhttpc_manager:list_pools()),
- proplists:get_value(max_pool_size,Opts).
-
-set_pool_size(Size) ->
- lhttpc_manager:set_max_pool_size(lhttpc_manager, Size).
-
--else.
-
-start() ->
- ejabberd:start_app(inets).
-
-stop() ->
- application:stop(inets).
-
-to_list(Str) when is_binary(Str) ->
- binary_to_list(Str);
-to_list(Str) ->
- Str.
-
-request(Method, URLRaw, HdrsRaw, Body, Opts) ->
- Hdrs = lists:map(fun({N, V}) ->
- {to_list(N), to_list(V)}
- end, HdrsRaw),
- URL = to_list(URLRaw),
-
- Request = case Method of
- get -> {URL, Hdrs};
- head -> {URL, Hdrs};
- delete -> {URL, Hdrs};
- _ -> % post, etc.
- {URL, Hdrs,
- to_list(proplists:get_value(<<"content-type">>, HdrsRaw, [])),
- Body}
- end,
- Options = case proplists:get_value(timeout, Opts,
- infinity)
- of
- infinity -> proplists:delete(timeout, Opts);
- _ -> Opts
- end,
- case httpc:request(Method, Request, Options, []) of
- {ok, {{_, Status, _}, Headers, Response}} ->
- {ok, Status, Headers, Response};
- {error, Reason} -> {error, Reason}
- end.
-
-get_pool_size() ->
- {ok, Size} = httpc:get_option(max_sessions),
- Size.
-
-set_pool_size(Size) ->
- httpc:set_option(max_sessions, Size).
-
--endif.
-
--endif.
-
--type({header,
- {type, 63, tuple,
- [{type, 63, union,
- [{type, 63, string, []}, {type, 63, atom, []}]},
- {type, 63, string, []}]},
- []}).
-
--type({headers,
- {type, 64, list, [{type, 64, header, []}]}, []}).
-
--type({option,
- {type, 67, union,
- [{type, 67, tuple,
- [{atom, 67, connect_timeout}, {type, 67, timeout, []}]},
- {type, 68, tuple,
- [{atom, 68, timeout}, {type, 68, timeout, []}]},
- {type, 70, tuple,
- [{atom, 70, send_retry},
- {type, 70, non_neg_integer, []}]},
- {type, 71, tuple,
- [{atom, 71, partial_upload},
- {type, 71, union,
- [{type, 71, non_neg_integer, []},
- {atom, 71, infinity}]}]},
- {type, 72, tuple,
- [{atom, 72, partial_download}, {type, 72, pid, []},
- {type, 72, union,
- [{type, 72, non_neg_integer, []},
- {atom, 72, infinity}]}]}]},
- []}).
-
--type({options,
- {type, 74, list, [{type, 74, option, []}]}, []}).
-
--type({result,
- {type, 76, union,
- [{type, 76, tuple,
- [{atom, 76, ok},
- {type, 76, tuple,
- [{type, 76, tuple,
- [{type, 76, pos_integer, []}, {type, 76, string, []}]},
- {type, 76, headers, []}, {type, 76, string, []}]}]},
- {type, 77, tuple,
- [{atom, 77, error}, {type, 77, atom, []}]}]},
- []}).
-
-%% @spec (URL) -> Result
-%% URL = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a GET request.
-%% Would be the same as calling `request(get, URL, [])',
-%% that is {@link request/3} with an empty header list.
-%% @end
-%% @see request/3
--spec get(string()) -> result().
-get(URL) -> request(get, URL, []).
-
-%% @spec (URL, Hdrs) -> Result
-%% URL = string()
-%% Hdrs = [{Header, Value}]
-%% Header = string()
-%% Value = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a GET request.
-%% Would be the same as calling `request(get, URL, Hdrs)'.
-%% @end
-%% @see request/3
--spec get(string(), headers()) -> result().
-get(URL, Hdrs) -> request(get, URL, Hdrs).
-
-%% @spec (URL, RequestBody) -> Result
-%% URL = string()
-%% RequestBody = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a POST request with form data.
-%% Would be the same as calling
-%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
-%% @end
-%% @see request/4
--spec post(string(), string()) -> result().
-post(URL, Body) ->
- request(post, URL,
- [{<<"content-type">>, <<"x-www-form-urlencoded">>}],
- Body).
-
-%% @spec (URL, Hdrs, RequestBody) -> Result
-%% URL = string()
-%% Hdrs = [{Header, Value}]
-%% Header = string()
-%% Value = string()
-%% RequestBody = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a POST request.
-%% Would be the same as calling
-%% `request(post, URL, Hdrs, Body)'.
-%% @end
-%% @see request/4
--spec post(string(), headers(), string()) -> result().
-post(URL, Hdrs, Body) ->
- NewHdrs = case [X
- || {X, _} <- Hdrs,
- str:to_lower(X) == <<"content-type">>]
- of
- [] ->
- [{<<"content-type">>, <<"x-www-form-urlencoded">>}
- | Hdrs];
- _ -> Hdrs
- end,
- request(post, URL, NewHdrs, Body).
-
-%% @spec (Method, URL, Hdrs) -> Result
-%% Method = atom()
-%% URL = string()
-%% Hdrs = [{Header, Value}]
-%% Header = string()
-%% Value = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a request without a body.
-%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
-%% that is {@link request/5} with an empty body.
-%% @end
-%% @see request/5
--spec request(atom(), string(), headers()) -> result().
-request(Method, URL, Hdrs) ->
- request(Method, URL, Hdrs, [], []).
-
-%% @spec (Method, URL, Hdrs, RequestBody) -> Result
-%% Method = atom()
-%% URL = string()
-%% Hdrs = [{Header, Value}]
-%% Header = string()
-%% Value = string()
-%% RequestBody = string()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a request with a body.
-%% Would be the same as calling
-%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
-%% with no options.
-%% @end
-%% @see request/5
--spec request(atom(), string(), headers(), string()) -> result().
-request(Method, URL, Hdrs, Body) ->
- request(Method, URL, Hdrs, Body, []).
-
-%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
-%% Method = atom()
-%% URL = string()
-%% Hdrs = [{Header, Value}]
-%% Header = string()
-%% Value = string()
-%% RequestBody = string()
-%% Options = [Option]
-%% Option = {timeout, Milliseconds | infinity} |
-%% {connect_timeout, Milliseconds | infinity} |
-%% {socket_options, [term()]} |
-
-%% Milliseconds = integer()
-%% Result = {ok, StatusCode, Hdrs, ResponseBody}
-%% | {error, Reason}
-%% StatusCode = integer()
-%% ResponseBody = string()
-%% Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a request with a body.
-%% Would be the same as calling
-%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
-%% with no options.
-%% @end
-%% @see request/5
--spec request(atom(), string(), headers(), string(), options()) -> result().
-
-% ibrowse {response_format, response_format()} |
-% Options - [option()]
-% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
-% boolean()} | {headers_as_is, boolean()}
-%body_format() = string() | binary()
-% The body_format option is only valid for the synchronous request and the default is string.
-% When making an asynchronous request the body will always be received as a binary.
-% lhttpc: always binary
-
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index 099387c9a..fd70f8b1e 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -32,8 +32,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
%%%----------------------------------------------------------------------
%%% API
@@ -112,45 +111,40 @@ process_xdb(User, Server,
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
- From = jid:make(User, Server, <<"">>),
+ From = jid:make(User, Server),
+ LUser = From#jid.luser,
+ LServer = From#jid.lserver,
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_AUTH ->
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;
+ catch mod_roster:set_items(User, Server, xmpp:decode(El)),
+ ok;
?NS_LAST ->
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),
+ binary_to_integer(TimeStamp),
Status),
ok;
?NS_VCARD ->
- catch mod_vcard:process_sm_iq(From,
- jid:make(<<"">>, Server, <<"">>),
- #iq{type = set, xmlns = ?NS_VCARD,
- sub_el = El}),
+ catch mod_vcard:set_vcard(User, LServer, El),
ok;
<<"jabber:x:offline">> ->
process_offline(Server, From, El), ok;
XMLNS ->
case fxml:get_attr_s(<<"j_private_flag">>, Attrs) of
<<"1">> ->
- catch mod_private:process_sm_iq(From,
- jid:make(<<"">>, Server,
- <<"">>),
- #iq{type = set,
- xmlns = ?NS_PRIVATE,
- sub_el =
- #xmlel{name =
- <<"query">>,
- attrs = [],
- children =
- [jlib:remove_attr(<<"j_private_flag">>,
- jlib:remove_attr(<<"xdbns">>,
- El))]}});
+ NewAttrs = lists:filter(
+ fun({<<"j_private_flag">>, _}) -> false;
+ ({<<"xdbns">>, _}) -> false;
+ (_) -> true
+ end, Attrs),
+ catch mod_private:set_data(
+ LUser, LServer,
+ [{XMLNS, El#xmlel{attrs = NewAttrs}}]);
_ ->
?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
end,
@@ -159,18 +153,21 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
process_offline(Server, To, #xmlel{children = Els}) ->
LServer = jid:nameprep(Server),
- lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
- FromS = fxml:get_attr_s(<<"from">>, Attrs),
- From = case FromS of
- <<"">> ->
- jid:make(<<"">>, Server, <<"">>);
- _ -> jid:from_string(FromS)
- end,
- case From of
- error -> ok;
- _ ->
- ejabberd_hooks:run(offline_message_hook,
- LServer, [From, To, El])
- end
- end,
- Els).
+ lists:foreach(
+ fun(#xmlel{} = El) ->
+ try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
+ #message{from = JID} ->
+ From = case JID of
+ undefined -> jid:make(Server);
+ _ -> JID
+ end,
+ ejabberd_hooks:run(offline_message_hook,
+ LServer, [From, To, El]);
+ _ ->
+ ok
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ ?ERROR_MSG("failed to decode XML '~s': ~s",
+ [fxml:element_to_binary(El), Txt])
+ end
+ end, Els).
diff --git a/src/jid.erl b/src/jid.erl
deleted file mode 100644
index a730bd949..000000000
--- a/src/jid.erl
+++ /dev/null
@@ -1,256 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% @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() ->
- {ok, Owner} = ets_owner(),
- SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]),
- %% Table is public to allow ETS insert to fix / update the table even if table already exist
- %% with another owner.
- catch ets:new(jlib, [named_table, public, set, {keypos, 1}, {heir, Owner, undefined}]),
- ets:insert(jlib, {string_to_jid_pattern, SplitPattern}),
- ok.
-
-ets_owner() ->
- case whereis(jlib_ets) of
- undefined ->
- Pid = spawn(fun() -> ets_keepalive() end),
- case catch register(jlib_ets, Pid) of
- true ->
- {ok, Pid};
- Error -> Error
- end;
- Pid ->
- {ok,Pid}
- end.
-
-%% Process used to keep jlib ETS table alive in case the original owner dies.
-%% The table need to be public, otherwise subsequent inserts would fail.
-ets_keepalive() ->
- receive
- _ ->
- ets_keepalive()
- end.
-
--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 3384e670e..096ef4012 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -35,27 +35,33 @@
binary_to_integer/1,
integer_to_binary/1]}).
+-export([tolower/1, term_to_base64/1, base64_to_term/1,
+ decode_base64/1, encode_base64/1, ip_to_list/1,
+ atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
+ l2i/1, i2l/1, i2l/2, queue_drop_while/2,
+ expr_to_term/1, term_to_expr/1]).
+
+%% The following functions are used by gen_iq_handler.erl for providing backward
+%% compatibility and must not be used in other parts of the code
+%% Use xmpp:decode() and xmpp:encode() instead
+-export([iq_query_info/1, iq_to_xml/1]).
+
+%% The following functions are deprecated and will be removed soon
+%% Use functions from xmpp.erl and xmpp_util.erl instead
-export([make_result_iq_reply/1, make_error_reply/3,
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, tolower/1,
- get_iq_namespace/1, iq_query_info/1,
+ remove_attr/2, get_iq_namespace/1,
iq_query_or_response_info/1, is_iq_request_type/1,
- iq_to_xml/1, parse_xdata_submit/1,
- unwrap_carbon/1, is_standalone_chat_state/1,
+ parse_xdata_submit/1, unwrap_carbon/1, is_standalone_chat_state/1,
add_delay_info/3, add_delay_info/4,
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,
- decode_base64/1, encode_base64/1, ip_to_list/1,
rsm_encode/1, rsm_encode/2, rsm_decode/1,
binary_to_integer/1, binary_to_integer/2,
- integer_to_binary/1, integer_to_binary/2,
- atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
- l2i/1, i2l/1, i2l/2, queue_drop_while/2,
- expr_to_term/1, term_to_expr/1]).
+ integer_to_binary/1, integer_to_binary/2]).
%% The following functions are deprecated and will be removed soon
%% Use corresponding functions from jid.erl instead
@@ -74,7 +80,38 @@
{resourceprep, 1},
{jid_tolower, 1},
{jid_remove_resource, 1},
- {jid_replace_resource, 2}]).
+ {jid_replace_resource, 2},
+ {add_delay_info, 3},
+ {add_delay_info, 4},
+ {make_result_iq_reply, 1},
+ {make_error_reply, 3},
+ {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},
+ {get_iq_namespace, 1},
+ {iq_query_or_response_info, 1},
+ {is_iq_request_type, 1},
+ {parse_xdata_submit, 1},
+ {unwrap_carbon, 1},
+ {is_standalone_chat_state, 1},
+ {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},
+ {rsm_encode, 1},
+ {rsm_encode, 2},
+ {rsm_decode, 1},
+ {binary_to_integer, 1},
+ {binary_to_integer, 2},
+ {integer_to_binary, 1},
+ {integer_to_binary, 2}]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -582,7 +619,7 @@ add_delay_info(El, From, Time) ->
binary()) -> xmlel().
add_delay_info(El, From, Time, Desc) ->
- DelayTag = create_delay_tag(Time, From, Desc),
+ DelayTag = create_delay_tag(Time, From, Desc),
fxml:append_subtags(El, [DelayTag]).
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
@@ -640,14 +677,14 @@ timestamp_to_iso({{Year, Month, Day},
%% 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",
+ (str: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",
+ (str: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().
@@ -666,7 +703,7 @@ now_to_utc_string({MegaSecs, Secs, MicroSecs}, Precision) ->
Max ->
now_to_utc_string({MegaSecs, Secs + 1, 0}, Precision);
FracOfSec ->
- list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT"
+ (str:format("~4..0B-~2..0B-~2..0BT"
"~2..0B:~2..0B:~2..0B.~*..0BZ",
[Year, Month, Day, Hour, Minute, Second,
Precision, FracOfSec]))
@@ -688,7 +725,7 @@ now_to_local_string({MegaSecs, Secs, MicroSecs}) ->
end,
{{Year, Month, Day}, {Hour, Minute, Second}} =
LocalTime,
- list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6."
+ (str:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6."
".0B~s~2..0B:~2..0B",
[Year, Month, Day, Hour, Minute, Second,
MicroSecs, Sign, H, M])).
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index e12e0de1e..5b582d82b 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -31,18 +31,15 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3,
- process_sm_iq/3, get_local_commands/5,
+-export([start/2, stop/1, process_local_iq/1,
+ process_sm_iq/1, 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, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
-
--include("adhoc.hrl").
+-include("xmpp.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -107,12 +104,9 @@ get_local_commands(Acc, _From,
{result, I} -> I;
_ -> []
end,
- Nodes = [#xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS},
- {<<"name">>,
- translate:translate(Lang, <<"Commands">>)}],
- children = []}],
+ Nodes = [#disco_item{jid = jid:make(Server),
+ node = ?NS_COMMANDS,
+ name = translate:translate(Lang, <<"Commands">>)}],
{result, Items ++ Nodes}
end;
get_local_commands(_Acc, From,
@@ -140,13 +134,9 @@ get_sm_commands(Acc, _From,
{result, I} -> I;
_ -> []
end,
- Nodes = [#xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, jid:to_string(To)},
- {<<"node">>, ?NS_COMMANDS},
- {<<"name">>,
- translate:translate(Lang, <<"Commands">>)}],
- children = []}],
+ Nodes = [#disco_item{jid = To,
+ node = ?NS_COMMANDS,
+ name = translate:translate(Lang, <<"Commands">>)}],
{result, Items ++ Nodes}
end;
get_sm_commands(_Acc, From,
@@ -160,21 +150,14 @@ get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc.
%% On disco info request to the ad-hoc node, return automation/command-list.
get_local_identity(Acc, _From, _To, ?NS_COMMANDS,
Lang) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-list">>},
- {<<"name">>,
- translate:translate(Lang, <<"Commands">>)}],
- children = []}
+ [#identity{category = <<"automation">>,
+ type = <<"command-list">>,
+ name = translate:translate(Lang, <<"Commands">>)}
| Acc];
get_local_identity(Acc, _From, _To, <<"ping">>, Lang) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-node">>},
- {<<"name">>, translate:translate(Lang, <<"Ping">>)}],
- children = []}
+ [#identity{category = <<"automation">>,
+ type = <<"command-node">>,
+ name = translate:translate(Lang, <<"Ping">>)}
| Acc];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
@@ -183,18 +166,16 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
%% On disco info request to the ad-hoc node, return automation/command-list.
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-list">>},
- {<<"name">>,
- translate:translate(Lang, <<"Commands">>)}],
- children = []}
+ [#identity{category = <<"automation">>,
+ type = <<"command-list">>,
+ name = translate:translate(Lang, <<"Commands">>)}
| Acc];
get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc.
%-------------------------------------------------------------------------
-
+-spec get_local_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]} | empty.
get_local_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of
{result, I} -> I;
@@ -225,62 +206,67 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
%-------------------------------------------------------------------------
-process_local_iq(From, To, IQ) ->
- process_adhoc_request(From, To, IQ,
- adhoc_local_commands).
+process_local_iq(IQ) ->
+ process_adhoc_request(IQ, local).
-process_sm_iq(From, To, IQ) ->
- process_adhoc_request(From, To, IQ, adhoc_sm_commands).
+process_sm_iq(IQ) ->
+ process_adhoc_request(IQ, sm).
-process_adhoc_request(From, To,
- #iq{sub_el = SubEl, lang = Lang} = IQ, Hook) ->
- ?DEBUG("About to parse ~p...", [IQ]),
- case adhoc:parse_request(IQ) of
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]};
- #adhoc_request{} = AdhocRequest ->
- Host = To#jid.lserver,
- case ejabberd_hooks:run_fold(Hook, Host, empty,
- [From, To, AdhocRequest])
- of
- ignore -> ignore;
- empty ->
- Txt = <<"No hook has processed this command">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]};
- Command -> IQ#iq{type = result, sub_el = [Command]}
- end
- end.
+process_adhoc_request(#iq{from = From, to = To,
+ type = set, lang = Lang,
+ sub_els = [#adhoc_command{} = SubEl]} = IQ, Type) ->
+ Host = To#jid.lserver,
+ Res = case Type of
+ local ->
+ ejabberd_hooks:run_fold(adhoc_local_commands, Host, empty,
+ [From, To, SubEl]);
+ sm ->
+ ejabberd_hooks:run_fold(adhoc_sm_commands, Host, empty,
+ [From, To, SubEl])
+ end,
+ case Res of
+ ignore ->
+ ignore;
+ empty ->
+ Txt = <<"No hook has processed this command">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ {error, Error} ->
+ xmpp:make_error(IQ, Error);
+ Command ->
+ xmpp:make_iq_result(IQ, Command)
+ end;
+process_adhoc_request(#iq{} = IQ, _Hooks) ->
+ xmpp:make_error(IQ, xmpp:err_bad_request()).
+-spec ping_item(empty | {error, stanza_error()} | {result, [disco_item()]},
+ jid(), jid(), binary()) -> {result, [disco_item()]}.
ping_item(Acc, _From, #jid{server = Server} = _To,
Lang) ->
Items = case Acc of
{result, I} -> I;
_ -> []
end,
- Nodes = [#xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Server}, {<<"node">>, <<"ping">>},
- {<<"name">>, translate:translate(Lang, <<"Ping">>)}],
- children = []}],
+ Nodes = [#disco_item{jid = jid:make(Server),
+ node = <<"ping">>,
+ name = translate:translate(Lang, <<"Ping">>)}],
{result, Items ++ Nodes}.
+-spec ping_command(adhoc_command(), jid(), jid(), adhoc_command()) ->
+ adhoc_command() | {error, stanza_error()}.
ping_command(_Acc, _From, _To,
- #adhoc_request{lang = Lang, node = <<"ping">>,
- sessionid = _Sessionid, action = Action} =
- Request) ->
- if Action == <<"">>; Action == <<"execute">> ->
- adhoc:produce_response(Request,
- #adhoc_response{status = completed,
- notes =
- [{<<"info">>,
- translate:translate(Lang,
- <<"Pong">>)}]});
+ #adhoc_command{lang = Lang, node = <<"ping">>,
+ action = Action} = Request) ->
+ if Action == execute ->
+ xmpp_util:make_adhoc_response(
+ Request,
+ #adhoc_command{
+ status = completed,
+ notes = [#adhoc_note{
+ type = info,
+ data = translate:translate(Lang, <<"Pong">>)}]});
true ->
Txt = <<"Incorrect value of 'action' attribute">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ {error, xmpp:err_bad_request(Txt, Lang)}
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index 48732ea35..4b117d50a 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -5,7 +5,7 @@
%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2008 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,31 +30,61 @@
-include("logger.hrl").
--export([start/2, stop/1, compile/1, get_cookie/0,
- remove_node/1, set_password/3, check_password/3,
- check_password_hash/4, delete_old_users/1,
- delete_old_users_vhost/2, ban_account/3,
+-export([start/2, stop/1, mod_opt_type/1,
+ get_commands_spec/0, depends/2]).
+
+% Commands API
+-export([
+ % Adminsys
+ compile/1, get_cookie/0, remove_node/1, export2sql/2,
+ restart_module/2,
+
+ % Sessions
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_presence/2, user_sessions_info/2, get_last/2,
+
+ % Accounts
+ set_password/3, check_password_hash/4, delete_old_users/1,
+ delete_old_users_vhost/2, ban_account/3, check_password/3,
+
+ % vCard
+ 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,
+ 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, get_last/2,
- private_get/4, private_set/3, srg_create/5,
+ push_roster_all/1, push_alltoall/2,
+
+ % Private storage
+ private_get/4, private_set/3,
+
+ % 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,
- 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, depends/2]).
+
+ % Send message
+ send_message/5, send_stanza/3, send_stanza_c2s/4,
+
+ % Privacy list
+ privacy_set/3,
+
+ % Stats
+ stats/1, stats/2
+ ]).
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
-include("mod_roster.hrl").
+-include("mod_privacy.hrl").
-include("ejabberd_sm.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
%%%
%%% gen_mod
@@ -124,9 +154,30 @@ get_commands_spec() ->
result = {res, rescode},
result_example = ok,
result_desc = "Status code: 0 on success, 1 otherwise"},
+ #ejabberd_commands{name = export2sql, tags = [mnesia],
+ desc = "Export Mnesia tables to files in directory",
+ module = ?MODULE, function = export2sql,
+ args = [{host, string}, {path, string}],
+ args_example = ["myserver.com","/tmp/export/sql"],
+ args_desc = ["Server name", "File to write sql export"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
+ #ejabberd_commands{name = restart_module, tags = [erlang],
+ desc = "Stop an ejabberd module, reload code and start",
+ module = ?MODULE, function = restart_module,
+ args = [{host, binary}, {module, binary}],
+ args_example = ["myserver.com","mod_admin_extra"],
+ args_desc = ["Server name", "Module to restart"],
+ result = {res, integer},
+ result_example = 0,
+ result_desc = "Returns integer code:\n"
+ " - 0: code reloaded, module restarted\n"
+ " - 1: error: module not loaded\n"
+ " - 2: code not reloaded, but module restarted"},
#ejabberd_commands{name = num_active_users, tags = [accounts, stats],
desc = "Get number of users active in the last days",
- policy = admin,
+ policy = admin,
module = ?MODULE, function = num_active_users,
args = [{host, binary}, {days, integer}],
args_example = [<<"myserver.com">>, 3],
@@ -175,8 +226,8 @@ get_commands_spec() ->
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, string},
- {hashmethod, string}],
+ args = [{user, binary}, {host, binary}, {passwordhash, binary},
+ {hashmethod, binary}],
args_example = [<<"peter">>, <<"myserver.com">>,
<<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>],
args_desc = ["User name to check", "Server to check",
@@ -235,7 +286,7 @@ get_commands_spec() ->
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,
+ policy = admin,
module = ?MODULE, function = status_num,
args = [{host, binary}, {status, binary}],
args_example = [<<"myserver.com">>, <<"dnd">>],
@@ -245,7 +296,7 @@ get_commands_spec() ->
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,
+ policy = admin,
module = ?MODULE, function = status_num,
args = [{status, binary}],
args_example = [<<"dnd">>],
@@ -297,11 +348,11 @@ get_commands_spec() ->
]}}
}}},
#ejabberd_commands{name = connected_users_vhost,
- tags = [session],
- desc = "Get the list of established sessions in a vhost",
- module = ?MODULE, function = connected_users_vhost,
- args = [{host, binary}],
- result = {connected_users_vhost, {list, {sessions, string}}}},
+ tags = [session],
+ desc = "Get the list of established sessions in a vhost",
+ module = ?MODULE, function = connected_users_vhost,
+ args = [{host, binary}],
+ result = {connected_users_vhost, {list, {sessions, string}}}},
#ejabberd_commands{name = user_sessions_info,
tags = [session],
desc = "Get information about all sessions of a user",
@@ -322,6 +373,28 @@ get_commands_spec() ->
]}}
}}},
+ #ejabberd_commands{name = get_presence, tags = [session],
+ desc =
+ "Retrieve the resource with highest priority, "
+ "and its presence (show and status message) "
+ "for a given user.",
+ longdesc =
+ "The 'jid' value contains the user jid "
+ "with resource.\nThe 'show' value contains "
+ "the user presence flag. It can take "
+ "limited values:\n - available\n - chat "
+ "(Free for chat)\n - away\n - dnd (Do "
+ "not disturb)\n - xa (Not available, "
+ "extended away)\n - unavailable (Not "
+ "connected)\n\n'status' is a free text "
+ "defined by the user client.",
+ module = ?MODULE, function = get_presence,
+ args = [{user, binary}, {server, binary}],
+ result =
+ {presence,
+ {tuple,
+ [{jid, string}, {show, string},
+ {status, string}]}}},
#ejabberd_commands{name = set_presence,
tags = [session],
desc = "Set presence of a session",
@@ -533,7 +606,7 @@ get_commands_spec() ->
#ejabberd_commands{name = get_offline_count,
tags = [offline],
desc = "Get the number of unread offline messages",
- policy = user,
+ policy = user,
module = mod_offline, function = count_offline_messages,
args = [],
result = {value, integer}},
@@ -561,13 +634,13 @@ get_commands_spec() ->
#ejabberd_commands{name = stats, tags = [stats],
desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds processes",
- policy = admin,
+ 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,
+ policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}, {host, binary}],
result = {stat, integer}}
@@ -588,6 +661,48 @@ remove_node(Node) ->
mnesia:del_table_copy(schema, list_to_atom(Node)),
ok.
+restart_module(Host, Module) when is_binary(Module) ->
+ restart_module(Host, jlib:binary_to_atom(Module));
+restart_module(Host, Module) when is_atom(Module) ->
+ List = gen_mod:loaded_modules_with_opts(Host),
+ case proplists:get_value(Module, List) of
+ undefined ->
+ % not a running module, force code reload anyway
+ code:purge(Module),
+ code:delete(Module),
+ code:load_file(Module),
+ 1;
+ Opts ->
+ gen_mod:stop_module(Host, Module),
+ case code:soft_purge(Module) of
+ true ->
+ code:delete(Module),
+ code:load_file(Module),
+ gen_mod:start_module(Host, Module, Opts),
+ 0;
+ false ->
+ gen_mod:start_module(Host, Module, Opts),
+ 2
+ end
+ end.
+
+export2sql(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 ejd2sql:TableFun(Host, Filename)),
+ io:format(" Result: ~p~n", [Res])
+ end,
+ lists:foreach(Export, Tables),
+ ok.
+
%%%
%%% Accounts
%%%
@@ -606,10 +721,10 @@ check_password_hash(User, Host, PasswordHash, HashMethod) ->
{A, _} when is_tuple(A) -> scrammed;
{_, <<"md5">>} -> get_md5(AccountPass);
{_, <<"sha">>} -> get_sha(AccountPass);
- {_, Method} ->
+ {_, Method} ->
?ERROR_MSG("check_password_hash called "
- "with hash method: ~p", [Method]),
- undefined
+ "with hash method: ~p", [Method]),
+ undefined
end,
case AccountPassHash of
scrammed ->
@@ -627,10 +742,11 @@ get_sha(AccountPass) ->
|| X <- binary_to_list(p1_sha:sha1(AccountPass))]).
num_active_users(Host, Days) ->
- list_last_activity(Host, true, Days).
+ DB_Type = gen_mod:db_type(Host, mod_last),
+ list_last_activity(Host, true, Days, DB_Type).
%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
-list_last_activity(Host, Integral, Days) ->
+list_last_activity(Host, Integral, Days, mnesia) ->
TimeStamp = p1_time_compat:system_time(seconds),
TS = TimeStamp - Days * 86400,
case catch mnesia:dirty_select(
@@ -656,7 +772,11 @@ list_last_activity(Host, Integral, Days) ->
end,
lists:nth(Days, Hist ++ Tail)
end
- end.
+ end;
+list_last_activity(_Host, _Integral, _Days, DB_Type) ->
+ throw({error, iolist_to_binary(io_lib:format("Unsupported backend: ~p",
+ [DB_Type]))}).
+
histogram(Values, Integral) ->
histogram(lists:sort(Values), Integral, 0, 0, []).
histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
@@ -760,7 +880,9 @@ set_random_password(User, Server, Reason) ->
set_password_auth(User, Server, NewPass).
build_random_password(Reason) ->
- Date = jlib:timestamp_to_legacy(calendar:universal_time()),
+ {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
+ Date = str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
+ [Year, Month, Day, Hour, Minute, Second]),
RandomString = randoms:get_string(),
<<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
@@ -865,9 +987,9 @@ connected_users_vhost(Host) ->
%% Code copied from ejabberd_sm.erl and customized
dirty_get_sessions_list2() ->
Ss = mnesia:dirty_select(
- session,
+ session,
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4',
- _ = '_'},
+ _ = '_'},
[],
[['$1', '$2', '$3', '$4']]}]),
lists:filter(fun([_USR, _SID, _Priority, Info]) ->
@@ -879,24 +1001,40 @@ stringize(String) ->
%% Replace newline characters with other code
ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
+get_presence(U, S) ->
+ Pids = [ejabberd_sm:get_session_pid(U, S, R)
+ || R <- ejabberd_sm:get_user_resources(U, S)],
+ OnlinePids = [Pid || Pid <- Pids, Pid=/=none],
+ case OnlinePids of
+ [] ->
+ {jid:to_string({U, S, <<>>}), <<"unavailable">>, <<"">>};
+ [SessionPid|_] ->
+ {_User, Resource, Show, Status} =
+ ejabberd_c2s:get_presence(SessionPid),
+ FullJID = jid:to_string({U, S, Resource}),
+ {FullJID, Show, Status}
+ end.
+
set_presence(User, Host, Resource, Type, Show, Status, Priority)
when is_integer(Priority) ->
BPriority = integer_to_binary(Priority),
set_presence(User, Host, Resource, Type, Show, Status, BPriority);
-set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
+set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
+ Priority = if is_integer(Priority0) -> Priority0;
+ true -> binary_to_integer(Priority0)
+ end,
case ejabberd_sm:get_session_pid(User, Host, Resource) of
none ->
error;
Pid ->
- 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}],
- [{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
- {xmlel, <<"status">>, [], [{xmlcdata, Status}]},
- {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
- Pid ! Message,
+ From = jid:make(User, Host, Resource),
+ To = jid:make(User, Host),
+ Presence = #presence{from = From, to = To,
+ type = jlib:binary_to_atom(Type),
+ show = jlib:binary_to_atom(Show),
+ status = xmpp:mk_text(Status),
+ priority = Priority},
+ Pid ! {route, From, To, Presence},
ok
end.
@@ -934,20 +1072,12 @@ user_sessions_info(User, Host) ->
%%%
set_nickname(User, Host, Nickname) ->
- R = mod_vcard:process_sm_iq(
- {jid, User, Host, <<>>, User, Host, <<>>},
- {jid, User, Host, <<>>, User, Host, <<>>},
- {iq, <<>>, set, <<>>, <<"en">>,
- {xmlel, <<"vCard">>, [
- {<<"xmlns">>, <<"vcard-temp">>}], [
- {xmlel, <<"NICKNAME">>, [], [{xmlcdata, Nickname}]}
- ]
- }}),
- case R of
- {iq, <<>>, result, <<>>, _L, []} ->
- ok;
- _ ->
- error
+ VCard = xmpp:encode(#vcard_temp{nickname = Nickname}),
+ case mod_vcard:set_vcard(User, jid:nameprep(Host), VCard) of
+ {error, badarg} ->
+ error;
+ ok ->
+ ok
end.
get_vcard(User, Host, Name) ->
@@ -971,26 +1101,17 @@ set_vcard(User, Host, Name, Subname, SomeContent) ->
%%
%% Internal vcard
-get_module_resource(Server) ->
- case gen_mod:get_module_opt(Server, ?MODULE, module_resource, fun(A) -> A end, none) of
- none -> list_to_binary(atom_to_list(?MODULE));
- R when is_binary(R) -> R
- end.
-
get_vcard_content(User, Server, Data) ->
- [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, 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,
- case A1#xmlel.children of
- [_|_] ->
- case get_vcard(Data, A1) of
+ case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of
+ [_|_] = Els ->
+ case get_vcard(Data, Els) of
[false] -> throw(error_no_value_found_in_vcard);
ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList]
end;
[] ->
- throw(error_no_vcard_found)
+ throw(error_no_vcard_found);
+ error ->
+ throw(database_failure)
end.
get_vcard([<<"TEL">>, TelType], {_, _, _, OldEls}) ->
@@ -1015,25 +1136,19 @@ set_vcard_content(User, Server, Data, SomeContent) ->
[Bin | _] when is_binary(Bin) -> SomeContent;
Bin when is_binary(Bin) -> [SomeContent]
end,
- [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
- JID = jid:make(User, Server, get_module_resource(Server)),
- IQ = #iq{type = get, xmlns = ?NS_VCARD},
- IQr = Module:Function(JID, JID, IQ),
-
%% Get old vcard
- A4 = case IQr#iq.sub_el of
+ A4 = case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of
[A1] ->
{_, _, _, A2} = A1,
update_vcard_els(Data, ContentList, A2);
[] ->
- update_vcard_els(Data, ContentList, [])
+ update_vcard_els(Data, ContentList, []);
+ error ->
+ throw(database_failure)
end,
-
%% Build new vcard
SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4},
- IQ2 = #iq{type=set, sub_el = SubEl},
-
- Module:Function(JID, JID, IQ2),
+ mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl),
ok.
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
@@ -1094,11 +1209,7 @@ add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) ->
ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}),
- mod_roster:set_items(
- LU, LS,
- {xmlel, <<"query">>,
- [{<<"xmlns">>, ?NS_ROSTER}],
- [ItemEl]}).
+ mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}).
delete_rosteritem(LocalUser, LocalServer, User, Server) ->
case unsubscribe(LocalUser, LocalServer, User, Server) of
@@ -1111,11 +1222,7 @@ delete_rosteritem(LocalUser, LocalServer, User, Server) ->
unsubscribe(LU, LS, User, Server) ->
ItemEl = build_roster_item(User, Server, remove),
- mod_roster:set_items(
- LU, LS,
- {xmlel, <<"query">>,
- [{<<"xmlns">>, ?NS_ROSTER}],
- [ItemEl]}).
+ mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}).
%% -----------------------------
%% Get Roster
@@ -1205,30 +1312,17 @@ push_roster_item(LU, LS, R, U, S, Action) ->
ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ).
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
- GNames = binary:split(Group,<<";">>, [global]),
- GroupEls = [{xmlel, <<"group">>, [], [{xmlcdata, GName}]} || GName <- GNames],
- {xmlel, <<"item">>,
- [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))},
- {<<"name">>, Nick},
- {<<"subscription">>, Subs}],
- GroupEls
- };
+ Groups = binary:split(Group,<<";">>, [global]),
+ #roster_item{jid = jid:make(U, S),
+ name = Nick,
+ subscription = jlib:binary_to_atom(Subs),
+ groups = Groups};
build_roster_item(U, S, remove) ->
- {xmlel, <<"item">>,
- [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))},
- {<<"subscription">>, <<"remove">>}],
- []
- }.
+ #roster_item{jid = jid:make(U, S), subscription = remove}.
build_iq_roster_push(Item) ->
- {xmlel, <<"iq">>,
- [{<<"type">>, <<"set">>}, {<<"id">>, <<"push">>}],
- [{xmlel, <<"query">>,
- [{<<"xmlns">>, ?NS_ROSTER}],
- [Item]
- }
- ]
- }.
+ #iq{type = set, id = <<"push">>,
+ sub_els = [#roster_query{items = [Item]}]}.
build_broadcast(U, S, {add, _Nick, Subs, _Group}) ->
build_broadcast(U, S, list_to_atom(binary_to_list(Subs)));
@@ -1274,17 +1368,9 @@ get_last(User, Server) ->
%% <aa xmlns='bb'>Cluth</aa>
private_get(Username, Host, Element, Ns) ->
- 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">>, ?NS_PRIVATE}],
- [SubEl]}] = ResIq#iq.sub_el,
- binary_to_list(fxml:element_to_binary(SubEl)).
+ Els = mod_private:get_data(jid:nodeprep(Username), jid:nameprep(Host),
+ [Ns, Element]),
+ binary_to_list(fxml:element_to_binary(xmpp:encode(#private{xml_els = Els}))).
private_set(Username, Host, ElementString) ->
case fxml_stream:parse_element(ElementString) of
@@ -1297,13 +1383,9 @@ private_set(Username, Host, ElementString) ->
end.
private_set2(Username, Host, Xml) ->
- From = jid:make(Username, Host, <<>>),
- To = jid:make(Username, Host, <<>>),
- IQ = {iq, <<>>, set, ?NS_PRIVATE, <<>>,
- {xmlel, <<"query">>,
- [{<<"xmlns">>, ?NS_PRIVATE}],
- [Xml]}},
- mod_private:process_sm_iq(From, To, IQ),
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, Xml),
+ mod_private:set_data(jid:nodeprep(Username), jid:nameprep(Host),
+ [{NS, Xml}]),
ok.
%%%
@@ -1366,23 +1448,25 @@ send_message(Type, From, To, Subject, Body) ->
ejabberd_router:route(FromJID, ToJID, Packet).
build_packet(Type, Subject, Body) ->
- Tail = if Subject == <<"">>; Type == <<"chat">> -> [];
- true -> [{xmlel, <<"subject">>, [], [{xmlcdata, Subject}]}]
- end,
- {xmlel, <<"message">>,
- [{<<"type">>, Type}, {<<"id">>, randoms:get_string()}],
- [{xmlel, <<"body">>, [], [{xmlcdata, Body}]} | Tail]
- }.
+ #message{type = jlib:binary_to_atom(Type),
+ body = xmpp:mk_text(Body),
+ subject = xmpp:mk_text(Subject)}.
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)
+ try
+ #xmlel{} = El = fxml_stream:parse_element(Stanza),
+ #jid{} = From = jid:from_string(FromString),
+ #jid{} = To = jid:to_string(ToString),
+ Pkt = xmpp:decode(El, ?NS_CLIENT, [ignore_els]),
+ ejabberd_router:route(From, To, Pkt)
+ catch _:{xmpp_codec, Why} ->
+ io:format("incorrect stanza: ~s~n", [xmpp:format_error(Why)]),
+ {error, Why};
+ _:{badmatch, {error, Why}} ->
+ io:format("invalid xml: ~p~n", [Why]),
+ {error, Why};
+ _:{badmatch, error} ->
+ {error, "JID malformed"}
end.
send_stanza_c2s(Username, Host, Resource, Stanza) ->
@@ -1398,17 +1482,16 @@ send_stanza_c2s(Username, Host, Resource, Stanza) ->
end.
privacy_set(Username, Host, QueryS) ->
- From = jid:make(Username, Host, <<"">>),
- To = jid:make(<<"">>, Host, <<"">>),
+ 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(
- privacy_iq_set,
- Host,
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ]
- ),
+ SubEl = xmpp:decode(QueryEl),
+ IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
+ from = From, to = To},
+ ejabberd_hooks:run_fold(privacy_iq_set,
+ Host,
+ {error, xmpp:err_feature_not_implemented()},
+ [IQ, #userlist{}]),
ok.
%%%
@@ -1589,5 +1672,4 @@ is_glob_match(String, <<"!", Glob/binary>>) ->
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].
+mod_opt_type(_) -> [].
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index d74c46bf9..2e182ed1e 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -31,20 +31,19 @@
-behaviour(gen_mod).
--export([start/2, init/0, stop/1, export/1, import/1,
- import/3, announce/3, send_motd/1, disco_identity/5,
+-export([start/2, init/0, stop/1, export/1, import_info/0,
+ import_start/2, import/5, announce/3, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4,
announce_items/4, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
--include("adhoc.hrl").
+-include("xmpp.hrl").
-include("mod_announce.hrl").
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
-callback delete_motd(binary()) -> {atomic, any()}.
@@ -131,41 +130,36 @@ stop(Host) ->
{wait, Proc}.
%% Announcing via messages to a custom resource
-announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) ->
+-spec announce(jid(), jid(), stanza()) -> ok | stop.
+announce(From, #jid{luser = <<>>} = To, #message{} = Packet) ->
Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME),
- case To#jid.lresource of
- <<"announce/all">> ->
- Proc ! {announce_all, From, To, Packet},
- stop;
- <<"announce/all-hosts/all">> ->
- Proc ! {announce_all_hosts_all, From, To, Packet},
- stop;
- <<"announce/online">> ->
- Proc ! {announce_online, From, To, Packet},
- stop;
- <<"announce/all-hosts/online">> ->
- Proc ! {announce_all_hosts_online, From, To, Packet},
- stop;
- <<"announce/motd">> ->
- Proc ! {announce_motd, From, To, Packet},
- stop;
- <<"announce/all-hosts/motd">> ->
- Proc ! {announce_all_hosts_motd, From, To, Packet},
- stop;
- <<"announce/motd/update">> ->
- Proc ! {announce_motd_update, From, To, Packet},
- stop;
- <<"announce/all-hosts/motd/update">> ->
- Proc ! {announce_all_hosts_motd_update, From, To, Packet},
- stop;
- <<"announce/motd/delete">> ->
- Proc ! {announce_motd_delete, From, To, Packet},
- stop;
- <<"announce/all-hosts/motd/delete">> ->
- Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
- stop;
- _ ->
- ok
+ Res = case To#jid.lresource of
+ <<"announce/all">> ->
+ Proc ! {announce_all, From, To, Packet};
+ <<"announce/all-hosts/all">> ->
+ Proc ! {announce_all_hosts_all, From, To, Packet};
+ <<"announce/online">> ->
+ Proc ! {announce_online, From, To, Packet};
+ <<"announce/all-hosts/online">> ->
+ Proc ! {announce_all_hosts_online, From, To, Packet};
+ <<"announce/motd">> ->
+ Proc ! {announce_motd, From, To, Packet};
+ <<"announce/all-hosts/motd">> ->
+ Proc ! {announce_all_hosts_motd, From, To, Packet};
+ <<"announce/motd/update">> ->
+ Proc ! {announce_motd_update, From, To, Packet};
+ <<"announce/all-hosts/motd/update">> ->
+ Proc ! {announce_all_hosts_motd_update, From, To, Packet};
+ <<"announce/motd/delete">> ->
+ Proc ! {announce_motd_delete, From, To, Packet};
+ <<"announce/all-hosts/motd/delete">> ->
+ Proc ! {announce_all_hosts_motd_delete, From, To, Packet};
+ _ ->
+ ok
+ end,
+ case Res of
+ ok -> ok;
+ _ -> stop
end;
announce(_From, _To, _Packet) ->
ok.
@@ -173,10 +167,9 @@ announce(_From, _To, _Packet) ->
%%-------------------------------------------------------------------------
%% Announcing via ad-hoc commands
-define(INFO_COMMAND(Lang, Node),
- [#xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-node">>},
- {<<"name">>, get_title(Lang, Node)}]}]).
+ [#identity{category = <<"automation">>,
+ type = <<"command-node">>,
+ name = get_title(Lang, Node)}]).
disco_identity(Acc, _From, _To, Node, Lang) ->
LNode = tokenize(Node),
@@ -210,7 +203,7 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
- {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
allow ->
{result, Feats}
end).
@@ -226,7 +219,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) -
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
Txt = <<"Denied by ACL">>,
- {error, ?ERRT_FORBIDDEN(Lang, Txt)};
+ {error, xmpp:err_forbidden(Txt, Lang)};
_ ->
{result, []}
end
@@ -269,26 +262,19 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
%%-------------------------------------------------------------------------
-define(NODE_TO_ITEM(Lang, Server, Node),
-(
- #xmlel{
- name = <<"item">>,
- attrs = [
- {<<"jid">>, Server},
- {<<"node">>, Node},
- {<<"name">>, get_title(Lang, Node)}
- ]
- }
-)).
+ #disco_item{jid = jid:make(Server),
+ node = Node,
+ name = get_title(Lang, Node)}).
-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
- {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
allow ->
{result, Items}
end).
-disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) ->
+disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<"">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -353,7 +339,10 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
end.
%%-------------------------------------------------------------------------
-
+-spec announce_items(empty | {error, stanza_error()} | {result, [disco_item()]},
+ jid(), jid(), binary()) -> {error, stanza_error()} |
+ {result, [disco_item()]} |
+ empty.
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
Access1 = get_access(LServer),
Nodes1 = case acl:match_rule(LServer, Access1, From) of
@@ -393,15 +382,16 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
- Lang = Request#adhoc_request.lang,
- {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ Lang = Request#adhoc_command.lang,
+ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
allow ->
announce_commands(From, To, Request)
end.
-
+-spec announce_commands(adhoc_command(), jid(), jid(), adhoc_command()) ->
+ adhoc_command() | {error, stanza_error()}.
announce_commands(Acc, From, #jid{lserver = LServer} = To,
- #adhoc_request{ node = Node} = Request) ->
+ #adhoc_command{node = Node} = Request) ->
LNode = tokenize(Node),
F = fun() ->
Access = get_access(global),
@@ -440,59 +430,35 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To,
%%-------------------------------------------------------------------------
announce_commands(From, To,
- #adhoc_request{lang = Lang,
+ #adhoc_command{lang = Lang,
node = Node,
- action = Action,
- xdata = XData} = Request) ->
- %% If the "action" attribute is not present, it is
- %% understood as "execute". If there was no <actions/>
- %% element in the first response (which there isn't in our
- %% case), "execute" and "complete" are equivalent.
- ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]),
- if Action == <<"cancel">> ->
+ sid = SID,
+ xdata = XData,
+ action = Action} = Request) ->
+ ActionIsExecute = Action == execute orelse Action == complete,
+ if Action == cancel ->
%% User cancels request
- adhoc:produce_response(Request, #adhoc_response{status = canceled});
- XData == false, ActionIsExecute ->
+ #adhoc_command{status = canceled, lang = Lang, node = Node,
+ sid = SID};
+ XData == undefined, ActionIsExecute ->
%% User requests form
- Elements = generate_adhoc_form(Lang, Node, To#jid.lserver),
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,elements = [Elements]});
- XData /= false, ActionIsExecute ->
- %% User returns form.
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
- Fields ->
- handle_adhoc_form(From, To, Request, Fields)
- end;
+ Form = generate_adhoc_form(Lang, Node, To#jid.lserver),
+ #adhoc_command{status = executing, lang = Lang, node = Node,
+ sid = SID, xdata = Form};
+ XData /= undefined, ActionIsExecute ->
+ handle_adhoc_form(From, To, Request);
true ->
- Txt = <<"Incorrect action or data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ Txt = <<"Unexpected action">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
end.
--define(VVALUE(Val),
-(
- #xmlel{
- name = <<"value">>,
- children = [{xmlcdata, Val}]
- }
-)).
-
-define(TVFIELD(Type, Var, Val),
-(
- #xmlel{
- name = <<"field">>,
- attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
- children = vvaluel(Val)
- }
-)).
-
--define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)).
+ #xdata_field{type = Type, var = Var, values = vvaluel(Val)}).
vvaluel(Val) ->
case Val of
<<>> -> [];
- _ -> [?VVALUE(Val)]
+ _ -> [Val]
end.
generate_adhoc_form(Lang, Node, ServerHost) ->
@@ -503,49 +469,27 @@ generate_adhoc_form(Lang, Node, ServerHost) ->
true ->
{<<>>, <<>>}
end,
- #xmlel{
- name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children = [
- ?HFIELD(),
- #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]}
- ]
- ++
- if (LNode == ?NS_ADMINL("delete-motd"))
- or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
- [#xmlel{
- name = <<"field">>,
- attrs = [
- {<<"var">>, <<"confirm">>},
- {<<"type">>, <<"boolean">>},
- {<<"label">>,
- translate:translate(Lang, <<"Really delete message of the day?">>)}
- ],
- children = [
- #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]}
- ]
- }
- ];
- true ->
- [#xmlel{
- name = <<"field">>,
- attrs = [
- {<<"var">>, <<"subject">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>, translate:translate(Lang, <<"Subject">>)}],
- children = vvaluel(OldSubject)
- },
- #xmlel{
- name = <<"field">>,
- attrs = [
- {<<"var">>, <<"body">>},
- {<<"type">>, <<"text-multi">>},
- {<<"label">>, translate:translate(Lang, <<"Message body">>)}],
- children = vvaluel(OldBody)
- }
- ]
-
- end}.
+ Fs = if (LNode == ?NS_ADMINL("delete-motd"))
+ or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
+ [#xdata_field{type = boolean,
+ var = <<"confirm">>,
+ label = translate:translate(
+ Lang, <<"Really delete message of the day?">>),
+ values = [<<"true">>]}];
+ true ->
+ [#xdata_field{type = 'text-single',
+ var = <<"subject">>,
+ label = translate:translate(Lang, <<"Subject">>),
+ values = vvaluel(OldSubject)},
+ #xdata_field{type = 'text-multi',
+ var = <<"body">>,
+ label = translate:translate(Lang, <<"Message body">>),
+ values = vvaluel(OldBody)}]
+ end,
+ #xdata{type = form,
+ title = get_title(Lang, Node),
+ fields = [#xdata_field{type = hidden, var = <<"FORM_TYPE">>,
+ values = [?NS_ADMIN]}|Fs]}.
join_lines([]) ->
<<>>;
@@ -558,103 +502,73 @@ join_lines([], Acc) ->
iolist_to_binary(lists:reverse(tl(Acc))).
handle_adhoc_form(From, #jid{lserver = LServer} = To,
- #adhoc_request{lang = Lang,
- node = Node,
- sessionid = SessionID},
- Fields) ->
- Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of
- {value, {<<"confirm">>, [<<"true">>]}} ->
- true;
- {value, {<<"confirm">>, [<<"1">>]}} ->
- true;
- _ ->
- false
+ #adhoc_command{lang = Lang, node = Node,
+ sid = SessionID, xdata = XData}) ->
+ Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of
+ [<<"true">>] -> true;
+ [<<"1">>] -> true;
+ _ -> false
end,
- Subject = case lists:keysearch(<<"subject">>, 1, Fields) of
- {value, {<<"subject">>, SubjectLines}} ->
- %% There really shouldn't be more than one
- %% subject line, but can we stop them?
- join_lines(SubjectLines);
- _ ->
- <<>>
- end,
- Body = case lists:keysearch(<<"body">>, 1, Fields) of
- {value, {<<"body">>, BodyLines}} ->
- join_lines(BodyLines);
- _ ->
- <<>>
- end,
- Response = #adhoc_response{lang = Lang,
- node = Node,
- sessionid = SessionID,
- status = completed},
- Packet = #xmlel{
- name = <<"message">>,
- attrs = [{<<"type">>, <<"headline">>}],
- children = if Subject /= <<>> ->
- [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}];
- true ->
- []
- end
- ++
- if Body /= <<>> ->
- [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}];
- true ->
- []
- end
- },
+ Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)),
+ Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)),
+ Response = #adhoc_command{lang = Lang, node = Node, sid = SessionID,
+ status = completed},
+ Packet = #message{type = headline,
+ body = xmpp:mk_text(Body),
+ subject = xmpp:mk_text(Subject)},
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
case {Node, Body} of
{?NS_ADMIN_DELETE_MOTD, _} ->
if Confirm ->
Proc ! {announce_motd_delete, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
true ->
- adhoc:produce_response(Response)
+ Response
end;
{?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} ->
if Confirm ->
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
true ->
- adhoc:produce_response(Response)
+ Response
end;
{_, <<>>} ->
%% An announce message with no body is definitely an operator error.
%% Throw an error and give him/her a chance to send message again.
- {error, ?ERRT_NOT_ACCEPTABLE(Lang,
- <<"No body provided for announce message">>)};
+ {error, xmpp:err_not_acceptable(
+ <<"No body provided for announce message">>, Lang)};
%% Now send the packet to ?PROCNAME.
%% We don't use direct announce_* functions because it
%% leads to large delay in response and <iq/> queries processing
{?NS_ADMIN_ANNOUNCE, _} ->
Proc ! {announce_online, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_online, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_ANNOUNCE_ALL, _} ->
Proc ! {announce_all, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_all, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_SET_MOTD, _} ->
Proc ! {announce_motd, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_SET_MOTD_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_motd, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_EDIT_MOTD, _} ->
Proc ! {announce_motd_update, From, To, Packet},
- adhoc:produce_response(Response);
+ Response;
{?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} ->
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
- adhoc:produce_response(Response);
- _ ->
+ Response;
+ Junk ->
%% This can't happen, as we haven't registered any other
%% command nodes.
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ ?ERROR_MSG("got unexpected node/body = ~p", [Junk]),
+ {error, xmpp:err_internal_server_error()}
end.
get_title(Lang, <<"announce">>) ->
@@ -687,12 +601,9 @@ announce_all(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
- Local = jid:make(<<>>, To#jid.server, <<>>),
+ Local = jid:make(To#jid.server),
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
@@ -704,12 +615,9 @@ announce_all_hosts_all(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
- Local = jid:make(<<>>, To#jid.server, <<>>),
+ Local = jid:make(To#jid.server),
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
@@ -722,10 +630,7 @@ announce_online(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
To#jid.server,
@@ -736,10 +641,7 @@ announce_all_hosts_online(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
To#jid.server,
@@ -747,7 +649,7 @@ announce_all_hosts_online(From, To, Packet) ->
end.
announce_online1(Sessions, Server, Packet) ->
- Local = jid:make(<<>>, Server, <<>>),
+ Local = jid:make(Server),
lists:foreach(
fun({U, S, R}) ->
Dest = jid:make(U, S, R),
@@ -759,10 +661,7 @@ announce_motd(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
announce_motd(Host, Packet)
end.
@@ -771,10 +670,7 @@ announce_all_hosts_motd(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
Hosts = ?MYHOSTS,
[announce_motd(Host, Packet) || Host <- Hosts]
@@ -793,10 +689,7 @@ announce_motd_update(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
announce_motd_update(Host, Packet)
end.
@@ -805,10 +698,7 @@ announce_all_hosts_motd_update(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
Hosts = ?MYHOSTS,
[announce_motd_update(Host, Packet) || Host <- Hosts]
@@ -817,17 +707,14 @@ announce_all_hosts_motd_update(From, To, Packet) ->
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:set_motd(LServer, Packet).
+ Mod:set_motd(LServer, xmpp:encode(Packet)).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
announce_motd_delete(Host)
end.
@@ -836,10 +723,7 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Denied by ACL">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ route_forbidden_error(From, To, Packet);
allow ->
Hosts = ?MYHOSTS,
[announce_motd_delete(Host) || Host <- Hosts]
@@ -849,17 +733,24 @@ announce_motd_delete(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer).
+-spec send_motd(jid()) -> ok | {atomic, any()}.
send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
- case Mod:is_motd_user(LUser, LServer) of
- false ->
- Local = jid:make(<<>>, LServer, <<>>),
- ejabberd_router:route(Local, JID, Packet),
- Mod:set_motd_user(LUser, LServer);
- true ->
- ok
+ try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
+ Msg ->
+ case Mod:is_motd_user(LUser, LServer) of
+ false ->
+ Local = jid:make(LServer),
+ ejabberd_router:route(Local, JID, Msg),
+ Mod:set_motd_user(LUser, LServer);
+ true ->
+ ok
+ end
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("failed to decode motd packet ~p: ~s",
+ [Packet, xmpp:format_error(Why)])
end;
error ->
ok
@@ -871,31 +762,24 @@ get_stored_motd(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
- {fxml:get_subtag_cdata(Packet, <<"subject">>),
- fxml:get_subtag_cdata(Packet, <<"body">>)};
+ try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
+ #message{body = Body, subject = Subject} ->
+ {xmpp:get_text(Subject), xmpp:get_text(Body)}
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("failed to decode motd packet ~p: ~s",
+ [Packet, xmpp:format_error(Why)])
+ end;
error ->
{<<>>, <<>>}
end.
%% This function is similar to others, but doesn't perform any ACL verification
send_announcement_to_all(Host, SubjectS, BodyS) ->
- SubjectEls = if SubjectS /= <<>> ->
- [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}];
- true ->
- []
- end,
- BodyEls = if BodyS /= <<>> ->
- [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}];
- true ->
- []
- end,
- Packet = #xmlel{
- name = <<"message">>,
- attrs = [{<<"type">>, <<"headline">>}],
- children = SubjectEls ++ BodyEls
- },
+ Packet = #message{type = headline,
+ body = xmpp:mk_text(BodyS),
+ subject = xmpp:mk_text(SubjectS)},
Sessions = ejabberd_sm:dirty_get_sessions_list(),
- Local = jid:make(<<>>, Host, <<>>),
+ Local = jid:make(Host),
lists:foreach(
fun({U, S, R}) ->
Dest = jid:make(U, S, R),
@@ -909,26 +793,32 @@ get_access(Host) ->
fun(A) -> A end,
none).
--spec add_store_hint(xmlel()) -> xmlel().
-
+-spec add_store_hint(stanza()) -> stanza().
add_store_hint(El) ->
- Hint = #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]},
- fxml:append_subtags(El, [Hint]).
+ xmpp:set_subtag(El, #hint{type = store}).
+
+-spec route_forbidden_error(jid(), jid(), stanza()) -> ok.
+route_forbidden_error(From, To, Packet) ->
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err).
%%-------------------------------------------------------------------------
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"motd">>, 3}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, LA) ->
+import(LServer, {sql, _}, DBType, Tab, List) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, LA).
+ Mod:import(LServer, Tab, List).
-mod_opt_type(access) ->
- fun acl:access_rules_validator/1;
+mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [access, db_type].
diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl
index c43eb853b..47753965d 100644
--- a/src/mod_announce_mnesia.erl
+++ b/src/mod_announce_mnesia.erl
@@ -11,9 +11,9 @@
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
- get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/3]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_announce.hrl").
-include("logger.hrl").
@@ -21,11 +21,11 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(motd,
+ ejabberd_mnesia:create(?MODULE, motd,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd)}]),
- mnesia:create_table(motd_users,
+ ejabberd_mnesia:create(?MODULE, motd_users,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd_users)}]),
@@ -81,10 +81,11 @@ set_motd_user(LUser, LServer) ->
end,
mnesia:transaction(F).
-import(_LServer, #motd{} = Motd) ->
- mnesia:dirty_write(Motd);
-import(_LServer, #motd_users{} = Users) ->
- mnesia:dirty_write(Users).
+import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) ->
+ El = fxml_stream:parse_element(XML),
+ mnesia:dirty_write(#motd{server = LServer, packet = El});
+import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) ->
+ mnesia:dirty_write(#motd_users{us = {LUser, LServer}}).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl
index 7ced0b3ce..242adee0c 100644
--- a/src/mod_announce_riak.erl
+++ b/src/mod_announce_riak.erl
@@ -11,9 +11,9 @@
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
- get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/3]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_announce.hrl").
%%%===================================================================
@@ -71,11 +71,13 @@ set_motd_user(LUser, LServer) ->
#motd_users{us = {LUser, LServer}}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}.
-import(_LServer, #motd{} = Motd) ->
- ejabberd_riak:put(Motd, motd_schema());
-import(_LServer, #motd_users{us = {_, S}} = Users) ->
+import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) ->
+ El = fxml_stream:parse_element(XML),
+ ejabberd_riak:put(#motd{server = LServer, packet = El}, motd_schema());
+import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) ->
+ Users = #motd_users{us = {LUser, LServer}},
ejabberd_riak:put(Users, motd_users_schema(),
- [{'2i', [{<<"server">>, S}]}]).
+ [{'2i', [{<<"server">>, LServer}]}]).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl
index 762c97ad6..90e3f9d75 100644
--- a/src/mod_announce_sql.erl
+++ b/src/mod_announce_sql.erl
@@ -13,10 +13,10 @@
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
- get_motd/1, is_motd_user/2, set_motd_user/2, import/1,
- import/2, export/1]).
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/3,
+ export/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_announce.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -108,19 +108,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select xml from motd where username='';">>,
- fun([XML]) ->
- El = fxml_stream:parse_element(XML),
- #motd{server = LServer, packet = El}
- end},
- {<<"select username from motd where xml='';">>,
- fun([LUser]) ->
- #motd_users{us = {LUser, LServer}}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index 818d53259..d2b187d26 100644
--- a/src/mod_blocking.erl
+++ b/src/mod_blocking.erl
@@ -29,13 +29,13 @@
-protocol({xep, 191, '1.2'}).
--export([start/2, stop/1, process_iq/3,
- process_iq_set/4, process_iq_get/5, mod_opt_type/1, depends/2]).
+-export([start/2, stop/1, process_iq/1,
+ process_iq_set/3, process_iq_get/3, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
@@ -43,6 +43,8 @@
-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
+-type block_event() :: {block, [jid()]} | {unblock, [jid()]} | unblock_all.
+
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
@@ -66,55 +68,64 @@ stop(Host) ->
depends(_Host, _Opts) ->
[{mod_privacy, hard}].
-process_iq(_From, _To, IQ) ->
- SubEl = IQ#iq.sub_el,
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+-spec process_iq(iq()) -> iq().
+process_iq(IQ) ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed()).
-process_iq_get(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING, lang = Lang,
- sub_el = #xmlel{name = <<"blocklist">>}},
- _) ->
+-spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined},
+ iq(), userlist()) ->
+ {error, stanza_error()} |
+ {result, xmpp_element() | undefined}.
+process_iq_get(_, #iq{lang = Lang, from = From,
+ sub_els = [#block_list{}]}, _) ->
#jid{luser = LUser, lserver = LServer} = From,
- {stop, process_blocklist_get(LUser, LServer, Lang)};
-process_iq_get(Acc, _, _, _, _) -> Acc.
+ process_blocklist_get(LUser, LServer, Lang);
+process_iq_get(Acc, _, _) -> Acc.
-process_iq_set(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING, lang = Lang,
- sub_el =
- #xmlel{name = SubElName, children = SubEls}}) ->
+-spec process_iq_set({error, stanza_error()} |
+ {result, xmpp_element() | undefined} |
+ {result, xmpp_element() | undefined, userlist()},
+ iq(), userlist()) ->
+ {error, stanza_error()} |
+ {result, xmpp_element() | undefined} |
+ {result, xmpp_element() | undefined, userlist()}.
+process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}, _) ->
#jid{luser = LUser, lserver = LServer} = From,
- Res = case {SubElName, fxml:remove_cdata(SubEls)} of
- {<<"block">>, []} ->
- Txt = <<"No items found in this query">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {<<"block">>, Els} ->
- JIDs = parse_blocklist_items(Els, []),
- process_blocklist_block(LUser, LServer, JIDs, Lang);
- {<<"unblock">>, []} ->
- process_blocklist_unblock_all(LUser, LServer, Lang);
- {<<"unblock">>, Els} ->
- JIDs = parse_blocklist_items(Els, []),
- process_blocklist_unblock(LUser, LServer, JIDs, Lang);
- _ ->
- Txt = <<"Unknown blocking command">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end,
- {stop, Res};
-process_iq_set(Acc, _, _, _) -> Acc.
+ case SubEl of
+ #block{items = []} ->
+ Txt = <<"No items found in this query">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ #block{items = Items} ->
+ JIDs = [jid:tolower(Item) || Item <- Items],
+ process_blocklist_block(LUser, LServer, JIDs, Lang);
+ #unblock{items = []} ->
+ process_blocklist_unblock_all(LUser, LServer, Lang);
+ #unblock{items = Items} ->
+ JIDs = [jid:tolower(Item) || Item <- Items],
+ process_blocklist_unblock(LUser, LServer, JIDs, Lang);
+ _ ->
+ Acc
+ end;
+process_iq_set(Acc, _, _) -> Acc.
+-spec list_to_blocklist_jids([listitem()], [ljid()]) -> [ljid()].
list_to_blocklist_jids([], JIDs) -> JIDs;
list_to_blocklist_jids([#listitem{type = jid,
action = deny, value = JID} =
Item
| Items],
JIDs) ->
- case Item of
- #listitem{match_all = true} -> Match = true;
- #listitem{match_iq = true, match_message = true,
- match_presence_in = true, match_presence_out = true} ->
- Match = true;
- _ -> Match = false
- end,
+ Match = case Item of
+ #listitem{match_all = true} ->
+ true;
+ #listitem{match_iq = true,
+ match_message = true,
+ match_presence_in = true,
+ match_presence_out = true} ->
+ true;
+ _ ->
+ false
+ end,
if Match -> list_to_blocklist_jids(Items, [JID | JIDs]);
true -> list_to_blocklist_jids(Items, JIDs)
end;
@@ -122,20 +133,10 @@ list_to_blocklist_jids([#listitem{type = jid,
list_to_blocklist_jids([_ | Items], JIDs) ->
list_to_blocklist_jids(Items, JIDs).
-parse_blocklist_items([], JIDs) -> JIDs;
-parse_blocklist_items([#xmlel{name = <<"item">>,
- attrs = Attrs}
- | Els],
- JIDs) ->
- case fxml:get_attr(<<"jid">>, Attrs) of
- {value, JID1} ->
- JID = jid:tolower(jid:from_string(JID1)),
- parse_blocklist_items(Els, [JID | JIDs]);
- false -> parse_blocklist_items(Els, JIDs)
- end;
-parse_blocklist_items([_ | Els], JIDs) ->
- parse_blocklist_items(Els, JIDs).
-
+-spec process_blocklist_block(binary(), binary(), [ljid()],
+ binary()) ->
+ {error, stanza_error()} |
+ {result, undefined, userlist()}.
process_blocklist_block(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
AlreadyBlocked = list_to_blocklist_jids(List, []),
@@ -161,13 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs, Lang) ->
broadcast_list_update(LUser, LServer, Default,
UserList),
broadcast_blocklist_event(LUser, LServer,
- {block, JIDs}),
- {result, [], UserList};
+ {block, [jid:make(J) || J <- JIDs]}),
+ {result, undefined, UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
end.
+-spec process_blocklist_unblock_all(binary(), binary(), binary()) ->
+ {error, stanza_error()} |
+ {result, undefined} |
+ {result, undefined, userlist()}.
process_blocklist_unblock_all(LUser, LServer, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = A}) -> A =/= deny
@@ -176,18 +181,22 @@ process_blocklist_unblock_all(LUser, LServer, Lang) ->
end,
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
- {atomic, ok} -> {result, []};
+ {atomic, ok} -> {result, undefined};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default,
UserList),
broadcast_blocklist_event(LUser, LServer, unblock_all),
- {result, [], UserList};
+ {result, undefined, UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
end.
+-spec process_blocklist_unblock(binary(), binary(), [ljid()], binary()) ->
+ {error, stanza_error()} |
+ {result, undefined} |
+ {result, undefined, userlist()}.
process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = deny, type = jid,
@@ -199,56 +208,50 @@ process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
end,
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
- {atomic, ok} -> {result, []};
+ {atomic, ok} -> {result, undefined};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default,
UserList),
broadcast_blocklist_event(LUser, LServer,
- {unblock, JIDs}),
- {result, [], UserList};
+ {unblock, [jid:make(J) || J <- JIDs]}),
+ {result, undefined, UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
end.
+-spec make_userlist(binary(), [listitem()]) -> userlist().
make_userlist(Name, List) ->
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
+-spec broadcast_list_update(binary(), binary(), binary(), userlist()) -> ok.
broadcast_list_update(LUser, LServer, Name, UserList) ->
- ejabberd_sm:route(jid:make(LUser, LServer,
- <<"">>),
+ ejabberd_sm:route(jid:make(LUser, LServer, <<"">>),
jid:make(LUser, LServer, <<"">>),
{broadcast, {privacy_list, UserList, Name}}).
+-spec broadcast_blocklist_event(binary(), binary(), block_event()) -> ok.
broadcast_blocklist_event(LUser, LServer, Event) ->
JID = jid:make(LUser, LServer, <<"">>),
ejabberd_sm:route(JID, JID,
{broadcast, {blocking, Event}}).
+-spec process_blocklist_get(binary(), binary(), binary()) ->
+ {error, stanza_error()} | {result, block_list()}.
process_blocklist_get(LUser, LServer, Lang) ->
Mod = db_mod(LServer),
case Mod:process_blocklist_get(LUser, LServer) of
error ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)};
List ->
- JIDs = list_to_blocklist_jids(List, []),
- Items = lists:map(fun (JID) ->
- ?DEBUG("JID: ~p", [JID]),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string(JID)}],
- children = []}
- end,
- JIDs),
- {result,
- [#xmlel{name = <<"blocklist">>,
- attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
- children = Items}]}
+ LJIDs = list_to_blocklist_jids(List, []),
+ Items = [jid:make(J) || J <- LJIDs],
+ {result, #block_list{items = Items}}
end.
+-spec db_mod(binary()) -> module().
db_mod(LServer) ->
DBType = gen_mod:db_type(LServer, mod_privacy),
gen_mod:db_mod(DBType, ?MODULE).
diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl
index 5a4bde64c..b64202717 100644
--- a/src/mod_blocking_mnesia.erl
+++ b/src/mod_blocking_mnesia.erl
@@ -14,7 +14,6 @@
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
--include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl
index 5dd5cfa92..1f15591ef 100644
--- a/src/mod_blocking_riak.erl
+++ b/src/mod_blocking_riak.erl
@@ -14,7 +14,6 @@
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
--include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl
index bffe5bd25..402d6de19 100644
--- a/src/mod_blocking_sql.erl
+++ b/src/mod_blocking_sql.erl
@@ -14,7 +14,6 @@
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
--include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl
new file mode 100644
index 000000000..038218739
--- /dev/null
+++ b/src/mod_bosh.erl
@@ -0,0 +1,296 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_bosh.erl
+%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Purpose : This module acts as a bridge to ejabberd_bosh which implements
+%%% the real stuff, this is to handle the new pluggable architecture
+%%% for extending ejabberd's http service.
+%%% Created : 20 Jul 2011 by Evgeniy 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(mod_bosh).
+
+-author('steve@zeank.in-berlin.de').
+
+%%-define(ejabberd_debug, true).
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-export([start_link/0]).
+-export([start/2, stop/1, process/2, open_session/2,
+ close_session/1, find_session/1]).
+
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3,
+ depends/2, mod_opt_type/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+
+-include("ejabberd_http.hrl").
+
+-include("bosh.hrl").
+
+-record(bosh, {sid = <<"">> :: binary() | '_',
+ timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
+ pid = self() :: pid() | '$1'}).
+
+-record(state, {}).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+process([], #request{method = 'POST', data = <<>>}) ->
+ ?DEBUG("Bad Request: no data", []),
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP, headers = Hdrs}) ->
+ ?DEBUG("Incoming data: ~p", [Data]),
+ Type = get_type(Hdrs),
+ ejabberd_bosh:process_request(Data, IP, Type);
+process([], #request{method = 'GET', data = <<>>}) ->
+ {200, ?HEADER(?CT_XML), get_human_html_xmlel()};
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, []};
+process(_Path, _Request) ->
+ ?DEBUG("Bad Request: ~p", [_Request]),
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+
+get_human_html_xmlel() ->
+ Heading = <<"ejabberd ", (jlib:atom_to_binary(?MODULE))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>, attrs = [],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children = [{xmlcdata, Heading}]}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [#xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, attrs = [],
+ 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">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]}.
+
+open_session(SID, Pid) ->
+ Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid},
+ lists:foreach(
+ fun(Node) when Node == node() ->
+ gen_server:call(?MODULE, {write, Session});
+ (Node) ->
+ cluster_send({?MODULE, Node}, {write, Session})
+ end, ejabberd_cluster:get_nodes()).
+
+close_session(SID) ->
+ case mnesia:dirty_read(bosh, SID) of
+ [Session] ->
+ lists:foreach(
+ fun(Node) when Node == node() ->
+ gen_server:call(?MODULE, {delete, Session});
+ (Node) ->
+ cluster_send({?MODULE, Node}, {delete, Session})
+ end, ejabberd_cluster:get_nodes());
+ [] ->
+ ok
+ end.
+
+write_session(#bosh{pid = Pid1, sid = SID, timestamp = T1} = S1) ->
+ case mnesia:dirty_read(bosh, SID) of
+ [#bosh{pid = Pid2, timestamp = T2} = S2] ->
+ if Pid1 == Pid2 ->
+ mnesia:dirty_write(S1);
+ T1 < T2 ->
+ cluster_send(Pid2, replaced),
+ mnesia:dirty_write(S1);
+ true ->
+ cluster_send(Pid1, replaced),
+ mnesia:dirty_write(S2)
+ end;
+ [] ->
+ mnesia:dirty_write(S1)
+ end.
+
+delete_session(#bosh{sid = SID, pid = Pid1}) ->
+ case mnesia:dirty_read(bosh, SID) of
+ [#bosh{pid = Pid2}] ->
+ if Pid1 == Pid2 ->
+ mnesia:dirty_delete(bosh, SID);
+ true ->
+ ok
+ end;
+ [] ->
+ ok
+ end.
+
+find_session(SID) ->
+ case mnesia:dirty_read(bosh, SID) of
+ [#bosh{pid = Pid}] ->
+ {ok, Pid};
+ [] ->
+ error
+ end.
+
+start(Host, Opts) ->
+ setup_database(),
+ start_jiffy(Opts),
+ TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME),
+ TmpSupSpec = {TmpSup,
+ {ejabberd_tmp_sup, start_link, [TmpSup, ejabberd_bosh]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
+ ProcSpec = {?MODULE,
+ {?MODULE, start_link, []},
+ transient, 2000, worker, [?MODULE]},
+ case supervisor:start_child(ejabberd_sup, ProcSpec) of
+ {ok, _} ->
+ supervisor:start_child(ejabberd_sup, TmpSupSpec);
+ {error, {already_started, _}} ->
+ supervisor:start_child(ejabberd_sup, TmpSupSpec);
+ Err ->
+ Err
+ end.
+
+stop(Host) ->
+ TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, TmpSup),
+ supervisor:delete_child(ejabberd_sup, TmpSup).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([]) ->
+ {ok, #state{}}.
+
+handle_call({write, Session}, _From, State) ->
+ Res = write_session(Session),
+ {reply, Res, State};
+handle_call({delete, Session}, _From, State) ->
+ Res = delete_session(Session),
+ {reply, Res, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({write, Session}, State) ->
+ write_session(Session),
+ {noreply, State};
+handle_info({delete, Session}, State) ->
+ delete_session(Session),
+ {noreply, State};
+handle_info(_Info, State) ->
+ ?ERROR_MSG("got unexpected info: ~p", [_Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+setup_database() ->
+ case catch mnesia:table_info(bosh, attributes) of
+ [sid, pid] ->
+ mnesia:delete_table(bosh);
+ _ ->
+ ok
+ end,
+ ejabberd_mnesia:create(?MODULE, bosh,
+ [{ram_copies, [node()]}, {local_content, true},
+ {attributes, record_info(fields, bosh)}]),
+ mnesia:add_table_copy(bosh, node(), ram_copies).
+
+start_jiffy(Opts) ->
+ case gen_mod:get_opt(json, Opts,
+ fun(false) -> false;
+ (true) -> true
+ end, false) of
+ false ->
+ ok;
+ true ->
+ case catch ejabberd:start_app(jiffy) of
+ ok ->
+ ok;
+ Err ->
+ ?WARNING_MSG("Failed to start JSON codec (jiffy): ~p. "
+ "JSON support will be disabled", [Err])
+ end
+ end.
+
+get_type(Hdrs) ->
+ try
+ {_, S} = lists:keyfind('Content-Type', 1, Hdrs),
+ [T|_] = str:tokens(S, <<";">>),
+ [_, <<"json">>] = str:tokens(T, <<"/">>),
+ json
+ catch _:_ ->
+ xml
+ end.
+
+cluster_send(NodePid, Msg) ->
+ erlang:send(NodePid, Msg, [noconnect, nosuspend]).
+
+depends(_Host, _Opts) ->
+ [].
+
+mod_opt_type(json) ->
+ fun (false) -> false;
+ (true) -> true
+ end;
+mod_opt_type(max_concat) ->
+ fun (unlimited) -> unlimited;
+ (N) when is_integer(N), N > 0 -> N
+ 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(prebind) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [json, max_concat, max_inactivity, max_pause, prebind].
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 3ed3149bb..3a4492f5c 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -54,33 +54,17 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
+-include("mod_caps.hrl").
-define(PROCNAME, ejabberd_mod_caps).
-define(BAD_HASH_LIFETIME, 600).
--record(caps,
-{
- node = <<"">> :: binary(),
- version = <<"">> :: binary(),
- hash = <<"">> :: binary(),
- exts = [] :: [binary()]
-}).
-
--type caps() :: #caps{}.
-
--export_type([caps/0]).
-
--record(caps_features,
-{
- node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
- features = [] :: [binary()] | pos_integer()
-}).
-
-record(state, {host = <<"">> :: binary()}).
-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), {binary(), binary()}, [binary() | pos_integer()]) -> ok.
-callback caps_read(binary(), {binary(), binary()}) ->
{ok, non_neg_integer() | [binary()]} | error.
-callback caps_write(binary(), {binary(), binary()},
@@ -103,6 +87,7 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
+-spec get_features(binary(), nothing | caps()) -> [binary()].
get_features(_Host, nothing) -> [];
get_features(Host, #caps{node = Node, version = Version,
exts = Exts}) ->
@@ -119,65 +104,37 @@ get_features(Host, #caps{node = Node, version = Version,
end,
[], SubNodes).
--spec read_caps([xmlel()]) -> nothing | caps().
-
-read_caps(Els) -> read_caps(Els, nothing).
-
-read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
- | Tail],
- Result) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_CAPS ->
- 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,
- exts = Exts});
- _ -> read_caps(Tail, Result)
- end;
-read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
- | Tail],
- Result) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC_USER -> nothing;
- _ -> read_caps(Tail, Result)
- end;
-read_caps([_ | Tail], Result) ->
- read_caps(Tail, Result);
-read_caps([], Result) -> Result.
+-spec read_caps(#presence{}) -> nothing | caps().
+read_caps(Presence) ->
+ case xmpp:get_subtag(Presence, #caps{}) of
+ false -> nothing;
+ Caps -> Caps
+ end.
-user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
- children = Els} = Pkt,
+-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
+user_send_packet(#presence{type = available} = Pkt,
_C2SState,
#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server,
lresource = <<"">>}) ->
- Type = fxml:get_attr_s(<<"type">>, Attrs),
- if Type == <<"">>; Type == <<"available">> ->
- case read_caps(Els) of
- nothing -> ok;
- #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
- end;
- true -> ok
+ case read_caps(Pkt) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
+ feature_request(Server, From, Caps, [Version | Exts])
end,
Pkt;
user_send_packet(Pkt, _C2SState, _From, _To) ->
Pkt.
-user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
- children = Els} = Pkt,
+-spec user_receive_packet(stanza(), ejabberd_c2s:state(),
+ jid(), jid(), jid()) -> stanza().
+user_receive_packet(#presence{type = available} = 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">>)) ->
- case read_caps(Els) of
+ if IsRemote ->
+ case read_caps(Pkt) of
nothing -> ok;
#caps{version = Version, exts = Exts} = Caps ->
feature_request(Server, From, Caps, [Version | Exts])
@@ -188,20 +145,19 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
user_receive_packet(Pkt, _C2SState, _JID, _From, _To) ->
Pkt.
--spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
+-spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of
<<"">> -> Acc;
Hash ->
- [#xmlel{name = <<"c">>,
- attrs =
- [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
- {<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
- children = []}
- | Acc]
+ [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc]
end.
+-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(),
+ binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]} | empty.
disco_features(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of
true ->
@@ -212,6 +168,9 @@ disco_features(Acc, From, To, Node, Lang) ->
Acc
end.
+-spec disco_identity([identity()], jid(), jid(),
+ binary(), binary()) ->
+ [identity()].
disco_identity(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of
true ->
@@ -222,24 +181,28 @@ disco_identity(Acc, From, To, Node, Lang) ->
Acc
end.
-disco_info(Acc, Host, Module, Node, Lang) ->
+-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
+ ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
+disco_info(Acc, Host, Module, Node, Lang) when is_atom(Module) ->
case is_valid_node(Node) of
true ->
ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, Module, <<"">>, Lang]);
false ->
Acc
- end.
+ end;
+disco_info(Acc, _, _, _Node, _Lang) ->
+ Acc.
+-spec c2s_presence_in(ejabberd_c2s:state(), {jid(), jid(), presence()}) ->
+ ejabberd_c2s:state().
c2s_presence_in(C2SState,
- {From, To, {_, _, Attrs, Els}}) ->
- Type = fxml:get_attr_s(<<"type">>, Attrs),
+ {From, To, #presence{type = Type} = Presence}) ->
Subscription = ejabberd_c2s:get_subscription(From,
C2SState),
- Insert = ((Type == <<"">>) or (Type == <<"available">>))
+ Insert = (Type == available)
and ((Subscription == both) or (Subscription == to)),
- Delete = (Type == <<"unavailable">>) or
- (Type == <<"error">>),
+ Delete = (Type == unavailable) or (Type == error),
if Insert or Delete ->
LFrom = jid:tolower(From),
Rs = case ejabberd_c2s:get_aux_field(caps_resources,
@@ -248,7 +211,7 @@ c2s_presence_in(C2SState,
{ok, Rs1} -> Rs1;
error -> gb_trees:empty()
end,
- Caps = read_caps(Els),
+ Caps = read_caps(Presence),
NewRs = case Caps of
nothing when Insert == true -> Rs;
_ when Insert == true ->
@@ -272,6 +235,9 @@ c2s_presence_in(C2SState,
true -> C2SState
end.
+-spec c2s_filter_packet(boolean(), binary(), ejabberd_c2s:state(),
+ {pep_message, binary()}, jid(), stanza()) ->
+ boolean().
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
{ok, Rs} ->
@@ -287,6 +253,9 @@ c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
end;
c2s_filter_packet(Acc, _, _, _, _, _) -> Acc.
+-spec c2s_broadcast_recipients([ljid()], binary(), ejabberd_c2s:state(),
+ {pep_message, binary()}, jid(), stanza()) ->
+ [ljid()].
c2s_broadcast_recipients(InAcc, Host, C2SState,
{pep_message, Feature}, _From, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources,
@@ -306,6 +275,7 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
end;
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
+-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
@@ -377,6 +347,7 @@ terminate(_Reason, State) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}.
+-spec feature_request(binary(), jid(), caps(), [binary()]) -> any().
feature_request(Host, From, Caps,
[SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
@@ -392,15 +363,9 @@ feature_request(Host, From, Caps,
_ -> true
end,
if NeedRequest ->
- IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_DISCO_INFO},
- {<<"node">>,
- <<Node/binary, "#",
- SubNode/binary>>}],
- children = []}]},
+ IQ = #iq{type = get,
+ sub_els = [#disco_info{node = <<Node/binary, "#",
+ SubNode/binary>>}]},
cache_tab:insert(caps_features, NodePair, now_ts(),
caps_write_fun(Host, NodePair, now_ts())),
F = fun (IQReply) ->
@@ -415,39 +380,43 @@ feature_request(Host, From, Caps,
end;
feature_request(_Host, _From, _Caps, []) -> ok.
-feature_response(#iq{type = result,
- sub_el = [#xmlel{children = Els}]},
+-spec feature_response(iq(), binary(), jid(), caps(), [binary()]) -> any().
+feature_response(#iq{type = result, sub_els = [El]},
Host, From, Caps, [SubNode | SubNodes]) ->
NodePair = {Caps#caps.node, SubNode},
- case check_hash(Caps, Els) of
- true ->
- Features = lists:flatmap(fun (#xmlel{name =
- <<"feature">>,
- attrs = FAttrs}) ->
- [fxml:get_attr_s(<<"var">>, FAttrs)];
- (_) -> []
- end,
- Els),
- cache_tab:insert(caps_features, NodePair,
- Features,
- caps_write_fun(Host, NodePair, Features));
- false -> ok
+ try
+ DiscoInfo = xmpp:decode(El),
+ case check_hash(Caps, DiscoInfo) of
+ true ->
+ Features = DiscoInfo#disco_info.features,
+ cache_tab:insert(caps_features, NodePair,
+ Features,
+ caps_write_fun(Host, NodePair, Features));
+ false -> ok
+ end
+ catch _:{xmpp_codec, _Why} ->
+ ok
end,
feature_request(Host, From, Caps, SubNodes);
feature_response(_IQResult, Host, From, Caps,
[_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes).
+-spec caps_read_fun(binary(), {binary(), binary()})
+ -> fun(() -> {ok, [binary()] | non_neg_integer()} | error).
caps_read_fun(Host, Node) ->
LServer = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_read(LServer, Node) end.
+-spec caps_write_fun(binary(), {binary(), binary()},
+ [binary()] | non_neg_integer()) -> fun().
caps_write_fun(Host, Node, Features) ->
LServer = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_write(LServer, Node, Features) end.
+-spec make_my_disco_hash(binary()) -> binary().
make_my_disco_hash(Host) ->
JID = jid:make(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features,
@@ -458,125 +427,79 @@ make_my_disco_hash(Host) ->
[Host, undefined, <<"">>, <<"">>])}
of
{{result, Features}, Identities, Info} ->
- Feats = lists:map(fun ({{Feat, _Host}}) ->
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}],
- children = []};
- (Feat) ->
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}],
- children = []}
+ Feats = lists:map(fun ({{Feat, _Host}}) -> Feat;
+ (Feat) -> Feat
end,
Features),
- make_disco_hash(Identities ++ Info ++ Feats, sha1);
+ DiscoInfo = #disco_info{identities = Identities,
+ features = Feats,
+ xdata = Info},
+ make_disco_hash(DiscoInfo, sha);
_Err -> <<"">>
end.
-make_disco_hash(DiscoEls, Algo) ->
- Concat = list_to_binary([concat_identities(DiscoEls),
- concat_features(DiscoEls), concat_info(DiscoEls)]),
+-type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
+-spec make_disco_hash(disco_info(), digest_type()) -> binary().
+make_disco_hash(DiscoInfo, Algo) ->
+ Concat = list_to_binary([concat_identities(DiscoInfo),
+ concat_features(DiscoInfo), concat_info(DiscoInfo)]),
jlib:encode_base64(case Algo of
md5 -> erlang:md5(Concat);
- sha1 -> p1_sha:sha1(Concat);
+ sha -> p1_sha:sha1(Concat);
sha224 -> p1_sha:sha224(Concat);
sha256 -> p1_sha:sha256(Concat);
sha384 -> p1_sha:sha384(Concat);
sha512 -> p1_sha:sha512(Concat)
end).
-check_hash(Caps, Els) ->
+-spec check_hash(caps(), disco_info()) -> boolean().
+check_hash(Caps, DiscoInfo) ->
case Caps#caps.hash of
<<"md5">> ->
- Caps#caps.version == make_disco_hash(Els, md5);
+ Caps#caps.version == make_disco_hash(DiscoInfo, md5);
<<"sha-1">> ->
- Caps#caps.version == make_disco_hash(Els, sha1);
+ Caps#caps.version == make_disco_hash(DiscoInfo, sha);
<<"sha-224">> ->
- Caps#caps.version == make_disco_hash(Els, sha224);
+ Caps#caps.version == make_disco_hash(DiscoInfo, sha224);
<<"sha-256">> ->
- Caps#caps.version == make_disco_hash(Els, sha256);
+ Caps#caps.version == make_disco_hash(DiscoInfo, sha256);
<<"sha-384">> ->
- Caps#caps.version == make_disco_hash(Els, sha384);
+ Caps#caps.version == make_disco_hash(DiscoInfo, sha384);
<<"sha-512">> ->
- Caps#caps.version == make_disco_hash(Els, sha512);
+ Caps#caps.version == make_disco_hash(DiscoInfo, sha512);
_ -> true
end.
-concat_features(Els) ->
- lists:usort(lists:flatmap(fun (#xmlel{name =
- <<"feature">>,
- attrs = Attrs}) ->
- [[fxml:get_attr_s(<<"var">>, Attrs), $<]];
- (_) -> []
- end,
- Els)).
-
-concat_identities(Els) ->
- lists:sort(lists:flatmap(fun (#xmlel{name =
- <<"identity">>,
- attrs = Attrs}) ->
- [[fxml:get_attr_s(<<"category">>, Attrs),
- $/, fxml:get_attr_s(<<"type">>, Attrs),
- $/,
- fxml:get_attr_s(<<"xml:lang">>, Attrs),
- $/, fxml:get_attr_s(<<"name">>, Attrs),
- $<]];
- (_) -> []
- end,
- Els)).
-
-concat_info(Els) ->
- lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
- attrs = Attrs, children = Fields}) ->
- case {fxml:get_attr_s(<<"xmlns">>, Attrs),
- fxml:get_attr_s(<<"type">>, Attrs)}
- of
- {?NS_XDATA, <<"result">>} ->
- [concat_xdata_fields(Fields)];
- _ -> []
- end;
- (_) -> []
- end,
- Els)).
-
-concat_xdata_fields(Fields) ->
- [Form, Res] = lists:foldl(fun (#xmlel{name =
- <<"field">>,
- attrs = Attrs, children = Els} =
- El,
- [FormType, VarFields] = Acc) ->
- case fxml:get_attr_s(<<"var">>, Attrs) of
- <<"">> -> Acc;
- <<"FORM_TYPE">> ->
- [fxml:get_subtag_cdata(El,
- <<"value">>),
- VarFields];
- Var ->
- [FormType,
- [[[Var, $<],
- lists:sort(lists:flatmap(fun
- (#xmlel{name
- =
- <<"value">>,
- children
- =
- VEls}) ->
- [[fxml:get_cdata(VEls),
- $<]];
- (_) ->
- []
- end,
- Els))]
- | VarFields]]
- end;
- (_, Acc) -> Acc
- end,
- [<<"">>, []], Fields),
+-spec concat_features(disco_info()) -> iolist().
+concat_features(#disco_info{features = Features}) ->
+ lists:usort([[Feat, $<] || Feat <- Features]).
+
+-spec concat_identities(disco_info()) -> iolist().
+concat_identities(#disco_info{identities = Identities}) ->
+ lists:sort(
+ [[Cat, $/, T, $/, Lang, $/, Name, $<] ||
+ #identity{category = Cat, type = T,
+ lang = Lang, name = Name} <- Identities]).
+
+-spec concat_info(disco_info()) -> iolist().
+concat_info(#disco_info{xdata = Xs}) ->
+ lists:sort(
+ [concat_xdata_fields(X) || #xdata{type = result} = X <- Xs]).
+
+-spec concat_xdata_fields(xdata()) -> iolist().
+concat_xdata_fields(#xdata{fields = Fields} = X) ->
+ Form = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+ Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])]
+ || #xdata_field{var = Var, values = Values} <- Fields,
+ is_binary(Var), Var /= <<"FORM_TYPE">>],
[Form, $<, lists:sort(Res)].
+-spec gb_trees_fold(fun((_, _, T) -> T), T, gb_trees:tree()) -> T.
gb_trees_fold(F, Acc, Tree) ->
Iter = gb_trees:iterator(Tree),
gb_trees_fold_iter(F, Acc, Iter).
+-spec gb_trees_fold_iter(fun((_, _, T) -> T), T, gb_trees:iter()) -> T.
gb_trees_fold_iter(F, Acc, Iter) ->
case gb_trees:next(Iter) of
{Key, Val, NewIter} ->
@@ -585,9 +508,11 @@ gb_trees_fold_iter(F, Acc, Iter) ->
_ -> Acc
end.
+-spec now_ts() -> integer().
now_ts() ->
p1_time_compat:system_time(seconds).
+-spec is_valid_node(binary()) -> boolean().
is_valid_node(Node) ->
case str:tokens(Node, <<"#">>) of
[?EJABBERD_URI|_] ->
@@ -596,9 +521,6 @@ is_valid_node(Node) ->
false
end.
-caps_features_schema() ->
- {record_info(fields, caps_features), #caps_features{}}.
-
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
@@ -614,7 +536,7 @@ import_start(LServer, DBType) ->
import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
[Node, SubNode, Feature, _TimeStamp]) ->
- Feature1 = case catch jlib:binary_to_integer(Feature) of
+ Feature1 = case catch binary_to_integer(Feature) of
I when is_integer(I), I>0 -> I;
_ -> Feature
end,
@@ -630,24 +552,8 @@ import_next(_LServer, _DBType, '$end_of_table') ->
ok;
import_next(LServer, DBType, NodePair) ->
Features = [F || {_, F} <- ets:lookup(caps_features_tmp, NodePair)],
- case Features of
- [I] when is_integer(I), DBType == mnesia ->
- mnesia:dirty_write(
- #caps_features{node_pair = NodePair, features = I});
- [I] when is_integer(I), DBType == riak ->
- ejabberd_riak:put(
- #caps_features{node_pair = NodePair, features = I},
- caps_features_schema());
- _ when DBType == mnesia ->
- mnesia:dirty_write(
- #caps_features{node_pair = NodePair, features = Features});
- _ when DBType == riak ->
- ejabberd_riak:put(
- #caps_features{node_pair = NodePair, features = Features},
- caps_features_schema());
- _ when DBType == sql ->
- ok
- end,
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, NodePair, Features),
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
mod_opt_type(cache_life_time) ->
diff --git a/src/mod_caps_mnesia.erl b/src/mod_caps_mnesia.erl
index 0bf04b2c3..ed22841e8 100644
--- a/src/mod_caps_mnesia.erl
+++ b/src/mod_caps_mnesia.erl
@@ -10,7 +10,7 @@
-behaviour(mod_caps).
%% API
--export([init/2, caps_read/2, caps_write/3]).
+-export([init/2, caps_read/2, caps_write/3, import/3]).
-include("mod_caps.hrl").
-include("logger.hrl").
@@ -27,7 +27,7 @@ init(_Host, _Opts) ->
_ ->
mnesia:delete_table(caps_features)
end,
- mnesia:create_table(caps_features,
+ ejabberd_mnesia:create(?MODULE, caps_features,
[{disc_only_copies, [node()]},
{local_content, true},
{attributes,
@@ -46,6 +46,13 @@ caps_write(_LServer, Node, Features) ->
mnesia:dirty_write(#caps_features{node_pair = Node,
features = Features}).
+import(_LServer, NodePair, [I]) when is_integer(I) ->
+ mnesia:dirty_write(
+ #caps_features{node_pair = NodePair, features = I});
+import(_LServer, NodePair, Features) ->
+ mnesia:dirty_write(
+ #caps_features{node_pair = NodePair, features = Features}).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
diff --git a/src/mod_caps_riak.erl b/src/mod_caps_riak.erl
index 6e59ba867..a504bb6ce 100644
--- a/src/mod_caps_riak.erl
+++ b/src/mod_caps_riak.erl
@@ -10,7 +10,7 @@
-behaviour(mod_caps).
%% API
--export([init/2, caps_read/2, caps_write/3]).
+-export([init/2, caps_read/2, caps_write/3, import/3]).
-include("mod_caps.hrl").
@@ -31,6 +31,15 @@ caps_write(_LServer, Node, Features) ->
features = Features},
caps_features_schema()).
+import(_LServer, NodePair, [I]) when is_integer(I) ->
+ ejabberd_riak:put(
+ #caps_features{node_pair = NodePair, features = I},
+ caps_features_schema());
+import(_LServer, NodePair, Features) ->
+ ejabberd_riak:put(
+ #caps_features{node_pair = NodePair, features = Features},
+ caps_features_schema()).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl
index 5faff98b6..fee0f0960 100644
--- a/src/mod_caps_sql.erl
+++ b/src/mod_caps_sql.erl
@@ -12,7 +12,7 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
--export([init/2, caps_read/2, caps_write/3, export/1]).
+-export([init/2, caps_read/2, caps_write/3, export/1, import/3]).
-include("mod_caps.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -29,7 +29,7 @@ caps_read(LServer, {Node, SubNode}) ->
?SQL("select @(feature)s from caps_features where"
" node=%(Node)s and subnode=%(SubNode)s")) of
{selected, [{H}|_] = Fs} ->
- case catch jlib:binary_to_integer(H) of
+ case catch binary_to_integer(H) of
Int when is_integer(Int), Int>=0 ->
{ok, Int};
_ ->
@@ -53,12 +53,15 @@ export(_Server) ->
[]
end}].
+import(_, _, _) ->
+ ok.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
sql_write_features_t({Node, SubNode}, Features) ->
NewFeatures = if is_integer(Features) ->
- [jlib:integer_to_binary(Features)];
+ [integer_to_binary(Features)];
true ->
Features
end,
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index e1e6d63a2..5839a65b2 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -36,37 +36,29 @@
stop/1]).
-export([user_send_packet/4, user_receive_packet/5,
- iq_handler2/3, iq_handler1/3, remove_connection/4,
+ iq_handler/1, remove_connection/4,
is_carbon_copy/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PROCNAME, ?MODULE).
+-type direction() :: sent | received.
+
-callback init(binary(), gen_mod:opts()) -> any().
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
-callback list(binary(), binary()) -> [{binary(), binary()}].
-is_carbon_copy(Packet) ->
- is_carbon_copy(Packet, <<"sent">>) orelse
- is_carbon_copy(Packet, <<"received">>).
-
-is_carbon_copy(Packet, Direction) ->
- case fxml:get_subtag(Packet, Direction) of
- #xmlel{name = Direction, attrs = Attrs} ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_CARBONS_2 -> true;
- ?NS_CARBONS_1 -> true;
- _ -> false
- end;
- _ -> false
- end.
+-spec is_carbon_copy(stanza()) -> boolean().
+is_carbon_copy(#message{meta = #{carbon_copy := true}}) ->
+ true;
+is_carbon_copy(_) ->
+ false.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
- mod_disco:register_feature(Host, ?NS_CARBONS_1),
mod_disco:register_feature(Host, ?NS_CARBONS_2),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:init(Host, Opts),
@@ -74,54 +66,53 @@ start(Host, Opts) ->
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler2, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1, ?MODULE, iq_handler1, IQDisc).
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler, IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
mod_disco:unregister_feature(Host, ?NS_CARBONS_2),
- mod_disco:unregister_feature(Host, ?NS_CARBONS_1),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
-iq_handler2(From, To, IQ) ->
- iq_handler(From, To, IQ, ?NS_CARBONS_2).
-iq_handler1(From, To, IQ) ->
- iq_handler(From, To, IQ, ?NS_CARBONS_1).
-
-iq_handler(From, _To,
- #iq{type=set, lang = Lang,
- sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
- ?DEBUG("carbons IQ received: ~p", [IQ]),
+-spec iq_handler(iq()) -> iq().
+iq_handler(#iq{type = set, lang = Lang, from = From,
+ sub_els = [El]} = IQ) when is_record(El, carbons_enable);
+ is_record(El, carbons_disable) ->
{U, S, R} = jid:tolower(From),
- Result = case Operation of
- <<"enable">>->
- ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
- enable(S,U,R,CC);
- <<"disable">>->
- ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
- disable(S, U, R)
- end,
+ Result = case El of
+ #carbons_enable{} ->
+ ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
+ enable(S, U, R, ?NS_CARBONS_2);
+ #carbons_disable{} ->
+ ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
+ disable(S, U, R)
+ end,
case Result of
- ok ->
+ ok ->
?DEBUG("carbons IQ result: ok", []),
- IQ#iq{type=result, sub_el=[]};
+ xmpp:make_iq_result(IQ);
{error,_Error} ->
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
Txt = <<"Database failure">>,
- IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
-
-iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
+iq_handler(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Only <enable/> or <disable/> tags are allowed">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+iq_handler(#iq{type = get, lang = Lang} = IQ)->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
- IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
+-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) ->
+ stanza() | {stop, stanza()}.
user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent).
+-spec user_receive_packet(stanza(), ejabberd_c2s:state(),
+ jid(), jid(), jid()) ->
+ stanza() | {stop, stanza()}.
user_receive_packet(Packet, _C2SState, JID, _From, To) ->
check_and_forward(JID, To, Packet, received).
@@ -129,11 +120,13 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) ->
% - 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
+-spec check_and_forward(jid(), jid(), stanza(), direction()) ->
+ stanza() | {stop, stanza()}.
check_and_forward(JID, To, Packet, Direction)->
case is_chat_message(Packet) andalso
- not is_muc_pm(To, Packet) andalso
- fxml:get_subtag(Packet, <<"private">>) == false andalso
- fxml:get_subtag(Packet, <<"no-copy">>) == false of
+ not is_muc_pm(To, Packet) andalso
+ xmpp:has_subtag(Packet, #carbons_private{}) == false andalso
+ xmpp:has_subtag(Packet, #hint{type = 'no-copy'}) == false of
true ->
case is_carbon_copy(Packet) of
false ->
@@ -148,6 +141,7 @@ check_and_forward(JID, To, Packet, Direction)->
Packet
end.
+-spec remove_connection(binary(), binary(), binary(), binary()) -> ok.
remove_connection(User, Server, Resource, _Status)->
disable(Server, User, Resource),
ok.
@@ -155,11 +149,12 @@ remove_connection(User, Server, Resource, _Status)->
%%% Internal
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
+-spec send_copies(jid(), jid(), message(), direction()) -> ok.
send_copies(JID, To, Packet, Direction)->
{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
+ {MaxPrio, _MaxRes} = case catch lists:max(PrioRes) of
{Prio, Res} -> {Prio, Res};
_ -> {0, undefined}
end,
@@ -172,19 +167,19 @@ send_copies(JID, To, Packet, Direction)->
end,
%% list of JIDs that should receive a carbon copy of this message (excluding the
%% receiver(s) of the original message
- TargetJIDs = case {IsBareTo, R} of
- {true, MaxRes} ->
- OrigTo = fun(Res) -> lists:member({MaxPrio, Res}, PrioRes) end,
- [ {jid:make({U, S, CCRes}), CC_Version}
- || {CCRes, CC_Version} <- list(U, S),
- lists:member(CCRes, AvailRs), not OrigTo(CCRes) ];
- {true, _} ->
+ TargetJIDs = case {IsBareTo, Packet} of
+ {true, #message{meta = #{sm_copy := true}}} ->
%% 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 create carbon
- %% copies only from one of those resources (the one where R equals
- %% MaxRes) in order to avoid duplicates.
+ %% copies only from one of those resources in order to avoid
+ %% duplicates.
[];
+ {true, _} ->
+ OrigTo = fun(Res) -> lists:member({MaxPrio, Res}, PrioRes) end,
+ [ {jid:make({U, S, CCRes}), CC_Version}
+ || {CCRes, CC_Version} <- list(U, S),
+ lists:member(CCRes, AvailRs), not OrigTo(CCRes) ];
{false, _} ->
[ {jid:make({U, S, CCRes}), CC_Version}
|| {CCRes, CC_Version} <- list(U, S),
@@ -192,93 +187,60 @@ send_copies(JID, To, Packet, Direction)->
%TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]),
end,
- lists:map(fun({Dest,Version}) ->
+ lists:map(fun({Dest, _Version}) ->
{_, _, Resource} = jid:tolower(Dest),
?DEBUG("Sending: ~p =/= ~p", [R, Resource]),
Sender = jid:make({U, S, <<>>}),
%{xmlelement, N, A, C} = Packet,
- New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version),
+ New = build_forward_packet(JID, Packet, Sender, Dest, Direction),
ejabberd_router:route(Sender, Dest, New)
end, TargetJIDs),
ok.
-build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
- #xmlel{name = <<"message">>,
- attrs = [{<<"xmlns">>, <<"jabber:client">>},
- {<<"type">>, message_type(Packet)},
- {<<"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">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}],
- children = [
- complete_packet(JID, Packet, Direction)]}
- ]}
- ]};
-build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
- #xmlel{name = <<"message">>,
- attrs = [{<<"xmlns">>, <<"jabber:client">>},
- {<<"type">>, message_type(Packet)},
- {<<"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">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}],
- children = [complete_packet(JID, Packet, Direction)]}
- ]}.
-
-
+-spec build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message().
+build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) ->
+ Forwarded = #forwarded{xml_els = [xmpp:encode(complete_packet(JID, Msg, Direction))]},
+ Carbon = case Direction of
+ sent -> #carbons_sent{forwarded = Forwarded};
+ received -> #carbons_received{forwarded = Forwarded}
+ end,
+ #message{from = Sender, to = Dest, type = T, sub_els = [Carbon],
+ meta = #{carbon_copy => true}}.
+
+-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:enable(U, Host, R, CC).
+-spec disable(binary(), binary(), binary()) -> ok | {error, any()}.
disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:disable(U, Host, R).
-complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
+-spec complete_packet(jid(), message(), direction()) -> message().
+complete_packet(From, #message{from = undefined} = Msg, sent) ->
%% if this is a packet sent by user on this host, then Packet doesn't
%% include the 'from' attribute. We must add it.
- Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
- case proplists:get_value(<<"from">>, Attrs) of
- undefined ->
- Packet#xmlel{attrs = [{<<"from">>, jid:to_string(From)}|Attrs]};
- _ ->
- Packet#xmlel{attrs = Attrs}
- end;
-complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, received) ->
- Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
- Packet#xmlel{attrs = Attrs}.
-
-message_type(#xmlel{attrs = Attrs}) ->
- case fxml:get_attr(<<"type">>, Attrs) of
- {value, Type} -> Type;
- false -> <<"normal">>
- end.
-
-is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
- case message_type(Packet) of
- <<"chat">> -> true;
- <<"normal">> -> has_non_empty_body(Packet);
- _ -> false
- end;
-is_chat_message(_Packet) -> false.
+ Msg#message{from = From};
+complete_packet(_From, Msg, _Direction) ->
+ Msg.
+
+-spec is_chat_message(stanza()) -> boolean().
+is_chat_message(#message{type = chat}) ->
+ true;
+is_chat_message(#message{type = normal, body = [_|_]}) ->
+ true;
+is_chat_message(_) ->
+ false.
is_muc_pm(#jid{lresource = <<>>}, _Packet) ->
false;
is_muc_pm(_To, Packet) ->
- fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) =/= false.
-
-has_non_empty_body(Packet) ->
- fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
+ xmpp:has_subtag(Packet, #muc_user{}).
+-spec list(binary(), binary()) -> [{binary(), binary()}].
%% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
diff --git a/src/mod_carboncopy_mnesia.erl b/src/mod_carboncopy_mnesia.erl
index bf69bd21c..4cc7e6049 100644
--- a/src/mod_carboncopy_mnesia.erl
+++ b/src/mod_carboncopy_mnesia.erl
@@ -30,7 +30,7 @@ init(_Host, _Opts) ->
%% probably table don't exist
ok
end,
- mnesia:create_table(carboncopy,
+ ejabberd_mnesia:create(?MODULE, carboncopy,
[{ram_copies, [node()]},
{attributes, record_info(fields, carboncopy)},
{type, bag}]),
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index 9d37d4f5b..2bae7a4f8 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -39,7 +39,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(CSI_QUEUE_MAX, 100).
@@ -151,67 +151,68 @@ depends(_Host, _Opts) ->
%% ejabberd_hooks callbacks.
%%--------------------------------------------------------------------
--spec filter_presence({term(), [xmlel()]}, binary(), jid(), xmlel())
- -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
+-spec filter_presence({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza())
+ -> {ejabberd_c2s:state(), [stanza()]} |
+ {stop, {ejabberd_c2s:state(), [stanza()]}}.
filter_presence({C2SState, _OutStanzas} = Acc, Host, To,
- #xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
- case fxml:get_attr(<<"type">>, Attrs) of
- {value, Type} when Type /= <<"unavailable">> ->
- Acc;
- _ ->
- ?DEBUG("Got availability presence stanza for ~s",
- [jid:to_string(To)]),
- queue_add(presence, Stanza, Host, C2SState)
+ #presence{type = Type} = Stanza) ->
+ if Type == available; Type == unavailable ->
+ ?DEBUG("Got availability presence stanza for ~s",
+ [jid:to_string(To)]),
+ queue_add(presence, Stanza, Host, C2SState);
+ true ->
+ Acc
end;
filter_presence(Acc, _Host, _To, _Stanza) -> Acc.
--spec filter_chat_states({term(), [xmlel()]}, binary(), jid(), xmlel())
- -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
+-spec filter_chat_states({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza())
+ -> {ejabberd_c2s:state(), [stanza()]} |
+ {stop, {ejabberd_c2s:state(), [stanza()]}}.
filter_chat_states({C2SState, _OutStanzas} = Acc, Host, To,
- #xmlel{name = <<"message">>} = Stanza) ->
- case jlib:is_standalone_chat_state(Stanza) of
- true ->
- From = fxml:get_tag_attr_s(<<"from">>, Stanza),
- case {jid:from_string(From), To} of
- {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
- %% Don't queue (carbon copies of) chat states from other
- %% resources, as they might be used to sync the state of
- %% conversations across clients.
- Acc;
- _ ->
+ #message{from = From} = Stanza) ->
+ case xmpp_util:is_standalone_chat_state(Stanza) of
+ true ->
+ case {From, To} of
+ {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
+ %% Don't queue (carbon copies of) chat states from other
+ %% resources, as they might be used to sync the state of
+ %% conversations across clients.
+ Acc;
+ _ ->
?DEBUG("Got standalone chat state notification for ~s",
[jid:to_string(To)]),
- queue_add(chatstate, Stanza, Host, C2SState)
- end;
- false ->
- Acc
+ queue_add(chatstate, Stanza, Host, C2SState)
+ end;
+ false ->
+ Acc
end;
filter_chat_states(Acc, _Host, _To, _Stanza) -> Acc.
--spec filter_pep({term(), [xmlel()]}, binary(), jid(), xmlel())
- -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
+-spec filter_pep({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza())
+ -> {ejabberd_c2s:state(), [stanza()]} |
+ {stop, {ejabberd_c2s:state(), [stanza()]}}.
-filter_pep({C2SState, _OutStanzas} = Acc, Host, To,
- #xmlel{name = <<"message">>} = Stanza) ->
+filter_pep({C2SState, _OutStanzas} = Acc, Host, To, #message{} = Stanza) ->
case get_pep_node(Stanza) of
- {value, Node} ->
- ?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]),
- queue_add({pep, Node}, Stanza, Host, C2SState);
- false ->
- Acc
+ undefined ->
+ Acc;
+ Node ->
+ ?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]),
+ queue_add({pep, Node}, Stanza, Host, C2SState)
end;
filter_pep(Acc, _Host, _To, _Stanza) -> Acc.
--spec filter_other({term(), [xmlel()]}, binary(), jid(), xmlel())
- -> {term(), [xmlel()]}.
+-spec filter_other({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza())
+ -> {ejabberd_c2s:state(), [stanza()]}.
filter_other({C2SState, _OutStanzas}, Host, To, Stanza) ->
?DEBUG("Won't add stanza for ~s to CSI queue", [jid:to_string(To)]),
queue_take(Stanza, Host, C2SState).
--spec flush_queue({term(), [xmlel()]}, binary(), jid()) -> {term(), [xmlel()]}.
+-spec flush_queue({ejabberd_c2s:state(), [stanza()]}, binary(), jid())
+ -> {ejabberd_c2s:state(), [stanza()]}.
flush_queue({C2SState, _OutStanzas}, Host, JID) ->
?DEBUG("Going to flush CSI queue of ~s", [jid:to_string(JID)]),
@@ -219,20 +220,17 @@ flush_queue({C2SState, _OutStanzas}, Host, JID) ->
NewState = set_queue([], C2SState),
{NewState, get_stanzas(Queue, Host)}.
--spec add_stream_feature([xmlel()], binary) -> [xmlel()].
+-spec add_stream_feature([stanza()], binary) -> [stanza()].
add_stream_feature(Features, _Host) ->
- Feature = #xmlel{name = <<"csi">>,
- attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
- children = []},
- [Feature | Features].
+ [#feature_csi{xmlns = <<"urn:xmpp:csi:0">>} | Features].
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
--spec queue_add(csi_type(), xmlel(), binary(), term())
- -> {stop, {term(), [xmlel()]}}.
+-spec queue_add(csi_type(), stanza(), binary(), term())
+ -> {stop, {term(), [stanza()]}}.
queue_add(Type, Stanza, Host, C2SState) ->
case get_queue(C2SState) of
@@ -242,19 +240,19 @@ queue_add(Type, Stanza, Host, C2SState) ->
{stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
Queue ->
?DEBUG("Adding stanza to CSI queue", []),
- From = fxml:get_tag_attr_s(<<"from">>, Stanza),
- Key = {jid:tolower(jid:from_string(From)), Type},
+ From = xmpp:get_from(Stanza),
+ Key = {jid:tolower(From), Type},
Entry = {Key, p1_time_compat:timestamp(), Stanza},
NewQueue = lists:keystore(Key, 1, Queue, Entry),
NewState = set_queue(NewQueue, C2SState),
{stop, {NewState, []}}
end.
--spec queue_take(xmlel(), binary(), term()) -> {term(), [xmlel()]}.
+-spec queue_take(stanza(), binary(), term()) -> {term(), [stanza()]}.
queue_take(Stanza, Host, C2SState) ->
- From = fxml:get_tag_attr_s(<<"from">>, Stanza),
- {LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)),
+ From = xmpp:get_from(Stanza),
+ {LUser, LServer, _LResource} = jid:tolower(From),
{Selected, Rest} = lists:partition(
fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
U == LUser andalso S == LServer
@@ -277,32 +275,23 @@ get_queue(C2SState) ->
[]
end.
--spec get_stanzas(csi_queue(), binary()) -> [xmlel()].
+-spec get_stanzas(csi_queue(), binary()) -> [stanza()].
get_stanzas(Queue, Host) ->
lists:map(fun({_Key, Time, Stanza}) ->
- jlib:add_delay_info(Stanza, Host, Time,
- <<"Client Inactive">>)
+ xmpp_util:add_delay_info(Stanza, jid:make(Host), Time,
+ <<"Client Inactive">>)
end, Queue).
--spec get_pep_node(xmlel()) -> {value, binary()} | false.
-
-get_pep_node(#xmlel{name = <<"message">>} = Stanza) ->
- From = fxml:get_tag_attr_s(<<"from">>, Stanza),
- case jid:from_string(From) of
- #jid{luser = <<>>} -> % It's not PEP.
- false;
- _ ->
- case fxml:get_subtag_with_xmlns(Stanza, <<"event">>,
- ?NS_PUBSUB_EVENT) of
- #xmlel{children = Els} ->
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"items">>, attrs = ItemsAttrs}] ->
- fxml:get_attr(<<"node">>, ItemsAttrs);
- _ ->
- false
- end;
- false ->
- false
- end
+-spec get_pep_node(message()) -> binary() | undefined.
+
+get_pep_node(#message{from = #jid{luser = <<>>}}) ->
+ %% It's not PEP.
+ undefined;
+get_pep_node(#message{} = Msg) ->
+ case xmpp:get_subtag(Msg, #ps_event{}) of
+ #ps_event{items = #ps_items{node = Node}} ->
+ Node;
+ _ ->
+ undefined
end.
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index 03779d027..436d736e6 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -40,10 +40,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_sm.hrl").
--include("adhoc.hrl").
-define(T(Lang, Text), translate:translate(Lang, Text)).
@@ -102,29 +100,19 @@ depends(_Host, _Opts) ->
%%%-----------------------------------------------------------------------
-define(INFO_IDENTITY(Category, Type, Name, Lang),
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, Category}, {<<"type">>, Type},
- {<<"name">>, ?T(Lang, Name)}],
- children = []}]).
+ [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]).
-define(INFO_COMMAND(Name, Lang),
?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
Name, Lang)).
-define(NODEJID(To, Name, Node),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, jid:to_string(To)},
- {<<"name">>, ?T(Lang, Name)}, {<<"node">>, Node}],
- children = []}).
+ #disco_item{jid = To, name = ?T(Lang, Name), node = Node}).
-define(NODE(Name, Node),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)},
- {<<"node">>, Node}],
- children = []}).
+ #disco_item{jid = jid:make(Server),
+ node = Node,
+ name = ?T(Lang, Name)}).
-define(NS_ADMINX(Sub),
<<(?NS_ADMIN)/binary, "#", Sub/binary>>).
@@ -204,7 +192,7 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
- deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
allow -> {result, Feats}
end).
@@ -282,7 +270,10 @@ get_local_features(Acc, From,
end.
%%%-----------------------------------------------------------------------
-
+-spec adhoc_sm_items(empty | {error, stanza_error()} | {result, [disco_item()]},
+ jid(), jid(), binary()) -> {error, stanza_error()} |
+ {result, [disco_item()]} |
+ empty.
adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
Lang) ->
case acl:match_rule(LServer, configure, From) of
@@ -291,12 +282,8 @@ adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
{result, Its} -> Its;
empty -> []
end,
- Nodes = [#xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, jid:to_string(To)},
- {<<"name">>, ?T(Lang, <<"Configuration">>)},
- {<<"node">>, <<"config">>}],
- children = []}],
+ Nodes = [#disco_item{jid = To, node = <<"config">>,
+ name = ?T(Lang, <<"Configuration">>)}],
{result, Items ++ Nodes};
_ -> Acc
end.
@@ -323,7 +310,7 @@ get_sm_items(Acc, From,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
{_, <<"config">>} ->
- {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
_ -> Acc
end
end.
@@ -331,18 +318,17 @@ get_sm_items(Acc, From,
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
lists:map(fun (R) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<User/binary, "@", Server/binary, "/",
- R/binary>>},
- {<<"name">>, User}],
- children = []}
+ #disco_item{jid = jid:make(User, Server, R),
+ name = User}
end,
lists:sort(Rs)).
%%%-----------------------------------------------------------------------
+-spec adhoc_local_items(empty | {error, stanza_error()} | {result, [disco_item()]},
+ jid(), jid(), binary()) -> {error, stanza_error()} |
+ {result, [disco_item()]} |
+ empty.
adhoc_local_items(Acc, From,
#jid{lserver = LServer, server = Server} = To, Lang) ->
case acl:match_rule(LServer, configure, From) of
@@ -383,25 +369,19 @@ recursively_get_local_items(PermLev, LServer, Node,
{result, Res} -> Res;
{error, _Error} -> []
end,
- Nodes = lists:flatten(lists:map(fun (N) ->
- S = fxml:get_tag_attr_s(<<"jid">>,
- N),
- Nd = fxml:get_tag_attr_s(<<"node">>,
- N),
- if (S /= Server) or
- (Nd == <<"">>) ->
- [];
- true ->
- [N,
- recursively_get_local_items(PermLev,
- LServer,
- Nd,
- Server,
- Lang)]
- end
- end,
- Items)),
- Nodes.
+ lists:flatten(
+ lists:map(
+ fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) ->
+ if (S /= Server) or
+ (Nd == <<"">>) ->
+ [];
+ true ->
+ [Item,
+ recursively_get_local_items(
+ PermLev, LServer, Nd, Server, Lang)]
+ end
+ end,
+ Items)).
get_permission_level(JID) ->
case acl:match_rule(global, configure, JID) of
@@ -453,7 +433,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
- Err = ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>),
+ Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
case LNode of
[<<"config">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
@@ -570,27 +550,18 @@ get_local_items({_, Host},
_Lang) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- case catch begin
- [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
- N1 = jlib:binary_to_integer(S1),
- N2 = jlib:binary_to_integer(S2),
- Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
- lists:map(fun ({S, U}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<U/binary, "@",
- S/binary>>},
- {<<"name">>,
- <<U/binary, "@",
- S/binary>>}],
- children = []}
- end,
- Sub)
- end
- of
- {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
- Res -> {result, Res}
+ try
+ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
+ N1 = binary_to_integer(S1),
+ N2 = binary_to_integer(S2),
+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
+ {result, lists:map(
+ fun({S, U}) ->
+ #disco_item{jid = jid:make(U, S),
+ name = <<U/binary, $@, S/binary>>}
+ end, Sub)}
+ catch _:_ ->
+ xmpp:err_not_acceptable()
end;
get_local_items({_, Host}, [<<"outgoing s2s">>],
_Server, Lang) ->
@@ -676,24 +647,18 @@ get_local_items(_Host,
_Lang) ->
{result, []};
get_local_items(_Host, _, _Server, _Lang) ->
- {error, ?ERR_ITEM_NOT_FOUND}.
+ {error, xmpp:err_item_not_found()}.
get_online_vh_users(Host) ->
case catch ejabberd_sm:get_vh_session_list(Host) of
{'EXIT', _Reason} -> [];
USRs ->
SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
- lists:map(fun ({S, U, R}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<U/binary, "@", S/binary, "/",
- R/binary>>},
- {<<"name">>,
- <<U/binary, "@", S/binary>>}],
- children = []}
- end,
- SURs)
+ lists:map(
+ fun({S, U, R}) ->
+ #disco_item{jid = jid:make(U, S, R),
+ name = <<U/binary, "@", S/binary>>}
+ end, SURs)
end.
get_all_vh_users(Host) ->
@@ -704,16 +669,10 @@ get_all_vh_users(Host) ->
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case length(SUsers) of
N when N =< 100 ->
- lists:map(fun ({S, U}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<U/binary, "@", S/binary>>},
- {<<"name">>,
- <<U/binary, "@", S/binary>>}],
- children = []}
- end,
- SUsers);
+ lists:map(fun({S, U}) ->
+ #disco_item{jid = jid:make(U, S),
+ name = <<U/binary, $@, S/binary>>}
+ end, SUsers);
N ->
NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1))
+ 1,
@@ -721,22 +680,18 @@ get_all_vh_users(Host) ->
lists:map(fun (K) ->
L = K + M - 1,
Node = <<"@",
- (iolist_to_binary(integer_to_list(K)))/binary,
+ (integer_to_binary(K))/binary,
"-",
- (iolist_to_binary(integer_to_list(L)))/binary>>,
+ (integer_to_binary(L))/binary>>,
{FS, FU} = lists:nth(K, SUsers),
{LS, LU} = if L < N -> lists:nth(L, SUsers);
true -> lists:last(SUsers)
end,
Name = <<FU/binary, "@", FS/binary, " -- ",
LU/binary, "@", LS/binary>>,
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Host},
- {<<"node">>,
- <<"all users/", Node/binary>>},
- {<<"name">>, Name}],
- children = []}
+ #disco_item{jid = jid:make(Host),
+ node = <<"all users/", Node/binary>>,
+ name = Name}
end,
lists:seq(1, N, M))
end
@@ -750,59 +705,43 @@ get_outgoing_s2s(Host, Lang) ->
TConns = [TH
|| {FH, TH} <- Connections,
Host == FH orelse str:suffix(DotHost, FH)],
- lists:map(fun (T) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Host},
- {<<"node">>,
- <<"outgoing s2s/", T/binary>>},
- {<<"name">>,
- iolist_to_binary(io_lib:format(?T(Lang,
- <<"To ~s">>),
- [T]))}],
- children = []}
- end,
- lists:usort(TConns))
+ lists:map(
+ fun (T) ->
+ Name = str:format(?T(Lang, <<"To ~s">>),[T]),
+ #disco_item{jid = jid:make(Host),
+ node = <<"outgoing s2s/", T/binary>>,
+ name = Name}
+ end, lists:usort(TConns))
end.
get_outgoing_s2s(Host, Lang, To) ->
case catch ejabberd_s2s:dirty_get_connections() of
{'EXIT', _Reason} -> [];
Connections ->
- lists:map(fun ({F, _T}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Host},
- {<<"node">>,
- <<"outgoing s2s/", To/binary, "/",
- F/binary>>},
- {<<"name">>,
- iolist_to_binary(io_lib:format(?T(Lang,
- <<"From ~s">>),
- [F]))}],
- children = []}
- end,
- lists:keysort(1,
- lists:filter(fun (E) -> element(2, E) == To
- end,
- Connections)))
+ lists:map(
+ fun ({F, _T}) ->
+ Node = <<"outgoing s2s/", To/binary, "/", F/binary>>,
+ Name = str:format(?T(Lang, <<"From ~s">>), [F]),
+ #disco_item{jid = jid:make(Host), node = Node, name = Name}
+ end,
+ lists:keysort(1,
+ lists:filter(fun (E) -> element(2, E) == To
+ end,
+ Connections)))
end.
get_running_nodes(Server, _Lang) ->
case catch mnesia:system_info(running_db_nodes) of
{'EXIT', _Reason} -> [];
DBNodes ->
- lists:map(fun (N) ->
- S = iolist_to_binary(atom_to_list(N)),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Server},
- {<<"node">>,
- <<"running nodes/", S/binary>>},
- {<<"name">>, S}],
- children = []}
- end,
- lists:sort(DBNodes))
+ lists:map(
+ fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ #disco_item{jid = jid:make(Server),
+ node = <<"running nodes/", S/binary>>,
+ name = S}
+ end,
+ lists:sort(DBNodes))
end.
get_stopped_nodes(_Lang) ->
@@ -812,17 +751,14 @@ get_stopped_nodes(_Lang) ->
of
{'EXIT', _Reason} -> [];
DBNodes ->
- lists:map(fun (N) ->
- S = iolist_to_binary(atom_to_list(N)),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, ?MYNAME},
- {<<"node">>,
- <<"stopped nodes/", S/binary>>},
- {<<"name">>, S}],
- children = []}
- end,
- lists:sort(DBNodes))
+ lists:map(
+ fun (N) ->
+ S = iolist_to_binary(atom_to_list(N)),
+ #disco_item{jid = jid:make(?MYNAME),
+ node = <<"stopped nodes/", S/binary>>,
+ name = S}
+ end,
+ lists:sort(DBNodes))
end.
%%-------------------------------------------------------------------------
@@ -830,13 +766,15 @@ get_stopped_nodes(_Lang) ->
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
- deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
allow -> adhoc_local_commands(From, To, Request)
end).
+-spec adhoc_local_commands(adhoc_command(), jid(), jid(), adhoc_command()) ->
+ adhoc_command() | {error, stanza_error()}.
adhoc_local_commands(Acc, From,
#jid{lserver = LServer} = To,
- #adhoc_request{node = Node, lang = Lang} = Request) ->
+ #adhoc_command{node = Node, lang = Lang} = Request) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, _ENode, <<"DB">>] ->
@@ -860,171 +798,107 @@ adhoc_local_commands(Acc, From,
adhoc_local_commands(From,
#jid{lserver = LServer} = _To,
- #adhoc_request{lang = Lang, node = Node,
- sessionid = SessionID, action = Action,
- xdata = XData} =
- Request) ->
+ #adhoc_command{lang = Lang, node = Node,
+ sid = SessionID, action = Action,
+ xdata = XData} = Request) ->
LNode = tokenize(Node),
- ActionIsExecute = lists:member(Action,
- [<<"">>, <<"execute">>, <<"complete">>]),
- if Action == <<"cancel">> ->
- adhoc:produce_response(Request,
- #adhoc_response{status = canceled});
- XData == false, ActionIsExecute ->
+ ActionIsExecute = Action == execute orelse Action == complete,
+ if Action == cancel ->
+ #adhoc_command{status = canceled, lang = Lang,
+ node = Node, sid = SessionID};
+ XData == undefined, ActionIsExecute ->
case get_form(LServer, LNode, Lang) of
{result, Form} ->
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,
- elements = Form});
+ xmpp_util:make_adhoc_response(
+ Request,
+ #adhoc_command{status = executing, xdata = Form});
{result, Status, Form} ->
- adhoc:produce_response(Request,
- #adhoc_response{status = Status,
- elements = Form});
+ xmpp_util:make_adhoc_response(
+ Request,
+ #adhoc_command{status = Status, xdata = Form});
{error, Error} -> {error, Error}
end;
- XData /= false, ActionIsExecute ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
- Fields ->
- case catch set_form(From, LServer, LNode, Lang, Fields)
- of
- {result, Res} ->
- adhoc:produce_response(#adhoc_response{lang = Lang,
- node = Node,
- sessionid =
- SessionID,
- elements = Res,
- status =
- completed});
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
- {error, Error} -> {error, Error}
- end
- end;
+ XData /= undefined, ActionIsExecute ->
+ case catch set_form(From, LServer, LNode, Lang, XData) of
+ {result, Res} ->
+ xmpp_util:make_adhoc_response(
+ Request,
+ #adhoc_command{xdata = Res, status = completed});
+ {'EXIT', _} -> {error, xmpp:err_bad_request()};
+ {error, Error} -> {error, Error}
+ end;
true ->
- {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)}
+ {error, xmpp:err_bad_request(<<"Unexpected action">>, Lang)}
end.
-define(TVFIELD(Type, Var, Val),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+ #xdata_field{type = Type, var = Var, values = [Val]}).
-define(HFIELD(),
- ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADMIN))).
+ ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))).
-define(TLFIELD(Type, Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
+ #xdata_field{type = Type, label = ?T(Lang, Label), var = Var}).
-define(XFIELD(Type, Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+ #xdata_field{type = Type, label = ?T(Lang, Label),
+ var = Var, values = [Val]}).
-define(XMFIELD(Type, Label, Var, Vals),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}
- || Val <- Vals]}).
+ #xdata_field{type = Type, label = ?T(Lang, Label),
+ var = Var, values = Vals}).
-define(TABLEFIELD(Table, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>, iolist_to_binary(atom_to_list(Table))},
- {<<"var">>, iolist_to_binary(atom_to_list(Table))}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(atom_to_list(Val))}]},
- #xmlel{name = <<"option">>,
- attrs = [{<<"label">>, ?T(Lang, <<"RAM copy">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, <<"ram_copies">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- ?T(Lang, <<"RAM and disc copy">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, <<"disc_copies">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>, ?T(Lang, <<"Disc only copy">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"disc_only_copies">>}]}]},
- #xmlel{name = <<"option">>,
- attrs = [{<<"label">>, ?T(Lang, <<"Remote copy">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, <<"unknown">>}]}]}]}).
+ #xdata_field{
+ type = 'list-single',
+ label = iolist_to_binary(atom_to_list(Table)),
+ var = iolist_to_binary(atom_to_list(Table)),
+ values = [iolist_to_binary(atom_to_list(Val))],
+ options = [#xdata_option{label = ?T(Lang, <<"RAM copy">>),
+ value = <<"ram_copies">>},
+ #xdata_option{label = ?T(Lang, <<"RAM and disc copy">>),
+ value = <<"disc_copies">>},
+ #xdata_option{label = ?T(Lang, <<"Disc only copy">>),
+ value = <<"disc_only_copies">>},
+ #xdata_option{label = ?T(Lang, <<"Remote copy">>),
+ value = <<"unknown">>}]}).
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
Lang) ->
case search_running_node(ENode) of
false ->
Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
+ {error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
"~s failed: ~p", [Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, xmpp:err_internal_server_error()};
Tables ->
STables = lists:sort(Tables),
- {result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Database Tables Configuration at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Choose storage type of tables">>)}]}
- | lists:map(fun (Table) ->
- case ejabberd_cluster:call(Node, mnesia,
- table_info,
- [Table,
- storage_type])
- of
- {badrpc, _} ->
- ?TABLEFIELD(Table,
- unknown);
- Type ->
- ?TABLEFIELD(Table, Type)
- end
- end,
- STables)]}]}
+ Title = <<(?T(Lang, <<"Database Tables Configuration at ">>))/binary,
+ ENode/binary>>,
+ Instr = ?T(Lang, <<"Choose storage type of tables">>),
+ try
+ Fs = lists:map(
+ fun(Table) ->
+ case ejabberd_cluster:call(
+ Node, mnesia, table_info,
+ [Table, storage_type]) of
+ Type when is_atom(Type) ->
+ ?TABLEFIELD(Table, Type)
+ end
+ end, STables),
+ {result, #xdata{title = Title,
+ type = form,
+ instructions = [Instr],
+ fields = [?HFIELD()|Fs]}}
+ catch _:{case_clause, {badrpc, Reason}} ->
+ ?ERROR_MSG("RPC call mnesia:table_info/2 "
+ "on node ~s failed: ~p", [Node, Reason]),
+ {error, xmpp:err_internal_server_error()}
+ end
end
end;
get_form(Host,
@@ -1033,37 +907,26 @@ get_form(Host,
case search_running_node(ENode) of
false ->
Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
+ {error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node "
"~s failed: ~p", [Host, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, xmpp:err_internal_server_error()};
Modules ->
SModules = lists:sort(Modules),
- {result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Stop Modules at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Choose modules to stop">>)}]}
- | lists:map(fun (M) ->
- S = jlib:atom_to_binary(M),
- ?XFIELD(<<"boolean">>, S, S,
- <<"0">>)
- end,
- SModules)]}]}
+ Title = <<(?T(Lang, <<"Stop Modules at ">>))/binary,
+ ENode/binary>>,
+ Instr = ?T(Lang, <<"Choose modules to stop">>),
+ Fs = lists:map(fun(M) ->
+ S = jlib:atom_to_binary(M),
+ ?XFIELD(boolean, S, S, <<"0">>)
+ end, SModules),
+ {result, #xdata{title = Title,
+ type = form,
+ instructions = [Instr],
+ fields = [?HFIELD()|Fs]}}
end
end;
get_form(_Host,
@@ -1071,153 +934,88 @@ get_form(_Host,
<<"start">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang, <<"Start Modules at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Enter list of {Module, [Options]}">>)}]},
- ?XFIELD(<<"text-multi">>,
- <<"List of modules to start">>, <<"modules">>,
- <<"[].">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Start Modules at ">>))/binary, ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter list of {Module, [Options]}">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-multi',
+ <<"List of modules to start">>, <<"modules">>,
+ <<"[].">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang, <<"Backup to File at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang, <<"Enter path to backup file">>)}]},
- ?XFIELD(<<"text-single">>, <<"Path to File">>,
- <<"path">>, <<"">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Backup to File at ">>))/binary, ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter path to backup file">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-single', <<"Path to File">>,
+ <<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Restore Backup from File at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang, <<"Enter path to backup file">>)}]},
- ?XFIELD(<<"text-single">>, <<"Path to File">>,
- <<"path">>, <<"">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Restore Backup from File at ">>))/binary,
+ ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter path to backup file">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-single', <<"Path to File">>,
+ <<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Dump Backup to Text File at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang, <<"Enter path to text file">>)}]},
- ?XFIELD(<<"text-single">>, <<"Path to File">>,
- <<"path">>, <<"">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Dump Backup to Text File at ">>))/binary,
+ ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter path to text file">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-single', <<"Path to File">>,
+ <<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Import User from File at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Enter path to jabberd14 spool file">>)}]},
- ?XFIELD(<<"text-single">>, <<"Path to File">>,
- <<"path">>, <<"">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Import User from File at ">>))/binary,
+ ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter path to jabberd14 spool file">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-single', <<"Path to File">>,
+ <<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang,
- <<"Import Users from Dir at ">>))/binary,
- ENode/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Enter path to jabberd14 spool dir">>)}]},
- ?XFIELD(<<"text-single">>, <<"Path to Dir">>,
- <<"path">>, <<"">>)]}]};
+ #xdata{title = <<(?T(Lang, <<"Import Users from Dir at ">>))/binary,
+ ENode/binary>>,
+ type = form,
+ instructions = [?T(Lang, <<"Enter path to jabberd14 spool dir">>)],
+ fields = [?HFIELD(),
+ ?XFIELD('text-single', <<"Path to Dir">>,
+ <<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, _ENode, <<"restart">>], Lang) ->
- Make_option = fun (LabelNum, LabelUnit, Value) ->
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- <<LabelNum/binary,
- (?T(Lang, LabelUnit))/binary>>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}]}
- end,
+ Make_option =
+ fun (LabelNum, LabelUnit, Value) ->
+ #xdata_option{
+ label = <<LabelNum/binary, (?T(Lang, LabelUnit))/binary>>,
+ value = Value}
+ end,
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"Restart Service">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>, ?T(Lang, <<"Time delay">>)},
- {<<"var">>, <<"delay">>}],
- children =
+ #xdata{title = ?T(Lang, <<"Restart Service">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{
+ type = 'list-single',
+ label = ?T(Lang, <<"Time delay">>),
+ var = <<"delay">>,
+ required = true,
+ options =
[Make_option(<<"">>, <<"immediately">>, <<"1">>),
Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
@@ -1229,55 +1027,35 @@ get_form(_Host,
Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
- Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>),
- #xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"fixed">>},
- {<<"label">>,
- ?T(Lang,
- <<"Send announcement to all online users "
- "on all hosts">>)}],
- children = []},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"subject">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>, ?T(Lang, <<"Subject">>)}],
- children = []},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"announcement">>},
- {<<"type">>, <<"text-multi">>},
- {<<"label">>, ?T(Lang, <<"Message body">>)}],
- children = []}]}]};
+ Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
+ #xdata_field{type = fixed,
+ label = ?T(Lang,
+ <<"Send announcement to all online users "
+ "on all hosts">>)},
+ #xdata_field{var = <<"subject">>,
+ type = 'text-single',
+ label = ?T(Lang, <<"Subject">>)},
+ #xdata_field{var = <<"announcement">>,
+ type = 'text-multi',
+ label = ?T(Lang, <<"Message body">>)}]}};
get_form(_Host,
[<<"running nodes">>, _ENode, <<"shutdown">>], Lang) ->
- Make_option = fun (LabelNum, LabelUnit, Value) ->
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- <<LabelNum/binary,
- (?T(Lang, LabelUnit))/binary>>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}]}
- end,
+ Make_option =
+ fun (LabelNum, LabelUnit, Value) ->
+ #xdata_option{
+ label = <<LabelNum/binary, (?T(Lang, LabelUnit))/binary>>,
+ value = Value}
+ end,
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"Shut Down Service">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>, ?T(Lang, <<"Time delay">>)},
- {<<"var">>, <<"delay">>}],
- children =
+ #xdata{title = ?T(Lang, <<"Shut Down Service">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{
+ type = 'list-single',
+ label = ?T(Lang, <<"Time delay">>),
+ var = <<"delay">>,
+ required = true,
+ options =
[Make_option(<<"">>, <<"immediately">>, <<"1">>),
Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
@@ -1289,323 +1067,185 @@ get_form(_Host,
Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
- Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>),
- #xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"fixed">>},
- {<<"label">>,
- ?T(Lang,
- <<"Send announcement to all online users "
- "on all hosts">>)}],
- children = []},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"subject">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>, ?T(Lang, <<"Subject">>)}],
- children = []},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"announcement">>},
- {<<"type">>, <<"text-multi">>},
- {<<"label">>, ?T(Lang, <<"Message body">>)}],
- children = []}]}]};
+ Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
+ #xdata_field{type = fixed,
+ label = ?T(Lang,
+ <<"Send announcement to all online users "
+ "on all hosts">>)},
+ #xdata_field{var = <<"subject">>,
+ type = 'text-single',
+ label = ?T(Lang, <<"Subject">>)},
+ #xdata_field{var = <<"announcement">>,
+ type = 'text-multi',
+ label = ?T(Lang, <<"Message body">>)}]}};
get_form(Host, [<<"config">>, <<"acls">>], Lang) ->
+ ACLs = str:tokens(
+ str:format("~p.",
+ [mnesia:dirty_select(
+ acl,
+ ets:fun2ms(
+ fun({acl, {Name, H}, Spec}) when H == Host ->
+ {acl, Name, Spec}
+ end))]),
+ <<"\n">>),
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang,
- <<"Access Control List Configuration">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-multi">>},
- {<<"label">>,
- ?T(Lang, <<"Access control lists">>)},
- {<<"var">>, <<"acls">>}],
- children =
- lists:map(fun (S) ->
- #xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, S}]}
- end,
- str:tokens(iolist_to_binary(io_lib:format("~p.",
- [mnesia:dirty_select(acl,
- [{{acl,
- {'$1',
- '$2'},
- '$3'},
- [{'==',
- '$2',
- Host}],
- [{{acl,
- '$1',
- '$3'}}]}])])),
- <<"\n">>))}]}]};
+ #xdata{title = ?T(Lang, <<"Access Control List Configuration">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'text-multi',
+ label = ?T(Lang, <<"Access control lists">>),
+ var = <<"acls">>,
+ values = ACLs}]}};
get_form(Host, [<<"config">>, <<"access">>], Lang) ->
+ Accs = str:tokens(
+ str:format("~p.",
+ [mnesia:dirty_select(
+ access,
+ ets:fun2ms(
+ fun({access, {Name, H}, Acc}) when H == Host ->
+ {access, Name, Acc}
+ end))]),
+ <<"\n">>),
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang, <<"Access Configuration">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-multi">>},
- {<<"label">>, ?T(Lang, <<"Access rules">>)},
- {<<"var">>, <<"access">>}],
- children =
- lists:map(fun (S) ->
- #xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, S}]}
- end,
- str:tokens(iolist_to_binary(io_lib:format("~p.",
- [mnesia:dirty_select(access,
- [{{access,
- {'$1',
- '$2'},
- '$3'},
- [{'==',
- '$2',
- Host}],
- [{{access,
- '$1',
- '$3'}}]}])])),
- <<"\n">>))}]}]};
+ #xdata{title = ?T(Lang, <<"Access Configuration">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'text-multi',
+ label = ?T(Lang, <<"Access rules">>),
+ var = <<"access">>,
+ values = Accs}]}};
get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children = [{xmlcdata, ?T(Lang, <<"Add User">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-private">>},
- {<<"label">>, ?T(Lang, <<"Password">>)},
- {<<"var">>, <<"password">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-private">>},
- {<<"label">>,
- ?T(Lang, <<"Password Verification">>)},
- {<<"var">>, <<"password-verify">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Add User">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ required = true,
+ var = <<"accountjid">>},
+ #xdata_field{type = 'text-private',
+ label = ?T(Lang, <<"Password">>),
+ required = true,
+ var = <<"password">>},
+ #xdata_field{type = 'text-private',
+ label = ?T(Lang, <<"Password Verification">>),
+ required = true,
+ var = <<"password-verify">>}]}};
get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children = [{xmlcdata, ?T(Lang, <<"Delete User">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-multi">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjids">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Delete User">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-multi',
+ label = ?T(Lang, <<"Jabber ID">>),
+ required = true,
+ var = <<"accountjids">>}]}};
get_form(_Host, ?NS_ADMINL(<<"end-user-session">>),
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"End User Session">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"End User Session">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ required = true,
+ var = <<"accountjid">>}]}};
get_form(_Host, ?NS_ADMINL(<<"get-user-password">>),
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Get User Password">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ var = <<"accountjid">>,
+ required = true}]}};
get_form(_Host, ?NS_ADMINL(<<"change-user-password">>),
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-private">>},
- {<<"label">>, ?T(Lang, <<"Password">>)},
- {<<"var">>, <<"password">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Get User Password">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ required = true,
+ var = <<"accountjid">>},
+ #xdata_field{type = 'text-private',
+ label = ?T(Lang, <<"Password">>),
+ required = true,
+ var = <<"password">>}]}};
get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>),
Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- ?T(Lang, <<"Get User Last Login Time">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Get User Last Login Time">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ var = <<"accountjid">>,
+ required = true}]}};
get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata, ?T(Lang, <<"Get User Statistics">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-single">>},
- {<<"label">>, ?T(Lang, <<"Jabber ID">>)},
- {<<"var">>, <<"accountjid">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]}]}]};
+ #xdata{title = ?T(Lang, <<"Get User Statistics">>),
+ type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'jid-single',
+ label = ?T(Lang, <<"Jabber ID">>),
+ var = <<"accountjid">>,
+ required = true}]}};
get_form(Host,
?NS_ADMINL(<<"get-registered-users-num">>), Lang) ->
- Num = list_to_binary(
- io_lib:format("~p",
- [ejabberd_auth:get_vh_registered_users_number(Host)])),
+ Num = integer_to_binary(ejabberd_auth:get_vh_registered_users_number(Host)),
{result, completed,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-single">>},
- {<<"label">>,
- ?T(Lang, <<"Number of registered users">>)},
- {<<"var">>, <<"registeredusersnum">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Num}]}]}]}]};
+ #xdata{type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'text-single',
+ label = ?T(Lang, <<"Number of registered users">>),
+ var = <<"registeredusersnum">>,
+ values = [Num]}]}};
get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>),
Lang) ->
- Num = list_to_binary(
- io_lib:format("~p",
- [length(ejabberd_sm:get_vh_session_list(Host))])),
+ Num = integer_to_binary(ejabberd_sm:get_vh_session_number(Host)),
{result, completed,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-single">>},
- {<<"label">>,
- ?T(Lang, <<"Number of online users">>)},
- {<<"var">>, <<"onlineusersnum">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Num}]}]}]}]};
+ #xdata{type = form,
+ fields = [?HFIELD(),
+ #xdata_field{type = 'text-single',
+ label = ?T(Lang, <<"Number of online users">>),
+ var = <<"onlineusersnum">>,
+ values = [Num]}]}};
get_form(_Host, _, _Lang) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+ {error, xmpp:err_service_unavailable()}.
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
+ {error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
- lists:foreach(fun ({SVar, SVals}) ->
- Table = jlib:binary_to_atom(SVar),
- Type = case SVals of
- [<<"unknown">>] -> unknown;
- [<<"ram_copies">>] -> ram_copies;
- [<<"disc_copies">>] -> disc_copies;
- [<<"disc_only_copies">>] ->
- disc_only_copies;
- _ -> false
- end,
- if Type == false -> ok;
- Type == unknown ->
- mnesia:del_table_copy(Table, Node);
- true ->
- case mnesia:add_table_copy(Table, Node,
- Type)
- of
- {aborted, _} ->
- mnesia:change_table_copy_type(Table,
- Node,
- Type);
- _ -> ok
- end
- end
- end,
- XData),
- {result, []}
+ lists:foreach(
+ fun(#xdata_field{var = SVar, values = SVals}) ->
+ Table = jlib:binary_to_atom(SVar),
+ Type = case SVals of
+ [<<"unknown">>] -> unknown;
+ [<<"ram_copies">>] -> ram_copies;
+ [<<"disc_copies">>] -> disc_copies;
+ [<<"disc_only_copies">>] -> disc_only_copies;
+ _ -> false
+ end,
+ if Type == false -> ok;
+ Type == unknown ->
+ mnesia:del_table_copy(Table, Node);
+ true ->
+ case mnesia:add_table_copy(Table, Node, Type) of
+ {aborted, _} ->
+ mnesia:change_table_copy_type(
+ Table, Node, Type);
+ _ -> ok
+ end
+ end
+ end, XData#xdata.fields),
+ {result, undefined}
end;
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
@@ -1613,187 +1253,193 @@ set_form(_From, Host,
case search_running_node(ENode) of
false ->
Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
+ {error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
- lists:foreach(fun ({Var, Vals}) ->
- case Vals of
- [<<"1">>] ->
- Module = jlib:binary_to_atom(Var),
- ejabberd_cluster:call(Node, gen_mod, stop_module,
- [Host, Module]);
- _ -> ok
- end
- end,
- XData),
- {result, []}
+ lists:foreach(
+ fun(#xdata_field{var = Var, values = Vals}) ->
+ case Vals of
+ [<<"1">>] ->
+ Module = jlib:binary_to_atom(Var),
+ ejabberd_cluster:call(Node, gen_mod, stop_module,
+ [Host, Module]);
+ _ -> ok
+ end
+ end, XData#xdata.fields),
+ {result, undefined}
end;
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>,
<<"start">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"modules">>, 1, XData) of
- false ->
- Txt = <<"No 'modules' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, Strings}} ->
- String = lists:foldl(fun (S, Res) ->
- <<Res/binary, S/binary, "\n">>
- end,
- <<"">>, Strings),
- case erl_scan:string(binary_to_list(String)) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Modules} ->
- lists:foreach(fun ({Module, Args}) ->
- ejabberd_cluster:call(Node, gen_mod,
- start_module,
- [Host, Module, Args])
- end,
- Modules),
- {result, []};
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
- end
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"modules">>, XData) of
+ [] ->
+ Txt = <<"No 'modules' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ Strings ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end, <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Modules} ->
+ lists:foreach(
+ fun ({Module, Args}) ->
+ ejabberd_cluster:call(
+ Node, gen_mod, start_module,
+ [Host, Module, Args])
+ end,
+ Modules),
+ {result, undefined};
+ _ ->
+ Txt = <<"Parse failed">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ _ ->
+ Txt = <<"Scan failed">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
+ end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"path">>, 1, XData) of
- false ->
- Txt = <<"No 'path' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, [String]}} ->
- case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
- {badrpc, Reason} ->
- ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
- "failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, Reason} ->
- ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
- "failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ -> {result, []}
- end;
- _ ->
- Txt = <<"Incorrect value of 'path' in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"path">>, XData) of
+ [] ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [String] ->
+ case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
+ "failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
+ "failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ _ ->
+ {result, undefined}
+ end;
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"path">>, 1, XData) of
- false ->
- Txt = <<"No 'path' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, [String]}} ->
- case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String])
- of
- {badrpc, Reason} ->
- ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
- "~s failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, Reason} ->
- ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
- "~s failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ -> {result, []}
- end;
- _ ->
- Txt = <<"Incorrect value of 'path' in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"path">>, XData) of
+ [] ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [String] ->
+ case ejabberd_cluster:call(Node, ejabberd_admin,
+ restore, [String]) of
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
+ "~s failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
+ "~s failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ _ ->
+ {result, undefined}
+ end;
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"path">>, 1, XData) of
- false ->
- Txt = <<"No 'path' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, [String]}} ->
- case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile,
- [String])
- of
- {badrpc, Reason} ->
- ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
- "to node ~s failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, Reason} ->
- ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
- "to node ~s failed: ~p", [String, Node, Reason]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
- _ -> {result, []}
- end;
- _ ->
- Txt = <<"Incorrect value of 'path' in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"path">>, XData) of
+ [] ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [String] ->
+ case ejabberd_cluster:call(Node, ejabberd_admin,
+ dump_to_textfile, [String]) of
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
+ "to node ~s failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
+ "to node ~s failed: ~p", [String, Node, Reason]),
+ {error, xmpp:err_internal_server_error()};
+ _ ->
+ {result, undefined}
+ end;
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"path">>, 1, XData) of
- false ->
- Txt = <<"No 'path' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, [String]}} ->
- ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
- {result, []};
- _ ->
- Txt = <<"Incorrect value of 'path' in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"path">>, XData) of
+ [] ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [String] ->
+ ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
+ {result, undefined};
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
Lang, XData) ->
case search_running_node(ENode) of
- false ->
- Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
- Node ->
- case lists:keysearch(<<"path">>, 1, XData) of
- false ->
- Txt = <<"No 'path' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, {_, [String]}} ->
- ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
- {result, []};
- _ ->
- Txt = <<"Incorrect value of 'path' in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ false ->
+ Txt = <<"No running node found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Node ->
+ case xmpp_util:get_xdata_values(<<"path">>, XData) of
+ [] ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [String] ->
+ ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
+ {result, undefined};
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
set_form(From, Host,
[<<"running nodes">>, ENode, <<"restart">>], _Lang,
@@ -1805,75 +1451,72 @@ set_form(From, Host,
stop_node(From, Host, ENode, stop, XData);
set_form(_From, Host, [<<"config">>, <<"acls">>], Lang,
XData) ->
- case lists:keysearch(<<"acls">>, 1, XData) of
- {value, {_, Strings}} ->
- String = lists:foldl(fun (S, Res) ->
- <<Res/binary, S/binary, "\n">>
- end,
- <<"">>, Strings),
- case erl_scan:string(binary_to_list(String)) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, ACLs} ->
- acl:add_list(Host, ACLs, true),
- {result, []};
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
- end;
- _ ->
- Txt = <<"No 'acls' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ case xmpp_util:get_xdata_values(<<"acls">>, XData) of
+ [] ->
+ Txt = <<"No 'acls' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ Strings ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end, <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, ACLs} ->
+ acl:add_list(Host, ACLs, true),
+ {result, undefined};
+ _ ->
+ Txt = <<"Parse failed">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ _ ->
+ {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)}
+ end
end;
set_form(_From, Host, [<<"config">>, <<"access">>],
Lang, XData) ->
- SetAccess = fun (Rs) ->
- mnesia:transaction(fun () ->
- Os = mnesia:select(access,
- [{{access,
- {'$1',
- '$2'},
- '$3'},
- [{'==',
- '$2',
- Host}],
- ['$_']}]),
- lists:foreach(fun (O) ->
- mnesia:delete_object(O)
- end,
- Os),
- lists:foreach(fun ({access,
- Name,
- Rules}) ->
- mnesia:write({access,
- {Name,
- Host},
- Rules})
- end,
- Rs)
- end)
- end,
- case lists:keysearch(<<"access">>, 1, XData) of
- {value, {_, Strings}} ->
- String = lists:foldl(fun (S, Res) ->
- <<Res/binary, S/binary, "\n">>
- end,
- <<"">>, Strings),
- case erl_scan:string(binary_to_list(String)) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, Rs} ->
- case SetAccess(Rs) of
- {atomic, _} -> {result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
- end;
- _ ->
- Txt = <<"No 'access' found in data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ SetAccess =
+ fun(Rs) ->
+ mnesia:transaction(
+ fun () ->
+ Os = mnesia:select(
+ access,
+ ets:fun2ms(
+ fun({access, {_, H}, _} = O) when H == Host ->
+ O
+ end)),
+ lists:foreach(fun mnesia:delete_object/1, Os),
+ lists:foreach(
+ fun({access, Name, Rules}) ->
+ mnesia:write({access, {Name, Host}, Rules})
+ end, Rs)
+ end)
+ end,
+ case xmpp_util:get_xdata_values(<<"access">>, XData) of
+ [] ->
+ Txt = <<"No 'access' found in data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ Strings ->
+ String = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end, <<"">>, Strings),
+ case erl_scan:string(binary_to_list(String)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, Rs} ->
+ case SetAccess(Rs) of
+ {atomic, _} ->
+ {result, undefined};
+ _ ->
+ {error, xmpp:err_bad_request()}
+ end;
+ _ ->
+ Txt = <<"Parse failed">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ _ ->
+ {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)}
+ end
end;
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
XData) ->
@@ -1887,7 +1530,7 @@ set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
true = Server == Host orelse
get_permission_level(From) == global,
ejabberd_auth:try_register(User, Server, Password),
- {result, []};
+ {result, undefined};
set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
_Lang, XData) ->
AccountStringList = get_values(<<"accountjids">>,
@@ -1905,7 +1548,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
AccountStringList),
[ejabberd_auth:remove_user(User, Server)
|| {User, Server} <- ASL2],
- {result, []};
+ {result, undefined};
set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
@@ -1914,14 +1557,14 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
LServer = JID#jid.lserver,
true = LServer == Host orelse
get_permission_level(From) == global,
- Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
+ Xmlelement = xmpp:serr_policy_violation(<<"has been kicked">>, Lang),
case JID#jid.lresource of
<<>> ->
SIs = mnesia:dirty_select(session,
[{#session{usr = {LUser, LServer, '_'},
sid = '$1',
info = '$2',
- _ = '_'},
+ _ = '_'},
[], [{{'$1', '$2'}}]}]),
Pids = [P || {{_, P}, Info} <- SIs,
not proplists:get_bool(offline, Info)],
@@ -1934,14 +1577,14 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
[{#session{usr = {LUser, LServer, R},
sid = '$1',
info = '$2',
- _ = '_'},
+ _ = '_'},
[], [{{'$1', '$2'}}]}]),
case proplists:get_bool(offline, Info) of
true -> ok;
false -> Pid ! {kick, kicked_by_admin, Xmlelement}
end
end,
- {result, []};
+ {result, undefined};
set_form(From, Host,
?NS_ADMINL(<<"get-user-password">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
@@ -1953,14 +1596,12 @@ set_form(From, Host,
Password = ejabberd_auth:get_password(User, Server),
true = is_binary(Password),
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
- <<"accountjid">>, AccountString),
- ?XFIELD(<<"text-single">>, <<"Password">>,
- <<"password">>, Password)]}]};
+ #xdata{type = form,
+ fields = [?HFIELD(),
+ ?XFIELD('jid-single', <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD('text-single', <<"Password">>,
+ <<"password">>, Password)]}};
set_form(From, Host,
?NS_ADMINL(<<"change-user-password">>), _Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
@@ -1972,7 +1613,7 @@ set_form(From, Host,
get_permission_level(From) == global,
true = ejabberd_auth:is_user_exists(User, Server),
ejabberd_auth:set_password(User, Server, Password),
- {result, []};
+ {result, undefined};
set_form(From, Host,
?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
@@ -1992,22 +1633,19 @@ set_form(From, Host,
TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
- iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ (str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour,
Minute, Second]))
end;
_ -> ?T(Lang, <<"Online">>)
end,
{result,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children =
- [?HFIELD(),
- ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
- <<"accountjid">>, AccountString),
- ?XFIELD(<<"text-single">>, <<"Last login">>,
- <<"lastlogin">>, FLast)]}]};
+ #xdata{type = form,
+ fields = [?HFIELD(),
+ ?XFIELD('jid-single', <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD('text-single', <<"Last login">>,
+ <<"lastlogin">>, FLast)]}};
set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
XData) ->
AccountString = get_value(<<"accountjid">>, XData),
@@ -2021,33 +1659,29 @@ set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource)
|| Resource <- Resources],
IPs = [<<(jlib:ip_to_list(IP))/binary, ":",
- (jlib:integer_to_binary(Port))/binary>>
+ (integer_to_binary(Port))/binary>>
|| {IP, Port} <- IPs1],
Items = ejabberd_hooks:run_fold(roster_get, Server, [],
[{User, Server}]),
- Rostersize = jlib:integer_to_binary(erlang:length(Items)),
+ Rostersize = integer_to_binary(erlang:length(Items)),
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- ?XFIELD(<<"jid-single">>, <<"Jabber ID">>,
- <<"accountjid">>, AccountString),
- ?XFIELD(<<"text-single">>, <<"Roster size">>,
- <<"rostersize">>, Rostersize),
- ?XMFIELD(<<"text-multi">>, <<"IP addresses">>,
- <<"ipaddresses">>, IPs),
- ?XMFIELD(<<"text-multi">>, <<"Resources">>,
- <<"onlineresources">>, Resources)]}]};
+ #xdata{type = form,
+ fields = [?HFIELD(),
+ ?XFIELD('jid-single', <<"Jabber ID">>,
+ <<"accountjid">>, AccountString),
+ ?XFIELD('text-single', <<"Roster size">>,
+ <<"rostersize">>, Rostersize),
+ ?XMFIELD('text-multi', <<"IP addresses">>,
+ <<"ipaddresses">>, IPs),
+ ?XMFIELD('text-multi', <<"Resources">>,
+ <<"onlineresources">>, Resources)]}};
set_form(_From, _Host, _, _Lang, _XData) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+ {error, xmpp:err_service_unavailable()}.
get_value(Field, XData) -> hd(get_values(Field, XData)).
get_values(Field, XData) ->
- {value, {_, ValueList}} = lists:keysearch(Field, 1,
- XData),
- ValueList.
+ xmpp_util:get_xdata_values(Field, XData).
search_running_node(SNode) ->
search_running_node(SNode,
@@ -2061,57 +1695,34 @@ search_running_node(SNode, [Node | Nodes]) ->
end.
stop_node(From, Host, ENode, Action, XData) ->
- Delay = jlib:binary_to_integer(get_value(<<"delay">>,
- XData)),
+ Delay = binary_to_integer(get_value(<<"delay">>, XData)),
Subject = case get_value(<<"subject">>, XData) of
- <<"">> -> [];
- S ->
- [#xmlel{name = <<"field">>,
- attrs = [{<<"var">>, <<"subject">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, S}]}]}]
+ <<"">> ->
+ [];
+ S ->
+ [#xdata_field{var = <<"subject">>, values = [S]}]
end,
- Announcement = case get_values(<<"announcement">>,
- XData)
- of
- [] -> [];
- As ->
- [#xmlel{name = <<"field">>,
- attrs = [{<<"var">>, <<"body">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Line}]}
- || Line <- As]}]
+ Announcement = case get_values(<<"announcement">>, XData) of
+ [] ->
+ [];
+ As ->
+ [#xdata_field{var = <<"body">>, values = As}]
end,
case Subject ++ Announcement of
- [] -> ok;
- SubEls ->
- Request = #adhoc_request{node =
- ?NS_ADMINX(<<"announce-allhosts">>),
- action = <<"complete">>,
- xdata =
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XDATA},
- {<<"type">>, <<"submit">>}],
- children = SubEls},
- others =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XDATA},
- {<<"type">>, <<"submit">>}],
- children = SubEls}]},
- To = jid:make(<<"">>, Host, <<"">>),
- mod_announce:announce_commands(empty, From, To, Request)
+ [] ->
+ ok;
+ Fields ->
+ Request = #adhoc_command{node = ?NS_ADMINX(<<"announce-allhosts">>),
+ action = complete,
+ xdata = #xdata{type = submit,
+ fields = Fields}},
+ To = jid:make(Host),
+ mod_announce:announce_commands(empty, From, To, Request)
end,
Time = timer:seconds(Delay),
Node = jlib:binary_to_atom(ENode),
- {ok, _} = timer:apply_after(Time, rpc, call,
- [Node, init, Action, []]),
- {result, []}.
+ {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]),
+ {result, undefined}.
get_last_info(User, Server) ->
case gen_mod:is_loaded(Server, mod_last) of
@@ -2120,113 +1731,83 @@ get_last_info(User, Server) ->
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+-spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command().
adhoc_sm_commands(_Acc, From,
- #jid{user = User, server = Server, lserver = LServer} =
- _To,
- #adhoc_request{lang = Lang, node = <<"config">>,
- action = Action, xdata = XData} =
- Request) ->
+ #jid{user = User, server = Server, lserver = LServer},
+ #adhoc_command{lang = Lang, node = <<"config">>,
+ action = Action, xdata = XData} = Request) ->
case acl:match_rule(LServer, configure, From) of
- deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
- allow ->
- ActionIsExecute = lists:member(Action,
- [<<"">>, <<"execute">>,
- <<"complete">>]),
- if Action == <<"cancel">> ->
- adhoc:produce_response(Request,
- #adhoc_response{status = canceled});
- XData == false, ActionIsExecute ->
- case get_sm_form(User, Server, <<"config">>, Lang) of
- {result, Form} ->
- adhoc:produce_response(Request,
- #adhoc_response{status =
- executing,
- elements = Form});
- {error, Error} -> {error, Error}
- end;
- XData /= false, ActionIsExecute ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- Fields ->
- set_sm_form(User, Server, <<"config">>, Request, Fields)
- end;
- true ->
- Txt = <<"Incorrect action or data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end
+ deny ->
+ {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
+ allow ->
+ ActionIsExecute = Action == execute orelse Action == complete,
+ if Action == cancel ->
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{status = canceled});
+ XData == undefined, ActionIsExecute ->
+ case get_sm_form(User, Server, <<"config">>, Lang) of
+ {result, Form} ->
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{status = executing,
+ xdata = Form});
+ {error, Error} ->
+ {error, Error}
+ end;
+ XData /= undefined, ActionIsExecute ->
+ set_sm_form(User, Server, <<"config">>, Request);
+ true ->
+ Txt = <<"Unexpected action">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end;
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
get_sm_form(User, Server, <<"config">>, Lang) ->
{result,
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [?HFIELD(),
- #xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(?T(Lang, <<"Administration of ">>))/binary,
- User/binary>>}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>, ?T(Lang, <<"Action on user">>)},
- {<<"var">>, <<"action">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, <<"edit">>}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- ?T(Lang, <<"Edit Properties">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"edit">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- ?T(Lang, <<"Remove User">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"remove">>}]}]}]},
- ?XFIELD(<<"text-private">>, <<"Password">>,
- <<"password">>,
- (ejabberd_auth:get_password_s(User, Server)))]}]};
+ #xdata{type = form,
+ title = <<(?T(Lang, <<"Administration of ">>))/binary, User/binary>>,
+ fields =
+ [?HFIELD(),
+ #xdata_field{
+ type = 'list-single',
+ label = ?T(Lang, <<"Action on user">>),
+ var = <<"action">>,
+ values = [<<"edit">>],
+ options = [#xdata_option{
+ label = ?T(Lang, <<"Edit Properties">>),
+ value = <<"edit">>},
+ #xdata_option{
+ label = ?T(Lang, <<"Remove User">>),
+ value = <<"remove">>}]},
+ ?XFIELD('text-private', <<"Password">>,
+ <<"password">>,
+ ejabberd_auth:get_password_s(User, Server))]}};
get_sm_form(_User, _Server, _Node, _Lang) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+ {error, xmpp:err_service_unavailable()}.
set_sm_form(User, Server, <<"config">>,
- #adhoc_request{lang = Lang, node = Node,
- sessionid = SessionID},
- XData) ->
- Response = #adhoc_response{lang = Lang, node = Node,
- sessionid = SessionID, status = completed},
- case lists:keysearch(<<"action">>, 1, XData) of
- {value, {_, [<<"edit">>]}} ->
- case lists:keysearch(<<"password">>, 1, XData) of
- {value, {_, [Password]}} ->
- ejabberd_auth:set_password(User, Server, Password),
- adhoc:produce_response(Response);
- _ ->
- Txt = <<"No 'password' found in data form">>,
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
- end;
- {value, {_, [<<"remove">>]}} ->
- catch ejabberd_auth:remove_user(User, Server),
- adhoc:produce_response(Response);
- _ ->
- Txt = <<"Incorrect value of 'action' in data form">>,
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
+ #adhoc_command{lang = Lang, node = Node,
+ sid = SessionID, xdata = XData}) ->
+ Response = #adhoc_command{lang = Lang, node = Node,
+ sid = SessionID, status = completed},
+ case xmpp_util:get_xdata_values(<<"action">>, XData) of
+ [<<"edit">>] ->
+ case xmpp_util:get_xdata_values(<<"password">>, XData) of
+ [Password] ->
+ ejabberd_auth:set_password(User, Server, Password),
+ xmpp_util:make_adhoc_response(Response);
+ _ ->
+ Txt = <<"No 'password' found in data form">>,
+ {error, xmpp:err_not_acceptable(Txt, Lang)}
+ end;
+ [<<"remove">>] ->
+ catch ejabberd_auth:remove_user(User, Server),
+ xmpp_util:make_adhoc_response(Response);
+ _ ->
+ Txt = <<"Incorrect value of 'action' in data form">>,
+ {error, xmpp:err_not_acceptable(Txt, Lang)}
end;
-set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+set_sm_form(_User, _Server, _Node, _Request) ->
+ {error, xmpp:err_service_unavailable()}.
mod_opt_type(_) -> [].
diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl
deleted file mode 100644
index 85b7740d0..000000000
--- a/src/mod_configure2.erl
+++ /dev/null
@@ -1,223 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : mod_configure2.erl
-%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : Support for online configuration of ejabberd
-%%% Created : 26 Oct 2003 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(mod_configure2).
-
--behaviour(ejabberd_config).
-
--author('alexey@process-one.net').
-
--behaviour(gen_mod).
-
--export([start/2, stop/1, process_local_iq/3,
- mod_opt_type/1, opt_type/1, depends/2]).
-
--include("ejabberd.hrl").
--include("logger.hrl").
-
--include("jlib.hrl").
-
--define(NS_ECONFIGURE,
- <<"http://ejabberd.jabberstudio.org/protocol/con"
- "figure">>).
-
-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_ECONFIGURE, ?MODULE, process_local_iq,
- IQDisc),
- ok.
-
-stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_ECONFIGURE).
-
-process_local_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case acl:match_rule(To#jid.lserver, configure, From) of
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- allow ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)]};
- %%case fxml:get_tag_attr_s("type", SubEl) of
- %% "cancel" ->
- %% IQ#iq{type = result,
- %% sub_el = [{xmlelement, "query",
- %% [{"xmlns", XMLNS}], []}]};
- %% "submit" ->
- %% XData = jlib:parse_xdata_submit(SubEl),
- %% case XData of
- %% invalid ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- %% _ ->
- %% Node =
- %% string:tokens(
- %% fxml:get_tag_attr_s("node", SubEl),
- %% "/"),
- %% case set_form(Node, Lang, XData) of
- %% {result, Res} ->
- %% IQ#iq{type = result,
- %% sub_el = [{xmlelement, "query",
- %% [{"xmlns", XMLNS}],
- %% Res
- %% }]};
- %% {error, Error} ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, Error]}
- %% end
- %% end;
- %% _ ->
- %% IQ#iq{type = error,
- %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
- %%end;
- get ->
- case process_get(SubEl, Lang) of
- {result, Res} -> IQ#iq{type = result, sub_el = [Res]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
- end
- end.
-
-process_get(#xmlel{name = <<"info">>}, _Lang) ->
- S2SConns = ejabberd_s2s:dirty_get_connections(),
- TConns = lists:usort([element(2, C) || C <- S2SConns]),
- Attrs = [{<<"registered-users">>,
- iolist_to_binary(integer_to_list(mnesia:table_info(passwd,
- size)))},
- {<<"online-users">>,
- iolist_to_binary(integer_to_list(mnesia:table_info(presence,
- size)))},
- {<<"running-nodes">>,
- iolist_to_binary(integer_to_list(length(mnesia:system_info(running_db_nodes))))},
- {<<"stopped-nodes">>,
- iolist_to_binary(integer_to_list(length(lists:usort(mnesia:system_info(db_nodes)
- ++
- mnesia:system_info(extra_db_nodes))
- --
- mnesia:system_info(running_db_nodes))))},
- {<<"outgoing-s2s-servers">>,
- iolist_to_binary(integer_to_list(length(TConns)))}],
- {result,
- #xmlel{name = <<"info">>,
- attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
- children = []}};
-process_get(#xmlel{name = <<"welcome-message">>,
- attrs = Attrs}, _Lang) ->
- {Subj, Body} = ejabberd_config:get_option(
- welcome_message,
- fun({Subj, Body}) ->
- {iolist_to_binary(Subj),
- iolist_to_binary(Body)}
- end,
- {<<"">>, <<"">>}),
- {result,
- #xmlel{name = <<"welcome-message">>, attrs = Attrs,
- children =
- [#xmlel{name = <<"subject">>, attrs = [],
- children = [{xmlcdata, Subj}]},
- #xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Body}]}]}};
-process_get(#xmlel{name = <<"registration-watchers">>,
- attrs = Attrs}, _Lang) ->
- SubEls = ejabberd_config:get_option(
- 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, []),
- {result,
- #xmlel{name = <<"registration_watchers">>,
- attrs = Attrs, children = SubEls}};
-process_get(#xmlel{name = <<"acls">>, attrs = Attrs}, _Lang) ->
- Str = iolist_to_binary(io_lib:format("~p.",
- [ets:tab2list(acl)])),
- {result,
- #xmlel{name = <<"acls">>, attrs = Attrs,
- children = [{xmlcdata, Str}]}};
-process_get(#xmlel{name = <<"access">>,
- attrs = Attrs}, _Lang) ->
- Str = iolist_to_binary(io_lib:format("~p.",
- [ets:select(local_config,
- [{{local_config, {access, '$1'},
- '$2'},
- [],
- [{{access, '$1',
- '$2'}}]}])])),
- {result,
- #xmlel{name = <<"access">>, attrs = Attrs,
- children = [{xmlcdata, Str}]}};
-process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
- case catch mnesia:dirty_select(last_activity,
- [{{last_activity, '_', '$1', '_'}, [],
- ['$1']}])
- of
- {'EXIT', _Reason} ->
- Txt = <<"Database failure">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)};
- Vals ->
- TimeStamp = p1_time_compat:system_time(seconds),
- Str = list_to_binary(
- [[jlib:integer_to_binary(TimeStamp - V),
- <<" ">>] || V <- Vals]),
- {result,
- #xmlel{name = <<"last">>, attrs = Attrs,
- children = [{xmlcdata, Str}]}}
- end;
-%%process_get({xmlelement, Name, Attrs, SubEls}) ->
-%% {result, };
-process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
-
-depends(_Host, _Opts) ->
- [].
-
-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_delegation.erl b/src/mod_delegation.erl
index f2d1a13b5..2cf9525fc 100644
--- a/src/mod_delegation.erl
+++ b/src/mod_delegation.erl
@@ -1,538 +1,362 @@
-%%%--------------------------------------------------------------------------------------
+%%%-------------------------------------------------------------------
%%% File : mod_delegation.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
-%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
-%%%--------------------------------------------------------------------------------------
-
+%%% Purpose : XEP-0355: Namespace Delegation
+%%%
+%%%
+%%% 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_delegation).
-author('amuhar3@gmail.com').
--behaviour(gen_mod).
-
-protocol({xep, 0355, '0.3'}).
--export([start/2, stop/1, depends/2, mod_opt_type/1]).
-
--export([advertise_delegations/1, process_iq/3,
- disco_local_features/5, disco_sm_features/5,
- disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]).
-
--include_lib("stdlib/include/ms_transform.hrl").
-
--include("ejabberd_service.hrl").
-
--define(CLEAN_INTERVAL, timer:minutes(10)).
-
-%%%--------------------------------------------------------------------------------------
-%%% API
-%%%--------------------------------------------------------------------------------------
+-behaviour(gen_server).
+-behaviour(gen_mod).
-start(Host, _Opts) ->
- mod_disco:register_feature(Host, ?NS_DELEGATION),
- %% start timer for hooks_tmp table cleaning
- timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
+%% API
+-export([start_link/2]).
+-export([start/2, stop/1, mod_opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([component_connected/1, component_disconnected/2,
+ ejabberd_local/1, ejabberd_sm/1, decode_iq_subel/1,
+ disco_local_features/5, disco_sm_features/5,
+ disco_local_identity/5, disco_sm_identity/5]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty.
+-record(state, {server_host = <<"">> :: binary(),
+ delegations = dict:new() :: ?TDICT}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 2000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, PingSpec).
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:call(Proc, stop),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(namespaces) -> validate_fun();
+mod_opt_type(_) ->
+ [namespaces, iqdisc].
+
+depends(_, _) ->
+ [].
+
+-spec decode_iq_subel(xmpp_element()) -> xmpp_element();
+ (xmlel()) -> xmlel().
+%% Tell gen_iq_handler not to auto-decode IQ payload
+decode_iq_subel(El) ->
+ El.
+
+-spec component_connected(binary()) -> ok.
+component_connected(Host) ->
+ lists:foreach(
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_connected, Host})
+ end, ?MYHOSTS).
+
+-spec component_disconnected(binary(), binary()) -> ok.
+component_disconnected(Host, _Reason) ->
+ lists:foreach(
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_disconnected, Host})
+ end, ?MYHOSTS).
+
+-spec ejabberd_local(iq()) -> iq().
+ejabberd_local(IQ) ->
+ process_iq(IQ, ejabberd_local).
+
+-spec ejabberd_sm(iq()) -> iq().
+ejabberd_sm(IQ) ->
+ process_iq(IQ, ejabberd_sm).
+
+-spec disco_local_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_local_features(Acc, From, To, Node, Lang) ->
+ disco_features(Acc, From, To, Node, Lang, ejabberd_local).
+
+-spec disco_sm_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_sm_features(Acc, From, To, Node, Lang) ->
+ disco_features(Acc, From, To, Node, Lang, ejabberd_sm).
+
+-spec disco_local_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_local_identity(Acc, From, To, Node, Lang) ->
+ disco_identity(Acc, From, To, Node, Lang, ejabberd_local).
+
+-spec disco_sm_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_sm_identity(Acc, From, To, Node, Lang) ->
+ disco_identity(Acc, From, To, Node, Lang, ejabberd_sm).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([Host, _Opts]) ->
+ ejabberd_hooks:add(component_connected, ?MODULE,
+ component_connected, 50),
+ ejabberd_hooks:add(component_disconnected, ?MODULE,
+ component_disconnected, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
- disco_local_features, 500), %% This hook should be the last
+ disco_local_features, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
- disco_local_identity, 500),
+ disco_local_identity, 50),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
- disco_sm_identity, 500),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
- disco_sm_features, 500),
- ejabberd_hooks:add(disco_info, Host, ?MODULE,
- disco_info, 500).
-
-
-stop(Host) ->
- mod_disco:unregister_feature(Host, ?NS_DELEGATION),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
- disco_local_features, 500),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE,
- disco_local_identity, 500),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
- disco_sm_identity, 500),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
- disco_sm_features, 500),
- ejabberd_hooks:delete(disco_info, Host, ?MODULE,
- disco_info, 500).
-
-depends(_Host, _Opts) -> [].
-
-mod_opt_type(_Opt) -> [].
-
-%%%--------------------------------------------------------------------------------------
-%%% 4.2 Functions to advertise service of delegated namespaces
-%%%--------------------------------------------------------------------------------------
-attribute_tag(Attrs) ->
- lists:map(fun(Attr) ->
- #xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]}
- end, Attrs).
-
-delegations(From, To, Delegations) ->
- {Elem0, DelegatedNs} =
- lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) ->
- case ets:insert_new(delegated_namespaces,
- {Ns, FiltAttr, self(), To, {}, {}}) of
- true ->
- Attrs =
- if
- FiltAttr == [] ->
- ?DEBUG("namespace ~s is delegated to ~s with"
- " no filtering attributes ~n",[Ns, To]),
- [];
- true ->
- ?DEBUG("namespace ~s is delegated to ~s with"
- " ~p filtering attributes ~n",[Ns, To, FiltAttr]),
- attribute_tag(FiltAttr)
- end,
- add_iq_handlers(Ns),
- {[#xmlel{name = <<"delegated">>,
- attrs = [{<<"namespace">>, Ns}],
- children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]};
- false -> {Acc, AccNs}
- end
- end, {[], []}, Delegations),
- case Elem0 of
- [] -> {ignore, DelegatedNs};
- _ ->
- Elem1 = #xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}],
- children = Elem0},
- Id = randoms:get_string(),
- {#xmlel{name = <<"message">>,
- attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
- children = [Elem1]}, DelegatedNs}
- end.
-
-add_iq_handlers(Ns) ->
- lists:foreach(fun(Host) ->
- IQDisc =
- gen_mod:get_module_opt(Host, ?MODULE, iqdisc,
- fun gen_iq_handler:check_type/1, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- Ns, ?MODULE,
- process_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- Ns, ?MODULE,
- process_iq, IQDisc)
- end, ?MYHOSTS).
-
-advertise_delegations(#state{delegations = []}) -> [];
-advertise_delegations(StateData) ->
- {Delegated, DelegatedNs} =
- delegations(?MYNAME, StateData#state.host, StateData#state.delegations),
- if
- Delegated /= ignore ->
- ejabberd_service:send_element(StateData, Delegated),
- % server asks available features for delegated namespaces
- disco_info(StateData#state{delegations = DelegatedNs});
- true -> ok
- end,
- DelegatedNs.
-
-%%%--------------------------------------------------------------------------------------
-%%% Delegated namespaces hook
-%%%--------------------------------------------------------------------------------------
-
-check_filter_attr([], _Children) -> true;
-check_filter_attr(_FilterAttr, []) -> false;
-check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) ->
- Attrs = proplists:get_keys(Stanza#xmlel.attrs),
- lists:all(fun(Attr) ->
- lists:member(Attr, Attrs)
- end, FilterAttr);
-check_filter_attr(_FilterAttr, _Children) -> false.
-
--spec get_client_server([attr()]) -> {jid(), jid()}.
-
-get_client_server(Attrs) ->
- Client = fxml:get_attr_s(<<"from">>, Attrs),
- ClientJID = jid:from_string(Client),
- ServerJID = jid:from_string(ClientJID#jid.lserver),
- {ClientJID, ServerJID}.
-
-decapsulate_result(#xmlel{children = []}) -> ok;
-decapsulate_result(#xmlel{children = Children}) ->
- decapsulate_result0(Children).
-
-decapsulate_result0([]) -> ok;
-decapsulate_result0([#xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) ->
- decapsulate_result1(Packet#xmlel.children);
-decapsulate_result0(_Children) -> ok.
-
-decapsulate_result1([]) -> ok;
-decapsulate_result1([#xmlel{name = <<"forwarded">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) ->
- decapsulate_result2(Packet#xmlel.children);
-decapsulate_result1(_Children) -> ok.
-
-decapsulate_result2([]) -> ok;
-decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) ->
- Ns = fxml:get_attr_s(<<"xmlns">>, Attrs),
- if
- Ns /= <<"jabber:client">> ->
- ok;
- true -> Packet
+ disco_sm_identity, 50),
+ {ok, #state{server_host = Host}}.
+
+handle_call(get_delegations, _From, State) ->
+ {reply, {ok, State#state.delegations}, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast({component_connected, Host}, State) ->
+ ServerHost = State#state.server_host,
+ To = jid:make(Host),
+ NSAttrsAccessList = gen_mod:get_module_opt(
+ ServerHost, ?MODULE, namespaces,
+ validate_fun(), []),
+ lists:foreach(
+ fun({NS, _Attrs, Access}) ->
+ case acl:match_rule(ServerHost, Access, To) of
+ allow ->
+ send_disco_queries(ServerHost, Host, NS);
+ deny ->
+ ok
+ end
+ end, NSAttrsAccessList),
+ {noreply, State};
+handle_cast({disco_info, Type, Host, NS, Info}, State) ->
+ From = jid:make(State#state.server_host),
+ To = jid:make(Host),
+ case dict:find({NS, Type}, State#state.delegations) of
+ error ->
+ Msg = #message{from = From, to = To,
+ sub_els = [#delegation{delegated = [#delegated{ns = NS}]}]},
+ Delegations = dict:store({NS, Type}, {Host, Info}, State#state.delegations),
+ gen_iq_handler:add_iq_handler(Type, State#state.server_host, NS,
+ ?MODULE, Type, one_queue),
+ ejabberd_router:route(From, To, Msg),
+ ?INFO_MSG("Namespace '~s' is delegated to external component '~s'",
+ [NS, Host]),
+ {noreply, State#state{delegations = Delegations}};
+ {ok, {AnotherHost, _}} ->
+ ?WARNING_MSG("Failed to delegate namespace '~s' to "
+ "external component '~s' because it's already "
+ "delegated to '~s'",
+ [NS, Host, AnotherHost]),
+ {noreply, State}
end;
-decapsulate_result2(_Children) -> ok.
-
--spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore.
-
-check_iq(#xmlel{attrs = Attrs} = Packet,
- #xmlel{attrs = AttrsOrigin} = OriginPacket) ->
- % Id attribute of OriginPacket Must be equil to Packet Id attribute
- Id1 = fxml:get_attr_s(<<"id">>, Attrs),
- Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin),
- % From attribute of OriginPacket Must be equil to Packet To attribute
- From = fxml:get_attr_s(<<"from">>, AttrsOrigin),
- To = fxml:get_attr_s(<<"to">>, Attrs),
- % Type attribute Must be error or result
- Type = fxml:get_attr_s(<<"type">>, Attrs),
- if
- ((Type == <<"result">>) or (Type == <<"error">>)),
- Id1 == Id2, To == From ->
- NewPacket = jlib:remove_attr(<<"xmlns">>, Packet),
- %% We can send the decapsulated stanza from Server to Client (To)
- NewPacket;
- true ->
- %% service-unavailable
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- Err
- end;
-check_iq(_Packet, _OriginPacket) -> ignore.
-
--spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok.
-
-manage_service_result(HookRes, HookErr, Service, OriginPacket) ->
- fun(Packet) ->
- {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
- Server = ClientJID#jid.lserver,
-
- ets:delete(hooks_tmp, {HookRes, Server}),
- ets:delete(hooks_tmp, {HookErr, Server}),
- % Check Packet "from" attribute
- % It Must be equil to current service host
- From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs),
- if
- From == Service ->
- % decapsulate iq result
- ResultIQ = decapsulate_result(Packet),
- ServResponse = check_iq(ResultIQ, OriginPacket),
- if
- ServResponse /= ignore ->
- ejabberd_router:route(ServerJID, ClientJID, ServResponse);
- true -> ok
- end;
- true ->
- % service unavailable
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(ServerJID, ClientJID, Err)
- end
+handle_cast({component_disconnected, Host}, State) ->
+ ServerHost = State#state.server_host,
+ Delegations =
+ dict:filter(
+ fun({NS, Type}, {H, _}) when H == Host ->
+ ?INFO_MSG("Remove delegation of namespace '~s' "
+ "from external component '~s'",
+ [NS, Host]),
+ gen_iq_handler:remove_iq_handler(Type, ServerHost, NS),
+ false;
+ (_, _) ->
+ true
+ end, State#state.delegations),
+ {noreply, State#state{delegations = Delegations}};
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ %% Note: we don't remove component_* hooks because they are global
+ %% and might be registered within a module on another virtual host
+ ServerHost = State#state.server_host,
+ ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE,
+ disco_local_features, 50),
+ ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE,
+ disco_local_identity, 50),
+ ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE,
+ disco_sm_identity, 50),
+ lists:foreach(
+ fun({NS, Type}) ->
+ gen_iq_handler:remove_iq_handler(Type, ServerHost, NS)
+ end, dict:fetch_keys(State#state.delegations)).
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+-spec get_delegations(binary()) -> ?TDICT.
+get_delegations(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ try gen_server:call(Proc, get_delegations) of
+ {ok, Delegations} -> Delegations
+ catch exit:{noproc, _} ->
+ %% No module is loaded for this virtual host
+ dict:new()
end.
--spec manage_service_error(atom(), atom(), xmlel()) -> ok.
-
-manage_service_error(HookRes, HookErr, OriginPacket) ->
- fun(_Packet) ->
- {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
- Server = ClientJID#jid.lserver,
- ets:delete(hooks_tmp, {HookRes, Server}),
- ets:delete(hooks_tmp, {HookErr, Server}),
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(ServerJID, ClientJID, Err)
+-spec process_iq(iq(), ejabberd_local | ejabberd_sm) -> ignore | iq().
+process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) ->
+ LServer = To#jid.lserver,
+ NS = xmpp:get_ns(SubEl),
+ Delegations = get_delegations(LServer),
+ case dict:find({NS, Type}, Delegations) of
+ {ok, {Host, _}} ->
+ Delegation = #delegation{
+ forwarded = #forwarded{xml_els = [xmpp:encode(IQ)]}},
+ NewFrom = jid:make(LServer),
+ NewTo = jid:make(Host),
+ ejabberd_local:route_iq(
+ NewFrom, NewTo,
+ #iq{type = set,
+ from = NewFrom,
+ to = NewTo,
+ sub_els = [Delegation]},
+ fun(Result) -> process_iq_result(IQ, Result) end),
+ ignore;
+ error ->
+ Txt = <<"Failed to map delegated namespace to external component">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-
--spec forward_iq(binary(), binary(), xmlel()) -> ok.
-
-forward_iq(Server, Service, Packet) ->
- Elem0 = #xmlel{name = <<"forwarded">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]},
- Elem1 = #xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]},
- Id = randoms:get_string(),
- Elem2 = #xmlel{name = <<"iq">>,
- attrs = [{<<"from">>, Server}, {<<"to">>, Service},
- {<<"type">>, <<"set">>}, {<<"id">>, Id}],
- children = [Elem1]},
-
- HookRes = {iq, result, Id},
- HookErr = {iq, error, Id},
-
- FunRes = manage_service_result(HookRes, HookErr, Service, Packet),
- FunErr = manage_service_error(HookRes, HookErr, Packet),
-
- Timestamp = p1_time_compat:system_time(seconds),
- ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}),
- ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}),
-
- From = jid:make(<<"">>, Server, <<"">>),
- To = jid:make(<<"">>, Service, <<"">>),
- ejabberd_router:route(From, To, Elem2).
-
-process_iq(From, #jid{lresource = <<"">>} = To,
- #iq{type = Type, xmlns = XMLNS} = IQ) ->
- %% check if stanza directed to server
- %% or directed to the bare JID of the sender
- case ((Type == get) or (Type == set)) of
- true ->
- Packet = jlib:iq_to_xml(IQ),
- #xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet,
- AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs],
- AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From),
- jid:to_string(To), AttrsNew),
- case ets:lookup(delegated_namespaces, XMLNS) of
- [{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] ->
- case check_filter_attr(FiltAttr, Children) of
- true ->
- forward_iq(From#jid.server, ServiceHost,
- Packet#xmlel{attrs = AttrsNew2});
- _ -> ok
- end;
- [] -> ok
- end,
- ignore;
- _ ->
- ignore
+-spec process_iq_result(iq(), iq()) -> ok.
+process_iq_result(#iq{from = From, to = To, id = ID, lang = Lang} = IQ,
+ #iq{type = result} = ResIQ) ->
+ try
+ #delegation{forwarded = #forwarded{xml_els = [SubEl]}} =
+ xmpp:get_subtag(ResIQ, #delegation{}),
+ case xmpp:decode(SubEl, ?NS_CLIENT, [ignore_els]) of
+ #iq{from = To, to = From, type = Type, id = ID} = Reply
+ when Type == error; Type == result ->
+ ejabberd_router:route(To, From, Reply)
+ end
+ catch _:_ ->
+ ?ERROR_MSG("got iq-result with invalid delegated "
+ "payload:~n~s", [xmpp:pp(ResIQ)]),
+ Txt = <<"External component failure">>,
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ ejabberd_router:route_error(To, From, IQ, Err)
end;
-process_iq(_From, _To, _IQ) -> ignore.
-
-%%%--------------------------------------------------------------------------------------
-%%% 7. Discovering Support
-%%%--------------------------------------------------------------------------------------
-
-decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) ->
- case fxml:get_attr_s(<<"node">>, Attrs) of
- Node ->
- PREFIX = << ?NS_DELEGATION/binary, "::" >>,
- Size = byte_size(PREFIX),
- BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>,
- SizeBare = byte_size(BARE_PREFIX),
-
- Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <-
- fxml:get_subtags(Packet, <<"feature">>)],
-
- Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)],
-
- Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)],
-
- case Node of
- << PREFIX:Size/binary, NS/binary >> ->
- ets:update_element(delegated_namespaces, NS,
- {5, {Features, Identity, Exten}});
- << BARE_PREFIX:SizeBare/binary, NS/binary >> ->
- ets:update_element(delegated_namespaces, NS,
- {6, {Features, Identity, Exten}});
- _ -> ok
- end;
- _ -> ok
- end;
-decapsulate_features(_Packet, _Node) -> ok.
-
--spec disco_result(atom(), atom(), binary()) -> ok.
-
-disco_result(HookRes, HookErr, Node) ->
- fun(Packet) ->
- Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO),
- decapsulate_features(Tag, Node),
-
- ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
- ets:delete(hooks_tmp, {HookErr, ?MYNAME})
- end.
-
--spec disco_error(atom(), atom()) -> ok.
-
-disco_error(HookRes, HookErr) ->
- fun(_Packet) ->
- ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
- ets:delete(hooks_tmp, {HookErr, ?MYNAME})
- end.
-
--spec disco_info(state()) -> ok.
-
-disco_info(StateData) ->
- disco_info(StateData, <<"::">>),
- disco_info(StateData, <<":bare:">>).
-
--spec disco_info(state(), binary()) -> ok.
-
-disco_info(StateData, Sep) ->
- lists:foreach(fun({Ns, _FilterAttr}) ->
- Id = randoms:get_string(),
- Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>,
-
- HookRes = {iq, result, Id},
- HookErr = {iq, error, Id},
-
- FunRes = disco_result(HookRes, HookErr, Node),
- FunErr = disco_error(HookRes, HookErr),
-
- Timestamp = p1_time_compat:system_time(seconds),
- ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}),
- ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}),
-
- Tag = #xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_INFO},
- {<<"node">>, Node}],
- children = []},
- DiscoReq = #xmlel{name = <<"iq">>,
- attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id},
- {<<"from">>, ?MYNAME},
- {<<"to">>, StateData#state.host }],
- children = [Tag]},
- ejabberd_service:send_element(StateData, DiscoReq)
-
- end, StateData#state.delegations).
-
-
-disco_features(Acc, Bare) ->
- Fun = fun(Feat) ->
- ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
- A or str:prefix(Ns, Feat)
- end, false, delegated_namespaces)
- end,
- % delete feature namespace which is delegated to service
- Features = lists:filter(fun ({{Feature, _Host}}) ->
- not Fun(Feature);
- (Feature) when is_binary(Feature) ->
- not Fun(Feature)
- end, Acc),
- % add service features
- FeaturesList =
- ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) ->
- if
- Bare -> A ++ FeatsBare;
- true -> A ++ Feats
- end;
- (_, A) -> A
- end, Features, delegated_namespaces),
- {result, FeaturesList}.
-
-disco_identity(Acc, Bare) ->
- % filter delegated identites
- Fun = fun(Ident) ->
- ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) ->
- Identity =
- if
- Bare -> IBare;
- true -> I
- end,
- (fxml:get_attr_s(<<"category">> , Ident) ==
- fxml:get_attr_s(<<"category">>, Identity)) and
- (fxml:get_attr_s(<<"type">> , Ident) ==
- fxml:get_attr_s(<<"type">>, Identity)) or A;
- (_, A) -> A
- end, false, delegated_namespaces)
- end,
-
- Identities =
- lists:filter(fun (#xmlel{attrs = Attrs}) ->
- not Fun(Attrs)
- end, Acc),
- % add service features
- ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) ->
- if
- Bare -> A ++ IBare;
- true -> A ++ I
- end;
- (_, A) -> A
- end, Identities, delegated_namespaces).
-
-%% xmlns from value element
-
--spec get_field_value([xmlel()]) -> binary().
-
-get_field_value([]) -> <<"">>;
-get_field_value([Elem| Elems]) ->
- case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and
- (fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of
- true ->
- Ns = fxml:get_subtag_cdata(Elem, <<"value">>),
- if
- Ns /= <<"">> -> Ns;
- true -> get_field_value(Elems)
- end;
- _ -> get_field_value(Elems)
- end.
-
-get_info(Acc, Bare) ->
- Fun = fun(Feat) ->
- ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
- (A or str:prefix(Ns, Feat))
- end, false, delegated_namespaces)
- end,
- Exten = lists:filter(fun(Xmlel) ->
- Tags = fxml:get_subtags(Xmlel, <<"field">>),
- case get_field_value(Tags) of
- <<"">> -> true;
- Value -> not Fun(Value)
- end
- end, Acc),
- ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) ->
- if
- Bare -> A ++ ExtBare;
- true -> A ++ Ext
- end;
- (_, A) -> A
- end, Exten, delegated_namespaces).
-
-%% 7.2.1 General Case
-
-disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
- Acc;
-disco_local_features(Acc, _From, _To, <<>>, _Lang) ->
- FeatsOld = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- disco_features(FeatsOld, false);
-disco_local_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_local_identity(Acc, _From, _To, <<>>, _Lang) ->
- disco_identity(Acc, false);
-disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-%% 7.2.2 Rediction Of Bare JID Disco Info
-
-disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From,
- #jid{lresource = <<"">>}, <<>>, _Lang) ->
- disco_features([], true);
-disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
- Acc;
-disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- FeatsOld = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- disco_features(FeatsOld, true);
-disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- disco_identity(Acc, true);
-disco_sm_identity(Acc, _From, _To, _Node, _Lang) ->
+process_iq_result(#iq{from = From, to = To}, #iq{type = error} = ResIQ) ->
+ Err = xmpp:set_from_to(ResIQ, To, From),
+ ejabberd_router:route(To, From, Err);
+process_iq_result(#iq{from = From, to = To, lang = Lang} = IQ, timeout) ->
+ Txt = <<"External component timeout">>,
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ ejabberd_router:route_error(To, From, IQ, Err).
+
+-spec send_disco_queries(binary(), binary(), binary()) -> ok.
+send_disco_queries(LServer, Host, NS) ->
+ From = jid:make(LServer),
+ To = jid:make(Host),
+ lists:foreach(
+ fun({Type, Node}) ->
+ ejabberd_local:route_iq(
+ From, To, #iq{type = get, from = From, to = To,
+ sub_els = [#disco_info{node = Node}]},
+ fun(#iq{type = result, sub_els = [SubEl]}) ->
+ try xmpp:decode(SubEl) of
+ #disco_info{} = Info->
+ Proc = gen_mod:get_module_proc(LServer, ?MODULE),
+ gen_server:cast(
+ Proc, {disco_info, Type, Host, NS, Info});
+ _ ->
+ ok
+ catch _:{xmpp_codec, _} ->
+ ok
+ end;
+ (_) ->
+ ok
+ end)
+ end, [{ejabberd_local, <<(?NS_DELEGATION)/binary, "::", NS/binary>>},
+ {ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]).
+
+-spec disco_features(disco_acc(), jid(), jid(), binary(), binary(),
+ ejabberd_local | ejabberd_sm) -> disco_acc().
+disco_features(Acc, _From, To, <<"">>, _Lang, Type) ->
+ Delegations = get_delegations(To#jid.lserver),
+ Features = my_features(Type) ++
+ lists:flatmap(
+ fun({{_, T}, {_, Info}}) when T == Type ->
+ Info#disco_info.features;
+ (_) ->
+ []
+ end, dict:to_list(Delegations)),
+ case Acc of
+ empty when Features /= [] -> {result, Features};
+ {result, Fs} -> {result, Fs ++ Features};
+ _ -> Acc
+ end;
+disco_features(Acc, _, _, _, _, _) ->
Acc.
-disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- get_info(Acc, true);
-disco_info(Acc, _Host, _Mod, <<>>, _Lang) ->
- get_info(Acc, false);
-disco_info(Acc, _Host, _Mod, _Node, _Lang) ->
+-spec disco_identity(disco_acc(), jid(), jid(), binary(), binary(),
+ ejabberd_local | ejabberd_sm) -> disco_acc().
+disco_identity(Acc, _From, To, <<"">>, _Lang, Type) ->
+ Delegations = get_delegations(To#jid.lserver),
+ Identities = lists:flatmap(
+ fun({{_, T}, {_, Info}}) when T == Type ->
+ Info#disco_info.identities;
+ (_) ->
+ []
+ end, dict:to_list(Delegations)),
+ case Acc of
+ empty when Identities /= [] -> {result, Identities};
+ {result, Ids} -> {result, Ids ++ Identities};
+ Acc -> Acc
+ end;
+disco_identity(Acc, _From, _To, _Node, _Lang, _Type) ->
Acc.
-%% clean hooks_tmp table
-
-clean() ->
- ?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]),
- Now = p1_time_compat:system_time(seconds),
- catch ets:select_delete(hooks_tmp,
- ets:fun2ms(fun({_, _, Timestamp}) ->
- Now - 300 >= Timestamp
- end)),
- %% start timer for table cleaning
- timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []).
+my_features(ejabberd_local) -> [?NS_DELEGATION];
+my_features(ejabberd_sm) -> [].
+
+validate_fun() ->
+ fun(L) ->
+ lists:map(
+ fun({NS, Opts}) ->
+ Attrs = proplists:get_value(filtering, Opts, []),
+ Access = proplists:get_value(access, Opts, none),
+ {NS, Attrs, Access}
+ end, L)
+ end.
diff --git a/src/mod_disco.erl b/src/mod_disco.erl
index 2e7b80c18..953d1da10 100644
--- a/src/mod_disco.erl
+++ b/src/mod_disco.erl
@@ -32,10 +32,10 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq_items/3,
- process_local_iq_info/3, get_local_identity/5,
+-export([start/2, stop/1, process_local_iq_items/1,
+ process_local_iq_info/1, get_local_identity/5,
get_local_features/5, get_local_services/5,
- process_sm_iq_items/3, process_sm_iq_info/3,
+ process_sm_iq_items/1, process_sm_iq_info/1,
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,
@@ -44,8 +44,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
-
+-include("xmpp.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-include("mod_roster.hrl").
start(Host, Opts) ->
@@ -126,158 +126,131 @@ stop(Host) ->
{{'_', Host}}),
ok.
+-spec register_feature(binary(), binary()) -> true.
register_feature(Host, Feature) ->
catch ets:new(disco_features,
[named_table, ordered_set, public]),
ets:insert(disco_features, {{Feature, Host}}).
+-spec unregister_feature(binary(), binary()) -> true.
unregister_feature(Host, Feature) ->
catch ets:new(disco_features,
[named_table, ordered_set, public]),
ets:delete(disco_features, {Feature, Host}).
+-spec register_extra_domain(binary(), binary()) -> true.
register_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]),
ets:insert(disco_extra_domains, {{Domain, Host}}).
+-spec unregister_extra_domain(binary(), binary()) -> true.
unregister_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]),
ets:delete(disco_extra_domains, {Domain, Host}).
-process_local_iq_items(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- 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])
- of
- {result, Items} ->
- ANode = case Node of
- <<"">> -> [];
- _ -> [{<<"node">>, Node}]
- end,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
- children = Items}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+-spec process_local_iq_items(iq()) -> iq().
+process_local_iq_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq_items(#iq{type = get, lang = Lang,
+ from = From, to = To,
+ sub_els = [#disco_items{node = Node}]} = IQ) ->
+ Host = To#jid.lserver,
+ case ejabberd_hooks:run_fold(disco_local_items, Host,
+ empty, [From, To, Node, Lang]) of
+ {result, Items} ->
+ xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
end.
-process_local_iq_info(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- Host = To#jid.lserver,
- 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, [],
- [Host, ?MODULE, Node, Lang]),
- case ejabberd_hooks:run_fold(disco_local_features, Host,
- empty, [From, To, Node, Lang])
- of
- {result, Features} ->
- ANode = case Node of
- <<"">> -> [];
- _ -> [{<<"node">>, Node}]
- end,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
- children =
- Identity ++
- Info ++ features_to_xml(Features)}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+-spec process_local_iq_info(iq()) -> iq().
+process_local_iq_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq_info(#iq{type = get, lang = Lang,
+ from = From, to = To,
+ sub_els = [#disco_info{node = Node}]} = IQ) ->
+ Host = To#jid.lserver,
+ Identity = ejabberd_hooks:run_fold(disco_local_identity,
+ Host, [], [From, To, Node, Lang]),
+ Info = ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, ?MODULE, Node, Lang]),
+ case ejabberd_hooks:run_fold(disco_local_features, Host,
+ empty, [From, To, Node, Lang]) of
+ {result, Features} ->
+ xmpp:make_iq_result(IQ, #disco_info{node = Node,
+ identities = Identity,
+ xdata = Info,
+ features = Features});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
end.
-get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
- Acc ++
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>},
- {<<"name">>, <<"ejabberd">>}],
- children = []}];
+-spec get_local_identity([identity()], jid(), jid(),
+ binary(), binary()) -> [identity()].
+get_local_identity(Acc, _From, _To, <<"">>, _Lang) ->
+ Acc ++ [#identity{category = <<"server">>,
+ type = <<"im">>,
+ name = <<"ejabberd">>}];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
+-spec get_local_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
get_local_features({error, _Error} = Acc, _From, _To,
_Node, _Lang) ->
Acc;
-get_local_features(Acc, _From, To, <<>>, _Lang) ->
+get_local_features(Acc, _From, To, <<"">>, _Lang) ->
Feats = case Acc of
- {result, Features} -> Features;
- empty -> []
+ {result, Features} -> Features;
+ empty -> []
end,
Host = To#jid.lserver,
{result,
ets:select(disco_features,
- [{{{'_', Host}}, [], ['$_']}])
- ++ Feats};
+ ets:fun2ms(fun({{F, H}}) when H == Host -> F end))
+ ++ Feats};
get_local_features(Acc, _From, _To, _Node, Lang) ->
case Acc of
{result, _Features} -> Acc;
empty ->
Txt = <<"No features available">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
+ {error, xmpp:err_item_not_found(Txt, Lang)}
end.
-features_to_xml(FeatureList) ->
- [#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}], children = []}
- || Feat
- <- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
- Feature;
- (Feature) when is_binary(Feature) ->
- Feature
- end,
- FeatureList))].
-
-domain_to_xml({Domain}) ->
- #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
- children = []};
-domain_to_xml(Domain) ->
- #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
- children = []}.
-
+-spec get_local_services({error, stanza_error()} | {result, [disco_item()]} | empty,
+ jid(), jid(),
+ binary(), binary()) ->
+ {error, stanza_error()} | {result, [disco_item()]}.
get_local_services({error, _Error} = Acc, _From, _To,
_Node, _Lang) ->
Acc;
-get_local_services(Acc, _From, To, <<>>, _Lang) ->
+get_local_services(Acc, _From, To, <<"">>, _Lang) ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
Host = To#jid.lserver,
{result,
- lists:usort(lists:map(fun domain_to_xml/1,
- get_vh_services(Host) ++
- ets:select(disco_extra_domains,
- [{{{'$1', Host}}, [], ['$1']}])))
- ++ Items};
+ lists:usort(
+ lists:map(
+ fun(Domain) -> #disco_item{jid = jid:make(Domain)} end,
+ get_vh_services(Host) ++
+ ets:select(disco_extra_domains,
+ ets:fun2ms(
+ fun({{D, H}}) when H == Host -> D end))))
+ ++ Items};
get_local_services({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
get_local_services(empty, _From, _To, _Node, Lang) ->
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
+ {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}.
+-spec get_vh_services(binary()) -> [binary()].
get_vh_services(Host) ->
Hosts = lists:sort(fun (H1, H2) ->
byte_size(H1) >= byte_size(H2)
@@ -300,47 +273,38 @@ get_vh_services(Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-process_sm_iq_items(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- case is_presence_subscribed(From, To) of
- true ->
- Host = To#jid.lserver,
- Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
- case ejabberd_hooks:run_fold(disco_sm_items, Host,
- empty, [From, To, Node, Lang])
- of
- {result, Items} ->
- ANode = case Node of
- <<"">> -> [];
- _ -> [{<<"node">>, Node}]
- end,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_DISCO_ITEMS}
- | ANode],
- children = Items}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end;
- false ->
- Txt = <<"Not subscribed">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
- end
+-spec process_sm_iq_items(iq()) -> iq().
+process_sm_iq_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_sm_iq_items(#iq{type = get, lang = Lang,
+ from = From, to = To,
+ sub_els = [#disco_items{node = Node}]} = IQ) ->
+ case is_presence_subscribed(From, To) of
+ true ->
+ Host = To#jid.lserver,
+ case ejabberd_hooks:run_fold(disco_sm_items, Host,
+ empty, [From, To, Node, Lang]) of
+ {result, Items} ->
+ xmpp:make_iq_result(
+ IQ, #disco_items{node = Node, items = Items});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end;
+ false ->
+ Txt = <<"Not subscribed">>,
+ xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
+-spec get_sm_items({error, stanza_error()} | {result, [disco_item()]} | empty,
+ jid(), jid(),
+ binary(), binary()) ->
+ {error, stanza_error()} | {result, [disco_item()]}.
get_sm_items({error, _Error} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
get_sm_items(Acc, From,
- #jid{user = User, server = Server} = To, <<>>, _Lang) ->
+ #jid{user = User, server = Server} = To, <<"">>, _Lang) ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
@@ -357,12 +321,13 @@ get_sm_items(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
- {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
+ {LTo, LSTo} -> {error, xmpp:err_item_not_found()};
_ ->
Txt = <<"Query to another users is forbidden">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+ {error, xmpp:err_not_allowed(Txt, Lang)}
end.
+-spec is_presence_subscribed(jid(), jid()) -> boolean().
is_presence_subscribed(#jid{luser = User, lserver = Server},
#jid{luser = User, lserver = Server}) -> true;
is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
@@ -377,86 +342,68 @@ is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
ejabberd_hooks:run_fold(roster_get, ToServer, [],
[{ToUser, ToServer}])).
-process_sm_iq_info(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- case is_presence_subscribed(From, To) of
- true ->
- Host = To#jid.lserver,
- 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, [],
+-spec process_sm_iq_info(iq()) -> iq().
+process_sm_iq_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_sm_iq_info(#iq{type = get, lang = Lang,
+ from = From, to = To,
+ sub_els = [#disco_info{node = Node}]} = IQ) ->
+ case is_presence_subscribed(From, To) of
+ true ->
+ Host = To#jid.lserver,
+ Identity = ejabberd_hooks:run_fold(disco_sm_identity,
+ Host, [],
[From, To, Node, Lang]),
- case ejabberd_hooks:run_fold(disco_sm_features, Host,
- empty, [From, To, Node, Lang])
- of
- {result, Features} ->
- ANode = case Node of
- <<"">> -> [];
- _ -> [{<<"node">>, Node}]
- end,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_DISCO_INFO}
- | ANode],
- children =
- Identity ++ Info ++
- features_to_xml(Features)}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end;
- false ->
- Txt = <<"Not subscribed">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
- end
+ 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
+ {result, Features} ->
+ xmpp:make_iq_result(IQ, #disco_info{node = Node,
+ identities = Identity,
+ xdata = Info,
+ features = Features});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end;
+ false ->
+ Txt = <<"Not subscribed">>,
+ xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
+-spec get_sm_identity([identity()], jid(), jid(),
+ binary(), binary()) -> [identity()].
get_sm_identity(Acc, _From,
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
Acc ++
case ejabberd_auth:is_user_exists(LUser, LServer) of
true ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"account">>},
- {<<"type">>, <<"registered">>}],
- children = []}];
+ [#identity{category = <<"account">>, type = <<"registered">>}];
_ -> []
end.
+-spec get_sm_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
get_sm_features(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
- {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
+ {LTo, LSTo} -> {error, xmpp:err_item_not_found()};
_ ->
Txt = <<"Query to another users is forbidden">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+ {error, xmpp:err_not_allowed(Txt, Lang)}
end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
+-spec get_user_resources(binary(), binary()) -> [disco_item()].
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
- lists:map(fun (R) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<User/binary, "@", Server/binary, "/",
- R/binary>>},
- {<<"name">>, User}],
- children = []}
- end,
- lists:sort(Rs)).
+ [#disco_item{jid = jid:make(User, Server, Resource), name = User}
+ || Resource <- lists:sort(Rs)].
+-spec transform_module_options(gen_mod:opts()) -> gen_mod:opts().
transform_module_options(Opts) ->
lists:map(
fun({server_info, Infos}) ->
@@ -477,27 +424,22 @@ transform_module_options(Opts) ->
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
-get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> ->
+-spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
+ ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
+get_info(_A, Host, Mod, Node, _Lang) when is_atom(Mod), Node == <<"">> ->
Module = case Mod of
undefined -> ?MODULE;
_ -> Mod
end,
- Serverinfo_fields = get_fields_xml(Host, Module),
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children =
- [#xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
- ++ Serverinfo_fields}];
+ [#xdata{type = result,
+ fields = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>,
+ values = [?NS_SERVERINFO]}
+ | get_fields(Host, Module)]}];
get_info(Acc, _, _, _Node, _) -> Acc.
-get_fields_xml(Host, Module) ->
+-spec get_fields(binary(), module()) -> [xdata_field()].
+get_fields(Host, Module) ->
Fields = gen_mod:get_module_opt(
Host, ?MODULE, server_info,
fun(L) ->
@@ -509,31 +451,17 @@ get_fields_xml(Host, Module) ->
{Mods, Name, URLs}
end, L)
end, []),
- Fields_good = lists:filter(fun ({Modules, _, _}) ->
- case Modules of
- all -> true;
- Modules ->
- lists:member(Module, Modules)
- end
- end,
- Fields),
- fields_to_xml(Fields_good).
-
-fields_to_xml(Fields) ->
- [field_to_xml(Field) || Field <- Fields].
-
-field_to_xml({_, Var, Values}) ->
- Values_xml = values_to_xml(Values),
- #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
- children = Values_xml}.
-
-values_to_xml(Values) ->
- lists:map(fun (Value) ->
- #xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}
- end,
- Values).
-
+ Fields1 = lists:filter(fun ({Modules, _, _}) ->
+ case Modules of
+ all -> true;
+ Modules ->
+ lists:member(Module, Modules)
+ end
+ end,
+ Fields),
+ [#xdata_field{var = Var, values = Values} || {_, Var, Values} <- Fields1].
+
+-spec depends(binary(), gen_mod:opts()) -> [].
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index da3f5cf0f..e7d64dd67 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -42,7 +42,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state, {host = <<"">> :: binary()}).
@@ -118,10 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of
<<"">> ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = xmpp:get_lang(Packet),
Txt = <<"User part of JID in 'from' is empty">>,
- jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
+ xmpp:make_error(
+ Packet, xmpp:err_bad_request(Txt, Lang));
_ -> Packet
end,
do_client_version(disabled, To, From),
@@ -168,37 +168,27 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% using exactly the same JID. We add a (mostly) random resource to
%% try to guarantee that the received response matches the request sent.
%% Finally, the received response is printed in the ejabberd log file.
+
+%% THIS IS **NOT** HOW TO WRITE ejabberd CODE. THIS CODE IS RETARDED.
+
do_client_version(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) ->
- ToS = jid:to_string(To),
- Random_resource =
- iolist_to_binary(integer_to_list(randoms:uniform(100000))),
+ Random_resource = randoms:get_string(),
From2 = From#jid{resource = Random_resource,
lresource = Random_resource},
- Packet = #xmlel{name = <<"iq">>,
- attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_VERSION}],
- children = []}]},
+ ID = randoms:get_string(),
+ Packet = #iq{from = From, to = To, type = get,
+ id = randoms:get_string(),
+ sub_els = [#version{}]},
ejabberd_router:route(From2, To, Packet),
- Els = receive
- {route, To, From2, IQ} ->
- #xmlel{name = <<"query">>, children = List} =
- fxml:get_subtag(IQ, <<"query">>),
- List
- after 5000 -> % Timeout in miliseconds: 5 seconds
- []
- end,
- Values = [{Name, Value}
- || #xmlel{name = Name, attrs = [],
- children = [{xmlcdata, Value}]}
- <- Els],
- Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
- || {N, V} <- Values],
- Values_string2 = iolist_to_binary(Values_string1),
- ?INFO_MSG("Information of the client: ~s~s",
- [ToS, Values_string2]).
+ receive
+ {route, To, From2,
+ #iq{id = ID, type = result, sub_els = [#version{} = V]}} ->
+ ?INFO_MSG("Version of the client ~s:~n~s",
+ [jid:to_string(To), xmpp:pp(V)])
+ after 5000 -> % Timeout in miliseconds: 5 seconds
+ []
+ end.
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl
index c57ac21b0..cc3b4bf7f 100644
--- a/src/mod_fail2ban.erl
+++ b/src/mod_fail2ban.erl
@@ -52,6 +52,8 @@ start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+-spec c2s_auth_result(boolean(), binary(), binary(),
+ {inet:ip_address(), non_neg_integer()}) -> ok.
c2s_auth_result(false, _User, LServer, {Addr, _Port}) ->
case is_whitelisted(LServer, Addr) of
true ->
@@ -71,11 +73,15 @@ c2s_auth_result(false, _User, LServer, {Addr, _Port}) ->
ets:insert(failed_auth, {Addr, N+1, UnbanTS, MaxFailures});
[] ->
ets:insert(failed_auth, {Addr, 1, UnbanTS, MaxFailures})
- end
+ end,
+ ok
end;
c2s_auth_result(true, _User, _Server, _AddrPort) ->
ok.
+-spec check_bl_c2s({true, binary(), binary()} | false,
+ {inet:ip_address(), non_neg_integer()},
+ binary()) -> {stop, {true, binary(), binary()}} | false.
check_bl_c2s(_Acc, Addr, Lang) ->
case ets:lookup(failed_auth, Addr) of
[{Addr, N, TS, MaxFailures}] when N >= MaxFailures ->
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 491383769..3700060cb 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -77,7 +77,7 @@
-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
@@ -134,28 +134,28 @@ depends(_Host, _Opts) ->
extract_auth(#request{auth = HTTPAuth, ip = {IP, _}}) ->
Info = case HTTPAuth of
- {SJID, Pass} ->
- case jid:from_string(SJID) of
+ {SJID, Pass} ->
+ case jid:from_string(SJID) of
#jid{luser = User, lserver = Server} ->
- case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
#{usr => {User, Server, <<"">>}, caller_server => Server};
false ->
{error, invalid_auth}
- end;
- _ ->
+ end;
+ _ ->
{error, invalid_auth}
- end;
- {oauth, Token, _} ->
+ end;
+ {oauth, Token, _} ->
case ejabberd_oauth:check_token(Token) of
{ok, {U, S}, Scope} ->
#{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S};
{false, Reason} ->
{error, Reason}
- end;
- _ ->
+ end;
+ _ ->
#{}
- end,
+ end,
case Info of
Map when is_map(Map) ->
Map#{caller_module => ?MODULE, ip => IP};
@@ -186,9 +186,9 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
throw:{error, unknown_command} ->
json_format({404, 44, <<"Command not found.">>});
_:{error,{_,invalid_json}} = _Err ->
- ?DEBUG("Bad Request: ~p", [_Err]),
- badrequest_response(<<"Invalid JSON input">>);
- _:_Error ->
+ ?DEBUG("Bad Request: ~p", [_Err]),
+ badrequest_response(<<"Invalid JSON input">>);
+ _:_Error ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
@@ -247,11 +247,11 @@ extract_args(Data) ->
get_api_version(#request{path = Path}) ->
get_api_version(lists:reverse(Path));
get_api_version([<<"v", String/binary>> | Tail]) ->
- case catch jlib:binary_to_integer(String) of
- N when is_integer(N) ->
- N;
- _ ->
- get_api_version(Tail)
+ case catch binary_to_integer(String) of
+ N when is_integer(N) ->
+ N;
+ _ ->
+ get_api_version(Tail)
end;
get_api_version([_Head | Tail]) ->
get_api_version(Tail);
@@ -273,7 +273,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
fun ({Key, binary}, Acc) ->
[{Key, <<>>}|Acc];
({Key, string}, Acc) ->
- [{Key, <<>>}|Acc];
+ [{Key, ""}|Acc];
({Key, integer}, Acc) ->
[{Key, 0}|Acc];
({Key, {list, _}}, Acc) ->
@@ -295,7 +295,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{401, jlib:atom_to_binary(Why)};
throw:{not_allowed, Msg} ->
{401, iolist_to_binary(Msg)};
- throw:{error, account_unprivileged} ->
+ throw:{error, account_unprivileged} ->
{403, 31, <<"Command need to be run with admin priviledge.">>};
throw:{error, access_rules_unauthorized} ->
{403, 32, <<"AccessRules: Account associated to token does not have the right to perform the operation.">>};
@@ -406,10 +406,10 @@ format_arg(Elements, {list, ElementsDef})
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(Arg, string) when is_list(Arg) -> Arg;
+format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg);
format_arg(undefined, binary) -> <<>>;
-format_arg(undefined, string) -> <<>>;
+format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
throw({invalid_parameter,
@@ -431,24 +431,24 @@ match(Args, Spec) ->
format_command_result(Cmd, Auth, Result, Version) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
- {{_, rescode}, V} when V == true; V == ok ->
- {200, 0};
- {{_, rescode}, _} ->
- {200, 1};
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, 0};
+ {{_, rescode}, _} ->
+ {200, 1};
{_, {error, ErrorAtom, Code, Msg}} ->
format_error_result(ErrorAtom, Code, Msg);
{{_, restuple}, {V, Text}} when V == true; V == ok ->
{200, iolist_to_binary(Text)};
{{_, restuple}, {ErrorAtom, Msg}} ->
format_error_result(ErrorAtom, 0, Msg);
- {{_, {list, _}}, _V} ->
- {_, L} = format_result(Result, ResultFormat),
- {200, L};
- {{_, {tuple, _}}, _V} ->
- {_, T} = format_result(Result, ResultFormat),
- {200, T};
- _ ->
- {200, {[format_result(Result, ResultFormat)]}}
+ {{_, {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}) ->
@@ -503,8 +503,8 @@ unauthorized_response() ->
invalid_token_response() ->
json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
-outofscope_response() ->
- json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
+%% outofscope_response() ->
+%% json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
badrequest_response() ->
badrequest_response(<<"400 Bad Request">>).
diff --git a/src/mod_http_bind.erl b/src/mod_http_bind.erl
index 9a3a379f7..68500f2c4 100644
--- a/src/mod_http_bind.erl
+++ b/src/mod_http_bind.erl
@@ -42,7 +42,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -89,7 +89,7 @@ stop(_Host) ->
setup_database() ->
migrate_database(),
- mnesia:create_table(http_bind,
+ ejabberd_mnesia:create(?MODULE, http_bind,
[{ram_copies, [node()]},
{attributes, record_info(fields, http_bind)}]).
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index 37e02edd8..a896cb8b4 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -51,15 +51,12 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
-
--include("jlib.hrl").
-
-include_lib("kernel/include/file.hrl").
-record(state,
{host, docroot, accesslog, accesslogfd,
directory_indices, custom_headers, default_content_type,
- content_types = []}).
+ content_types = [], user_access = none}).
-define(PROCNAME, ejabberd_mod_http_fileserver).
@@ -136,7 +133,8 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
try initialize(Host, Opts) of
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
- CustomHeaders, DefaultContentType, ContentTypes} ->
+ CustomHeaders, DefaultContentType, ContentTypes,
+ UserAccess} ->
{ok, #state{host = Host,
accesslog = AccessLog,
accesslogfd = AccessLogFD,
@@ -144,7 +142,8 @@ init([Host, Opts]) ->
directory_indices = DirectoryIndices,
custom_headers = CustomHeaders,
default_content_type = DefaultContentType,
- content_types = ContentTypes}}
+ content_types = ContentTypes,
+ user_access = UserAccess}}
catch
throw:Reason ->
{stop, Reason}
@@ -168,7 +167,15 @@ initialize(Host, Opts) ->
[]),
DefaultContentType = gen_mod:get_opt(default_content_type, Opts,
fun iolist_to_binary/1,
- ?DEFAULT_CONTENT_TYPE),
+ ?DEFAULT_CONTENT_TYPE),
+ UserAccess0 = gen_mod:get_opt(must_authenticate_with, Opts,
+ mod_opt_type(must_authenticate_with),
+ []),
+ UserAccess = case UserAccess0 of
+ [] -> none;
+ _ ->
+ dict:from_list(UserAccess0)
+ end,
ContentTypes = build_list_content_types(
gen_mod:get_opt(content_types, Opts,
fun(L) when is_list(L) ->
@@ -183,7 +190,7 @@ initialize(Host, Opts) ->
[str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes],
<<", ">>)]),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
- CustomHeaders, DefaultContentType, ContentTypes}.
+ CustomHeaders, DefaultContentType, ContentTypes, UserAccess}.
%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT]
@@ -249,10 +256,11 @@ try_open_log(FN, Host) ->
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
-handle_call({serve, LocalPath}, _From, State) ->
- Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices,
+handle_call({serve, LocalPath, Auth}, _From, State) ->
+ Reply = serve(LocalPath, Auth, State#state.docroot, State#state.directory_indices,
State#state.custom_headers,
- State#state.default_content_type, State#state.content_types),
+ State#state.default_content_type, State#state.content_types,
+ State#state.user_access),
{reply, Reply, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -308,9 +316,9 @@ code_change(_OldVsn, State, _Extra) ->
%% @doc Handle an HTTP request.
%% LocalPath is the part of the requested URL path that is "local to the module".
%% Returns the page to be sent back to the client and/or HTTP status code.
-process(LocalPath, Request) ->
+process(LocalPath, #request{host = Host, auth = Auth} = Request) ->
?DEBUG("Requested ~p", [LocalPath]),
- try gen_server:call(get_proc_name(Request#request.host), {serve, LocalPath}) of
+ try gen_server:call(get_proc_name(Host), {serve, LocalPath, Auth}) of
{FileSize, Code, Headers, Contents} ->
add_to_log(FileSize, Code, Request),
{Code, Headers, Contents}
@@ -321,21 +329,38 @@ process(LocalPath, Request) ->
ejabberd_web:error(not_found)
end.
-serve(LocalPath, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType, ContentTypes) ->
- FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
- case file:read_file_info(FileName) of
- {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
- {ok, #file_info{type = directory}} -> serve_index(FileName,
- DirectoryIndices,
- CustomHeaders,
- DefaultContentType,
- ContentTypes);
- {ok, FileInfo} -> serve_file(FileInfo, FileName,
- CustomHeaders,
- DefaultContentType,
- ContentTypes)
+
+serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType,
+ ContentTypes, UserAccess) ->
+ CanProceed = case {UserAccess, Auth} of
+ {none, _} -> true;
+ {_, {User, Pass}} ->
+ case dict:find(User, UserAccess) of
+ {ok, Pass} -> true;
+ _ -> false
+ end;
+ _ ->
+ false
+ end,
+ case CanProceed of
+ true ->
+ FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
+ case file:read_file_info(FileName) of
+ {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
+ {ok, #file_info{type = directory}} -> serve_index(FileName,
+ DirectoryIndices,
+ CustomHeaders,
+ DefaultContentType,
+ ContentTypes);
+ {ok, FileInfo} -> serve_file(FileInfo, FileName,
+ CustomHeaders,
+ DefaultContentType,
+ ContentTypes)
+ end;
+ _ ->
+ ?HTTP_ERR_FORBIDDEN
end.
%% Troll through the directory indices attempting to find one which
@@ -469,6 +494,14 @@ mod_opt_type(default_content_type) ->
mod_opt_type(directory_indices) ->
fun (L) when is_list(L) -> L end;
mod_opt_type(docroot) -> fun (A) -> A end;
+mod_opt_type(must_authenticate_with) ->
+ fun (L) when is_list(L) ->
+ lists:map(fun(UP) when is_binary(UP) ->
+ [K, V] = binary:split(UP, <<":">>),
+ {K, V}
+ end, L)
+ end;
mod_opt_type(_) ->
[accesslog, content_types, custom_headers,
- default_content_type, directory_indices, docroot].
+ default_content_type, directory_indices, docroot,
+ must_authenticate_with].
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index b166f2b66..37eaad27a 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -35,7 +35,7 @@
-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(STR_TO_INT(Str, B), binary_to_integer(iolist_to_binary(Str), B)).
-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
-define(CONTENT_TYPES,
[{<<".avi">>, <<"video/avi">>},
@@ -92,7 +92,7 @@
-include("ejabberd.hrl").
-include("ejabberd_http.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-record(state,
@@ -360,9 +360,9 @@ handle_cast(Request, 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
+handle_info({route, From, To, #iq{} = Packet}, State) ->
+ IQ = xmpp:decode_els(Packet),
+ {Reply, NewState} = case process_iq(From, IQ, State) of
R when is_record(R, iq) ->
{R, State};
{R, S} ->
@@ -371,7 +371,7 @@ handle_info({route, From, To, #xmlel{name = <<"iq">>} = Stanza}, State) ->
{none, State}
end,
if Reply /= none ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(Reply));
+ ejabberd_router:route(To, From, Reply);
true ->
ok
end,
@@ -531,100 +531,58 @@ expand_host(Subject, Host) ->
%% XMPP request handling.
--spec process_iq(jid(), iq_request() | reply | invalid, state())
- -> {iq_reply(), state()} | iq_reply() | not_request.
+-spec process_iq(jid(), iq(), state()) -> {iq(), state()} | iq() | not_request.
process_iq(_From,
- #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
+ #iq{type = get, lang = Lang, sub_els = [#disco_info{}]} = 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 ->
+ xmpp:make_iq_result(IQ, iq_disco_info(ServerHost, Lang, Name, AddInfo));
+process_iq(From, #iq{type = get, lang = Lang,
+ sub_els = [#upload_request{filename = File,
+ size = Size,
+ 'content-type' = CType,
+ xmlns = XMLNS}]} = IQ,
+ #state{server_host = ServerHost, access = Access} = State) ->
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;
+ ContentType = yield_content_type(CType),
+ 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),
+ NewSlot = mk_slot(Slot, State, XMLNS),
+ {xmpp:make_iq_result(IQ, NewSlot), NewState};
+ {ok, PutURL, GetURL} ->
+ Slot = mk_slot(PutURL, GetURL, XMLNS),
+ xmpp:make_iq_result(IQ, Slot);
{error, Error} ->
- ?DEBUG("Cannot parse request from ~s",
- [jid:to_string(From)]),
- IQ#iq{type = error, sub_el = [SubEl, Error]}
+ xmpp:make_error(IQ, Error)
end;
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:to_string(From)]),
Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
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) ->
+process_iq(_From, #iq{type = T} = IQ, _State) when T == get; T == set ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed());
+process_iq(_From, #iq{}, _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;
- _ ->
- Text = <<"No or invalid XML namespace">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Text)}
- 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.">>,
+ Text = {<<"File larger than ~w bytes">>, [MaxSize]},
?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
[File, jid:to_string(JID), Size]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, Text)};
+ {error, xmpp:err_not_acceptable(Text, Lang)};
create_slot(#state{service_url = undefined,
jid_in_url = JIDinURL,
secret_length = SecretLength,
@@ -642,8 +600,8 @@ create_slot(#state{service_url = undefined,
[jid:to_string(JID), File]),
{ok, [UserStr, RandStr, FileStr]};
deny ->
- {error, ?ERR_SERVICE_UNAVAILABLE};
- #xmlel{} = Error ->
+ {error, xmpp:err_service_unavailable()};
+ #stanza_error{} = Error ->
{error, Error}
end;
create_slot(#state{service_url = ServiceURL},
@@ -651,7 +609,7 @@ create_slot(#state{service_url = ServiceURL},
Lang) ->
Options = [{body_format, binary}, {full_result, false}],
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
- SizeStr = jlib:integer_to_binary(Size),
+ SizeStr = integer_to_binary(Size),
GetRequest = binary_to_list(ServiceURL) ++
"?jid=" ++ ?URL_ENC(jid:to_string({U, S, <<"">>})) ++
"&name=" ++ ?URL_ENC(File) ++
@@ -669,28 +627,28 @@ create_slot(#state{service_url = ServiceURL},
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
[jid:to_string(JID), ServiceURL, Lines]),
Txt = <<"Failed to parse HTTP response">>,
- {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
+ {error, xmpp:err_service_unavailable(Txt, Lang)}
end;
{ok, {402, _Body}} ->
?INFO_MSG("Got status code 402 for ~s from <~s>",
[jid:to_string(JID), ServiceURL]),
- {error, ?ERR_RESOURCE_CONSTRAINT};
+ {error, xmpp: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};
+ {error, xmpp: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};
+ {error, xmpp: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, xmpp: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}
+ {error, xmpp:err_service_unavailable()}
end.
-spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state().
@@ -710,19 +668,15 @@ 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().
+-spec mk_slot(slot(), state(), binary()) -> upload_slot();
+ (binary(), binary(), binary()) -> upload_slot().
-slot_el(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) ->
+mk_slot(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}]}]}.
+ mk_slot(PutURL, GetURL, XMLNS);
+mk_slot(PutURL, GetURL, XMLNS) ->
+ #upload_slot{get = GetURL, put = PutURL, xmlns = XMLNS}.
-spec make_user_string(jid(), sha1 | node) -> binary().
@@ -762,44 +716,30 @@ map_int_to_char(N) when N =< 61 -> N + 61. % Lower-case character.
yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
yield_content_type(Type) -> Type.
--spec iq_disco_info(binary(), binary(), binary()) -> [xmlel()].
+-spec iq_disco_info(binary(), binary(), binary(), [xdata()]) -> [xmlel()].
-iq_disco_info(Host, Lang, Name) ->
+iq_disco_info(Host, Lang, Name, AddInfo) ->
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 ->
- [];
+ AddInfo;
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}]
+ MaxSizeStr = integer_to_binary(MaxSize),
+ Fields = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>,
+ values = [?NS_HTTP_UPLOAD]},
+ #xdata_field{var = <<"max-file-size">>,
+ values = [MaxSizeStr]}],
+ [#xdata{type = result, fields = Fields}|AddInfo]
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].
+ #disco_info{identities = [#identity{category = <<"store">>,
+ type = <<"file">>,
+ name = translate:translate(Lang, Name)}],
+ features = [?NS_HTTP_UPLOAD, ?NS_HTTP_UPLOAD_OLD],
+ xdata = Form}.
%% HTTP request handling.
@@ -984,20 +924,14 @@ convert(Path, #media_info{type = T, width = W, height = H}) ->
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.
+ xmpp:encode(
+ case identify(Path) of
+ {ok, #media_info{height = H, width = W}} ->
+ #thumbnail{'media-type' = ContentType, uri = URI,
+ height = H, width = W};
+ pass ->
+ #thumbnail{uri = URI, 'media-type' = ContentType}
+ end).
%%--------------------------------------------------------------------
%% Remove user.
diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl
index 3051a4e87..9522cd3d4 100644
--- a/src/mod_http_upload_quota.erl
+++ b/src/mod_http_upload_quota.erl
@@ -53,7 +53,7 @@
%% ejabberd_hooks callback.
-export([handle_slot_request/5]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("logger.hrl").
-include_lib("kernel/include/file.hrl").
@@ -263,8 +263,8 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
%% ejabberd_hooks callback.
%%--------------------------------------------------------------------
--spec handle_slot_request(term(), jid(), binary(), non_neg_integer(), binary())
- -> term().
+-spec handle_slot_request(allow | deny, jid(), binary(),
+ non_neg_integer(), binary()) -> allow | deny.
handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size,
_Lang) ->
diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl
index 4f54ecd79..ab17a8891 100644
--- a/src/mod_ip_blacklist.erl
+++ b/src/mod_ip_blacklist.erl
@@ -89,9 +89,9 @@ loop(_State) -> receive stop -> ok end.
%% TODO: Support comment lines starting by %
update_bl_c2s() ->
?INFO_MSG("Updating C2S Blacklist", []),
- case httpc:request(?BLC2S) of
+ case p1_http:get(?BLC2S) of
{ok, 200, _Headers, Body} ->
- IPs = str:tokens(Body, <<"\n">>),
+ IPs = str:tokens(iolist_to_binary(Body), <<"\n">>),
ets:delete_all_objects(bl_c2s),
lists:foreach(fun (IP) ->
ets:insert(bl_c2s,
@@ -109,6 +109,10 @@ update_bl_c2s() ->
%% Return: false: IP not blacklisted
%% true: IP is blacklisted
%% IPV4 IP tuple:
+-spec is_ip_in_c2s_blacklist(
+ {true, binary(), binary()} | false,
+ {inet:ip_address(), non_neg_integer()},
+ binary()) -> {stop, {true, binary(), binary()}} | false.
is_ip_in_c2s_blacklist(_Val, IP, Lang) when is_tuple(IP) ->
BinaryIP = jlib:ip_to_list(IP),
case ets:lookup(bl_c2s, BinaryIP) of
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index e2203a306..f43a6653d 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -34,7 +34,8 @@
%% API
-export([start_link/2, start/2, stop/1, export/1, import/1,
import/3, closed_connection/3, get_connection_params/3,
- data_to_binary/2]).
+ data_to_binary/2, process_disco_info/1, process_disco_items/1,
+ process_register/1, process_vcard/1, process_command/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -42,11 +43,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
-
--include("adhoc.hrl").
-
+-include("xmpp.hrl").
-include("mod_irc.hrl").
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
@@ -69,10 +66,8 @@
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #irc_custom{}) -> ok | pass.
--callback get_data(binary(), binary(), {binary(), binary()}) ->
- error | empty | irc_data().
--callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) ->
- {atomic, any()}.
+-callback get_data(binary(), binary(), jid()) -> error | empty | irc_data().
+-callback set_data(binary(), binary(), jid(), irc_data()) -> {atomic, any()}.
%%====================================================================
%% API
@@ -125,6 +120,18 @@ init([Host, Opts]) ->
catch ets:new(irc_connection,
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER,
+ ?MODULE, process_register, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS,
+ ?MODULE, process_command, IQDisc),
ejabberd_router:register_route(MyHost, Host),
{ok,
#state{host = MyHost, server_host = Host,
@@ -176,8 +183,13 @@ handle_info(_Info, State) -> {noreply, State}.
%% 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.
+terminate(_Reason, #state{host = MyHost}) ->
+ ejabberd_router:unregister_route(MyHost),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -203,287 +215,221 @@ stop_supervisor(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-do_route(Host, ServerHost, Access, From, To, Packet) ->
+do_route(Host, ServerHost, Access, From,
+ #jid{luser = LUser, lresource = LResource} = To, Packet) ->
case acl:match_rule(ServerHost, Access, From) of
- allow -> do_route1(Host, ServerHost, From, To, Packet);
- _ ->
- #xmlel{attrs = Attrs} = Packet,
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
- ErrText = <<"Access denied by service policy">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(To, From, Err)
+ allow ->
+ case Packet of
+ #iq{} when LUser == <<"">>, LResource == <<"">> ->
+ ejabberd_router:process_iq(From, To, Packet);
+ #iq{} when LUser == <<"">>, LResource /= <<"">> ->
+ Err = xmpp:err_service_unavailable(),
+ ejabberd_router:route_error(To, From, Packet, Err);
+ _ ->
+ sm_route(Host, ServerHost, From, To, Packet)
+ end;
+ deny ->
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end.
+
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, lang = Lang, to = To,
+ sub_els = [#disco_info{node = Node}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [], [ServerHost, ?MODULE, <<"">>, <<"">>]),
+ case iq_disco(ServerHost, Node, Lang) of
+ undefined ->
+ xmpp:make_iq_result(IQ, #disco_info{});
+ DiscoInfo ->
+ xmpp:make_iq_result(IQ, DiscoInfo#disco_info{xdata = Info})
+ end.
+
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get, lang = Lang, to = To,
+ sub_els = [#disco_items{node = Node}]} = IQ) ->
+ case Node of
+ <<"">> ->
+ xmpp:make_iq_result(IQ, #disco_items{});
+ <<"join">> ->
+ xmpp:make_iq_result(IQ, #disco_items{});
+ <<"register">> ->
+ xmpp:make_iq_result(IQ, #disco_items{});
+ ?NS_COMMANDS ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ xmpp:make_iq_result(
+ IQ, #disco_items{node = Node,
+ items = command_items(ServerHost, Host, Lang)});
+ _ ->
+ Txt = <<"Node not found">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
+ end.
+
+process_register(#iq{type = get, to = To, from = From, lang = Lang} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ case get_form(ServerHost, Host, From, Lang) of
+ {result, Res} ->
+ xmpp:make_iq_result(IQ, Res);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end;
+process_register(#iq{type = set, lang = Lang, to = To, from = From,
+ sub_els = [#register{xdata = #xdata{} = X}]} = IQ) ->
+ case X#xdata.type of
+ cancel ->
+ xmpp:make_iq_result(IQ, #register{});
+ submit ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ case set_form(ServerHost, Host, From, Lang, X) of
+ {result, Res} ->
+ xmpp:make_iq_result(IQ, Res);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end;
+ _ ->
+ Txt = <<"Incorrect value of 'type' attribute">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end;
+process_register(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"No data form found">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
+
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{type = get, lang = Lang} = IQ) ->
+ xmpp:make_iq_result(IQ, iq_get_vcard(Lang)).
+
+process_command(#iq{type = get, lang = Lang} = IQ) ->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_command(#iq{type = set, lang = Lang, to = To, from = From,
+ sub_els = [#adhoc_command{node = Node} = Request]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ case lists:keyfind(Node, 1, commands(ServerHost)) of
+ {_, _, Function} ->
+ try Function(From, To, Request) of
+ ignore ->
+ ignore;
+ {error, Error} ->
+ xmpp:make_error(IQ, Error);
+ Command ->
+ xmpp:make_iq_result(IQ, Command)
+ catch E:R ->
+ ?ERROR_MSG("ad-hoc handler failed: ~p",
+ [{E, {R, erlang:get_stacktrace()}}]),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error())
+ end;
+ _ ->
+ Txt = <<"Node not found">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
end.
-do_route1(Host, ServerHost, From, To, Packet) ->
+sm_route(Host, ServerHost, From, To, Packet) ->
#jid{user = ChanServ, resource = Resource} = To,
- #xmlel{} = Packet,
- case ChanServ of
- <<"">> ->
- case Resource of
- <<"">> ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
- sub_el = SubEl, lang = Lang} =
- IQ ->
- Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
- Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
- [],
- [ServerHost, ?MODULE,
- <<"">>, <<"">>]),
- case iq_disco(ServerHost, Node, Lang) of
- [] ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children = []}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- DiscoInfo ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children =
- DiscoInfo ++ Info}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res))
- end;
- #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS,
- sub_el = SubEl, lang = Lang} =
- IQ ->
- Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
- case Node of
- <<>> ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- XMLNS}],
- children = []}]},
- Res = jlib:iq_to_xml(ResIQ);
- <<"join">> ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- XMLNS}],
- children = []}]},
- Res = jlib:iq_to_xml(ResIQ);
- <<"register">> ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- XMLNS}],
- children = []}]},
- Res = jlib:iq_to_xml(ResIQ);
- ?NS_COMMANDS ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS},
- {<<"node">>, Node}],
- children =
- command_items(ServerHost,
- Host,
- Lang)}]},
- Res = jlib:iq_to_xml(ResIQ);
- _ ->
- Txt = <<"Node not found">>,
- Res = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt))
- end,
- ejabberd_router:route(To, From, Res);
- #iq{xmlns = ?NS_REGISTER} = IQ ->
- process_register(ServerHost, Host, From, To, IQ);
- #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
- lang = Lang} =
- IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang,
- sub_el = SubEl} =
- IQ ->
- Request = adhoc:parse_request(IQ),
- case lists:keysearch(Request#adhoc_request.node, 1,
- commands(ServerHost))
- of
- {value, {_, _, Function}} ->
- case catch Function(From, To, Request) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p~nfor ad-hoc handler of ~p",
- [Reason, {From, To, IQ}]),
- Res = IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERR_INTERNAL_SERVER_ERROR]};
- ignore -> Res = ignore;
- {error, Error} ->
- Res = IQ#iq{type = error,
- sub_el = [SubEl, Error]};
- Command ->
- Res = IQ#iq{type = result, sub_el = [Command]}
- end,
- if Res /= ignore ->
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- true -> ok
- end;
- _ ->
- Txt = <<"Node not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
- end;
- #iq{} = _IQ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ -> ok
- end;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
- ejabberd_router:route(To, From, Err)
- end;
- _ ->
- case str:tokens(ChanServ, <<"%">>) of
- [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] ->
- case ets:lookup(irc_connection, {From, Server, Host}) of
- [] ->
- ?DEBUG("open new connection~n", []),
- {Username, Encoding, Port, Password} =
- get_connection_params(Host, ServerHost, From, Server),
- ConnectionUsername = case Packet of
+ case str:tokens(ChanServ, <<"%">>) of
+ [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] ->
+ case ets:lookup(irc_connection, {From, Server, Host}) of
+ [] ->
+ ?DEBUG("open new connection~n", []),
+ {Username, Encoding, Port, Password} =
+ get_connection_params(Host, ServerHost, From, Server),
+ ConnectionUsername = case Packet of
%% If the user tries to join a
%% chatroom, the packet for sure
%% contains the desired username.
- #xmlel{name = <<"presence">>} ->
- Resource;
+ #presence{} -> Resource;
%% Otherwise, there is no firm
%% conclusion from the packet.
%% Better to use the configured
%% username (which defaults to the
%% 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, Ident, RemoteAddr, RealName, WebircPassword, ?MODULE),
- ets:insert(irc_connection,
- #irc_connection{jid_server_host =
- {From, Server, Host},
- pid = Pid}),
- mod_irc_connection:route_chan(Pid, Channel, Resource,
- Packet),
- ok;
- [R] ->
- Pid = R#irc_connection.pid,
- ?DEBUG("send to process ~p~n", [Pid]),
- mod_irc_connection:route_chan(Pid, Channel, Resource,
- Packet),
- ok
- end;
- _ ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- case str:tokens(ChanServ, <<"!">>) of
- [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
- case ets:lookup(irc_connection, {From, Server, Host}) of
+ 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, Ident, RemoteAddr, RealName,
+ WebircPassword, ?MODULE),
+ ets:insert(irc_connection,
+ #irc_connection{
+ jid_server_host = {From, Server, Host},
+ pid = Pid}),
+ mod_irc_connection:route_chan(Pid, Channel, Resource, Packet);
+ [R] ->
+ Pid = R#irc_connection.pid,
+ ?DEBUG("send to process ~p~n", [Pid]),
+ mod_irc_connection:route_chan(Pid, Channel, Resource, Packet)
+ end;
+ _ ->
+ Lang = xmpp:get_lang(Packet),
+ case str:tokens(ChanServ, <<"!">>) of
+ [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
+ case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
Txt = <<"IRC connection not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
+ Err = xmpp:err_service_unavailable(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err);
[R] ->
Pid = R#irc_connection.pid,
?DEBUG("send to process ~p~n", [Pid]),
- mod_irc_connection:route_nick(Pid, Nick, Packet),
- ok
- end;
- _ ->
- Txt = <<"Failed to parse chanserv">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
- end
- end
+ mod_irc_connection:route_nick(Pid, Nick, Packet)
+ end;
+ _ ->
+ Txt = <<"Failed to parse chanserv">>,
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end
end.
closed_connection(Host, From, Server) ->
ets:delete(irc_connection, {From, Server, Host}).
-iq_disco(_ServerHost, <<>>, Lang) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"conference">>},
- {<<"type">>, <<"irc">>},
- {<<"name">>,
- translate:translate(Lang, <<"IRC Transport">>)}],
- children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUC}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}];
+iq_disco(_ServerHost, <<"">>, Lang) ->
+ #disco_info{
+ identities = [#identity{category = <<"conference">>,
+ type = <<"irc">>,
+ name = translate:translate(Lang, <<"IRC Transport">>)}],
+ features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC,
+ ?NS_REGISTER, ?NS_VCARD, ?NS_COMMANDS]};
iq_disco(ServerHost, Node, Lang) ->
- case lists:keysearch(Node, 1, commands(ServerHost)) of
- {value, {_, Name, _}} ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-node">>},
- {<<"name">>, translate:translate(Lang, Name)}],
- children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_COMMANDS}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_XDATA}], children = []}];
- _ -> []
+ case lists:keyfind(Node, 1, commands(ServerHost)) of
+ {_, Name, _} ->
+ #disco_info{
+ identities = [#identity{category = <<"automation">>,
+ type = <<"command-node">>,
+ name = translate:translate(Lang, Name)}],
+ features = [?NS_COMMANDS, ?NS_XDATA]};
+ _ ->
+ undefined
end.
iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_irc">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd IRC module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
+ Desc = translate:translate(Lang, <<"ejabberd IRC module">>),
+ #vcard_temp{fn = <<"ejabberd/mod_irc">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>}.
command_items(ServerHost, Host, Lang) ->
- lists:map(fun ({Node, Name, _Function}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, Host}, {<<"node">>, Node},
- {<<"name">>,
- translate:translate(Lang, Name)}],
- children = []}
- end,
- commands(ServerHost)).
+ lists:map(fun({Node, Name, _Function}) ->
+ #disco_item{jid = jid:make(Host),
+ node = Node,
+ name = translate:translate(Lang, Name)}
+ end, commands(ServerHost)).
commands(ServerHost) ->
[{<<"join">>, <<"Join channel">>, fun adhoc_join/3},
@@ -494,243 +440,118 @@ commands(ServerHost) ->
adhoc_register(ServerHost, From, To, Request)
end}].
-process_register(ServerHost, Host, From, To,
- #iq{} = IQ) ->
- case catch process_irc_register(ServerHost, Host, From,
- To, IQ)
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
- ResIQ ->
- if ResIQ /= ignore ->
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- true -> ok
- end
- end.
-
-find_xdata_el(#xmlel{children = SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
- children = SubEls}
- | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- #xmlel{name = Name, attrs = Attrs, children = SubEls};
- _ -> find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
-
-process_irc_register(ServerHost, Host, From, _To,
- #iq{type = Type, xmlns = XMLNS, lang = Lang,
- sub_el = SubEl} =
- IQ) ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Txt1 = <<"No data form found">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]};
- #xmlel{attrs = Attrs} ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"cancel">> ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = []}]};
- <<"submit">> ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Txt2 = <<"Incorrect data form">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]};
- _ ->
- Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
- SubEl),
- <<"/">>),
- case set_form(ServerHost, Host, From, Node, Lang,
- XData)
- of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children = Res}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
- end;
- _ ->
- Txt3 = <<"Incorrect value of 'type' attribute">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]}
- end
- end;
- get ->
- Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
- <<"/">>),
- case get_form(ServerHost, Host, From, Node, Lang) of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = Res}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
- end.
-
get_data(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_data(LServer, Host, From).
-get_form(ServerHost, Host, From, [], Lang) ->
+get_form(ServerHost, Host, From, Lang) ->
#jid{user = User, server = Server} = From,
DefaultEncoding = get_default_encoding(Host),
Customs = case get_data(ServerHost, Host, From) of
- error ->
+ error ->
Txt1 = <<"Database failure">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)};
- empty -> {User, []};
- Data -> get_username_and_connection_params(Data)
+ {error, xmpp:err_internal_server_error(Txt1, Lang)};
+ empty -> {User, []};
+ Data -> get_username_and_connection_params(Data)
end,
case Customs of
- {error, _Error} -> Customs;
- {Username, ConnectionsParams} ->
- {result,
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "configure mod_irc settings">>)}]},
- #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Registration in mod_irc for ">>))/binary,
- User/binary, "@", Server/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Enter username, encodings, ports and "
- "passwords you wish to use for connecting "
- "to IRC servers">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"IRC Username">>)},
- {<<"var">>, <<"username">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Username}]}]},
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"fixed">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"If you want to specify"
- " different ports, "
- "passwords, encodings "
- "for IRC servers, "
- "fill this list with "
- "values in format "
- "'{\"irc server\", "
- "\"encoding\", port, "
- "\"password\"}'. "
- "By default this "
- "service use \"~s\" "
- "encoding, port ~p, "
- "empty password.">>),
- [DefaultEncoding,
- ?DEFAULT_IRC_PORT]))}]}]},
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"fixed">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Example: [{\"irc.lucky.net\", \"koi8-r\", "
- "6667, \"secret\"}, {\"vendetta.fef.net\", "
- "\"iso8859-1\", 7000}, {\"irc.sometestserver.n"
- "et\", \"utf-8\"}].">>)}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-multi">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Connections parameters">>)},
- {<<"var">>, <<"connections_params">>}],
- children =
- lists:map(fun (S) ->
- #xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, S}]}
- end,
- str:tokens(list_to_binary(
- io_lib:format(
- "~p.",
- [conn_params_to_list(
- ConnectionsParams)])),
- <<"\n">>))}]}]}
- end;
-get_form(_ServerHost, _Host, _, _, _Lang) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+ {error, _Error} ->
+ Customs;
+ {Username, ConnectionsParams} ->
+ Fs = [#xdata_field{type = 'text-single',
+ label = translate:translate(Lang, <<"IRC Username">>),
+ var = <<"username">>,
+ values = [Username]},
+ #xdata_field{type = fixed,
+ values = [str:format(
+ translate:translate(
+ Lang,
+ <<"If you want to specify"
+ " different ports, "
+ "passwords, encodings "
+ "for IRC servers, "
+ "fill this list with "
+ "values in format "
+ "'{\"irc server\", "
+ "\"encoding\", port, "
+ "\"password\"}'. "
+ "By default this "
+ "service use \"~s\" "
+ "encoding, port ~p, "
+ "empty password.">>),
+ [DefaultEncoding, ?DEFAULT_IRC_PORT])]},
+ #xdata_field{type = fixed,
+ values = [translate:translate(
+ Lang,
+ <<"Example: [{\"irc.lucky.net\", \"koi8-r\", "
+ "6667, \"secret\"}, {\"vendetta.fef.net\", "
+ "\"iso8859-1\", 7000}, {\"irc.sometestserver.n"
+ "et\", \"utf-8\"}].">>)]},
+ #xdata_field{type = 'text-multi',
+ label = translate:translate(
+ Lang, <<"Connections parameters">>),
+ var = <<"connections_params">>,
+ values = str:tokens(str:format(
+ "~p.",
+ [conn_params_to_list(
+ ConnectionsParams)]),
+ <<"\n">>)}],
+ X = #xdata{type = form,
+ title = <<(translate:translate(
+ Lang, <<"Registration in mod_irc for ">>))/binary,
+ User/binary, "@", Server/binary>>,
+ instructions =
+ [translate:translate(
+ Lang,
+ <<"Enter username, encodings, ports and "
+ "passwords you wish to use for connecting "
+ "to IRC servers">>)],
+ fields = Fs},
+ {result,
+ #register{instructions =
+ translate:translate(Lang,
+ <<"You need an x:data capable client to "
+ "configure mod_irc settings">>),
+ xdata = X}}
+ end.
set_data(ServerHost, Host, From, Data) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LServer, Host, From, data_to_binary(From, Data)).
-set_form(ServerHost, Host, From, [], Lang, XData) ->
- case {lists:keysearch(<<"username">>, 1, XData),
- lists:keysearch(<<"connections_params">>, 1, XData)}
- of
- {{value, {_, [Username]}}, {value, {_, Strings}}} ->
- EncString = lists:foldl(fun (S, Res) ->
- <<Res/binary, S/binary, "\n">>
- end,
- <<"">>, Strings),
- case erl_scan:string(binary_to_list(EncString)) of
- {ok, Tokens, _} ->
- case erl_parse:parse_term(Tokens) of
- {ok, ConnectionsParams} ->
- case set_data(ServerHost, Host, From,
- [{username, Username},
- {connections_params, ConnectionsParams}])
- of
- {atomic, _} -> {result, []};
- _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)}
- end;
- _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)}
- end;
- _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)}
- end;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
-set_form(_ServerHost, _Host, _, _, _Lang, _XData) ->
- {error, ?ERR_SERVICE_UNAVAILABLE}.
+set_form(ServerHost, Host, From, Lang, XData) ->
+ case {xmpp_util:get_xdata_values(<<"username">>, XData),
+ xmpp_util:get_xdata_values(<<"connections_params">>, XData)} of
+ {[Username], [_|_] = Strings} ->
+ EncString = lists:foldl(fun (S, Res) ->
+ <<Res/binary, S/binary, "\n">>
+ end, <<"">>, Strings),
+ case erl_scan:string(binary_to_list(EncString)) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, ConnectionsParams} ->
+ case set_data(ServerHost, Host, From,
+ [{username, Username},
+ {connections_params, ConnectionsParams}]) of
+ {atomic, _} ->
+ {result, undefined};
+ _ ->
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
+ end;
+ _ ->
+ Txt = <<"Parse error">>,
+ {error, xmpp:err_not_acceptable(Txt, Lang)}
+ end;
+ _ ->
+ {error, xmpp:err_not_acceptable(<<"Scan error">>, Lang)}
+ end;
+ _ ->
+ Txt = <<"Incorrect value in data form">>,
+ {error, xmpp:err_not_acceptable(Txt, Lang)}
+ end.
get_connection_params(Host, From, IRCServer) ->
[_ | HostTail] = str:tokens(Host, <<".">>),
@@ -805,212 +626,115 @@ get_connection_params(Host, ServerHost, From,
iolist_to_binary(NewPassword)}
end.
-adhoc_join(_From, _To,
- #adhoc_request{action = <<"cancel">>} = Request) ->
- adhoc:produce_response(Request,
- #adhoc_response{status = canceled});
-adhoc_join(From, To,
- #adhoc_request{lang = Lang, node = _Node,
- action = _Action, xdata = XData} =
- Request) ->
- if XData == false ->
- Form = #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Join IRC channel">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"channel">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"IRC channel (don't put the first #)">>)}],
- children =
- [#xmlel{name = <<"required">>,
- attrs = [], children = []}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"server">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"IRC server">>)}],
- children =
- [#xmlel{name = <<"required">>,
- attrs = [], children = []}]}]},
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,
- elements = [Form]});
+adhoc_join(_From, _To, #adhoc_command{action = cancel} = Request) ->
+ xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled});
+adhoc_join(_From, _To, #adhoc_command{lang = Lang, xdata = undefined} = Request) ->
+ X = #xdata{type = form,
+ title = translate:translate(Lang, <<"Join IRC channel">>),
+ fields = [#xdata_field{var = <<"channel">>,
+ type = 'text-single',
+ label = translate:translate(
+ Lang, <<"IRC channel (don't put the first #)">>),
+ required = true},
+ #xdata_field{var = <<"server">>,
+ type = 'text-single',
+ label = translate:translate(Lang, <<"IRC server">>),
+ required = true}]},
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{status = executing, xdata = X});
+adhoc_join(From, To, #adhoc_command{lang = Lang, xdata = X} = Request) ->
+ Channel = case xmpp_util:get_xdata_values(<<"channel">>, X) of
+ [C] -> C;
+ _ -> false
+ end,
+ Server = case xmpp_util:get_xdata_values(<<"server">>, X) of
+ [S] -> S;
+ _ -> false
+ end,
+ if Channel /= false, Server /= false ->
+ RoomJID = jid:make(<<Channel/binary, "%", Server/binary>>,
+ To#jid.server),
+ Reason = translate:translate(Lang, <<"Join the IRC channel here.">>),
+ BodyTxt = {<<"Join the IRC channel in this Jabber ID: ~s">>,
+ [jid:to_string(RoomJID)]},
+ Invite = #message{
+ body = xmpp:mk_text(BodyTxt, Lang),
+ sub_els = [#muc_user{
+ invites = [#muc_invite{from = From,
+ reason = Reason}]},
+ #x_conference{reason = Reason,
+ jid = RoomJID}]},
+ ejabberd_router:route(RoomJID, From, Invite),
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{status = completed});
true ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- Txt1 = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt1)};
- Fields ->
- Channel = case lists:keysearch(<<"channel">>, 1, Fields)
- of
- {value, {<<"channel">>, [C]}} -> C;
- _ -> false
- end,
- Server = case lists:keysearch(<<"server">>, 1, Fields)
- of
- {value, {<<"server">>, [S]}} -> S;
- _ -> false
- end,
- if Channel /= false, Server /= false ->
- RoomJID = <<Channel/binary, "%", Server/binary, "@",
- (To#jid.server)/binary>>,
- Invite = #xmlel{name = <<"message">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"invite">>,
- attrs =
- [{<<"from">>,
- jid:to_string(From)}],
- children =
- [#xmlel{name
- =
- <<"reason">>,
- attrs
- =
- [],
- children
- =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Join the IRC channel here.">>)}]}]}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XCONFERENCE}],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Join the IRC channel here.">>)}]},
- #xmlel{name = <<"body">>,
- attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Join the IRC channel in this Jabber ID: ~s">>),
- [RoomJID]))}]}]},
- ejabberd_router:route(jid:from_string(RoomJID), From,
- Invite),
- adhoc:produce_response(Request,
- #adhoc_response{status =
- completed});
- true -> {error, ?ERR_BAD_REQUEST}
- end
- end
+ Txt = <<"Missing 'channel' or 'server' in the data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
end.
+-spec adhoc_register(binary(), jid(), jid(), adhoc_command()) ->
+ adhoc_command() | {error, stanza_error()}.
adhoc_register(_ServerHost, _From, _To,
- #adhoc_request{action = <<"cancel">>} = Request) ->
- adhoc:produce_response(Request,
- #adhoc_response{status = canceled});
+ #adhoc_command{action = cancel} = Request) ->
+ xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled});
adhoc_register(ServerHost, From, To,
- #adhoc_request{lang = Lang, node = _Node, xdata = XData,
- action = Action} =
- Request) ->
+ #adhoc_command{lang = Lang, xdata = X,
+ action = Action} = Request) ->
#jid{user = User} = From,
#jid{lserver = Host} = To,
- if XData == false ->
- case get_data(ServerHost, Host, From) of
- error -> Username = User, ConnectionsParams = [];
- empty -> Username = User, ConnectionsParams = [];
- Data ->
- {Username, ConnectionsParams} =
- get_username_and_connection_params(Data)
- end,
- Error = false;
- true ->
- case jlib:parse_xdata_submit(XData) of
- invalid ->
- Txt1 = <<"Incorrect data form">>,
- Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)},
- Username = false,
- ConnectionsParams = false;
- Fields ->
- Username = case lists:keysearch(<<"username">>, 1,
- Fields)
- of
- {value, {<<"username">>, U}} -> U;
- _ -> User
- end,
- ConnectionsParams = parse_connections_params(Fields),
- Error = false
- end
- end,
- if Error /= false -> Error;
- Action == <<"complete">> ->
- case set_data(ServerHost, Host, From,
- [{username, Username},
- {connections_params, ConnectionsParams}])
- of
- {atomic, _} ->
- adhoc:produce_response(Request,
- #adhoc_response{status = completed});
- _ ->
- Txt2 = <<"Database failure">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)}
- end;
+ {Username, ConnectionsParams} =
+ if X == undefined ->
+ case get_data(ServerHost, Host, From) of
+ error -> {User, []};
+ empty -> {User, []};
+ Data -> get_username_and_connection_params(Data)
+ end;
+ true ->
+ {case xmpp_util:get_xdata_values(<<"username">>, X) of
+ [U] -> U;
+ _ -> User
+ end, parse_connections_params(X)}
+ end,
+ if Action == complete ->
+ case set_data(ServerHost, Host, From,
+ [{username, Username},
+ {connections_params, ConnectionsParams}]) of
+ {atomic, _} ->
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{status = completed});
+ _ ->
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
+ end;
true ->
- Form = generate_adhoc_register_form(Lang, Username,
- ConnectionsParams),
- adhoc:produce_response(Request,
- #adhoc_response{status = executing,
- elements = [Form],
- actions =
- [<<"next">>,
- <<"complete">>]})
+ Form = generate_adhoc_register_form(Lang, Username,
+ ConnectionsParams),
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{
+ status = executing,
+ xdata = Form,
+ actions = #adhoc_actions{next = true,
+ complete = true}})
end.
generate_adhoc_register_form(Lang, Username,
ConnectionsParams) ->
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang, <<"IRC settings">>)}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Enter username and encodings you wish "
- "to use for connecting to IRC servers. "
- " Press 'Next' to get more fields to "
- "fill in. Press 'Complete' to save settings.">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"username">>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang, <<"IRC username">>)}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []},
- #xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Username}]}]}]
- ++
- generate_connection_params_fields(Lang,
- ConnectionsParams, 1, [])}.
+ #xdata{type = form,
+ title = translate:translate(Lang, <<"IRC settings">>),
+ instructions = [translate:translate(
+ Lang,
+ <<"Enter username and encodings you wish "
+ "to use for connecting to IRC servers. "
+ " Press 'Next' to get more fields to "
+ "fill in. Press 'Complete' to save settings.">>)],
+ fields = [#xdata_field{
+ var = <<"username">>,
+ type = 'text-single',
+ label = translate:translate(Lang, <<"IRC username">>),
+ required = true,
+ values = [Username]}
+ | generate_connection_params_fields(
+ Lang, ConnectionsParams, 1, [])]}.
generate_connection_params_fields(Lang, [], Number,
Acc) ->
@@ -1053,99 +777,69 @@ generate_connection_params_field(Lang, Server, Encoding,
Port;
true -> ?DEFAULT_IRC_PORT
end,
- PortUsed =
- iolist_to_binary(integer_to_list(PortUsedInt)),
+ PortUsed = integer_to_binary(PortUsedInt),
PasswordUsed = case Password of
<<>> -> <<>>;
_ -> Password
end,
- NumberString =
- iolist_to_binary(integer_to_list(Number)),
- [#xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"password", NumberString/binary>>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- iolist_to_binary(
- io_lib:format(
- translate:translate(Lang, <<"Password ~b">>),
- [Number]))}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, PasswordUsed}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"port", NumberString/binary>>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- iolist_to_binary(
- io_lib:format(translate:translate(Lang, <<"Port ~b">>),
- [Number]))}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, PortUsed}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"encoding", NumberString/binary>>},
- {<<"type">>, <<"list-single">>},
- {<<"label">>,
- list_to_binary(
- io_lib:format(translate:translate(
- Lang,
- <<"Encoding for server ~b">>),
- [Number]))}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, EncodingUsed}]}
- | lists:map(fun (E) ->
- #xmlel{name = <<"option">>,
- attrs = [{<<"label">>, E}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, E}]}]}
- end,
- ?POSSIBLE_ENCODINGS)]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"server", NumberString/binary>>},
- {<<"type">>, <<"text-single">>},
- {<<"label">>,
- list_to_binary(
- io_lib:format(translate:translate(Lang, <<"Server ~b">>),
- [Number]))}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Server}]}]}].
-
-parse_connections_params(Fields) ->
+ NumberString = integer_to_binary(Number),
+ [#xdata_field{var = <<"password", NumberString/binary>>,
+ type = 'text-single',
+ label = str:format(
+ translate:translate(Lang, <<"Password ~b">>),
+ [Number]),
+ values = [PasswordUsed]},
+ #xdata_field{var = <<"port", NumberString/binary>>,
+ type = 'text-single',
+ label = str:format(
+ translate:translate(Lang, <<"Port ~b">>),
+ [Number]),
+ values = [PortUsed]},
+ #xdata_field{var = <<"encoding", NumberString/binary>>,
+ type = 'list-single',
+ label = str:format(
+ translate:translate(Lang, <<"Encoding for server ~b">>),
+ [Number]),
+ values = [EncodingUsed],
+ options = [#xdata_option{label = E, value = E}
+ || E <- ?POSSIBLE_ENCODINGS]},
+ #xdata_field{var = <<"server", NumberString/binary>>,
+ type = 'text-single',
+ label = str:format(
+ translate:translate(Lang, <<"Server ~b">>),
+ [Number]),
+ values = [Server]}].
+
+parse_connections_params(#xdata{fields = Fields}) ->
Servers = lists:flatmap(
- fun({<<"server", Var/binary>>, Value}) ->
- [{Var, Value}];
+ fun(#xdata_field{var = <<"server", Var/binary>>,
+ values = Values}) ->
+ [{Var, Values}];
(_) ->
[]
end, Fields),
Encodings = lists:flatmap(
- fun({<<"encoding", Var/binary>>, Value}) ->
- [{Var, Value}];
+ fun(#xdata_field{var = <<"encoding", Var/binary>>,
+ values = Values}) ->
+ [{Var, Values}];
(_) ->
[]
end, Fields),
Ports = lists:flatmap(
- fun({<<"port", Var/binary>>, Value}) ->
- [{Var, Value}];
+ fun(#xdata_field{var = <<"port", Var/binary>>,
+ values = Values}) ->
+ [{Var, Values}];
(_) ->
[]
end, Fields),
Passwords = lists:flatmap(
- fun({<<"password", Var/binary>>, Value}) ->
- [{Var, Value}];
+ fun(#xdata_field{var = <<"password", Var/binary>>,
+ values = Values}) ->
+ [{Var, Values}];
(_) ->
[]
end, Fields),
- parse_connections_params(Servers, Encodings, Ports,
- Passwords).
+ parse_connections_params(Servers, Encodings, Ports, Passwords).
retrieve_connections_params(ConnectionParams,
ServerN) ->
@@ -1263,28 +957,19 @@ mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) ->
[access, db_type, default_encoding, host].
+-spec extract_ident(stanza()) -> binary().
extract_ident(Packet) ->
- case fxml:get_subtag(Packet, <<"headers">>) of
- {xmlel, _Name, _Attrs, Headers} ->
- extract_header(<<"X-Irc-Ident">>, Headers);
- _ ->
- "chatmovil"
- end.
+ Hdrs = extract_headers(Packet),
+ proplists:get_value(<<"X-Irc-Ident">>, Hdrs, <<"chatmovil">>).
+-spec extract_ip_address(stanza()) -> binary().
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"
+ Hdrs = extract_headers(Packet),
+ proplists:get_value(<<"X-Forwarded-For">>, Hdrs, <<"127.0.0.1">>).
+
+-spec extract_headers(stanza()) -> [{binary(), binary()}].
+extract_headers(Packet) ->
+ case xmpp:get_subtag(Packet, #shim{}) of
+ #shim{headers = Hs} -> Hs;
+ false -> []
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 098c8c286..2e604203c 100644
--- a/src/mod_irc_connection.erl
+++ b/src/mod_irc_connection.erl
@@ -41,8 +41,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(SETS, gb_sets).
@@ -66,6 +65,8 @@
inbuf = <<"">> :: binary(),
outbuf = <<"">> :: binary()}).
+-type state() :: #state{}.
+
%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -228,27 +229,13 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
(iolist_to_binary(S))/binary>>}
end).
-get_password_from_presence(#xmlel{name = <<"presence">>,
- children = Els}) ->
- case lists:filter(fun (El) ->
- case El of
- #xmlel{name = <<"x">>, attrs = Attrs} ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC -> true;
- _ -> false
- end;
- _ -> false
- end
- end,
- Els)
- of
- [ElXMUC | _] ->
- case fxml:get_subtag(ElXMUC, <<"password">>) of
- #xmlel{name = <<"password">>} = PasswordTag ->
- {true, fxml:get_tag_cdata(PasswordTag)};
- _ -> false
- end;
- _ -> false
+-spec get_password_from_presence(presence()) -> {true, binary()} | false.
+get_password_from_presence(#presence{} = Pres) ->
+ case xmpp:get_subtag(Pres, #muc{}) of
+ #muc{password = Password} ->
+ {true, Password};
+ _ ->
+ false
end.
%%----------------------------------------------------------------------
@@ -257,284 +244,243 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-handle_info({route_chan, Channel, Resource,
- #xmlel{name = <<"presence">>, attrs = Attrs} =
- Presence},
+handle_info({route_chan, _, _, #presence{type = error}}, _, StateData) ->
+ {stop, normal, StateData};
+handle_info({route_chan, Channel, _, #presence{type = unavailable}},
StateName, StateData) ->
- NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"unavailable">> ->
- send_stanza_unavailable(Channel, StateData),
- S1 = (?SEND((io_lib:format("PART #~s\r\n",
- [Channel])))),
- S1#state{channels =
- dict:erase(Channel, S1#state.channels)};
- <<"subscribe">> -> StateData;
- <<"subscribed">> -> StateData;
- <<"unsubscribe">> -> StateData;
- <<"unsubscribed">> -> StateData;
- <<"error">> -> stop;
- _ ->
- Nick = case Resource of
- <<"">> -> StateData#state.nick;
- _ -> Resource
- end,
- S1 = if Nick /= StateData#state.nick ->
- S11 = (?SEND((io_lib:format("NICK ~s\r\n",
- [Nick])))),
- S11#state{nickchannel = Channel};
- true -> StateData
- end,
- case dict:is_key(Channel, S1#state.channels) of
- true -> S1;
- _ ->
- case get_password_from_presence(Presence) of
- {true, Password} ->
- S2 =
- (?SEND((io_lib:format("JOIN #~s ~s\r\n",
- [Channel,
- Password]))));
- _ ->
- S2 = (?SEND((io_lib:format("JOIN #~s\r\n",
- [Channel]))))
- end,
- S2#state{channels =
- dict:store(Channel, (?SETS):new(),
- S1#state.channels)}
- end
- end,
- if NewStateData == stop -> {stop, normal, StateData};
- true ->
- case dict:fetch_keys(NewStateData#state.channels) of
- [] -> {stop, normal, NewStateData};
- _ -> {next_state, StateName, NewStateData}
- end
- end;
+ send_stanza_unavailable(Channel, StateData),
+ S1 = (?SEND((io_lib:format("PART #~s\r\n", [Channel])))),
+ S2 = S1#state{channels = dict:erase(Channel, S1#state.channels)},
+ {next_state, StateName, S2};
handle_info({route_chan, Channel, Resource,
- #xmlel{name = <<"message">>, attrs = Attrs} = El},
+ #presence{type = available} = Presence},
StateName, StateData) ->
- NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"groupchat">> ->
- case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata])
- of
- <<"">> ->
- ejabberd_router:route(
- jid:make(
- iolist_to_binary([Channel,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- StateData#state.nick),
- StateData#state.user, El),
- Body = fxml:get_path_s(El,
- [{elem, <<"body">>},
- cdata]),
- case Body of
- <<"/quote ", Rest/binary>> ->
- ?SEND(<<Rest/binary, "\r\n">>);
- <<"/msg ", Rest/binary>> ->
- ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
- <<"/me ", Rest/binary>> ->
- Strings = str:tokens(Rest, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format(
- "PRIVMSG #~s :\001ACTION ~s\001\r\n",
- [Channel, S])
- end,
- Strings)),
- ?SEND(Res);
- <<"/ctcp ", Rest/binary>> ->
- Words = str:tokens(Rest, <<" ">>),
- case Words of
- [CtcpDest | _] ->
- CtcpCmd = str:to_upper(
- str:substr(Rest,
- str:str(Rest,
- <<" ">>)
- + 1)),
- Res =
- io_lib:format("PRIVMSG ~s :\001~s\001\r\n",
- [CtcpDest,
- CtcpCmd]),
- ?SEND(Res);
- _ -> ok
- end;
- _ ->
- Strings = str:tokens(Body, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format("PRIVMSG #~s :~s\r\n",
- [Channel, S])
- end,
- Strings)),
- ?SEND(Res)
- end;
- Subject ->
- Strings = str:tokens(Subject, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format("TOPIC #~s :~s\r\n",
- [Channel, S])
- end,
- Strings)),
- ?SEND(Res)
- end;
- Type
- when Type == <<"chat">>;
- Type == <<"">>;
- Type == <<"normal">> ->
- Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
- case Body of
- <<"/quote ", Rest/binary>> ->
- ?SEND(<<Rest/binary, "\r\n">>);
- <<"/msg ", Rest/binary>> ->
- ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
- <<"/me ", Rest/binary>> ->
- Strings = str:tokens(Rest, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format(
- "PRIVMSG ~s :\001ACTION ~s\001\r\n",
- [Resource, S])
- end,
- Strings)),
- ?SEND(Res);
- <<"/ctcp ", Rest/binary>> ->
- Words = str:tokens(Rest, <<" ">>),
- case Words of
- [CtcpDest | _] ->
- CtcpCmd = str:to_upper(
- str:substr(Rest,
- str:str(Rest,
- <<" ">>)
- + 1)),
- Res = io_lib:format("PRIVMSG ~s :~s\r\n",
- [CtcpDest,
- <<"\001",
- CtcpCmd/binary,
- "\001">>]),
- ?SEND(Res);
- _ -> ok
- end;
- _ ->
- Strings = str:tokens(Body, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format(
- "PRIVMSG ~s :~s\r\n",
- [Resource, S])
- end,
- Strings)),
- ?SEND(Res)
- end;
- <<"error">> -> stop;
- _ -> StateData
- end,
- if NewStateData == stop -> {stop, normal, StateData};
- true -> {next_state, StateName, NewStateData}
- end;
-handle_info({route_chan, Channel, Resource,
- #xmlel{name = <<"iq">>} = El},
+ Nick = case Resource of
+ <<"">> -> StateData#state.nick;
+ _ -> Resource
+ end,
+ S1 = if Nick /= StateData#state.nick ->
+ S11 = (?SEND((io_lib:format("NICK ~s\r\n", [Nick])))),
+ S11#state{nickchannel = Channel};
+ true -> StateData
+ end,
+ {next_state, StateName,
+ case dict:is_key(Channel, S1#state.channels) of
+ true -> S1;
+ _ ->
+ case get_password_from_presence(Presence) of
+ {true, Password} ->
+ S2 = ?SEND((io_lib:format("JOIN #~s ~s\r\n",
+ [Channel, Password])));
+ _ ->
+ S2 = ?SEND((io_lib:format("JOIN #~s\r\n", [Channel])))
+ end,
+ S2#state{channels = dict:store(Channel, ?SETS:new(),
+ S1#state.channels)}
+ end};
+handle_info({route_chan, Channel, _Resource, #message{type = groupchat} = Msg},
StateName, StateData) ->
+ {next_state, StateName,
+ case xmpp:get_text(Msg#message.subject) of
+ <<"">> ->
+ ejabberd_router:route(
+ jid:make(
+ iolist_to_binary([Channel,
+ <<"%">>,
+ StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user, Msg),
+ Body = xmpp:get_text(Msg#message.body),
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG #~s :\001ACTION ~s\001\r\n",
+ [Channel, S])
+ end,
+ Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(
+ Rest, str:str(Rest, <<" ">>) + 1)),
+ Res = io_lib:format("PRIVMSG ~s :\001~s\001\r\n",
+ [CtcpDest, CtcpCmd]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format("PRIVMSG #~s :~s\r\n",
+ [Channel, S])
+ end, Strings)),
+ ?SEND(Res)
+ end;
+ Subject ->
+ Strings = str:tokens(Subject, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format("TOPIC #~s :~s\r\n",
+ [Channel, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end};
+handle_info({route_chan, _Channel, Resource, #message{type = Type} = Msg},
+ StateName, StateData) when Type == chat; Type == normal ->
+ Body = xmpp:get_text(Msg#message.body),
+ {next_state, StateName,
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :\001ACTION ~s\001\r\n",
+ [Resource, S])
+ end, Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(
+ Rest, str:str(Rest, <<" ">>) + 1)),
+ Res = io_lib:format("PRIVMSG ~s :~s\r\n",
+ [CtcpDest,
+ <<"\001", CtcpCmd/binary, "\001">>]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format("PRIVMSG ~s :~s\r\n",
+ [Resource, S])
+ end, Strings)),
+ ?SEND(Res)
+ end};
+handle_info({route_chan, _, _, #message{type = error}}, _, StateData) ->
+ {stop, normal, StateData};
+handle_info({route_chan, Channel, Resource,
+ #iq{type = T, sub_els = [_]} = Packet},
+ StateName, StateData) when T == set; T == get ->
From = StateData#state.user,
- To = jid:make(iolist_to_binary([Channel, <<"%">>,
- StateData#state.server]),
- StateData#state.host, StateData#state.nick),
- _ = case jlib:iq_query_info(El) of
- #iq{xmlns = ?NS_MUC_ADMIN} = IQ ->
- iq_admin(StateData, Channel, From, To, IQ);
- #iq{xmlns = ?NS_VERSION} ->
- Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n",
- [Resource]),
- _ = (?SEND(Res)),
- Err = jlib:make_error_reply(El,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{xmlns = ?NS_TIME} ->
- Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n",
- [Resource]),
- _ = (?SEND(Res)),
- Err = jlib:make_error_reply(El,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{xmlns = ?NS_VCARD} ->
- Res = io_lib:format("WHOIS ~s \r\n", [Resource]),
- _ = (?SEND(Res)),
- Err = jlib:make_error_reply(El,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- #iq{} ->
- Err = jlib:make_error_reply(El,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ -> ok
- end,
+ To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]),
+ StateData#state.host, StateData#state.nick),
+ try xmpp:decode_els(Packet) of
+ #iq{sub_els = [SubEl]} = IQ ->
+ case xmpp:get_ns(SubEl) of
+ ?NS_MUC_ADMIN ->
+ iq_admin(StateData, Channel, From, To, IQ);
+ ?NS_VERSION ->
+ Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n",
+ [Resource]),
+ _ = (?SEND(Res)),
+ Err = xmpp:err_feature_not_implemented(),
+ ejabberd_router:route_error(To, From, Packet, Err);
+ ?NS_TIME ->
+ Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n",
+ [Resource]),
+ _ = (?SEND(Res)),
+ Err = xmpp:err_feature_not_implemented(),
+ ejabberd_router:route_error(To, From, Packet, Err);
+ ?NS_VCARD ->
+ Res = io_lib:format("WHOIS ~s \r\n", [Resource]),
+ _ = (?SEND(Res)),
+ Err = xmpp:err_feature_not_implemented(),
+ ejabberd_router:route_error(To, From, Packet, Err);
+ _ ->
+ Err = xmpp:err_feature_not_implemented(),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end
+ catch _:{xmpp_codec, Why} ->
+ Err = xmpp:err_bad_request(
+ xmpp:format_error(Why), xmpp:get_lang(Packet)),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end,
{next_state, StateName, StateData};
-handle_info({route_chan, _Channel, _Resource, _Packet},
- StateName, StateData) ->
+handle_info({route_chan, Channel, _, #iq{} = IQ}, StateName, StateData) ->
+ From = StateData#state.user,
+ To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]),
+ StateData#state.host, StateData#state.nick),
+ Err = xmpp:err_feature_not_implemented(),
+ ejabberd_router:route_error(To, From, IQ, Err),
{next_state, StateName, StateData};
-handle_info({route_nick, Nick,
- #xmlel{name = <<"message">>, attrs = Attrs} = El},
+handle_info({route_nick, Nick, #message{type = chat} = Msg},
StateName, StateData) ->
- NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"chat">> ->
- Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
- case Body of
- <<"/quote ", Rest/binary>> ->
- ?SEND(<<Rest/binary, "\r\n">>);
- <<"/msg ", Rest/binary>> ->
- ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
- <<"/me ", Rest/binary>> ->
- Strings = str:tokens(Rest, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format(
- "PRIVMSG ~s :\001ACTION ~s\001\r\n",
- [Nick, S])
- end,
- Strings)),
- ?SEND(Res);
- <<"/ctcp ", Rest/binary>> ->
- Words = str:tokens(Rest, <<" ">>),
- case Words of
- [CtcpDest | _] ->
- CtcpCmd = str:to_upper(
- str:substr(Rest,
- str:str(Rest,
- <<" ">>)
- + 1)),
- Res = io_lib:format("PRIVMSG ~s :~s\r\n",
- [CtcpDest,
- <<"\001",
- CtcpCmd/binary,
- "\001">>]),
- ?SEND(Res);
- _ -> ok
- end;
- _ ->
- Strings = str:tokens(Body, <<"\n">>),
- Res = iolist_to_binary(
- lists:map(
- fun (S) ->
- io_lib:format(
- "PRIVMSG ~s :~s\r\n",
- [Nick, S])
- end,
- Strings)),
- ?SEND(Res)
- end;
- <<"error">> -> stop;
- _ -> StateData
- end,
- if NewStateData == stop -> {stop, normal, StateData};
- true -> {next_state, StateName, NewStateData}
- end;
+ Body = xmpp:get_text(Msg#message.body),
+ {next_state, StateName,
+ case Body of
+ <<"/quote ", Rest/binary>> ->
+ ?SEND(<<Rest/binary, "\r\n">>);
+ <<"/msg ", Rest/binary>> ->
+ ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>);
+ <<"/me ", Rest/binary>> ->
+ Strings = str:tokens(Rest, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :\001ACTION ~s\001\r\n",
+ [Nick, S])
+ end,
+ Strings)),
+ ?SEND(Res);
+ <<"/ctcp ", Rest/binary>> ->
+ Words = str:tokens(Rest, <<" ">>),
+ case Words of
+ [CtcpDest | _] ->
+ CtcpCmd = str:to_upper(
+ str:substr(Rest,
+ str:str(Rest,
+ <<" ">>)
+ + 1)),
+ Res = io_lib:format("PRIVMSG ~s :~s\r\n",
+ [CtcpDest,
+ <<"\001",
+ CtcpCmd/binary,
+ "\001">>]),
+ ?SEND(Res);
+ _ -> ok
+ end;
+ _ ->
+ Strings = str:tokens(Body, <<"\n">>),
+ Res = iolist_to_binary(
+ lists:map(
+ fun (S) ->
+ io_lib:format(
+ "PRIVMSG ~s :~s\r\n",
+ [Nick, S])
+ end,
+ Strings)),
+ ?SEND(Res)
+ end};
+handle_info({route_nick, _, #message{type = error}}, _, StateData) ->
+ {stop, normal, StateData};
handle_info({route_nick, _Nick, _Packet}, StateName,
StateData) ->
{next_state, StateName, StateData};
@@ -561,13 +507,13 @@ handle_info({ircstring, <<$:, String/binary>>},
{error,
{error,
error_unknown_num(StateData, String,
- <<"cancel">>),
+ cancel),
StateData}};
[_, <<$5, _, _>> | _] ->
{error,
{error,
error_unknown_num(StateData, String,
- <<"cancel">>),
+ cancel),
StateData}};
_ ->
?DEBUG("unknown irc command '~s'~n",
@@ -638,12 +584,12 @@ handle_info({ircstring, <<$:, String/binary>>},
[From, <<"MODE">>, <<$#, Chan/binary>>, <<"+o">>, Nick
| _] ->
process_mode_o(StateData, Chan, From, Nick,
- <<"admin">>, <<"moderator">>),
+ admin, moderator),
StateData;
[From, <<"MODE">>, <<$#, Chan/binary>>, <<"-o">>, Nick
| _] ->
process_mode_o(StateData, Chan, From, Nick,
- <<"member">>, <<"participant">>),
+ member, participant),
StateData;
[From, <<"KICK">>, <<$#, Chan/binary>>, Nick | _] ->
process_kick(StateData, Chan, From, Nick, String),
@@ -702,11 +648,8 @@ terminate(_Reason, _StateName, FullStateData) ->
{Error, StateData} = case FullStateData of
{error, SError, SStateData} -> {SError, SStateData};
_ ->
- {#xmlel{name = <<"error">>,
- attrs = [{<<"code">>, <<"502">>}],
- children =
- [{xmlcdata,
- <<"Server Connect Failed">>}]},
+ {xmpp:err_internal_server_error(
+ <<"Server Connect Failed">>, ?MYLANG),
FullStateData}
end,
(StateData#state.mod):closed_connection(StateData#state.host,
@@ -714,9 +657,7 @@ terminate(_Reason, _StateName, FullStateData) ->
StateData#state.server),
bounce_messages(<<"Server Connect Failed">>),
lists:foreach(fun (Chan) ->
- Stanza = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"error">>}],
- children = [Error]},
+ Stanza = xmpp:make_error(#presence{}, Error),
send_stanza(Chan, StateData, Stanza)
end,
dict:fetch_keys(StateData#state.channels)),
@@ -726,34 +667,24 @@ terminate(_Reason, _StateName, FullStateData) ->
end,
ok.
+-spec send_stanza(binary(), state(), stanza()) -> ok.
send_stanza(Chan, StateData, Stanza) ->
ejabberd_router:route(
- jid:make(
- iolist_to_binary([Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- StateData#state.nick),
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
StateData#state.user, Stanza).
+-spec send_stanza_unavailable(binary(), state()) -> ok.
send_stanza_unavailable(Chan, StateData) ->
- Affiliation = <<"member">>,
- Role = <<"none">>,
- Stanza = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- Affiliation},
- {<<"role">>, Role}],
- children = []},
- #xmlel{name = <<"status">>,
- attrs = [{<<"code">>, <<"110">>}],
- children = []}]}]},
+ Affiliation = member,
+ Role = none,
+ Stanza = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{
+ items = [#muc_item{affiliation = Affiliation,
+ role = Role}],
+ status_codes = [110]}]},
send_stanza(Chan, StateData, Stanza).
%%%----------------------------------------------------------------------
@@ -776,20 +707,14 @@ send_text(#state{socket = Socket, encoding = Encoding},
bounce_messages(Reason) ->
receive
- {send_element, El} ->
- #xmlel{attrs = Attrs} = El,
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- _ ->
- Err = jlib:make_error_reply(El, <<"502">>, Reason),
- From = jid:from_string(fxml:get_attr_s(<<"from">>,
- Attrs)),
- To = jid:from_string(fxml:get_attr_s(<<"to">>,
- Attrs)),
- ejabberd_router:route(To, From, Err)
- end,
- bounce_messages(Reason)
- after 0 -> ok
+ {send_element, El} ->
+ From = xmpp:get_from(El),
+ To = xmpp:get_to(El),
+ Lang = xmpp:get_lang(El),
+ Err = xmpp:err_internal_server_error(Reason, Lang),
+ ejabberd_router:route_error(To, From, El, Err),
+ bounce_messages(Reason)
+ after 0 -> ok
end.
route_chan(Pid, Channel, Resource, Packet) ->
@@ -831,62 +756,43 @@ process_channel_list_user(StateData, Chan, User) ->
end,
{User2, Affiliation, Role} = case User1 of
<<$@, U2/binary>> ->
- {U2, <<"admin">>, <<"moderator">>};
+ {U2, admin, moderator};
<<$+, U2/binary>> ->
- {U2, <<"member">>, <<"participant">>};
+ {U2, member, participant};
<<$%, U2/binary>> ->
- {U2, <<"admin">>, <<"moderator">>};
+ {U2, admin, moderator};
<<$&, U2/binary>> ->
- {U2, <<"admin">>, <<"moderator">>};
+ {U2, admin, moderator};
<<$~, U2/binary>> ->
- {U2, <<"admin">>, <<"moderator">>};
- _ -> {User1, <<"member">>, <<"participant">>}
+ {U2, admin, moderator};
+ _ -> {User1, member, participant}
end,
- ejabberd_router:route(jid:make(iolist_to_binary([Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, User2),
- StateData#state.user,
- #xmlel{name = <<"presence">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- Affiliation},
- {<<"role">>,
- Role}],
- children = []}]}]}),
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, User2),
+ StateData#state.user,
+ #presence{
+ sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation,
+ role = Role}]}]}),
case catch dict:update(Chan,
fun (Ps) -> (?SETS):add_element(User2, Ps) end,
- StateData#state.channels)
- of
- {'EXIT', _} -> StateData;
- NS -> StateData#state{channels = NS}
+ StateData#state.channels) of
+ {'EXIT', _} -> StateData;
+ NS -> StateData#state{channels = NS}
end.
process_channel_topic(StateData, Chan, String) ->
Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>,
<<"">>),
- Msg1 = filter_message(Msg),
- ejabberd_router:route(jid:make(iolist_to_binary([Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"subject">>, attrs = [],
- children = [{xmlcdata, Msg1}]},
- #xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- <<"Topic for #", Chan/binary,
- ": ", Msg1/binary>>}]}]}).
+ Subject = filter_message(Msg),
+ Body = <<"Topic for #", Chan/binary, ": ", Subject/binary>>,
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host),
+ StateData#state.user,
+ #message{type = groupchat,
+ subject = xmpp:mk_text(Subject),
+ body = xmpp:mk_text(Body)}).
process_channel_topic_who(StateData, Chan, String) ->
Words = str:tokens(String, <<" ">>),
@@ -901,30 +807,17 @@ process_channel_topic_who(StateData, Chan, String) ->
_ -> String
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jid:make(iolist_to_binary([Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #message{type = groupchat, body = xmpp:mk_text(Msg2)}).
error_nick_in_use(_StateData, String) ->
Msg = ejabberd_regexp:replace(String,
<<".*433 +[^ ]* +">>, <<"">>),
Msg1 = filter_message(Msg),
- #xmlel{name = <<"error">>,
- attrs =
- [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}],
- children =
- [#xmlel{name = <<"conflict">>,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
- #xmlel{name = <<"text">>,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}],
- children = [{xmlcdata, Msg1}]}]}.
+ xmpp:err_conflict(Msg1, ?MYLANG).
process_nick_in_use(StateData, String) ->
Error = error_nick_in_use(StateData, String),
@@ -933,121 +826,73 @@ process_nick_in_use(StateData, String) ->
% Shouldn't happen with a well behaved server
StateData;
Chan ->
- ejabberd_router:route(jid:make(iolist_to_binary([Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- StateData#state.nick),
- StateData#state.user,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"error">>}],
- children = [Error]}),
- StateData#state{nickchannel = undefined}
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ xmpp:make_error(#presence{}, Error)),
+ StateData#state{nickchannel = undefined}
end.
process_num_error(StateData, String) ->
- Error = error_unknown_num(StateData, String,
- <<"continue">>),
- lists:foreach(fun (Chan) ->
- ejabberd_router:route(
- jid:make(
- iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- StateData#state.nick),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs =
- [{<<"type">>,
- <<"error">>}],
- children = [Error]})
- end,
- dict:fetch_keys(StateData#state.channels)),
+ Error = error_unknown_num(StateData, String, continue),
+ lists:foreach(
+ fun(Chan) ->
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, $%, StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ xmpp:make_error(#message{}, Error))
+ end, dict:fetch_keys(StateData#state.channels)),
StateData.
process_endofwhois(StateData, _String, Nick) ->
- ejabberd_router:route(jid:make(iolist_to_binary([Nick,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- <<"End of WHOIS">>}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]),
+ StateData#state.host),
+ StateData#state.user,
+ #message{type = chat, body = xmpp:mk_text(<<"End of WHOIS">>)}).
process_whois311(StateData, String, Nick, Ident,
Irchost) ->
Fullname = ejabberd_regexp:replace(String,
<<".*311[^:]*:">>, <<"">>),
- ejabberd_router:route(jid:make(iolist_to_binary([Nick,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- [<<"WHOIS: ">>,
- Nick,
- <<" is ">>,
- Ident,
- <<"@">>,
- Irchost,
- <<" : ">>,
- Fullname])}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #message{type = chat,
+ body = xmpp:mk_text(
+ iolist_to_binary(
+ [<<"WHOIS: ">>, Nick, <<" is ">>, Ident,
+ <<"@">>, Irchost, <<" : ">>, Fullname]))}).
process_whois312(StateData, String, Nick, Ircserver) ->
Ircserverdesc = ejabberd_regexp:replace(String,
<<".*312[^:]*:">>, <<"">>),
- ejabberd_router:route(jid:make(iolist_to_binary([Nick,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- [<<"WHOIS: ">>,
- Nick,
- <<" use ">>,
- Ircserver,
- <<" : ">>,
- Ircserverdesc])}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #message{type = chat,
+ body = xmpp:mk_text(
+ iolist_to_binary(
+ [<<"WHOIS: ">>, Nick, <<" use ">>, Ircserver,
+ <<" : ">>, Ircserverdesc]))}).
process_whois319(StateData, String, Nick) ->
Chanlist = ejabberd_regexp:replace(String,
<<".*319[^:]*:">>, <<"">>),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Nick,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- [<<"WHOIS: ">>,
- Nick,
- <<" is on ">>,
- Chanlist])}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #message{type = chat,
+ body = xmpp:mk_text(
+ iolist_to_binary(
+ [<<"WHOIS: ">>, Nick, <<" is on ">>, Chanlist]))}).
process_chanprivmsg(StateData, Chan, From, String) ->
[FromUser | _] = str:tokens(From, <<"!">>),
@@ -1059,17 +904,11 @@ process_chanprivmsg(StateData, Chan, From, String) ->
_ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #message{type = groupchat, body = xmpp:mk_text(Msg2)}).
process_channotice(StateData, Chan, From, String) ->
[FromUser | _] = str:tokens(From, <<"!">>),
@@ -1081,17 +920,11 @@ process_channotice(StateData, Chan, From, String) ->
_ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #message{type = groupchat, body = xmpp:mk_text(Msg2)}).
process_privmsg(StateData, _Nick, From, String) ->
[FromUser | _] = str:tokens(From, <<"!">>),
@@ -1103,17 +936,11 @@ process_privmsg(StateData, _Nick, From, String) ->
_ -> Msg
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [FromUser,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]),
+ StateData#state.host, <<"">>),
+ StateData#state.user,
+ #message{type = chat, body = xmpp:mk_text(Msg2)}).
process_notice(StateData, _Nick, From, String) ->
[FromUser | _] = str:tokens(From, <<"!">>),
@@ -1125,17 +952,11 @@ process_notice(StateData, _Nick, From, String) ->
_ -> <<"/me NOTICE: ", Msg/binary>>
end,
Msg2 = filter_message(Msg1),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [FromUser,
- <<"!">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]),
+ StateData#state.host),
+ StateData#state.user,
+ #message{type = chat, body = xmpp:mk_text(Msg2)}).
process_version(StateData, _Nick, From) ->
[FromUser | _] = str:tokens(From, <<"!">>),
@@ -1160,54 +981,30 @@ process_topic(StateData, Chan, From, String) ->
Msg = ejabberd_regexp:replace(String,
<<".*TOPIC[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"subject">>, attrs = [],
- children = [{xmlcdata, Msg1}]},
- #xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- <<"/me has changed the subject to: ",
- Msg1/binary>>}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #message{type = groupchat,
+ subject = xmpp:mk_text(Msg1),
+ body = xmpp:mk_text(<<"/me has changed the subject to: ",
+ Msg1/binary>>)}).
process_part(StateData, Chan, From, String) ->
[FromUser | FromIdent] = str:tokens(From, <<"!">>),
Msg = ejabberd_regexp:replace(String,
<<".*PART[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- <<"member">>},
- {<<"role">>,
- <<"none">>}],
- children = []}]},
- #xmlel{name = <<"status">>, attrs = [],
- children =
- [{xmlcdata,
- list_to_binary(
- [Msg1, " (",
- FromIdent, ")"])}]}]}),
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #presence{type = unavailable,
+ sub_els = [#muc_user{
+ items = [#muc_item{affiliation = member,
+ role = none}]}],
+ status = xmpp:mk_text(
+ list_to_binary([Msg1, " (", FromIdent, ")"]))}),
case catch dict:update(Chan,
fun (Ps) -> remove_element(FromUser, Ps) end,
StateData#state.channels)
@@ -1221,81 +1018,40 @@ process_quit(StateData, From, String) ->
Msg = ejabberd_regexp:replace(String,
<<".*QUIT[^:]*:">>, <<"">>),
Msg1 = filter_message(Msg),
- dict:map(fun (Chan, Ps) ->
- case (?SETS):is_member(FromUser, Ps) of
- true ->
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- FromUser),
- StateData#state.user,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- [{<<"affiliation">>,
- <<"member">>},
- {<<"role">>,
- <<"none">>}],
- children
- =
- []}]},
- #xmlel{name =
- <<"status">>,
- attrs = [],
- children =
- [{xmlcdata,
- list_to_binary(
- [Msg1, " (",
- FromIdent,
- ")"])}]}]}),
- remove_element(FromUser, Ps);
- _ -> Ps
- end
- end,
- StateData#state.channels),
+ dict:map(
+ fun(Chan, Ps) ->
+ case (?SETS):is_member(FromUser, Ps) of
+ true ->
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, $%, StateData#state.server]),
+ StateData#state.host,
+ FromUser),
+ StateData#state.user,
+ #presence{type = unavailable,
+ sub_els = [#muc_user{
+ items = [#muc_item{
+ affiliation = member,
+ role = none}]}],
+ status = xmpp:mk_text(
+ list_to_binary([Msg1, " (", FromIdent, ")"]))}),
+ remove_element(FromUser, Ps);
+ _ ->
+ Ps
+ end
+ end, StateData#state.channels),
StateData.
process_join(StateData, Channel, From, _String) ->
[FromUser | FromIdent] = str:tokens(From, <<"!">>),
[Chan | _] = binary:split(Channel, <<":#">>),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, FromUser),
- StateData#state.user,
- #xmlel{name = <<"presence">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- <<"member">>},
- {<<"role">>,
- <<"participant">>}],
- children = []}]},
- #xmlel{name = <<"status">>, attrs = [],
- children =
- [{xmlcdata,
- list_to_binary(FromIdent)}]}]}),
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, FromUser),
+ StateData#state.user,
+ #presence{
+ sub_els = [#muc_user{items = [#muc_item{affiliation = member,
+ role = participant}]}],
+ status = xmpp:mk_text(list_to_binary(FromIdent))}),
case catch dict:update(Chan,
fun (Ps) -> (?SETS):add_element(FromUser, Ps) end,
StateData#state.channels)
@@ -1306,160 +1062,67 @@ process_join(StateData, Channel, From, _String) ->
process_mode_o(StateData, Chan, _From, Nick,
Affiliation, Role) ->
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, Nick),
- StateData#state.user,
- #xmlel{name = <<"presence">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- Affiliation},
- {<<"role">>,
- Role}],
- children = []}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, Nick),
+ StateData#state.user,
+ #presence{
+ sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation,
+ role = Role}]}]}).
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(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, <<"">>),
- StateData#state.user,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg2}]}]}),
- ejabberd_router:route(jid:make(iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host, Nick),
- StateData#state.user,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- <<"none">>},
- {<<"role">>,
- <<"none">>}],
- children = []},
- #xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>,
- <<"307">>}],
- children = []}]}]}).
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host),
+ StateData#state.user,
+ #message{type = groupchat, body = xmpp:mk_text(Msg2)}),
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]),
+ StateData#state.host, Nick),
+ StateData#state.user,
+ #presence{type = unavailable,
+ sub_els = [#muc_user{items = [#muc_item{
+ affiliation = none,
+ role = none}],
+ status_codes = [307]}]}).
process_nick(StateData, From, NewNick) ->
[FromUser | _] = str:tokens(From, <<"!">>),
[Nick | _] = binary:split(NewNick, <<":">>),
- NewChans = dict:map(fun (Chan, Ps) ->
- case (?SETS):is_member(FromUser, Ps) of
- true ->
- ejabberd_router:route(jid:make(
- iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- FromUser),
- StateData#state.user,
- #xmlel{name =
- <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name
- =
- <<"x">>,
- attrs
- =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children
- =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- [{<<"affiliation">>,
- <<"member">>},
- {<<"role">>,
- <<"participant">>},
- {<<"nick">>,
- Nick}],
- children
- =
- []},
- #xmlel{name
- =
- <<"status">>,
- attrs
- =
- [{<<"code">>,
- <<"303">>}],
- children
- =
- []}]}]}),
- ejabberd_router:route(jid:make(
- iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- Nick),
- StateData#state.user,
- #xmlel{name =
- <<"presence">>,
- attrs = [],
- children =
- [#xmlel{name
- =
- <<"x">>,
- attrs
- =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children
- =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- [{<<"affiliation">>,
- <<"member">>},
- {<<"role">>,
- <<"participant">>}],
- children
- =
- []}]}]}),
- (?SETS):add_element(Nick,
- remove_element(FromUser,
- Ps));
- _ -> Ps
- end
- end,
- StateData#state.channels),
+ NewChans =
+ dict:map(
+ fun(Chan, Ps) ->
+ case (?SETS):is_member(FromUser, Ps) of
+ true ->
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, $%, StateData#state.server]),
+ StateData#state.host,
+ FromUser),
+ StateData#state.user,
+ #presence{
+ type = unavailable,
+ sub_els = [#muc_user{
+ items = [#muc_item{
+ affiliation = member,
+ role = participant,
+ nick = Nick}],
+ status_codes = [303]}]}),
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, $%, StateData#state.server]),
+ StateData#state.host, Nick),
+ StateData#state.user,
+ #presence{
+ sub_els = [#muc_user{
+ items = [#muc_item{
+ affiliation = member,
+ role = participant}]}]}),
+ (?SETS):add_element(Nick, remove_element(FromUser, Ps));
+ _ -> Ps
+ end
+ end, StateData#state.channels),
if FromUser == StateData#state.nick ->
StateData#state{nick = Nick, nickchannel = undefined,
channels = NewChans};
@@ -1467,43 +1130,23 @@ process_nick(StateData, From, NewNick) ->
end.
process_error(StateData, String) ->
- lists:foreach(fun (Chan) ->
- ejabberd_router:route(jid:make(
- iolist_to_binary(
- [Chan,
- <<"%">>,
- StateData#state.server]),
- StateData#state.host,
- StateData#state.nick),
- StateData#state.user,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"error">>}],
- children =
- [#xmlel{name =
- <<"error">>,
- attrs =
- [{<<"code">>,
- <<"502">>}],
- children =
- [{xmlcdata,
- String}]}]})
- end,
- dict:fetch_keys(StateData#state.channels)).
+ lists:foreach(
+ fun(Chan) ->
+ ejabberd_router:route(
+ jid:make(iolist_to_binary([Chan, $%, StateData#state.server]),
+ StateData#state.host,
+ StateData#state.nick),
+ StateData#state.user,
+ xmpp:make_error(
+ #presence{},
+ xmpp:err_internal_server_error(String, ?MYLANG)))
+ end, dict:fetch_keys(StateData#state.channels)).
error_unknown_num(_StateData, String, Type) ->
Msg = ejabberd_regexp:replace(String,
<<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>),
Msg1 = filter_message(Msg),
- #xmlel{name = <<"error">>,
- attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}],
- children =
- [#xmlel{name = <<"undefined-condition">>,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
- #xmlel{name = <<"text">>,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}],
- children = [{xmlcdata, Msg1}]}]}.
+ xmpp:err_undefined_condition(Type, Msg1, ?MYLANG).
remove_element(E, Set) ->
case (?SETS):is_element(E, Set) of
@@ -1512,49 +1155,31 @@ remove_element(E, Set) ->
end.
iq_admin(StateData, Channel, From, To,
- #iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) ->
- case catch process_iq_admin(StateData, Channel, Type,
- SubEl)
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
- Res ->
- if Res /= ignore ->
- ResIQ = case Res of
- {result, ResEls} ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = ResEls}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end,
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- true -> ok
- end
+ #iq{type = Type, sub_els = [SubEl]} = IQ) ->
+ try process_iq_admin(StateData, Channel, Type, SubEl) of
+ {result, Result} ->
+ ejabberd_router:route(To, From, xmpp:make_iq_result(IQ, Result));
+ {error, Error} ->
+ ejabberd_router:route_error(To, From, IQ, Error)
+ catch E:R ->
+ ?ERROR_MSG("failed to process admin query from ~s: ~p",
+ [jid:to_string(From), {E, {R, erlang:get_stacktrace()}}]),
+ ejabberd_router:route_error(
+ To, From, IQ, xmpp:err_internal_server_error())
end.
-process_iq_admin(StateData, Channel, set, SubEl) ->
- case fxml:get_subtag(SubEl, <<"item">>) of
- false -> {error, ?ERR_BAD_REQUEST};
- ItemEl ->
- Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl),
- Affiliation = fxml:get_tag_attr_s(<<"affiliation">>,
- 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)
- end;
-process_iq_admin(_StateData, _Channel, get, _SubEl) ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
-
-process_admin(_StateData, _Channel, <<"">>,
- _Affiliation, _Role, _Reason) ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
-process_admin(StateData, Channel, Nick, _Affiliation,
- <<"none">>, Reason) ->
+process_iq_admin(_StateData, _Channel, set, #muc_admin{items = []}) ->
+ {error, xmpp:err_bad_request()};
+process_iq_admin(StateData, Channel, set, #muc_admin{items = [Item|_]}) ->
+ process_admin(StateData, Channel, Item);
+process_iq_admin(_StateData, _Channel, _, _SubEl) ->
+ {error, xmpp:err_feature_not_implemented()}.
+
+process_admin(_StateData, _Channel, #muc_item{nick = <<"">>}) ->
+ {error, xmpp:err_feature_not_implemented()};
+process_admin(StateData, Channel, #muc_item{nick = Nick,
+ reason = Reason,
+ role = none}) ->
case Reason of
<<"">> ->
send_text(StateData,
@@ -1564,10 +1189,9 @@ process_admin(StateData, Channel, Nick, _Affiliation,
io_lib:format("KICK #~s ~s :~s\r\n",
[Channel, Nick, Reason]))
end,
- {result, []};
-process_admin(_StateData, _Channel, _Nick, _Affiliation,
- _Role, _Reason) ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
+ {result, undefined};
+process_admin(_StateData, _Channel, _Item) ->
+ {error, xmpp:err_feature_not_implemented()}.
filter_message(Msg) ->
list_to_binary(
@@ -1589,5 +1213,5 @@ unixtime2string(Unixtime) ->
{0, 0, 0}}),
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:universal_time_to_local_time(calendar:gregorian_seconds_to_datetime(Secs)),
- iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
+ (str:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second])).
diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl
index 9f8117ad3..e23f5a268 100644
--- a/src/mod_irc_mnesia.erl
+++ b/src/mod_irc_mnesia.erl
@@ -13,7 +13,7 @@
%% API
-export([init/2, get_data/3, set_data/4, import/2]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("mod_irc.hrl").
-include("logger.hrl").
@@ -21,7 +21,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(irc_custom,
+ ejabberd_mnesia:create(?MODULE, irc_custom,
[{disc_copies, [node()]},
{attributes, record_info(fields, irc_custom)}]),
update_table().
diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl
index 6ac7befdf..a71859c5c 100644
--- a/src/mod_irc_riak.erl
+++ b/src/mod_irc_riak.erl
@@ -13,7 +13,7 @@
%% API
-export([init/2, get_data/3, set_data/4, import/2]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("mod_irc.hrl").
%%%===================================================================
diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl
index 8aa428e54..7905db91f 100644
--- a/src/mod_irc_sql.erl
+++ b/src/mod_irc_sql.erl
@@ -15,7 +15,7 @@
%% API
-export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("mod_irc.hrl").
-include("ejabberd_sql_pt.hrl").
diff --git a/src/mod_last.erl b/src/mod_last.erl
index ce9148841..463eac051 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -33,16 +33,16 @@
-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,
+-export([start/2, stop/1, process_local_iq/1, export/1,
+ process_sm_iq/1, on_presence_update/4, import_info/0,
+ import/5, import_start/2, store_last_info/4, get_last_info/2,
remove_user/2, transform_options/1, mod_opt_type/1,
opt_type/1, register_user/2, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
-include("mod_last.hrl").
@@ -87,25 +87,14 @@ stop(Host) ->
%%% Uptime of ejabberd node
%%%
-process_local_iq(_From, _To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- Sec = get_node_uptime(),
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_LAST},
- {<<"seconds">>,
- iolist_to_binary(integer_to_list(Sec))}],
- children = []}]}
- end.
+-spec process_local_iq(iq()) -> iq().
+process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq(#iq{type = get} = IQ) ->
+ xmpp:make_iq_result(IQ, #last{seconds = get_node_uptime()}).
-%% @spec () -> integer()
+-spec get_node_uptime() -> non_neg_integer().
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() ->
@@ -118,6 +107,7 @@ get_node_uptime() ->
p1_time_compat:system_time(seconds) - Now
end.
+-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
@@ -125,83 +115,63 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%% Serve queries about user last online
%%%
-process_sm_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- User = To#jid.luser,
- Server = To#jid.lserver,
- {Subscription, _Groups} =
- ejabberd_hooks:run_fold(roster_get_jid_info, Server,
- {none, []}, [User, Server, From]),
- if (Subscription == both) or (Subscription == from) or
- (From#jid.luser == To#jid.luser) and
- (From#jid.lserver == To#jid.lserver) ->
- UserListRecord =
- ejabberd_hooks:run_fold(privacy_get_user_list, Server,
- #userlist{}, [User, Server]),
- case ejabberd_hooks:run_fold(privacy_check_packet,
- Server, allow,
- [User, Server, UserListRecord,
- {To, From,
- #xmlel{name = <<"presence">>,
- attrs = [],
- children = []}},
- out])
- of
- allow -> get_last_iq(IQ, SubEl, User, Server);
- deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
- end;
- true ->
- Txt = <<"Not subscribed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
- end
+-spec process_sm_iq(iq()) -> iq().
+process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
+ User = To#jid.luser,
+ Server = To#jid.lserver,
+ {Subscription, _Groups} =
+ ejabberd_hooks:run_fold(roster_get_jid_info, Server,
+ {none, []}, [User, Server, From]),
+ if (Subscription == both) or (Subscription == from) or
+ (From#jid.luser == To#jid.luser) and
+ (From#jid.lserver == To#jid.lserver) ->
+ UserListRecord =
+ ejabberd_hooks:run_fold(privacy_get_user_list, Server,
+ #userlist{}, [User, Server]),
+ case ejabberd_hooks:run_fold(privacy_check_packet,
+ Server, allow,
+ [User, Server, UserListRecord,
+ {To, From, #presence{}}, out]) of
+ allow -> get_last_iq(IQ, User, Server);
+ deny -> xmpp:make_error(IQ, xmpp:err_forbidden())
+ end;
+ true ->
+ Txt = <<"Not subscribed">>,
+ xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
+-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
+ not_found | {error, any()}.
get_last(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_last(LUser, LServer).
-get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
+-spec get_last_iq(iq(), binary(), binary()) -> iq().
+get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
case ejabberd_sm:get_user_resources(LUser, LServer) of
[] ->
case get_last(LUser, LServer) of
{error, _Reason} ->
Txt = <<"Database failure">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
not_found ->
Txt = <<"No info about last activity found">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]};
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang));
{ok, TimeStamp, Status} ->
TimeStamp2 = p1_time_compat:system_time(seconds),
Sec = TimeStamp2 - TimeStamp,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_LAST},
- {<<"seconds">>,
- iolist_to_binary(integer_to_list(Sec))}],
- children = [{xmlcdata, Status}]}]}
+ xmpp:make_iq_result(IQ, #last{seconds = Sec, status = Status})
end;
_ ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_LAST},
- {<<"seconds">>, <<"0">>}],
- children = []}]}
+ xmpp:make_iq_result(IQ, #last{seconds = 0})
end.
+-spec register_user(binary(), binary()) -> {atomic, any()}.
register_user(User, Server) ->
on_presence_update(
User,
@@ -209,42 +179,56 @@ register_user(User, Server) ->
<<"RegisterResource">>,
<<"Registered but didn't login">>).
+-spec on_presence_update(binary(), binary(), binary(), binary()) -> {atomic, any()}.
on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = p1_time_compat:system_time(seconds),
store_last_info(User, Server, TimeStamp, Status).
+-spec store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
+ {atomic, any()}.
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_last_info(LUser, LServer, TimeStamp, Status).
-%% @spec (LUser::string(), LServer::string()) ->
-%% {ok, TimeStamp::integer(), Status::string()} | not_found
+-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
+ not_found.
get_last_info(LUser, LServer) ->
case get_last(LUser, LServer) of
{error, _Reason} -> not_found;
Res -> Res
end.
+-spec remove_user(binary(), binary()) -> any().
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
-export(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:export(LServer).
-
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"last">>, 3}].
-import(LServer, DBType, LA) ->
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
+
+import(LServer, {sql, _}, DBType, <<"last">>, [LUser, TimeStamp, State]) ->
+ TS = case TimeStamp of
+ <<"">> -> 0;
+ _ -> binary_to_integer(TimeStamp)
+ end,
+ LA = #last_activity{us = {LUser, LServer},
+ timestamp = TS,
+ status = State},
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, LA).
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
+
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl
index 7a1610abb..269ed4ba0 100644
--- a/src/mod_last_mnesia.erl
+++ b/src/mod_last_mnesia.erl
@@ -19,7 +19,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(last_activity,
+ ejabberd_mnesia:create(?MODULE, last_activity,
[{disc_copies, [node()]},
{attributes,
record_info(fields, last_activity)}]),
diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl
index 13b028c6f..0351e668c 100644
--- a/src/mod_last_sql.erl
+++ b/src/mod_last_sql.erl
@@ -13,7 +13,7 @@
%% API
-export([init/2, get_last/2, store_last_info/4, remove_user/2,
- import/1, import/2, export/1]).
+ import/2, export/1]).
-include("mod_last.hrl").
-include("logger.hrl").
@@ -43,9 +43,6 @@ store_last_info(LUser, LServer, TimeStamp, Status) ->
remove_user(LUser, LServer) ->
sql_queries:del_last(LServer, LUser).
-import(_LServer, _LA) ->
- pass.
-
export(_Server) ->
[{last_activity,
fun(Host, #last_activity{us = {LUser, LServer},
@@ -58,15 +55,5 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, seconds, state from last">>,
- fun([LUser, TimeStamp, State]) ->
- #last_activity{us = {LUser, LServer},
- timestamp = jlib:binary_to_integer(
- TimeStamp),
- status = State}
- end}].
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
+import(_LServer, _LA) ->
+ pass.
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index 8f6492047..edb0d1485 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -34,12 +34,12 @@
-export([start/2, stop/1, depends/2]).
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
- process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
- remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
+ process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
+ remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
- get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
+ get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/3]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_muc_room.hrl").
-include("ejabberd_commands.hrl").
@@ -54,17 +54,13 @@
-callback delete_old_messages(binary() | global,
erlang:timestamp(),
all | chat | groupchat) -> any().
--callback extended_fields() -> [xmlel()].
+-callback extended_fields() -> [mam_query:property() | #xdata_field{}].
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send) -> {ok, binary()} | any().
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
--callback select(binary(), jid(), jid(),
- none | erlang:timestamp(),
- none | erlang:timestamp(),
- none | ljid() | {text, binary()},
- none | #rsm_in{},
- chat | groupchat) ->
+-callback select(binary(), jid(), jid(), mam_query:result(),
+ #rsm_set{} | undefined, chat | groupchat) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
%%%===================================================================
@@ -174,72 +170,46 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_user(LUser, LServer),
+ cache_tab:dirty_delete(archive_prefs, {LUser, LServer}, fun() -> ok end),
+ ok.
+-spec remove_room(binary(), binary(), binary()) -> ok.
remove_room(LServer, Name, Host) ->
LName = jid:nodeprep(Name),
LHost = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_room(LServer, LName, LHost).
+ Mod:remove_room(LServer, LName, LHost),
+ ok.
-get_room_config(X, RoomState, _From, Lang) ->
+-spec get_room_config([muc_roomconfig:property()], mod_muc_room:state(),
+ jid(), binary()) -> [muc_roomconfig:property()].
+get_room_config(Fields, RoomState, _From, _Lang) ->
Config = RoomState#state.config,
- Label = <<"Enable message archiving">>,
- Var = <<"muc#roomconfig_mam">>,
- Val = case Config#config.mam of
- true -> <<"1">>;
- _ -> <<"0">>
- end,
- XField = #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"boolean">>},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]},
- X ++ [XField].
+ Fields ++ [{mam, Config#config.mam}].
-set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
- try
- Val = case Vals of
- [<<"0">>|_] -> false;
- [<<"false">>|_] -> false;
- [<<"1">>|_] -> true;
- [<<"true">>|_] -> true
- end,
- {#config.mam, Val}
- catch _:{case_clause, _} ->
- Txt = <<"Value of '~s' should be boolean">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
- end;
-set_room_option(Acc, _Opt, _Vals, _Lang) ->
+-spec set_room_option({pos_integer(), _}, muc_roomconfig:property(), binary())
+ -> {pos_integer(), _}.
+set_room_option(_Acc, {mam, Val}, _Lang) ->
+ {#config.mam, Val};
+set_room_option(Acc, _Property, _Lang) ->
Acc.
-user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
+-spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
+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 ->
+ true ->
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};
+ set_stanza_id(NewPkt, JID, ID);
_ ->
NewPkt
end;
@@ -247,25 +217,17 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
Pkt
end.
+-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
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),
- case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
+ case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
LUser, LServer, Peer, send) 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};
+ set_stanza_id(NewPkt, JID, ID);
_ ->
NewPkt
end;
@@ -273,10 +235,14 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
Pkt
end.
+-spec user_send_packet_strip_tag(stanza(), ejabberd_c2s:state(),
+ jid(), jid()) -> stanza().
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
LServer = JID#jid.lserver,
strip_my_archived_tag(Pkt, LServer).
+-spec muc_filter_message(message(), mod_muc_room:state(),
+ jid(), jid(), binary()) -> message().
muc_filter_message(Pkt, #state{config = Config} = MUCState,
RoomJID, From, FromNick) ->
if Config#config.mam ->
@@ -285,16 +251,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
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};
+ set_stanza_id(NewPkt, RoomJID, ID);
_ ->
NewPkt
end;
@@ -302,75 +259,87 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
Pkt
end.
+set_stanza_id(Pkt, JID, ID) ->
+ BareJID = jid:remove_resource(JID),
+ Archived = #mam_archived{by = BareJID, id = ID},
+ StanzaID = #stanza_id{by = BareJID, id = ID},
+ NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
+ xmpp:set_els(Pkt, NewEls).
+
% 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).
+process_iq_v0_2(#iq{from = #jid{lserver = LServer},
+ to = #jid{lserver = LServer},
+ type = get, sub_els = [#mam_query{}]} = IQ) ->
+ process_iq(LServer, IQ, chat);
+process_iq_v0_2(IQ) ->
+ process_iq(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_v0_3(#iq{from = #jid{lserver = LServer},
+ to = #jid{lserver = LServer},
+ type = set, sub_els = [#mam_query{}]} = IQ) ->
+ process_iq(LServer, IQ, chat);
+process_iq_v0_3(#iq{from = #jid{lserver = LServer},
+ to = #jid{lserver = LServer},
+ type = get, sub_els = [#mam_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 ->
+process_iq_v0_3(IQ) ->
+ process_iq(IQ).
+
+-spec muc_process_iq(ignore | iq(), mod_muc_room:state()) -> ignore | iq().
+muc_process_iq(#iq{type = T, lang = Lang,
+ from = From,
+ sub_els = [#mam_query{xmlns = NS}]} = IQ,
+ MUCState)
+ when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse
+ (T == get andalso NS == ?NS_MAM_TMP) ->
+ case may_enter_room(From, MUCState) of
+ true ->
LServer = MUCState#state.server_host,
- process_iq(LServer, IQ);
- _ ->
- IQ
+ Role = mod_muc_room:get_role(From, MUCState),
+ process_iq(LServer, IQ, {groupchat, Role, MUCState});
+ false ->
+ Text = <<"Only members may query archives of this room">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang))
end;
-muc_process_iq(IQ, _MUCState, _From, _To) ->
+muc_process_iq(#iq{type = get,
+ sub_els = [#mam_query{xmlns = NS}]} = IQ,
+ MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+ LServer = MUCState#state.server_host,
+ process_iq(LServer, IQ);
+muc_process_iq(IQ, _MUCState) ->
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.
+parse_query(#mam_query{xmlns = ?NS_MAM_TMP,
+ start = Start, 'end' = End,
+ with = With, withtext = Text}, _Lang) ->
+ {ok, [{start, Start}, {'end', End},
+ {with, With}, {withtext, Text}]};
+parse_query(#mam_query{xdata = #xdata{}} = Query, Lang) ->
+ X = xmpp_util:set_xdata_field(
+ #xdata_field{var = <<"FORM_TYPE">>,
+ type = hidden, values = [?NS_MAM_1]},
+ Query#mam_query.xdata),
+ try mam_query:decode(X#xdata.fields) of
+ Form -> {ok, Form}
+ catch _:{mam_query, Why} ->
+ Txt = mam_query:format_error(Why),
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+parse_query(#mam_query{}, _Lang) ->
+ {ok, []}.
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) ->
+ #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.
+-spec message_is_archived(boolean(), ejabberd_c2s:state(),
+ jid(), jid(), message()) -> boolean().
message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
true;
message_is_archived(false, C2SState, Peer,
@@ -415,210 +384,159 @@ delete_old_messages(_TypeBin, _Days) ->
%%% 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">>}]}],
+process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
+ CommonFields = [{with, undefined},
+ {start, undefined},
+ {'end', undefined}],
ExtendedFields = Mod:extended_fields(),
- Fields = ExtendedFields ++ CommonFields,
- 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]}]}.
+ Fields = mam_query:encode(CommonFields ++ ExtendedFields),
+ X = xmpp_util:set_xdata_field(
+ #xdata_field{var = <<"FORM_TYPE">>, type = hidden, values = [NS]},
+ #xdata{type = form, fields = Fields}),
+ xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = X}).
% Preference setting (both v0.2 & v0.3)
-process_iq(#jid{luser = LUser, lserver = LServer},
- #jid{lserver = LServer},
- #iq{type = set, lang = Lang, 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 ->
- Txt = <<"Database failure">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
- end
- catch _:_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+process_iq(#iq{type = set, lang = Lang,
+ sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) ->
+ Why = {missing_attr, <<"default">>, <<"prefs">>, NS},
+ ErrTxt = xmpp:format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang));
+process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
+ to = #jid{lserver = LServer},
+ type = set, lang = Lang,
+ sub_els = [#mam_prefs{xmlns = NS,
+ default = Default,
+ always = Always0,
+ never = Never0}]} = IQ) ->
+ Always = lists:usort(get_jids(Always0)),
+ Never = lists:usort(get_jids(Never0)),
+ case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
+ ok ->
+ NewPrefs = prefs_el(Default, Always, Never, NS),
+ xmpp:make_iq_result(IQ, NewPrefs);
+ _Err ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
-process_iq(#jid{luser = LUser, lserver = LServer},
- #jid{lserver = LServer},
- #iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
+process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
+ to = #jid{lserver = LServer},
+ type = get, sub_els = [#mam_prefs{xmlns = NS}]} = 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]}.
+ NS),
+ xmpp:make_iq_result(IQ, PrefsEl);
+process_iq(IQ) ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed()).
-process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
+process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
+ sub_els = [SubEl]} = IQ, 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]}
+ case SubEl of
+ #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
+ xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
+ #mam_query{rsm = RSM, xmlns = NS} ->
+ case parse_query(SubEl, Lang) of
+ {ok, Query} ->
+ NewRSM = limit_max(RSM, NS),
+ select_and_send(LServer, Query, NewRSM, IQ, MsgType);
+ {error, Err} ->
+ xmpp:make_error(IQ, Err)
+ end
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) ->
+should_archive(#message{type = error}, _LServer) ->
+ false;
+should_archive(#message{meta = #{sm_copy := true}}, _LServer) ->
+ false;
+should_archive(#message{body = Body, subject = Subject,
+ type = Type} = Pkt, LServer) ->
case is_resent(Pkt, LServer) of
true ->
false;
false ->
- case {check_store_hint(Pkt),
- fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs)} of
- {_Hint, <<"error">>} ->
- false;
- {store, _Type} ->
+ case check_store_hint(Pkt) of
+ store ->
true;
- {no_store, _Type} ->
- false;
- {none, <<"groupchat">>} ->
+ no_store ->
false;
- {none, <<"headline">>} ->
+ none when Type == groupchat; Type == headline ->
false;
- {none, _Type} ->
- case fxml:get_subtag_cdata(Pkt, <<"body">>) of
- <<>> ->
- %% Empty body
- false;
- _ ->
- true
- end
+ none ->
+ xmpp:get_text(Body) /= <<>> orelse
+ xmpp:get_text(Subject) /= <<>>
end
end;
-should_archive(#xmlel{}, _LServer) ->
+should_archive(_, _LServer) ->
false.
+-spec strip_my_archived_tag(stanza(), binary()) -> stanza().
strip_my_archived_tag(Pkt, LServer) ->
+ Els = xmpp:get_els(Pkt),
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}.
-
+ fun(El) ->
+ Name = xmpp:get_name(El),
+ NS = xmpp:get_ns(El),
+ if (Name == <<"archived">> andalso NS == ?NS_MAM_TMP);
+ (Name == <<"stanza-id">> andalso NS == ?NS_SID_0) ->
+ try xmpp:decode(El) of
+ #mam_archived{by = By} ->
+ By#jid.lserver /= LServer;
+ #stanza_id{by = By} ->
+ By#jid.lserver /= LServer
+ catch _:{xmpp_codec, _} ->
+ false
+ end;
+ true ->
+ true
+ end
+ end, Els),
+ xmpp:set_els(Pkt, NewEls).
+
+-spec strip_x_jid_tags(stanza()) -> stanza().
strip_x_jid_tags(Pkt) ->
+ Els = xmpp:get_els(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}.
+ fun(El) ->
+ case xmpp:get_name(El) of
+ <<"x">> ->
+ NS = xmpp:get_ns(El),
+ Items = if NS == ?NS_MUC_USER;
+ NS == ?NS_MUC_ADMIN;
+ NS == ?NS_MUC_OWNER ->
+ try xmpp:decode(El) of
+ #muc_user{items = Is} -> Is;
+ #muc_admin{items = Is} -> Is;
+ #muc_owner{items = Is} -> Is
+ catch _:{xmpp_codec, _} ->
+ []
+ end;
+ true ->
+ []
+ end,
+ not lists:any(
+ fun(#muc_item{jid = JID}) ->
+ JID /= undefined
+ end, Items);
+ _ ->
+ true
+ end
+ end, Els),
+ xmpp:set_els(Pkt, NewEls).
should_archive_peer(C2SState,
#archive_prefs{default = Default,
always = Always,
never = Never},
Peer) ->
- LPeer = jid:tolower(Peer),
+ LPeer = jid:remove_resource(jid:tolower(Peer)),
case lists:member(LPeer, Always) of
true ->
true;
@@ -642,30 +560,28 @@ should_archive_peer(C2SState,
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;
+should_archive_muc(#message{type = groupchat,
+ body = Body, subject = Subj} = Pkt) ->
+ case check_store_hint(Pkt) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case xmpp:get_text(Body) of
+ <<"">> ->
+ case xmpp:get_text(Subj) of
+ <<"">> ->
+ false;
_ ->
true
- end
- end;
- _ ->
- false
- end.
+ end;
+ _ ->
+ true
+ end
+ end;
+should_archive_muc(_) ->
+ false.
check_store_hint(Pkt) ->
case has_store_hint(Pkt) of
@@ -680,30 +596,24 @@ check_store_hint(Pkt) ->
end
end.
+
+-spec has_store_hint(message()) -> boolean().
has_store_hint(Message) ->
- fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
- /= false.
+ xmpp:has_subtag(Message, #hint{type = 'store'}).
+-spec has_no_store_hint(message()) -> boolean().
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.
+ xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse
+ xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse
+ xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse
+ xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}).
+-spec is_resent(message(), binary()) -> boolean().
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 ->
+ case xmpp:get_subtag(Pkt, #stanza_id{}) of
+ #stanza_id{by = #jid{lserver = LServer}} ->
+ true;
+ _ ->
false
end.
@@ -724,7 +634,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:store(NewPkt, LServer, US, chat, Peer, <<"">>, Dir)
+ El = xmpp:encode(NewPkt),
+ Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir)
end;
false ->
pass
@@ -741,7 +652,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:store(NewPkt, LServer, {U, S}, groupchat, Peer, Nick, recv)
+ El = xmpp:encode(NewPkt),
+ Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv)
end;
false ->
pass
@@ -783,20 +695,10 @@ get_prefs(LUser, LServer) ->
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]}.
+ #mam_prefs{default = Default,
+ always = [jid:make(LJ) || LJ <- Always],
+ never = [jid:make(LJ) || LJ <- Never],
+ xmlns = NS}.
maybe_activate_mam(LUser, LServer) ->
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
@@ -825,72 +727,68 @@ maybe_activate_mam(LUser, LServer) ->
ok
end.
-select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
- {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
- With, RSM, MsgType),
+select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
+ {Msgs, IsComplete, Count} =
+ case MsgType of
+ chat ->
+ select(LServer, From, From, Query, RSM, MsgType);
+ {groupchat, _Role, _MUCState} ->
+ select(LServer, From, To, Query, RSM, MsgType)
+ end,
SortedMsgs = lists:keysort(2, Msgs),
- send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
-
-select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
- case MsgType of
- chat ->
- select(LServer, From, From, Start, End, With, RSM, MsgType);
- {groupchat, _Role, _MUCState} ->
- select(LServer, From, To, Start, End, With, RSM, MsgType)
- end.
+ send(SortedMsgs, Count, IsComplete, IQ).
-select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
+select(_LServer, JidRequestor, JidArchive, Query, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
history = History}} = MsgType) ->
+ Start = proplists:get_value(start, Query),
+ End = proplists:get_value('end', Query),
#lqueue{len = L, queue = Q} = History,
- {Msgs0, _} =
- lists:mapfoldl(
- fun({Nick, Pkt, _HaveSubject, UTCDateTime, _Size}, I) ->
- Now = datetime_to_now(UTCDateTime, I),
+ Msgs =
+ lists:flatmap(
+ fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
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};
+ [{integer_to_binary(TS), TS,
+ msg_to_el(#archive_msg{
+ type = groupchat,
+ timestamp = Now,
+ peer = undefined,
+ nick = Nick,
+ packet = Pkt},
+ MsgType, JidRequestor, JidArchive)}];
false ->
- {[], I+1}
+ []
end
- end, 0, queue:to_list(Q)),
- Msgs = lists:flatten(Msgs0),
+ end, queue:to_list(Q)),
case RSM of
- #rsm_in{max = Max, direction = before} ->
+ #rsm_set{max = Max, before = Before} when is_binary(Before) ->
{NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
{NewMsgs, IsComplete, L};
- #rsm_in{max = Max} ->
+ #rsm_set{max = Max} ->
{NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
{NewMsgs, IsComplete, L};
_ ->
{Msgs, true, L}
end;
-select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
+select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
- MsgType).
+ Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
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,
+ #forwarded{xml_els = [xmpp:encode(Pkt2)],
+ delay = #delay{stamp = TS, from = jid:make(LServer)}}.
+
+maybe_update_from_to(#xmlel{} = El, JidRequestor, JidArchive, Peer,
+ {groupchat, _, _} = MsgType, Nick) ->
+ Pkt = xmpp:decode(El, ?NS_CLIENT, [ignore_els]),
+ maybe_update_from_to(Pkt, JidRequestor, JidArchive, Peer, MsgType, Nick);
+maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
Peer, {groupchat, Role,
#state{config = #config{anonymous = Anon}}},
Nick) ->
@@ -906,110 +804,58 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
end,
Items = case ExposeJID of
true ->
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs = [{<<"jid">>,
- jid:to_string(Peer)}]}]}];
+ [#muc_user{items = [#muc_item{jid = Peer}]}];
false ->
[]
end,
- Pkt1 = Pkt#xmlel{children = Items ++ Els},
- Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
- jlib:remove_attr(<<"to">>, Pkt2);
+ Pkt#message{from = jid:replace_resource(JidArchive, Nick),
+ to = undefined,
+ sub_els = Items ++ Els};
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,
- Hint = [#xmlel{name = <<"no-store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
+-spec send([{binary(), integer(), xmlel()}],
+ non_neg_integer(), boolean(), iq()) -> iq() | ignore.
+send(Msgs, Count, IsComplete,
+ #iq{from = From, to = To,
+ sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
+ Hint = #hint{type = 'no-store'},
Els = lists:map(
fun({ID, _IDInt, El}) ->
- #xmlel{name = <<"message">>,
- children = [#xmlel{name = <<"result">>,
- attrs = [{<<"xmlns">>, NS},
- {<<"id">>, ID}|QIDAttr],
- children = [El]} | Hint]}
+ #message{sub_els = [#mam_result{xmlns = NS,
+ id = ID,
+ queryid = QID,
+ sub_els = [El]}]}
end, Msgs),
- RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
+ RSMOut = make_rsm_out(Msgs, Count),
+ Result = if NS == ?NS_MAM_TMP ->
+ #mam_query{xmlns = NS, id = QID, rsm = RSMOut};
+ true ->
+ #mam_fin{xmlns = NS, id = QID, rsm = RSMOut,
+ complete = IsComplete}
+ end,
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};
+ xmpp:make_iq_result(IQ, Result);
NS == ?NS_MAM_0 ->
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
+ ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)),
lists:foreach(
fun(El) ->
ejabberd_router:route(To, From, El)
end, Els),
- ejabberd_router:route(
- To, From, #xmlel{name = <<"message">>,
- children = RSMOut ++ Hint}),
+ ejabberd_router:route(To, From, #message{sub_els = [Result, Hint]}),
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) ->
+-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
+make_rsm_out([], Count) ->
+ #rsm_set{count = Count};
+make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
{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})}].
+ #rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}.
filter_by_max(Msgs, undefined) ->
{Msgs, true};
@@ -1018,25 +864,28 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
filter_by_max(_Msgs, _Junk) ->
{[], true}.
+-spec limit_max(rsm_set(), binary()) -> rsm_set() | undefined.
limit_max(RSM, ?NS_MAM_TMP) ->
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
-limit_max(none, _NS) ->
- #rsm_in{max = ?DEF_PAGE_SIZE};
-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(undefined, _NS) ->
+ #rsm_set{max = ?DEF_PAGE_SIZE};
+limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) ->
+ RSM#rsm_set{max = ?DEF_PAGE_SIZE};
+limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
+ RSM#rsm_set{max = ?MAX_PAGE_SIZE};
limit_max(RSM, _NS) ->
RSM.
+match_interval(Now, Start, undefined) ->
+ Now >= Start;
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))),
+match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
+ Now1 = (catch usec_to_now(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))),
+match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
+ Now1 = (catch usec_to_now(binary_to_integer(ID))),
Now < Now1;
match_rsm(_Now, _) ->
true.
@@ -1051,20 +900,10 @@ usec_to_now(Int) ->
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).
+get_jids(undefined) ->
+ [];
+get_jids(Js) ->
+ [jid:tolower(jid:remove_resource(J)) || J <- Js].
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index be14d0fff..89ab92ff1 100644
--- a/src/mod_mam_mnesia.erl
+++ b/src/mod_mam_mnesia.erl
@@ -12,10 +12,10 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
- extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+ extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
-include_lib("stdlib/include/ms_transform.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_mam.hrl").
@@ -32,11 +32,11 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(archive_msg,
+ ejabberd_mnesia:create(?MODULE, archive_msg,
[{disc_only_copies, [node()]},
{type, bag},
{attributes, record_info(fields, archive_msg)}]),
- mnesia:create_table(archive_prefs,
+ ejabberd_mnesia:create(?MODULE, archive_prefs,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, archive_prefs)}]).
@@ -97,7 +97,7 @@ store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
_ ->
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
TS = p1_time_compat:timestamp(),
- ID = jlib:integer_to_binary(now_to_usec(TS)),
+ ID = integer_to_binary(now_to_usec(TS)),
F = fun() ->
mnesia:write(
#archive_msg{us = {LUser, LServer},
@@ -132,8 +132,14 @@ get_prefs(LUser, LServer) ->
select(_LServer, JidRequestor,
#jid{luser = LUser, lserver = LServer} = JidArchive,
- Start, End, With, RSM, MsgType) ->
- MS = make_matchspec(LUser, LServer, Start, End, With),
+ Query, RSM, MsgType) ->
+ Start = proplists:get_value(start, Query),
+ End = proplists:get_value('end', Query),
+ With = proplists:get_value(with, Query),
+ LWith = if With /= undefined -> jid:tolower(With);
+ true -> undefined
+ end,
+ MS = make_matchspec(LUser, LServer, Start, End, LWith),
Msgs = mnesia:dirty_select(archive_msg, MS),
SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
{FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
@@ -141,7 +147,7 @@ select(_LServer, JidRequestor,
Result = {lists:map(
fun(Msg) ->
{Msg#archive_msg.id,
- jlib:binary_to_integer(Msg#archive_msg.id),
+ binary_to_integer(Msg#archive_msg.id),
mod_mam:msg_to_el(Msg, MsgType, JidRequestor,
JidArchive)}
end, FilteredMsgs), IsComplete, Count},
@@ -154,6 +160,9 @@ select(_LServer, JidRequestor,
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
+make_matchspec(LUser, LServer, Start, undefined, With) ->
+ %% List is always greater than a tuple
+ make_matchspec(LUser, LServer, Start, [], With);
make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
ets:fun2ms(
fun(#archive_msg{timestamp = TS,
@@ -174,7 +183,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
Peer == With ->
Msg
end);
-make_matchspec(LUser, LServer, Start, End, none) ->
+make_matchspec(LUser, LServer, Start, End, undefined) ->
ets:fun2ms(
fun(#archive_msg{timestamp = TS,
us = US,
@@ -184,28 +193,27 @@ make_matchspec(LUser, LServer, Start, End, none) ->
Msg
end).
-filter_by_rsm(Msgs, none) ->
+filter_by_rsm(Msgs, undefined) ->
{Msgs, true};
-filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+filter_by_rsm(_Msgs, #rsm_set{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 /= <<"">> ->
+filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) ->
+ NewMsgs = if is_binary(After), After /= <<"">> ->
lists:filter(
fun(#archive_msg{id = I}) ->
- ?BIN_GREATER_THAN(I, ID)
+ ?BIN_GREATER_THAN(I, After)
end, Msgs);
- before when ID /= <<"">> ->
+ is_binary(Before), Before /= <<"">> ->
lists:foldl(
fun(#archive_msg{id = I} = Msg, Acc)
- when ?BIN_LESS_THAN(I, ID) ->
+ when ?BIN_LESS_THAN(I, Before) ->
[Msg|Acc];
(_, Acc) ->
Acc
end, [], Msgs);
- before when ID == <<"">> ->
+ is_binary(Before), Before == <<"">> ->
lists:reverse(Msgs);
- _ ->
+ true ->
Msgs
end,
filter_by_max(NewMsgs, Max).
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
index 20ed8d4f1..c500745a3 100644
--- a/src/mod_mam_sql.erl
+++ b/src/mod_mam_sql.erl
@@ -14,10 +14,10 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
- extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+ extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
-include_lib("stdlib/include/ms_transform.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_mam.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -51,13 +51,11 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
ok.
extended_fields() ->
- [#xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"text-single">>},
- {<<"var">>, <<"withtext">>}]}].
+ [{withtext, <<"">>}].
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
TSinteger = p1_time_compat:system_time(micro_seconds),
- ID = jlib:integer_to_binary(TSinteger),
+ ID = integer_to_binary(TSinteger),
SUser = case Type of
chat -> LUser;
groupchat -> jid:to_string({LUser, LHost, <<>>})
@@ -126,13 +124,12 @@ get_prefs(LUser, LServer) ->
end.
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
- Start, End, With, RSM, MsgType) ->
+ MAMQuery, RSM, MsgType) ->
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),
+ {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, 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
@@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
case {ejabberd_sql:sql_query(LServer, Query),
ejabberd_sql:sql_query(LServer, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
- {Max, Direction} = case RSM of
- #rsm_in{max = M, direction = D} -> {M, D};
- _ -> {undefined, undefined}
- end,
+ {Max, Direction, _} = get_max_direction_id(RSM),
{Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
@@ -160,14 +154,14 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
fun([TS, XML, PeerBin, Kind, Nick]) ->
try
#xmlel{} = El = fxml_stream:parse_element(XML),
- Now = usec_to_now(jlib:binary_to_integer(TS)),
+ Now = usec_to_now(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),
+ [{TS, binary_to_integer(TS),
mod_mam:msg_to_el(#archive_msg{timestamp = Now,
packet = El,
type = T,
@@ -182,7 +176,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
[Err, TS, XML, PeerBin, Kind, Nick]),
[]
end
- end, Res1), IsComplete, jlib:binary_to_integer(Count)};
+ end, Res1), IsComplete, binary_to_integer(Count)};
_ ->
{[], false, 0}
end.
@@ -200,15 +194,12 @@ usec_to_now(Int) ->
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
-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,
+make_sql_query(User, LServer, MAMQuery, RSM) ->
+ Start = proplists:get_value(start, MAMQuery),
+ End = proplists:get_value('end', MAMQuery),
+ With = proplists:get_value(with, MAMQuery),
+ WithText = proplists:get_value(withtext, MAMQuery),
+ {Max, Direction, ID} = get_max_direction_id(RSM),
ODBCType = ejabberd_config:get_option(
{sql_type, LServer},
ejabberd_sql:opt_type(sql_type)),
@@ -219,21 +210,22 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
_ -> fun ejabberd_sql:escape/1
end,
LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
- [<<" limit ">>, jlib:integer_to_binary(Max+1)];
+ [<<" limit ">>, integer_to_binary(Max+1)];
true ->
[]
end,
TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql ->
- [<<" TOP ">>, jlib:integer_to_binary(Max+1)];
+ [<<" TOP ">>, integer_to_binary(Max+1)];
true ->
[]
end,
- WithClause = case With of
- {text, <<>>} ->
- [];
- {text, Txt} ->
- [<<" and match (txt) against ('">>,
- Escape(Txt), <<"')">>];
+ WithTextClause = if is_binary(WithText), WithText /= <<>> ->
+ [<<" and match (txt) against ('">>,
+ Escape(WithText), <<"')">>];
+ true ->
+ []
+ end,
+ WithClause = case catch jid:tolower(With) of
{_, _, <<>>} ->
[<<" and bare_peer='">>,
Escape(jid:to_string(With)),
@@ -242,15 +234,15 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
[<<" and peer='">>,
Escape(jid:to_string(With)),
<<"'">>];
- none ->
+ _ ->
[]
end,
- PageClause = case catch jlib:binary_to_integer(ID) of
+ PageClause = case catch binary_to_integer(ID) of
I when is_integer(I), I >= 0 ->
case Direction of
before ->
[<<" AND timestamp < ">>, ID];
- aft ->
+ 'after' ->
[<<" AND timestamp > ">>, ID];
_ ->
[]
@@ -261,14 +253,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
StartClause = case Start of
{_, _, _} ->
[<<" and timestamp >= ">>,
- jlib:integer_to_binary(now_to_usec(Start))];
+ integer_to_binary(now_to_usec(Start))];
_ ->
[]
end,
EndClause = case End of
{_, _, _} ->
[<<" and timestamp <= ">>,
- jlib:integer_to_binary(now_to_usec(End))];
+ integer_to_binary(now_to_usec(End))];
_ ->
[]
end,
@@ -276,7 +268,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause,
+ SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause,
PageClause],
QueryPage =
@@ -294,4 +286,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
end,
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
+ SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}.
+
+-spec get_max_direction_id(rsm_set() | undefined) ->
+ {integer() | undefined,
+ before | 'after' | undefined,
+ binary()}.
+get_max_direction_id(RSM) ->
+ case RSM of
+ #rsm_set{max = Max, before = Before} when is_binary(Before) ->
+ {Max, before, Before};
+ #rsm_set{max = Max, 'after' = After} when is_binary(After) ->
+ {Max, 'after', After};
+ #rsm_set{max = Max} ->
+ {Max, undefined, <<>>};
+ _ ->
+ {undefined, undefined, <<>>}
+ end.
diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl
index f1d487e0e..7861542c5 100644
--- a/src/mod_metrics.erl
+++ b/src/mod_metrics.erl
@@ -31,13 +31,7 @@
-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]).
+-include("xmpp.hrl").
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1,
depends/2]).
@@ -53,12 +47,26 @@
%%====================================================================
start(Host, _Opts) ->
- [ejabberd_hooks:add(Hook, Host, ?MODULE, Hook, 20)
- || Hook <- ?HOOKS].
+ ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, offline_message_hook, 20),
+ ejabberd_hooks:add(sm_register_connection_hook, Host, ?MODULE, sm_register_connection_hook, 20),
+ ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, sm_remove_connection_hook, 20),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 20),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, user_receive_packet, 20),
+ ejabberd_hooks:add(s2s_send_packet, Host, ?MODULE, s2s_send_packet, 20),
+ ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 20),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 20),
+ ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 20).
stop(Host) ->
- [ejabberd_hooks:delete(Hook, Host, ?MODULE, Hook, 20)
- || Hook <- ?HOOKS].
+ ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, offline_message_hook, 20),
+ ejabberd_hooks:delete(sm_register_connection_hook, Host, ?MODULE, sm_register_connection_hook, 20),
+ ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, sm_remove_connection_hook, 20),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 20),
+ ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, user_receive_packet, 20),
+ ejabberd_hooks:delete(s2s_send_packet, Host, ?MODULE, s2s_send_packet, 20),
+ ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 20),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 20),
+ ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 20).
depends(_Host, _Opts) ->
[].
@@ -66,29 +74,41 @@ depends(_Host, _Opts) ->
%%====================================================================
%% Hooks handlers
%%====================================================================
-
+-spec offline_message_hook(jid(), jid(), message()) -> any().
offline_message_hook(_From, #jid{lserver=LServer}, _Packet) ->
push(LServer, offline_message).
+-spec sm_register_connection_hook(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
sm_register_connection_hook(_SID, #jid{lserver=LServer}, _Info) ->
push(LServer, sm_register_connection).
+
+-spec sm_remove_connection_hook(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
sm_remove_connection_hook(_SID, #jid{lserver=LServer}, _Info) ->
push(LServer, sm_remove_connection).
+-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
user_send_packet(Packet, _C2SState, #jid{lserver=LServer}, _To) ->
push(LServer, user_send_packet),
Packet.
+
+-spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
user_receive_packet(Packet, _C2SState, _JID, _From, #jid{lserver=LServer}) ->
push(LServer, user_receive_packet),
Packet.
+-spec s2s_send_packet(jid(), jid(), stanza()) -> any().
s2s_send_packet(#jid{lserver=LServer}, _To, _Packet) ->
push(LServer, s2s_send_packet).
+
+-spec s2s_receive_packet(jid(), jid(), stanza()) -> any().
s2s_receive_packet(_From, #jid{lserver=LServer}, _Packet) ->
push(LServer, s2s_receive_packet).
+-spec remove_user(binary(), binary()) -> any().
remove_user(_User, Server) ->
push(jid:nameprep(Server), remove_user).
+
+-spec register_user(binary(), binary()) -> any().
register_user(_User, Server) ->
push(jid:nameprep(Server), register_user).
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
index a81efd5ce..f7bd0ec9a 100644
--- a/src/mod_mix.erl
+++ b/src/mod_mix.erl
@@ -12,7 +12,7 @@
-behaviour(gen_mod).
%% API
--export([start_link/2, start/2, stop/1, process_iq/3,
+-export([start_link/2, start/2, stop/1, process_iq/1,
disco_items/5, disco_identity/5, disco_info/5,
disco_features/5, mod_opt_type/1, depends/2]).
@@ -21,8 +21,7 @@
terminate/2, code_change/3]).
-include("logger.hrl").
--include("jlib.hrl").
--include("pubsub.hrl").
+-include("xmpp.hrl").
-define(PROCNAME, ejabberd_mod_mix).
-define(NODES, [?NS_MIX_NODES_MESSAGES,
@@ -53,88 +52,67 @@ stop(Host) ->
supervisor:delete_child(ejabberd_sup, Proc),
ok.
+-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) -> {result, [binary()]}.
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]};
+ BareTo = jid:remove_resource(To),
+ {result, [#disco_item{jid = BareTo, 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">>}]}];
+ Acc ++ [#identity{category = <<"conference">>,
+ name = <<"MIX service">>,
+ type = <<"text">>}];
disco_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc ++ [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"conference">>},
- {<<"type">>, <<"mix">>}]}].
+ Acc ++ [#identity{category = <<"conference">>,
+ type = <<"mix">>}].
+-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
+ ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
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}]}]}]}];
+ [#xdata{type = result,
+ fields = [#xdata_field{var = <<"FORM_TYPE">>,
+ type = hidden,
+ values = [?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),
+process_iq(#iq{type = set, from = From, to = To,
+ sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) ->
+ Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)],
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}]};
+ BareFrom = jid:remove_resource(From),
+ xmpp:make_iq_result(
+ IQ, #mix_join{jid = BareFrom, subscribe = Nodes});
{error, Err} ->
- IQ#iq{type = error, sub_el = [SubEl, Err]}
+ xmpp:make_error(IQ, Err)
end;
{error, Err} ->
- IQ#iq{type = error, sub_el = [SubEl, Err]}
+ xmpp:make_error(IQ, Err)
end;
-process_iq(From, To,
- #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) ->
+process_iq(#iq{type = set, from = From, to = To,
+ sub_els = [#mix_leave{}]} = IQ) ->
case delete_participant(From, To) of
{result, _} ->
case unsubscribe_nodes(From, To, ?NODES) of
{result, _} ->
- IQ#iq{type = result, sub_el = []};
+ xmpp:make_iq_result(IQ);
{error, Err} ->
- IQ#iq{type = error, sub_el = [SubEl, Err]}
+ xmpp:make_error(IQ, Err)
end;
{error, Err} ->
- IQ#iq{type = error, sub_el = [SubEl, Err]}
+ xmpp:make_error(IQ, Err)
end;
-process_iq(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+process_iq(#iq{lang = Lang} = IQ) ->
Txt = <<"Unsupported MIX query">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}.
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
%%%===================================================================
%%% gen_server callbacks
@@ -185,8 +163,8 @@ handle_info({route, From, To, Packet}, State) ->
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)
+ Error = xmpp:err_internal_server_error(),
+ ejabberd_router:route_error(To, From, Packet, Error)
catch _:_ ->
ok
end;
@@ -220,39 +198,29 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
%%% 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)
+do_route(_State, From, To, #iq{} = Packet) ->
+ ejabberd_router:process_iq(From, To, Packet);
+do_route(_State, From, To, #presence{type = unavailable})
when To#jid.luser /= <<"">> ->
- case fxml:get_tag_attr_s(<<"type">>, Packet) of
- <<"unavailable">> ->
- delete_presence(From, To);
- _ ->
- ok
- end;
+ delete_presence(From, To);
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
+ case mod_pubsub:subscribe_node(LTo, Node, From, From, []) 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, []);
+ mod_pubsub:subscribe_node(LTo, Node, From, From, []);
Error ->
Error
end;
@@ -266,13 +234,12 @@ subscribe_nodes(From, To, 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),
+ BareFrom = jid:remove_resource(From),
lists:foldl(
fun(_Node, {error, _} = Err) ->
Err;
(Node, {result, _} = Result) ->
- case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of
+ case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of
{error, _} = Err ->
case is_not_subscribed(Err) of
true -> Result;
@@ -284,15 +251,14 @@ unsubscribe_nodes(From, To, Nodes) ->
end, {result, []}, Nodes).
publish_participant(From, To) ->
- LFrom = jid:tolower(jid:remove_resource(From)),
+ BareFrom = jid:remove_resource(From),
+ LFrom = jid:tolower(BareFrom),
LTo = jid:tolower(jid:remove_resource(To)),
- Participant = #xmlel{name = <<"participant">>,
- attrs = [{<<"xmlns">>, ?NS_MIX_0},
- {<<"jid">>, jid:to_string(LFrom)}]},
+ Participant = #mix_participant{jid = BareFrom},
ItemID = p1_sha:sha(jid:to_string(LFrom)),
mod_pubsub:publish_item(
LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
- From, ItemID, [Participant]).
+ From, ItemID, [xmpp:encode(Participant)]).
delete_presence(From, To) ->
LFrom = jid:tolower(From),
@@ -300,8 +266,8 @@ delete_presence(From, 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 ->
+ fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _})
+ when LJID == LFrom ->
delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
(_) ->
ok
@@ -329,19 +295,16 @@ delete_item(From, To, Node, ItemID) ->
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.
+-spec is_item_not_found({error, stanza_error()}) -> boolean().
+is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true;
+is_item_not_found({error, _}) -> false.
-is_not_subscribed({error, ErrEl}) ->
- case fxml:get_subtag_with_xmlns(
- ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of
- #xmlel{} -> true;
- _ -> false
- end.
+-spec is_not_subscribed({error, stanza_error()}) -> boolean().
+is_not_subscribed({error, #stanza_error{sub_els = Els}}) ->
+ %% TODO: make xmpp:get_els function working for any XMPP element
+ %% with sub_els field
+ xmpp:has_subtag(#message{sub_els = Els},
+ #ps_error{type = 'not-subscribed'}).
depends(_Host, _Opts) ->
[{mod_pubsub, hard}].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 6b878b05b..298749329 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -43,11 +43,17 @@
forget_room/3,
create_room/5,
shutdown_rooms/1,
- process_iq_disco_items/5,
+ process_disco_info/1,
+ process_disco_items/1,
+ process_vcard/1,
+ process_register/1,
+ process_muc_unique/1,
+ process_mucsub/1,
broadcast_service_message/2,
export/1,
- import/1,
- import/3,
+ import_info/0,
+ import/5,
+ import_start/2,
opts_to_binary/1,
can_use_nick/4]).
@@ -57,8 +63,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("xmpp.hrl").
-include("mod_muc.hrl").
-record(state,
@@ -74,7 +80,7 @@
-type muc_room_opts() :: [{atom(), any()}].
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass.
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}.
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
@@ -153,17 +159,6 @@ forget_room(ServerHost, Host, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
-process_iq_disco_items(Host, From, To, MaxRoomsDiscoItems,
- #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, MaxRoomsDiscoItems, 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 = jid:nameprep(ServerHost),
@@ -175,12 +170,16 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
%%====================================================================
init([Host, Opts]) ->
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, [{host, MyHost}|Opts]),
- mnesia:create_table(muc_online_room,
+ update_tables(),
+ ejabberd_mnesia:create(?MODULE, muc_online_room,
[{ram_copies, [node()]},
+ {type, ordered_set},
{attributes, record_info(fields, muc_online_room)}]),
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
@@ -258,6 +257,18 @@ init([Host, Opts]) ->
RoomShaper = gen_mod:get_opt(room_shaper, Opts,
fun(A) when is_atom(A) -> A end,
none),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER,
+ ?MODULE, process_register, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB,
+ ?MODULE, process_mucsub, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE,
+ ?MODULE, process_muc_unique, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
@@ -319,8 +330,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-terminate(_Reason, State) ->
- ejabberd_router:unregister_route(State#state.host),
+terminate(_Reason, #state{host = MyHost}) ->
+ ejabberd_router:unregister_route(MyHost),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
@@ -330,215 +347,196 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) ->
+ From, To, Packet, DefRoomOpts, _MaxRoomsDiscoItems) ->
{AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
case acl:match_rule(ServerHost, AccessRoute, From) of
allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems);
- _ ->
- #xmlel{attrs = Attrs} = Packet,
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ From, To, Packet, DefRoomOpts);
+ deny ->
+ Lang = xmpp:get_lang(Packet),
ErrText = <<"Access denied by service policy">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route_error(To, From, Err, Packet)
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
end.
-
+do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
+ From, #jid{luser = <<"">>, lresource = <<"">>} = To,
+ #iq{} = IQ, _DefRoomOpts) ->
+ ejabberd_local:process_iq(From, To, IQ);
+do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
+ From, #jid{luser = <<"">>, lresource = <<"">>} = To,
+ #message{lang = Lang, body = Body, type = Type} = Packet, _) ->
+ {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access,
+ if Type == error ->
+ ok;
+ true ->
+ case acl:match_rule(ServerHost, AccessAdmin, From) of
+ allow ->
+ Msg = xmpp:get_text(Body),
+ broadcast_service_message(Host, Msg);
+ deny ->
+ ErrText = <<"Only service administrators are allowed "
+ "to send service messages">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end
+ end;
+do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
+ From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) ->
+ Err = xmpp:err_service_unavailable(),
+ ejabberd_router:route_error(To, From, Packet, Err);
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) ->
- {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
+ From, To, Packet, DefRoomOpts) ->
+ {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To),
- #xmlel{name = Name, attrs = Attrs} = Packet,
- case Room of
- <<"">> ->
- case Nick of
- <<"">> ->
- case Name of
- <<"iq">> ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
- sub_el = _SubEl, lang = Lang} =
- IQ ->
- Info = ejabberd_hooks:run_fold(disco_info,
- ServerHost, [],
- [ServerHost, ?MODULE,
- <<"">>, <<"">>]),
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children =
- iq_disco_info(
- ServerHost, Lang) ++
- Info}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
- spawn(?MODULE, process_iq_disco_items,
- [Host, From, To, MaxRoomsDiscoItems, IQ]);
- #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
- lang = Lang, sub_el = _SubEl} =
- IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children =
- iq_get_register_info(ServerHost,
- Host,
- From,
- Lang)}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
- lang = Lang, sub_el = SubEl} =
- IQ ->
- case process_iq_register_set(ServerHost, Host, From,
- SubEl, Lang)
- of
- {result, IQRes} ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- XMLNS}],
- children = IQRes}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- {error, Error} ->
- Err = jlib:make_error_reply(Packet, Error),
- ejabberd_router:route(To, From, Err)
- end;
- #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
- lang = Lang, sub_el = _SubEl} =
- IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs =
- [{<<"xmlns">>, XMLNS}],
- children =
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- #iq{type = get, xmlns = ?NS_MUCSUB,
- sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
- RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
- Subs = lists:map(
- fun(J) ->
- #xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>,
- jid:to_string(J)}]}
- end, RoomJIDs),
- Res = IQ#iq{type = result,
- sub_el = [SubEl#xmlel{children = Subs}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"unique">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_UNIQUE}],
- children =
- [iq_get_unique(From)]}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(Res));
- #iq{} ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ -> ok
- end;
- <<"message">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- _ ->
- case acl:match_rule(ServerHost, AccessAdmin, From)
- of
- allow ->
- Msg = fxml:get_path_s(Packet,
- [{elem, <<"body">>},
- cdata]),
- broadcast_service_message(Host, Msg);
- _ ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
- ErrText =
- <<"Only service administrators are allowed "
- "to send service messages">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang,
- ErrText)),
- ejabberd_router:route(To, From, Err)
- end
- end;
- <<"presence">> -> ok
- end;
- _ ->
- 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;
- _ ->
- case mnesia:dirty_read(muc_online_room, {Room, Host}) of
- [] ->
- case is_create_request(Packet) of
+ case mnesia:dirty_read(muc_online_room, {Room, Host}) of
+ [] ->
+ case is_create_request(Packet) of
+ true ->
+ case check_user_can_create_room(
+ ServerHost, AccessCreate, From, Room) and
+ check_create_roomid(ServerHost, Room) of
true ->
- case check_user_can_create_room(ServerHost,
- 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),
- register_room(Host, Room, Pid),
- mod_muc_room:route(Pid, From, Nick, Packet),
- ok;
- false ->
- 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;
+ {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 = 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)
+ Lang = xmpp:get_lang(Packet),
+ ErrText = <<"Room creation is denied by service policy">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(To, From, Packet, 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
+ false ->
+ Lang = xmpp:get_lang(Packet),
+ ErrText = <<"Conference room does not exist">>,
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(To, From, Packet, 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.
--spec is_create_request(xmlel()) -> boolean().
-is_create_request(#xmlel{name = <<"presence">>} = Packet) ->
- <<"">> == fxml:get_tag_attr_s(<<"type">>, Packet);
-is_create_request(#xmlel{name = <<"iq">>} = Packet) ->
- case jlib:iq_query_info(Packet) of
- #iq{type = set, xmlns = ?NS_MUCSUB,
- sub_el = #xmlel{name = <<"subscribe">>}} ->
- true;
- #iq{type = get, xmlns = ?NS_MUC_OWNER, sub_el = SubEl} ->
- [] == fxml:remove_cdata(SubEl#xmlel.children);
- _ ->
- false
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) ->
+ Desc = translate:translate(Lang, <<"ejabberd MUC module">>),
+ xmpp:make_iq_result(
+ IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>});
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_register(iq()) -> iq().
+process_register(#iq{type = get, from = From, to = To, lang = Lang,
+ sub_els = [#register{}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang));
+process_register(#iq{type = set, from = From, to = To,
+ lang = Lang, sub_els = [El = #register{}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ case process_iq_register_set(ServerHost, Host, From, El, Lang) of
+ {result, Result} ->
+ xmpp:make_iq_result(IQ, Result);
+ {error, Err} ->
+ xmpp:make_error(IQ, Err)
+ end.
+
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, to = To, lang = Lang,
+ sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
+ [ServerHost, ?MODULE, <<"">>, Lang]),
+ MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
+ true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
+ false -> []
+ end,
+ Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_REGISTER, ?NS_MUC, ?NS_RSM,
+ ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures],
+ Identity = #identity{category = <<"conference">>,
+ type = <<"text">>,
+ name = translate:translate(Lang, <<"Chatrooms">>)},
+ xmpp:make_iq_result(
+ IQ, #disco_info{features = Features,
+ identities = [Identity],
+ xdata = X});
+process_disco_info(#iq{type = get, lang = Lang,
+ sub_els = [#disco_info{}]} = IQ) ->
+ xmpp:make_error(IQ, xmpp:err_item_not_found(<<"Node not found">>, Lang));
+process_disco_info(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
+ sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ MaxRoomsDiscoItems = gen_mod:get_module_opt(
+ ServerHost, ?MODULE, max_rooms_discoitems,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 100),
+ case iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) of
+ {error, Err} ->
+ xmpp:make_error(IQ, Err);
+ {result, Result} ->
+ xmpp:make_iq_result(IQ, Result)
end;
+process_disco_items(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_muc_unique(iq()) -> iq().
+process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_muc_unique(#iq{from = From, type = get,
+ sub_els = [#muc_unique{}]} = IQ) ->
+ Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
+ randoms:get_string()])),
+ xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
+
+-spec process_mucsub(iq()) -> iq().
+process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_mucsub(#iq{type = get, from = From, to = To,
+ sub_els = [#muc_subscriptions{}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
+ xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs});
+process_mucsub(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec is_create_request(stanza()) -> boolean().
+is_create_request(#presence{type = available}) ->
+ true;
+is_create_request(#iq{type = T} = IQ) when T == get; T == set ->
+ xmpp:has_subtag(IQ, #muc_subscribe{}) orelse
+ xmpp:has_subtag(IQ, #muc_owner{});
is_create_request(_) ->
false.
@@ -603,207 +601,151 @@ register_room(Host, Room, Pid) ->
end,
mnesia:transaction(F).
-
-iq_disco_info(ServerHost, Lang) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"conference">>},
- {<<"type">>, <<"text">>},
- {<<"name">>,
- translate:translate(Lang, <<"Chatrooms">>)}],
- children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUC}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_RSM}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUCSUB}], children = []},
- #xmlel{name = <<"feature">>,
- 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, MaxRoomsDiscoItems, <<>>, none) ->
- Rooms = get_vh_rooms(Host),
- case erlang:length(Rooms) < MaxRoomsDiscoItems of
- true ->
- iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
- false ->
- iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<"nonemptyrooms">>, none)
- end;
-iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"nonemptyrooms">>, none) ->
- XmlEmpty = #xmlel{name = <<"item">>,
- attrs =
- [{<<"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, _MaxRoomsDiscoItems, <<"emptyrooms">>, none) ->
- iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
-iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, _DiscoNode, Rsm) ->
- {Rooms, RsmO} = get_vh_rooms(Host, Rsm),
- RsmOut = jlib:rsm_encode(RsmO),
- iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
-
-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,
- Query,
- 100)
- of
- {item, Desc} ->
- flush(),
- {true,
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string({Name, Host,
- <<"">>})},
- {<<"name">>, Desc}],
- children = []}};
- _ -> false
- end
- end, Rooms).
-
-get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
- AllRooms = lists:sort(get_vh_rooms(Host)),
- Count = erlang:length(AllRooms),
- Guard = case Direction of
- _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
- aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
- before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
- _ -> [{'==', {element, 2, '$1'}, Host}]
+-spec iq_disco_items(binary(), jid(), binary(), integer(), binary(),
+ rsm_set() | undefined) ->
+ {result, disco_items()} | {error, stanza_error()}.
+iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
+ when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> ->
+ Count = get_vh_rooms_count(Host),
+ Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems ->
+ {get_disco_item, only_non_empty, From, Lang};
+ Node == <<"nonemptyrooms">> ->
+ {get_disco_item, only_non_empty, From, Lang};
+ Node == <<"emptyrooms">> ->
+ {get_disco_item, 0, From, Lang};
+ true ->
+ {get_disco_item, all, From, Lang}
end,
- L = lists:sort(
- mnesia:dirty_select(muc_online_room,
- [{#muc_online_room{name_host = '$1', _ = '_'},
- Guard,
- ['$_']}])),
- L2 = if
- Index == undefined andalso Direction == before ->
- lists:reverse(lists:sublist(lists:reverse(L), 1, M));
- Index == undefined ->
- lists:sublist(L, 1, M);
- Index > Count orelse Index < 0 ->
- [];
- true ->
- lists:sublist(L, Index+1, M)
- end,
- if L2 == [] -> {L2, #rsm_out{count = Count}};
- true ->
- H = hd(L2),
- NewIndex = get_room_pos(H, AllRooms),
- T = lists:last(L2),
- {F, _} = H#muc_online_room.name_host,
- {Last, _} = T#muc_online_room.name_host,
- {L2,
- #rsm_out{first = F, last = Last, count = Count,
- index = NewIndex}}
+ Items = get_vh_rooms(Host, Query, RSM),
+ ResRSM = case Items of
+ [_|_] when RSM /= undefined ->
+ #disco_item{jid = #jid{luser = First}} = hd(Items),
+ #disco_item{jid = #jid{luser = Last}} = lists:last(Items),
+ #rsm_set{first = #rsm_first{data = First},
+ last = Last,
+ count = Count};
+ [] when RSM /= undefined ->
+ #rsm_set{count = Count};
+ _ ->
+ undefined
+ end,
+ {result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
+iq_disco_items(_Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
+ {error, xmpp:err_item_not_found(<<"Node not found">>, Lang)}.
+
+-spec get_vh_rooms(binary, term(), rsm_set() | undefined) -> [disco_item()].
+get_vh_rooms(Host, Query,
+ #rsm_set{max = Max, 'after' = After, before = undefined})
+ when is_binary(After), After /= <<"">> ->
+ lists:reverse(get_vh_rooms(next, {After, Host}, Host, Query, 0, Max, []));
+get_vh_rooms(Host, Query,
+ #rsm_set{max = Max, 'after' = undefined, before = Before})
+ when is_binary(Before), Before /= <<"">> ->
+ get_vh_rooms(prev, {Before, Host}, Host, Query, 0, Max, []);
+get_vh_rooms(Host, Query,
+ #rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
+ get_vh_rooms(last, {<<"">>, Host}, Host, Query, 0, Max, []);
+get_vh_rooms(Host, Query, #rsm_set{max = Max}) ->
+ lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, Max, []));
+get_vh_rooms(Host, Query, undefined) ->
+ lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, undefined, [])).
+
+-spec get_vh_rooms(prev | next | last | first,
+ {binary(), binary()}, binary(), term(),
+ non_neg_integer(), non_neg_integer() | undefined,
+ [disco_item()]) -> [disco_item()].
+get_vh_rooms(_Action, _Key, _Host, _Query, Count, Max, Items) when Count >= Max ->
+ Items;
+get_vh_rooms(Action, Key, Host, Query, Count, Max, Items) ->
+ Call = fun() ->
+ case Action of
+ prev -> mnesia:dirty_prev(muc_online_room, Key);
+ next -> mnesia:dirty_next(muc_online_room, Key);
+ last -> mnesia:dirty_last(muc_online_room);
+ first -> mnesia:dirty_first(muc_online_room)
+ end
+ end,
+ NewAction = case Action of
+ last -> prev;
+ first -> next;
+ _ -> Action
+ end,
+ try Call() of
+ '$end_of_table' ->
+ Items;
+ {_, Host} = NewKey ->
+ case get_room_disco_item(NewKey, Query) of
+ {ok, Item} ->
+ get_vh_rooms(NewAction, NewKey, Host, Query,
+ Count + 1, Max, [Item|Items]);
+ {error, _} ->
+ get_vh_rooms(NewAction, NewKey, Host, Query,
+ Count, Max, Items)
+ end;
+ NewKey ->
+ get_vh_rooms(NewAction, NewKey, Host, Query, Count, Max, Items)
+ catch _:{aborted, {badarg, _}} ->
+ Items
+ end.
+
+-spec get_room_disco_item({binary(), binary()}, term()) -> {ok, disco_item()} |
+ {error, timeout | notfound}.
+get_room_disco_item({Name, Host}, Query) ->
+ case mnesia:dirty_read(muc_online_room, {Name, Host}) of
+ [#muc_online_room{pid = Pid}|_] ->
+ RoomJID = jid:make(Name, Host),
+ try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
+ {item, Desc} ->
+ {ok, #disco_item{jid = RoomJID, name = Desc}};
+ false ->
+ {error, notfound}
+ catch _:{timeout, _} ->
+ {error, timeout};
+ _:{noproc, _} ->
+ {error, notfound}
+ end;
+ _ ->
+ {error, notfound}
end.
-get_subscribed_rooms(_ServerHost, Host1, From) ->
- Rooms = get_vh_rooms(Host1),
+get_subscribed_rooms(_ServerHost, Host, From) ->
+ Rooms = get_vh_rooms(Host),
BareFrom = jid:remove_resource(From),
lists:flatmap(
- fun(#muc_online_room{name_host = {Name, Host}, pid = Pid}) ->
+ fun(#muc_online_room{name_host = {Name, _}, pid = Pid}) ->
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
- true -> [jid:make(Name, Host, <<>>)];
+ true -> [jid:make(Name, Host)];
false -> []
end;
(_) ->
[]
end, Rooms).
-%% @doc Return the position of desired room in the list of rooms.
-%% The room must exist in the list. The count starts in 0.
-%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
-get_room_pos(Desired, Rooms) ->
- get_room_pos(Desired, Rooms, 0).
-
-get_room_pos(Desired, [HeadRoom | _], HeadPosition)
- when Desired#muc_online_room.name_host ==
- HeadRoom#muc_online_room.name_host ->
- HeadPosition;
-get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
- get_room_pos(Desired, Rooms, HeadPosition + 1).
-
-flush() -> receive _ -> flush() after 0 -> ok end.
-
--define(XFIELD(Type, Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
-iq_get_unique(From) ->
- {xmlcdata,
- p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
- randoms:get_string()]))}.
-
get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_nick(LServer, Host, From).
iq_get_register_info(ServerHost, Host, From, Lang) ->
- {Nick, Registered} = case get_nick(ServerHost, Host,
- From)
- of
- error -> {<<"">>, []};
- N ->
- {N,
- [#xmlel{name = <<"registered">>, attrs = [],
- children = []}]}
+ {Nick, Registered} = case get_nick(ServerHost, Host, From) of
+ error -> {<<"">>, false};
+ N -> {N, true}
end,
- Registered ++
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need a client that supports x:data "
- "to register the nickname">>)}]},
- #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Nickname Registration at ">>))/binary,
- Host/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Enter nickname you want to register">>)}]},
- ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
- Nick)]}].
+ Title = <<(translate:translate(
+ Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
+ Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
+ Fields = muc_register:encode(
+ [{roomnick, Nick}],
+ fun(T) -> translate:translate(Lang, T) end),
+ X = #xdata{type = form, title = Title,
+ instructions = [Inst], fields = Fields},
+ #register{nick = Nick,
+ registered = Registered,
+ instructions =
+ translate:translate(
+ Lang, <<"You need a client that supports x:data "
+ "to register the nickname">>),
+ xdata = X}.
set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost),
@@ -813,66 +755,44 @@ set_nick(ServerHost, Host, From, Nick) ->
iq_set_register_info(ServerHost, Host, From, Nick,
Lang) ->
case set_nick(ServerHost, Host, From, Nick) of
- {atomic, ok} -> {result, []};
+ {atomic, ok} -> {result, undefined};
{atomic, false} ->
ErrText = <<"That nickname is registered by another "
"person">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
+ {error, xmpp:err_conflict(ErrText, Lang)};
_ ->
Txt = <<"Database failure">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
end.
-process_iq_register_set(ServerHost, Host, From, SubEl,
- Lang) ->
- #xmlel{children = Els} = SubEl,
- case fxml:get_subtag(SubEl, <<"remove">>) of
- false ->
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>} = XEl] ->
- case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
- fxml:get_tag_attr_s(<<"type">>, XEl)}
- of
- {?NS_XDATA, <<"cancel">>} -> {result, []};
- {?NS_XDATA, <<"submit">>} ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- _ ->
- case lists:keysearch(<<"nick">>, 1, XData) of
- {value, {_, [Nick]}} when Nick /= <<"">> ->
- iq_set_register_info(ServerHost, Host, From,
- Nick, Lang);
- _ ->
- ErrText =
- <<"You must fill in field \"Nickname\" "
- "in the form">>,
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
- end
- end;
- _ -> {error, ?ERR_BAD_REQUEST}
- end;
- _ -> {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- iq_set_register_info(ServerHost, Host, From, <<"">>,
- Lang)
+process_iq_register_set(ServerHost, Host, From,
+ #register{remove = true}, Lang) ->
+ iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
+process_iq_register_set(_ServerHost, _Host, _From,
+ #register{xdata = #xdata{type = cancel}}, _Lang) ->
+ {result, undefined};
+process_iq_register_set(ServerHost, Host, From,
+ #register{nick = Nick, xdata = XData}, Lang) ->
+ case XData of
+ #xdata{type = submit, fields = Fs} ->
+ try
+ Options = muc_register:decode(Fs),
+ N = proplists:get_value(roomnick, Options),
+ iq_set_register_info(ServerHost, Host, From, N, Lang)
+ catch _:{muc_register, Why} ->
+ ErrText = muc_register:format_error(Why),
+ {error, xmpp:err_bad_request(ErrText, Lang)}
+ end;
+ #xdata{} ->
+ Txt = <<"Incorrect data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ _ when is_binary(Nick), Nick /= <<"">> ->
+ iq_set_register_info(ServerHost, Host, From, Nick, Lang);
+ _ ->
+ ErrText = <<"You must fill in field \"Nickname\" in the form">>,
+ {error, xmpp:err_not_acceptable(ErrText, Lang)}
end.
-iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd MUC module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
broadcast_service_message(Host, Msg) ->
lists:foreach(
fun(#muc_online_room{pid = Pid}) ->
@@ -887,6 +807,13 @@ get_vh_rooms(Host) ->
[{'==', {element, 2, '$1'}, Host}],
['$_']}]).
+-spec get_vh_rooms_count(binary()) -> non_neg_integer().
+get_vh_rooms_count(Host) ->
+ ets:select_count(muc_online_room,
+ ets:fun2ms(
+ fun(#muc_online_room{name_host = {_, H}}) ->
+ H == Host
+ end)).
clean_table_from_bad_node(Node) ->
F = fun() ->
@@ -916,6 +843,23 @@ clean_table_from_bad_node(Node, Host) ->
end,
mnesia:async_dirty(F).
+update_tables() ->
+ try
+ case mnesia:table_info(muc_online_room, type) of
+ ordered_set -> ok;
+ _ ->
+ case mnesia:delete_table(muc_online_room) of
+ {atomic, ok} -> ok;
+ Err -> erlang:error(Err)
+ end
+ end
+ catch _:{aborted, {no_exists, muc_online_room}} -> ok;
+ _:{aborted, {no_exists, muc_online_room, type}} -> ok;
+ E:R ->
+ ?ERROR_MSG("failed to update mnesia table '~s': ~p",
+ [muc_online_room, {E, R}])
+ end.
+
opts_to_binary(Opts) ->
lists:map(
fun({title, Title}) ->
@@ -958,13 +902,16 @@ export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"muc_room">>, 4}, {<<"muc_registered">>, 4}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, Data) ->
+import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
+ Mod:import(LServer, Tab, L).
mod_opt_type(access) ->
fun acl:access_rules_validator/1;
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index bd1c55f66..91ccce559 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -26,7 +26,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_muc_room.hrl").
-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
@@ -270,7 +270,7 @@ web_menu_host(Acc, _Host, Lang) ->
-define(TDTD(L, N),
?XE(<<"tr">>, [?XCT(<<"td">>, L),
- ?XC(<<"td">>, jlib:integer_to_binary(N))
+ ?XC(<<"td">>, integer_to_binary(N))
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
@@ -312,7 +312,7 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
- Integer = jlib:binary_to_integer(String),
+ Integer = binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
@@ -338,7 +338,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
- NCS = jlib:integer_to_binary(Num_column),
+ NCS = integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
@@ -388,7 +388,7 @@ build_info_room({Name, Host, Pid}) ->
false ->
Last_message1 = queue:last(History),
{_, _, _, Ts_last, _} = Last_message1,
- jlib:timestamp_to_legacy(Ts_last)
+ xmpp_util:encode_timestamp(Ts_last)
end,
{<<Name/binary, "@", Host/binary>>,
@@ -412,7 +412,7 @@ prepare_room_info(Room_info) ->
Just_created,
Title} = Room_info,
[NameHost,
- jlib:integer_to_binary(Num_participants),
+ integer_to_binary(Num_participants),
Ts_last_message,
jlib:atom_to_binary(Public),
jlib:atom_to_binary(Persistent),
@@ -806,7 +806,7 @@ format_room_option(OptionString, ValueString) ->
password -> ValueString;
subject ->ValueString;
subject_author ->ValueString;
- max_users -> jlib:binary_to_integer(ValueString);
+ max_users -> binary_to_integer(ValueString);
_ -> jlib:binary_to_atom(ValueString)
end,
{Option, Value}.
@@ -871,7 +871,7 @@ get_options(Config) ->
Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
[config | ValuesRaw] = tuple_to_list(Config),
Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
- (V) when is_integer(V) -> jlib:integer_to_binary(V);
+ (V) when is_integer(V) -> integer_to_binary(V);
(V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
(V) -> V end, ValuesRaw),
lists:zip(Fields, Values).
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index d5ced9116..2675db9b5 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -46,7 +46,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_muc.hrl").
-include("mod_muc_room.hrl").
@@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
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)},
+ case {Packet#message.subject, Packet#message.body} of
+ {[], []} -> ok;
+ {[], Body} ->
+ Message = {body, xmpp:get_text(Body)},
add_message_to_log(Nick, Message, Room, Opts, State);
- {SubEl, _} ->
- Message = {subject, fxml:get_tag_cdata(SubEl)},
+ {Subj, _} ->
+ Message = {subject, xmpp:get_text(Subj)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
true -> ok
@@ -249,18 +247,18 @@ build_filename_string(TimeStamp, OutDir, RoomJID,
{Dir, Filename, Rel} = case DirType of
subdirs ->
SYear =
- iolist_to_binary(io_lib:format("~4..0w",
+ (str:format("~4..0w",
[Year])),
SMonth =
- iolist_to_binary(io_lib:format("~2..0w",
+ (str:format("~2..0w",
[Month])),
- SDay = iolist_to_binary(io_lib:format("~2..0w",
+ SDay = (str:format("~2..0w",
[Day])),
{fjoin([SYear, SMonth]), SDay,
<<"../..">>};
plain ->
Date =
- iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w",
+ (str:format("~4..0w-~2..0w-~2..0w",
[Year,
Month,
Day])),
@@ -729,7 +727,7 @@ fw(F, S, FileFormat) when is_atom(FileFormat) ->
fw(F, S, [], FileFormat).
fw(F, S, O, FileFormat) ->
- S1 = list_to_binary(io_lib:format(binary_to_list(S) ++ "~n", O)),
+ S1 = (str:format(binary_to_list(S) ++ "~n", O)),
S2 = case FileFormat of
html ->
S1;
@@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
max_users ->
<<"<div class=\"rcot\">",
OptText/binary, ": \"",
- (htmlize(jlib:integer_to_binary(T),
+ (htmlize(integer_to_binary(T),
FileFormat))/binary,
"\"</div>">>;
title ->
@@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
allow_private_messages_from_visitors ->
<<"<div class=\"rcot\">",
OptText/binary, ": \"",
- (htmlize(?T((jlib:atom_to_binary(T))),
+ (htmlize(?T(jlib:atom_to_binary(T)),
FileFormat))/binary,
"\"</div>">>;
_ -> <<"\"", T/binary, "\"">>
@@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) ->
[{U#user.jid, U#user.nick, U#user.role}
|| {_, U} <- (?DICT):to_list(StateData#state.users)].
--spec get_room_state(binary(), binary()) -> muc_room_state().
+-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
get_room_state(RoomName, MucService) ->
case mnesia:dirty_read(muc_online_room,
@@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) ->
[] -> #state{}
end.
--spec get_room_state(pid()) -> muc_room_state().
+-spec get_room_state(pid()) -> mod_muc_room:state().
get_room_state(RoomPid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
@@ -1204,14 +1202,10 @@ 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.
+ xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse
+ xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse
+ xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse
+ xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}).
mod_opt_type(access_log) ->
fun (A) when is_atom(A) -> A end;
diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl
index e3ae36978..8f570746c 100644
--- a/src/mod_muc_mnesia.erl
+++ b/src/mod_muc_mnesia.erl
@@ -9,11 +9,15 @@
-module(mod_muc_mnesia).
-behaviour(mod_muc).
+-behaviour(mod_muc_room).
%% API
--export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
+ get_affiliations/3, search_affiliation/4]).
+-include("jlib.hrl").
-include("mod_muc.hrl").
-include("logger.hrl").
@@ -22,11 +26,11 @@
%%%===================================================================
init(_Host, Opts) ->
MyHost = proplists:get_value(host, Opts),
- mnesia:create_table(muc_room,
+ ejabberd_mnesia:create(?MODULE, muc_room,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_room)}]),
- mnesia:create_table(muc_registered,
+ ejabberd_mnesia:create(?MODULE, muc_registered,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_registered)}]),
@@ -113,10 +117,33 @@ set_nick(_LServer, Host, From, Nick) ->
end,
mnesia:transaction(F).
-import(_LServer, #muc_room{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, #muc_registered{} = R) ->
- mnesia:dirty_write(R).
+set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) ->
+ {error, not_implemented}.
+
+set_affiliations(_ServerHost, _Room, _Host, _Affiliations) ->
+ {error, not_implemented}.
+
+get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) ->
+ {error, not_implemented}.
+
+get_affiliations(_ServerHost, _Room, _Host) ->
+ {error, not_implemented}.
+
+search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
+ {error, not_implemented}.
+
+import(_LServer, <<"muc_room">>,
+ [Name, RoomHost, SOpts, _TimeStamp]) ->
+ Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
+ mnesia:dirty_write(
+ #muc_room{name_host = {Name, RoomHost},
+ opts = Opts});
+import(_LServer, <<"muc_registered">>,
+ [J, RoomHost, Nick, _TimeStamp]) ->
+ #jid{user = U, server = S} = jid:from_string(J),
+ mnesia:dirty_write(
+ #muc_registered{us_host = {{U, S}, RoomHost},
+ nick = Nick}).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl
index bc6e5959a..ada08ace6 100644
--- a/src/mod_muc_riak.erl
+++ b/src/mod_muc_riak.erl
@@ -9,11 +9,15 @@
-module(mod_muc_riak).
-behaviour(mod_muc).
+-behaviour(mod_muc_room).
%% API
--export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
+ get_affiliations/3, search_affiliation/4]).
+-include("jlib.hrl").
-include("mod_muc.hrl").
%%%===================================================================
@@ -101,11 +105,33 @@ set_nick(LServer, Host, From, Nick) ->
end
end}.
-import(_LServer, #muc_room{} = R) ->
- ejabberd_riak:put(R, muc_room_schema());
-import(_LServer, #muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
+set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) ->
+ {error, not_implemented}.
+
+set_affiliations(_ServerHost, _Room, _Host, _Affiliations) ->
+ {error, not_implemented}.
+
+get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) ->
+ {error, not_implemented}.
+
+get_affiliations(_ServerHost, _Room, _Host) ->
+ {error, not_implemented}.
+
+search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
+ {error, not_implemented}.
+
+import(_LServer, <<"muc_room">>,
+ [Name, RoomHost, SOpts, _TimeStamp]) ->
+ Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
+ ejabberd_riak:put(
+ #muc_room{name_host = {Name, RoomHost}, opts = Opts},
+ muc_room_schema());
+import(_LServer, <<"muc_registered">>,
+ [J, RoomHost, Nick, _TimeStamp]) ->
+ #jid{user = U, server = S} = jid:from_string(J),
+ R = #muc_registered{us_host = {{U, S}, RoomHost}, nick = Nick},
ejabberd_riak:put(R, muc_registered_schema(),
- [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]).
+ [{'2i', [{<<"nick_host">>, {Nick, RoomHost}}]}]).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 6010e0bbf..957220540 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -51,7 +51,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_muc_room.hrl").
@@ -72,6 +72,23 @@
-endif.
+-type state() :: #state{}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_next() :: {next_state, normal_state, state()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
+
+-export_type([state/0]).
+
+-callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(),
+ binary()) -> ok | {error, any()}.
+-callback set_affiliations(binary(), binary(), binary(),
+ ?TDICT) -> ok | {error, any()}.
+-callback get_affiliation(binary(), binary(), binary(),
+ binary(), binary()) -> {ok, affiliation()} | {error, any()}.
+-callback get_affiliations(binary(), binary(), binary()) -> {ok, ?TDICT} | {error, any()}.
+-callback search_affiliation(binary(), binary(), binary(), affiliation()) ->
+ {ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}.
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@@ -133,343 +150,193 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
{ok, normal_state, State}.
normal_state({route, From, <<"">>,
- #xmlel{name = <<"message">>, attrs = Attrs,
- children = Els} =
- Packet},
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ #message{type = Type, lang = Lang} = Packet}, StateData) ->
case is_user_online(From, StateData) orelse
is_subscriber(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData)
- of
- true ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"groupchat">> ->
- Activity = get_user_activity(From, StateData),
- 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)
- * 1000000),
- Size = element_size(Packet),
- {MessageShaper, MessageShaperInterval} =
- shaper:update(Activity#activity.message_shaper, Size),
- if Activity#activity.message /= undefined ->
- ErrText = <<"Traffic rate limit is exceeded">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_RESOURCE_CONSTRAINT(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- Now >=
- Activity#activity.message_time + MinMessageInterval,
- MessageShaperInterval == 0 ->
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- RoomQueueEmpty =
- queue:is_empty(StateData#state.room_queue),
- if RoomShaperInterval == 0, RoomQueueEmpty ->
- NewActivity = Activity#activity{message_time =
- Now,
- message_shaper =
- MessageShaper},
- StateData1 = store_user_activity(From,
- NewActivity,
- StateData),
- StateData2 = StateData1#state{room_shaper =
- RoomShaper},
- process_groupchat_message(From, Packet,
- StateData2);
- true ->
- StateData1 = if RoomQueueEmpty ->
- erlang:send_after(RoomShaperInterval,
- self(),
- process_room_queue),
- StateData#state{room_shaper =
- RoomShaper};
- true -> StateData
- end,
- NewActivity = Activity#activity{message_time =
- Now,
- message_shaper =
- MessageShaper,
- message = Packet},
- RoomQueue = queue:in({message, From},
- StateData#state.room_queue),
- StateData2 = store_user_activity(From,
- NewActivity,
- StateData1),
- StateData3 = StateData2#state{room_queue =
- RoomQueue},
- {next_state, normal_state, StateData3}
- end;
- true ->
- MessageInterval = (Activity#activity.message_time +
- MinMessageInterval
- - Now)
- div 1000,
- Interval = lists:max([MessageInterval,
- MessageShaperInterval]),
- erlang:send_after(Interval, self(),
- {process_user_message, From}),
- NewActivity = Activity#activity{message = Packet,
- message_shaper =
- MessageShaper},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- {next_state, normal_state, StateData1}
- end;
- <<"error">> ->
- case is_user_online(From, StateData) of
- true ->
- 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)),
- close_room_if_temporary_and_empty(NewState);
- _ -> {next_state, normal_state, StateData}
- end;
- <<"chat">> ->
- ErrText =
- <<"It is not allowed to send private messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- Type when (Type == <<"">>) or (Type == <<"normal">>) ->
- IsInvitation = is_invitation(Els),
- IsVoiceRequest = is_voice_request(Els) and
- is_visitor(From, StateData),
- IsVoiceApprovement = is_voice_approvement(Els) and
- not is_visitor(From, StateData),
- if IsInvitation ->
- case catch check_invitation(From, Packet, Lang, StateData)
- of
- {error, Error} ->
- Err = jlib:make_error_reply(Packet, Error),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- IJIDs ->
- Config = StateData#state.config,
- case Config#config.members_only of
- true ->
- NSD = process_invitees(IJIDs, StateData),
- store_room(NSD),
- {next_state, normal_state, NSD};
- false -> {next_state, normal_state, StateData}
- end
- end;
- IsVoiceRequest ->
- NewStateData = case
- (StateData#state.config)#config.allow_voice_requests
- of
- true ->
- MinInterval =
- (StateData#state.config)#config.voice_request_min_interval,
- BareFrom =
- jid:remove_resource(jid:tolower(From)),
- NowPriority = -p1_time_compat:system_time(micro_seconds),
- CleanPriority = NowPriority +
- MinInterval *
- 1000000,
- Times =
- clean_treap(StateData#state.last_voice_request_time,
- CleanPriority),
- case treap:lookup(BareFrom, Times)
- of
- error ->
- Times1 =
- treap:insert(BareFrom,
- NowPriority,
- true, Times),
- NSD =
- StateData#state{last_voice_request_time
- =
- Times1},
- send_voice_request(From, NSD),
- NSD;
- {ok, _, _} ->
- ErrText =
- <<"Please, wait for a while before sending "
- "new voice request">>,
- Err =
- jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData#state{last_voice_request_time
- = Times}
- end;
- false ->
- ErrText =
- <<"Voice requests are disabled in this "
- "conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- IsVoiceApprovement ->
- NewStateData = case is_moderator(From, StateData) of
- true ->
- case
- extract_jid_from_voice_approvement(Els)
- of
- error ->
- ErrText =
- <<"Failed to extract JID from your voice "
- "request approval">>,
- Err =
- jlib:make_error_reply(Packet,
- ?ERRT_BAD_REQUEST(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData;
- {ok, TargetJid} ->
- case is_visitor(TargetJid,
- StateData)
- of
- true ->
- Reason = <<>>,
- NSD =
- set_role(TargetJid,
- participant,
- StateData),
- catch
- send_new_presence(TargetJid,
- Reason,
- NSD,
- StateData),
- NSD;
- _ -> StateData
- end
- end;
- _ ->
- ErrText =
- <<"Only moderators can approve voice requests">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ALLOWED(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- true -> {next_state, normal_state, StateData}
- end;
- _ ->
- ErrText = <<"Improper message type">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
- end;
- _ ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- _ ->
- handle_roommessage_from_nonparticipant(Packet, Lang,
- StateData, From)
- end,
- {next_state, normal_state, StateData}
- end;
-normal_state({route, From, <<"">>,
- #xmlel{name = <<"iq">>} = Packet},
- StateData) ->
- case jlib:iq_query_info(Packet) of
- 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)),
+ is_user_allowed_message_nonparticipant(From, StateData) of
+ true when Type == groupchat ->
+ Activity = get_user_activity(From, StateData),
+ 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)
+ * 1000000),
+ Size = element_size(Packet),
+ {MessageShaper, MessageShaperInterval} =
+ shaper:update(Activity#activity.message_shaper, Size),
+ if Activity#activity.message /= undefined ->
+ ErrText = <<"Traffic rate limit is exceeded">>,
+ Err = xmpp:err_resource_constraint(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
{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_MUCSUB))
- 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, _} ->
- Txt = <<"Disco info is not available for this node">>,
- {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
- end;
- ?NS_DISCO_ITEMS ->
- process_iq_disco_items(From, Type, Lang, StateData);
- ?NS_VCARD ->
- process_iq_vcard(From, Type, Lang, SubEl, StateData);
- ?NS_MUCSUB ->
- process_iq_mucsub(From, Packet, IQ, 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};
- {ignore, SD} -> {ignore, SD};
- {error, Error, ResStateData} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- ResStateData};
- {error, Error} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- StateData}
- end,
- if IQRes /= ignore ->
- ejabberd_router:route(
- StateData#state.jid, From, jlib:iq_to_xml(IQRes));
+ Now >= Activity#activity.message_time + MinMessageInterval,
+ MessageShaperInterval == 0 ->
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
+ if RoomShaperInterval == 0, RoomQueueEmpty ->
+ NewActivity = Activity#activity{
+ message_time = Now,
+ message_shaper = MessageShaper},
+ StateData1 = store_user_activity(From,
+ NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_shaper =
+ RoomShaper},
+ process_groupchat_message(From, Packet,
+ StateData2);
true ->
- ok
- end,
- case NewStateData of
- stop -> {stop, normal, StateData};
- _ -> {next_state, normal_state, NewStateData}
+ StateData1 = if RoomQueueEmpty ->
+ erlang:send_after(RoomShaperInterval,
+ self(),
+ process_room_queue),
+ StateData#state{room_shaper =
+ RoomShaper};
+ true -> StateData
+ end,
+ NewActivity = Activity#activity{
+ message_time = Now,
+ message_shaper = MessageShaper,
+ message = Packet},
+ RoomQueue = queue:in({message, From},
+ StateData#state.room_queue),
+ StateData2 = store_user_activity(From,
+ NewActivity,
+ StateData1),
+ StateData3 = StateData2#state{room_queue = RoomQueue},
+ {next_state, normal_state, StateData3}
end;
+ true ->
+ MessageInterval = (Activity#activity.message_time +
+ MinMessageInterval - Now) div 1000,
+ Interval = lists:max([MessageInterval,
+ MessageShaperInterval]),
+ erlang:send_after(Interval, self(),
+ {process_user_message, From}),
+ NewActivity = Activity#activity{
+ message = Packet,
+ message_shaper = MessageShaper},
+ StateData1 = store_user_activity(From, NewActivity, StateData),
+ {next_state, normal_state, StateData1}
+ end;
+ true when Type == error ->
+ case is_user_online(From, StateData) of
+ true ->
+ 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)),
+ close_room_if_temporary_and_empty(NewState);
_ ->
- 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;
+ true when Type == chat ->
+ ErrText = <<"It is not allowed to send private messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
+ {next_state, normal_state, StateData};
+ true when Type == normal ->
+ {next_state, normal_state,
+ try xmpp:decode_els(Packet) of
+ Pkt -> process_normal_message(From, Pkt, StateData)
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
+ StateData
+ end};
+ true ->
+ ErrText = <<"Improper message type">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
+ {next_state, normal_state, StateData};
+ false when Type /= error ->
+ handle_roommessage_from_nonparticipant(Packet, StateData, From),
+ {next_state, normal_state, StateData};
+ false ->
+ {next_state, normal_state, StateData}
end;
-normal_state({route, From, Nick,
- #xmlel{name = <<"presence">>} = Packet},
- StateData) ->
+normal_state({route, From, <<"">>,
+ #iq{type = Type, lang = Lang, sub_els = [_]} = IQ0},
+ StateData) when Type == get; Type == set ->
+ try
+ case ejabberd_hooks:run_fold(
+ muc_process_iq,
+ StateData#state.server_host,
+ xmpp:set_from_to(xmpp:decode_els(IQ0),
+ From, StateData#state.jid),
+ [StateData]) of
+ ignore ->
+ {next_state, normal_state, StateData};
+ #iq{type = T} = IQRes when T == error; T == result ->
+ ejabberd_router:route(StateData#state.jid, From, IQRes),
+ {next_state, normal_state, StateData};
+ #iq{sub_els = [SubEl]} = IQ ->
+ Res1 = case xmpp:get_ns(SubEl) of
+ ?NS_MUC_ADMIN ->
+ process_iq_admin(From, IQ, StateData);
+ ?NS_MUC_OWNER ->
+ process_iq_owner(From, IQ, StateData);
+ ?NS_DISCO_INFO when SubEl#disco_info.node == <<>> ->
+ process_iq_disco_info(From, IQ, StateData);
+ ?NS_DISCO_ITEMS ->
+ process_iq_disco_items(From, IQ, StateData);
+ ?NS_VCARD ->
+ process_iq_vcard(From, IQ, StateData);
+ ?NS_MUCSUB ->
+ process_iq_mucsub(From, IQ, StateData);
+ ?NS_CAPTCHA ->
+ process_iq_captcha(From, IQ, StateData);
+ _ ->
+ Txt = <<"The feature requested is not "
+ "supported by the conference">>,
+ {error, xmpp:err_service_unavailable(Txt, Lang)}
+ end,
+ {IQRes, NewStateData} =
+ case Res1 of
+ {result, Res, SD} ->
+ {xmpp:make_iq_result(IQ, Res), SD};
+ {result, Res} ->
+ {xmpp:make_iq_result(IQ, Res), StateData};
+ {ignore, SD} ->
+ {ignore, SD};
+ {error, Error, ResStateData} ->
+ {xmpp:make_error(IQ0, Error), ResStateData};
+ {error, Error} ->
+ {xmpp:make_error(IQ0, Error), StateData}
+ end,
+ if IQRes /= ignore ->
+ ejabberd_router:route(StateData#state.jid, From, IQRes);
+ true ->
+ ok
+ end,
+ case NewStateData of
+ stop ->
+ {stop, normal, StateData};
+ _ when NewStateData#state.just_created ->
+ close_room_if_temporary_and_empty(NewStateData);
+ _ ->
+ {next_state, normal_state, NewStateData}
+ end
+ end
+ catch _:{xmpp_codec, Why} ->
+ ErrTxt = xmpp:format_error(Why),
+ Err = xmpp:err_bad_request(ErrTxt, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, IQ0, Err)
+ end;
+normal_state({route, From, <<"">>, #iq{} = IQ}, StateData) ->
+ Err = xmpp:err_bad_request(),
+ ejabberd_router:route_error(StateData#state.jid, From, IQ, Err),
+ case StateData#state.just_created of
+ true -> {stop, normal, StateData};
+ false -> {next_state, normal_state, StateData}
+ end;
+normal_state({route, From, Nick, #presence{} = Packet}, StateData) ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
MinPresenceInterval =
@@ -479,184 +346,134 @@ normal_state({route, From, Nick,
I
end, 0)
* 1000000),
- if (Now >=
- Activity#activity.presence_time + MinPresenceInterval)
- and (Activity#activity.presence == undefined) ->
- NewActivity = Activity#activity{presence_time = Now},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- process_presence(From, Nick, Packet, StateData1);
+ if (Now >= Activity#activity.presence_time + MinPresenceInterval)
+ and (Activity#activity.presence == undefined) ->
+ NewActivity = Activity#activity{presence_time = Now},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ process_presence(From, Nick, Packet, StateData1);
true ->
- if Activity#activity.presence == undefined ->
- Interval = (Activity#activity.presence_time +
- MinPresenceInterval
- - Now)
- div 1000,
- erlang:send_after(Interval, self(),
- {process_user_presence, From});
- true -> ok
- end,
- NewActivity = Activity#activity{presence =
- {Nick, Packet}},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- {next_state, normal_state, StateData1}
+ if Activity#activity.presence == undefined ->
+ Interval = (Activity#activity.presence_time +
+ MinPresenceInterval - Now) div 1000,
+ erlang:send_after(Interval, self(),
+ {process_user_presence, From});
+ true -> ok
+ end,
+ NewActivity = Activity#activity{presence = {Nick, Packet}},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ {next_state, normal_state, StateData1}
end;
normal_state({route, From, ToNick,
- #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
+ #message{type = Type, lang = Lang} = Packet},
StateData) ->
- 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 = <<"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};
- forget_message -> {next_state, normal_state, StateData};
- continue_delivery ->
- case
- {(StateData#state.config)#config.allow_private_messages,
- is_user_online(From, StateData) orelse
- is_subscriber(From, StateData)}
- of
- {true, true} ->
- case Type of
- <<"groupchat">> ->
- ErrText =
- <<"It is not allowed to send private messages "
- "of type \"groupchat\"">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_BAD_REQUEST(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
- _ ->
- case find_jids_by_nick(ToNick, StateData) of
- false ->
- ErrText =
- <<"Recipient is not in the conference room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
+ case decide_fate_message(Packet, From, StateData) of
+ {expulse_sender, Reason} ->
+ ?DEBUG(Reason, []),
+ 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};
+ forget_message ->
+ {next_state, normal_state, StateData};
+ continue_delivery ->
+ case {(StateData#state.config)#config.allow_private_messages,
+ is_user_online(From, StateData) orelse
+ is_subscriber(From, StateData)} of
+ {true, true} when Type == groupchat ->
+ ErrText = <<"It is not allowed to send private messages "
+ "of type \"groupchat\"">>,
+ Err = xmpp:err_bad_request(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ {true, true} ->
+ case find_jids_by_nick(ToNick, StateData) of
+ [] ->
+ ErrText = <<"Recipient is not in the conference room">>,
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
ToJIDs ->
SrcIsVisitor = is_visitor(From, StateData),
- DstIsModerator = is_moderator(hd(ToJIDs),
- StateData),
+ DstIsModerator = is_moderator(hd(ToJIDs), StateData),
PmFromVisitors =
(StateData#state.config)#config.allow_private_messages_from_visitors,
if SrcIsVisitor == false;
PmFromVisitors == anyone;
(PmFromVisitors == moderators) and
- DstIsModerator ->
+ DstIsModerator ->
{FromNick, _} = get_participant_data(From, StateData),
- FromNickJID =
- jid:replace_resource(StateData#state.jid,
- FromNick),
- X = #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
- PrivMsg = fxml:append_subtags(Packet, [X]),
- [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
- || ToJID <- ToJIDs];
+ FromNickJID =
+ jid:replace_resource(StateData#state.jid,
+ FromNick),
+ X = #muc_user{},
+ PrivMsg = xmpp:set_subtag(Packet, X),
+ [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
+ || ToJID <- ToJIDs];
true ->
- ErrText =
- <<"It is not allowed to send private messages">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
+ ErrText = <<"It is not allowed to send private messages">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
end
- end
- end;
- {true, false} ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
- {false, _} ->
- ErrText =
- <<"It is not allowed to send private messages">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end,
+ end;
+ {true, false} ->
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ {false, _} ->
+ ErrText = <<"It is not allowed to send private messages">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
+ end,
{next_state, normal_state, StateData}
end;
normal_state({route, From, ToNick,
- #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
+ #iq{id = StanzaId, lang = Lang} = Packet},
StateData) ->
- 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
- {true, {true, NewId, FromFull}} ->
- case find_jid_by_nick(ToNick, StateData) of
- false ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText = <<"Recipient is not in the conference room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end;
- ToJID ->
- {ok, #user{nick = FromNick}} =
- (?DICT):find(jid:tolower(FromFull),
- StateData#state.users),
- {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
- StanzaId, NewId, Packet),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- FromNick),
- ToJID2, Packet2)
- end;
- {_, {false, _, _}} ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText =
- <<"Only occupants are allowed to send queries "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end;
- _ ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText = <<"Queries to the conference members are "
- "not allowed in this room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ALLOWED(Lang, ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end
+ is_user_online_iq(StanzaId, From, StateData)} of
+ {true, {true, NewId, FromFull}} ->
+ case find_jid_by_nick(ToNick, StateData) of
+ false ->
+ ErrText = <<"Recipient is not in the conference room">>,
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ ToJID ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jid:tolower(FromFull), StateData#state.users),
+ {ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet),
+ ejabberd_router:route(
+ jid:replace_resource(StateData#state.jid, FromNick),
+ ToJID2, Packet2)
+ end;
+ {_, {false, _, _}} ->
+ ErrText = <<"Only occupants are allowed to send queries "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ _ ->
+ ErrText = <<"Queries to the conference members are "
+ "not allowed in this room">>,
+ Err = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
end,
{next_state, normal_state, StateData};
normal_state(_Event, StateData) ->
@@ -664,11 +481,7 @@ normal_state(_Event, StateData) ->
handle_event({service_message, Msg}, _StateName,
StateData) ->
- MessagePkt = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg}]}]},
+ MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
send_wrapped_multiple(
StateData#state.jid,
get_users_and_subscribers(StateData),
@@ -680,22 +493,9 @@ handle_event({service_message, Msg}, _StateName,
{next_state, normal_state, NSD};
handle_event({destroy, Reason}, _StateName,
StateData) ->
- {result, [], stop} = destroy_room(#xmlel{name =
- <<"destroy">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_OWNER}],
- children =
- case Reason of
- none -> [];
- _Else ->
- [#xmlel{name =
- <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata,
- Reason}]}]
- end},
- StateData),
+ {result, undefined, stop} =
+ destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason},
+ StateData),
?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
[jid:to_string(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
@@ -703,7 +503,7 @@ handle_event({destroy, Reason}, _StateName,
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
[jid:to_string(StateData#state.jid)]),
- handle_event({destroy, none}, StateName, StateData);
+ handle_event({destroy, undefined}, StateName, StateData);
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
{next_state, StateName,
@@ -712,8 +512,7 @@ handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) ->
- Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
- StateData#state.users),
+ Len = ?DICT:size(StateData#state.users),
Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
true ->
get_roomdesc_reply(JID, StateData,
@@ -734,40 +533,39 @@ handle_sync_event(get_state, _From, StateName,
{reply, {ok, StateData}, StateName, StateData};
handle_sync_event({change_config, Config}, _From,
StateName, StateData) ->
- {result, [], NSD} = change_config(Config, StateData),
+ {result, undefined, NSD} = change_config(Config, StateData),
{reply, {ok, NSD#state.config}, StateName, NSD};
handle_sync_event({change_state, NewStateData}, _From,
StateName, _StateData) ->
{reply, {ok, NewStateData}, StateName, NewStateData};
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
- NSD = process_item_change(Item, StateData, UJID),
- {reply, {ok, NSD}, StateName, NSD};
+ case process_item_change(Item, StateData, UJID) of
+ {error, _} = Err ->
+ {reply, Err, StateName, StateData};
+ NSD ->
+ {reply, {ok, NSD}, StateName, NSD}
+ end;
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
JIDs = lists:map(fun jid:make/1,
?DICT:fetch_keys(StateData#state.subscribers)),
{reply, {ok, JIDs}, StateName, StateData};
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
StateName, StateData) ->
- SubEl = #xmlel{name = <<"subscribe">>,
- attrs = [{<<"xmlns">>, ?NS_MUCSUB}, {<<"nick">>, Nick}],
- children = [#xmlel{name = <<"event">>,
- attrs = [{<<"node">>, Node}]}
- || Node <- Nodes]},
IQ = #iq{type = set, id = randoms:get_string(),
- xmlns = ?NS_MUCSUB, sub_el = SubEl},
- Packet = jlib:iq_to_xml(IQ#iq{sub_el = [SubEl]}),
+ from = From, sub_els = [#muc_subscribe{nick = Nick,
+ events = Nodes}]},
Config = StateData#state.config,
CaptchaRequired = Config#config.captcha_protected,
PasswordProtected = Config#config.password_protected,
TmpConfig = Config#config{captcha_protected = false,
password_protected = false},
TmpState = StateData#state{config = TmpConfig},
- case process_iq_mucsub(From, Packet, IQ, TmpState) of
- {result, _, NewState} ->
+ case process_iq_mucsub(From, IQ, TmpState) of
+ {result, #muc_subscribe{events = NewNodes}, NewState} ->
NewConfig = (NewState#state.config)#config{
captcha_protected = CaptchaRequired,
password_protected = PasswordProtected},
- {reply, {ok, get_subscription_nodes(Packet)}, StateName,
+ {reply, {ok, NewNodes}, StateName,
NewState#state{config = NewConfig}};
{ignore, NewState} ->
NewConfig = (NewState#state.config)#config{
@@ -785,12 +583,9 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
- SubEl = #xmlel{name = <<"unsubscribe">>,
- attrs = [{<<"xmlns">>, ?NS_MUCSUB}]},
IQ = #iq{type = set, id = randoms:get_string(),
- xmlns = ?NS_MUCSUB, sub_el = SubEl},
- Packet = jlib:iq_to_xml(IQ),
- case process_iq_mucsub(From, Packet, IQ, StateData) of
+ from = From, sub_els = [#muc_unsubscribe{}]},
+ case process_iq_mucsub(From, IQ, StateData) of
{result, _, NewState} ->
{reply, ok, StateName, NewState};
{ignore, NewState} ->
@@ -875,12 +670,11 @@ handle_info({captcha_failed, From}, normal_state,
{ok, {Nick, Packet}} ->
Robots = (?DICT):erase(From, StateData#state.robots),
Txt = <<"The CAPTCHA verification has failed">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)),
- ejabberd_router:route % TODO: s/Nick/""/
- (jid:replace_resource(StateData#state.jid,
- Nick),
- From, Err),
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_not_authorized(Txt, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
StateData#state{robots = Robots};
_ -> StateData
end,
@@ -899,22 +693,12 @@ terminate(Reason, _StateName, StateData) ->
"because of a system shutdown">>;
_ -> <<"Room terminates">>
end,
- ItemAttrs = [{<<"affiliation">>, <<"none">>},
- {<<"role">>, <<"none">>}],
- ReasonEl = #xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, ReasonT}]},
- Packet = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs = ItemAttrs,
- children = [ReasonEl]},
- #xmlel{name = <<"status">>,
- attrs = [{<<"code">>, <<"332">>}],
- children = []}]}]},
+ Packet = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{items = [#muc_item{affiliation = none,
+ reason = ReasonT,
+ role = none}],
+ status_codes = [332]}]},
(?DICT):fold(fun (LJID, Info, _) ->
Nick = Info#user.nick,
case Reason of
@@ -937,14 +721,12 @@ terminate(Reason, _StateName, StateData) ->
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-
+-spec route(pid(), jid(), binary(), stanza()) -> ok.
route(Pid, From, ToNick, Packet) ->
gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
-process_groupchat_message(From,
- #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec process_groupchat_message(jid(), message(), state()) -> fsm_next().
+process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) ->
IsSubscriber = is_subscriber(From, StateData),
case is_user_online(From, StateData) orelse IsSubscriber orelse
is_user_allowed_message_nonparticipant(From, StateData)
@@ -988,7 +770,7 @@ process_groupchat_message(From,
drop ->
{next_state, normal_state, StateData};
NewPacket1 ->
- NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+ NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}),
Node = if Subject == false -> ?NS_MUCSUB_NODES_MESSAGES;
true -> ?NS_MUCSUB_NODES_SUBJECT
end,
@@ -1007,41 +789,167 @@ process_groupchat_message(From,
{next_state, normal_state, NewStateData2}
end;
_ ->
- Err = case
- (StateData#state.config)#config.allow_change_subj
- of
+ Err = case (StateData#state.config)#config.allow_change_subj of
true ->
- ?ERRT_FORBIDDEN(Lang,
- <<"Only moderators and participants are "
- "allowed to change the subject in this "
- "room">>);
+ xmpp:err_forbidden(
+ <<"Only moderators and participants are "
+ "allowed to change the subject in this "
+ "room">>, Lang);
_ ->
- ?ERRT_FORBIDDEN(Lang,
- <<"Only moderators are allowed to change "
- "the subject in this room">>)
+ xmpp:err_forbidden(
+ <<"Only moderators are allowed to change "
+ "the subject in this room">>, Lang)
end,
- ejabberd_router:route(StateData#state.jid, From,
- jlib:make_error_reply(Packet, Err)),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end;
true ->
ErrText = <<"Visitors are not allowed to send messages "
"to all occupants">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end;
false ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end.
+-spec process_normal_message(jid(), message(), state()) -> state().
+process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
+ Action = lists:foldl(
+ fun(_, {error, _} = Err) ->
+ Err;
+ (#muc_user{invites = [#muc_invite{to = undefined}]}, _) ->
+ Txt = <<"No 'to' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ (#muc_user{invites = [I]}, _) ->
+ {ok, I};
+ (#muc_user{invites = [_|_]}, _) ->
+ Txt = <<"Multiple invitations are not allowed">>,
+ {error, xmpp:err_resource_constraint(Txt, Lang)};
+ (#xdata{type = submit, fields = Fs}, _) ->
+ try {ok, muc_request:decode(Fs)}
+ catch _:{muc_request, Why} ->
+ Txt = muc_request:format_error(Why),
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ (_, Acc) ->
+ Acc
+ end, ok, xmpp:get_els(Pkt)),
+ case Action of
+ {ok, #muc_invite{} = Invitation} ->
+ process_invitation(From, Pkt, Invitation, StateData);
+ {ok, [{role, participant}]} ->
+ process_voice_request(From, Pkt, StateData);
+ {ok, VoiceApproval} ->
+ process_voice_approval(From, Pkt, VoiceApproval, StateData);
+ {error, Err} ->
+ ejabberd_router:route_error(StateData#state.jid, From, Pkt, Err),
+ StateData;
+ ok ->
+ StateData
+ end.
+
+-spec process_invitation(jid(), message(), muc_invite(), state()) -> state().
+process_invitation(From, Pkt, Invitation, StateData) ->
+ Lang = xmpp:get_lang(Pkt),
+ case check_invitation(From, Invitation, Lang, StateData) of
+ {error, Error} ->
+ ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error),
+ StateData;
+ IJID ->
+ Config = StateData#state.config,
+ case Config#config.members_only of
+ true ->
+ case get_affiliation(IJID, StateData) of
+ none ->
+ NSD = set_affiliation(IJID, member, StateData),
+ send_affiliation(IJID, member, StateData),
+ store_room(NSD),
+ NSD;
+ _ ->
+ StateData
+ end;
+ false ->
+ StateData
+ end
+ end.
+
+-spec process_voice_request(jid(), message(), state()) -> state().
+process_voice_request(From, Pkt, StateData) ->
+ Lang = xmpp:get_lang(Pkt),
+ case (StateData#state.config)#config.allow_voice_requests of
+ true ->
+ MinInterval = (StateData#state.config)#config.voice_request_min_interval,
+ BareFrom = jid:remove_resource(jid:tolower(From)),
+ NowPriority = -p1_time_compat:system_time(micro_seconds),
+ CleanPriority = NowPriority + MinInterval * 1000000,
+ Times = clean_treap(StateData#state.last_voice_request_time,
+ CleanPriority),
+ case treap:lookup(BareFrom, Times) of
+ error ->
+ Times1 = treap:insert(BareFrom,
+ NowPriority,
+ true, Times),
+ NSD = StateData#state{last_voice_request_time = Times1},
+ send_voice_request(From, Lang, NSD),
+ NSD;
+ {ok, _, _} ->
+ ErrText = <<"Please, wait for a while before sending "
+ "new voice request">>,
+ Err = xmpp:err_resource_constraint(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData#state{last_voice_request_time = Times}
+ end;
+ false ->
+ ErrText = <<"Voice requests are disabled in this conference">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData
+ end.
+
+-spec process_voice_approval(jid(), message(), [muc_request:property()], state()) -> state().
+process_voice_approval(From, Pkt, VoiceApproval, StateData) ->
+ Lang = xmpp:get_lang(Pkt),
+ case is_moderator(From, StateData) of
+ true ->
+ case lists:keyfind(jid, 1, VoiceApproval) of
+ {_, TargetJid} ->
+ Allow = proplists:get_bool(request_allow, VoiceApproval),
+ case is_visitor(TargetJid, StateData) of
+ true when Allow ->
+ Reason = <<>>,
+ NSD = set_role(TargetJid, participant, StateData),
+ catch send_new_presence(
+ TargetJid, Reason, NSD, StateData),
+ NSD;
+ _ ->
+ StateData
+ end;
+ false ->
+ ErrText = <<"Failed to extract JID from your voice "
+ "request approval">>,
+ Err = xmpp:err_bad_request(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData
+ end;
+ false ->
+ ErrText = <<"Only moderators can approve voice requests">>,
+ Err = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData
+ end.
+
%% @doc Check if this non participant can send message to room.
%%
%% XEP-0045 v1.23:
@@ -1049,6 +957,7 @@ process_groupchat_message(From,
%% an implementation MAY allow users with certain privileges
%% (e.g., a room owner, room admin, or service-level admin)
%% to send messages to the room even if those users are not occupants.
+-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean().
is_user_allowed_message_nonparticipant(JID,
StateData) ->
case get_service_affiliation(JID, StateData) of
@@ -1058,6 +967,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.
+-spec get_participant_data(jid(), state()) -> {binary(), role()}.
get_participant_data(From, StateData) ->
case (?DICT):find(jid:tolower(From),
StateData#state.users)
@@ -1074,13 +984,11 @@ get_participant_data(From, StateData) ->
end
end.
-process_presence(From, Nick,
- #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
- StateData) ->
- Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
+-spec process_presence(jid(), binary(), presence(), state()) -> fsm_transition().
+process_presence(From, Nick, #presence{type = Type0} = Packet0, StateData) ->
IsOnline = is_user_online(From, StateData),
- if Type0 == <<"">>;
- IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
+ if Type0 == available;
+ IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
case ejabberd_hooks:run_fold(muc_filter_presence,
StateData#state.server_host,
Packet0,
@@ -1089,107 +997,91 @@ process_presence(From, Nick,
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 = maybe_strip_status_from_presence(
- From, Packet, StateData),
- NewState = add_user_presence(From, Stanza,
- StateData),
- send_new_presence(From, NewState, StateData),
- NewState
- end
- end
- end,
- close_room_if_temporary_and_empty(StateData1)
+ #presence{} = Packet ->
+ close_room_if_temporary_and_empty(
+ do_process_presence(From, Nick, Packet, StateData))
end;
true ->
- {next_state, normal_state, StateData}
+ {next_state, normal_state, StateData}
end.
+-spec do_process_presence(jid(), binary(), presence(), state()) ->
+ state().
+do_process_presence(From, Nick, #presence{type = available, lang = Lang} = Packet,
+ StateData) ->
+ case is_user_online(From, StateData) of
+ false ->
+ 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 = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ {true, _, _} ->
+ ErrText = <<"That nickname is already in use by another "
+ "occupant">>,
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ {_, false, _} ->
+ ErrText = <<"That nickname is registered by another "
+ "person">>,
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ _ ->
+ change_nick(From, Nick, StateData)
+ end;
+ false ->
+ Stanza = maybe_strip_status_from_presence(
+ From, Packet, StateData),
+ NewState = add_user_presence(From, Stanza,
+ StateData),
+ send_new_presence(From, NewState, StateData),
+ NewState
+ end
+ end;
+do_process_presence(From, Nick, #presence{type = unavailable} = Packet,
+ StateData) ->
+ 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 = xmpp:get_text(NewPacket#presence.status),
+ remove_online_user(From, NewState, Reason);
+do_process_presence(From, _Nick, #presence{type = error, lang = Lang} = Packet,
+ StateData) ->
+ 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)).
+
+-spec maybe_strip_status_from_presence(jid(), presence(),
+ state()) -> presence().
maybe_strip_status_from_presence(From, Packet, StateData) ->
case {(StateData#state.config)#config.allow_visitor_status,
is_visitor(From, StateData)} of
@@ -1198,6 +1090,7 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
_Allowed -> Packet
end.
+-spec close_room_if_temporary_and_empty(state()) -> fsm_transition().
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
andalso (?DICT):size(StateData1#state.users) == 0
@@ -1237,15 +1130,18 @@ get_users_and_subscribers(StateData) ->
end
end, StateData#state.users, StateData#state.subscribers).
+-spec is_user_online(jid(), state()) -> boolean().
is_user_online(JID, StateData) ->
LJID = jid:tolower(JID),
(?DICT):is_key(LJID, StateData#state.users).
+-spec is_subscriber(jid(), state()) -> boolean().
is_subscriber(JID, StateData) ->
LJID = jid:tolower(jid:remove_resource(JID)),
(?DICT):is_key(LJID, StateData#state.subscribers).
%% Check if the user is occupant of the room, or at least is an admin or owner.
+-spec is_occupant_or_admin(jid(), state()) -> boolean().
is_occupant_or_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FRole = get_role(JID, StateData),
@@ -1260,6 +1156,8 @@ is_occupant_or_admin(JID, StateData) ->
%%%
%%% Handle IQ queries of vCard
%%%
+-spec is_user_online_iq(binary(), jid(), state()) ->
+ {boolean(), binary(), jid()}.
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
@@ -1267,99 +1165,61 @@ is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
{OriginalId, Resource} ->
- JIDWithResource = jid:replace_resource(JID,
- Resource),
+ JIDWithResource = jid:replace_resource(JID, Resource),
{is_user_online(JIDWithResource, StateData), OriginalId,
JIDWithResource}
catch
_:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
-handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
- Packet) ->
+-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}.
+handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
ToBareJID = jid:remove_resource(ToJID),
- IQ = jlib:iq_query_info(Packet),
- handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId,
- NewId, IQ, Packet).
-
-handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId,
- _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet)
- when ToBareJID /= ToJID ->
- {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
-handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
- _StanzaId, NewId, _IQ, Packet) ->
- {ToJID, change_stanzaid(NewId, Packet)}.
+ case SubEls of
+ [SubEl] when Type == get, ToBareJID /= ToJID ->
+ case xmpp:get_ns(SubEl) of
+ ?NS_VCARD ->
+ {ToBareJID, change_stanzaid(ToJID, IQ)};
+ _ ->
+ {ToJID, xmpp:set_id(IQ, NewId)}
+ end;
+ _ ->
+ {ToJID, xmpp:set_id(IQ, NewId)}
+ end.
+-spec stanzaid_pack(binary(), binary()) -> binary().
stanzaid_pack(OriginalId, Resource) ->
<<"berd",
(jlib:encode_base64(<<"ejab\000",
OriginalId/binary, "\000",
Resource/binary>>))/binary>>.
+-spec stanzaid_unpack(binary()) -> {binary(), binary()}.
stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
StanzaId = jlib:decode_base64(StanzaIdBase64),
[<<"ejab">>, OriginalId, Resource] =
str:tokens(StanzaId, <<"\000">>),
{OriginalId, Resource}.
-change_stanzaid(NewId, Packet) ->
- #xmlel{name = Name, attrs = Attrs, children = Els} =
- jlib:remove_attr(<<"id">>, Packet),
- #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs],
- children = Els}.
-
-change_stanzaid(PreviousId, ToJID, Packet) ->
+-spec change_stanzaid(jid(), iq()) -> iq().
+change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) ->
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
- change_stanzaid(NewId, Packet).
-
-%%%
-%%%
-
-role_to_list(Role) ->
- case Role of
- moderator -> <<"moderator">>;
- participant -> <<"participant">>;
- visitor -> <<"visitor">>;
- none -> <<"none">>
- end.
-
-affiliation_to_list(Affiliation) ->
- case Affiliation of
- owner -> <<"owner">>;
- admin -> <<"admin">>;
- member -> <<"member">>;
- outcast -> <<"outcast">>;
- none -> <<"none">>
- end.
-
-list_to_role(Role) ->
- case Role of
- <<"moderator">> -> moderator;
- <<"participant">> -> participant;
- <<"visitor">> -> visitor;
- <<"none">> -> none
- end.
-
-list_to_affiliation(Affiliation) ->
- case Affiliation of
- <<"owner">> -> owner;
- <<"admin">> -> admin;
- <<"member">> -> member;
- <<"outcast">> -> outcast;
- <<"none">> -> none
- end.
+ xmpp:set_id(Packet, NewId).
%% Decide the fate of the message and its sender
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-decide_fate_message(<<"error">>, Packet, From,
- StateData) ->
- PD = case check_error_kick(Packet) of
+-spec decide_fate_message(message(), jid(), state()) ->
+ continue_delivery | forget_message |
+ {expulse_sender, binary()}.
+decide_fate_message(#message{type = error} = Msg,
+ From, StateData) ->
+ Err = xmpp:get_error(Msg),
+ PD = case check_error_kick(Err) of
%% If this is an error stanza and its condition matches a criteria
true ->
- Reason =
- io_lib:format("This participant is considered a ghost "
- "and is expulsed: ~s",
- [jid:to_string(From)]),
+ Reason = str:format("This participant is considered a ghost "
+ "and is expulsed: ~s",
+ [jid:to_string(From)]),
{expulse_sender, Reason};
false -> continue_delivery
end,
@@ -1371,40 +1231,37 @@ decide_fate_message(<<"error">>, Packet, From,
end;
Other -> Other
end;
-decide_fate_message(_, _, _, _) -> continue_delivery.
+decide_fate_message(_, _, _) -> continue_delivery.
%% Check if the elements of this error stanza indicate
%% that the sender is a dead participant.
%% If so, return true to kick the participant.
-check_error_kick(Packet) ->
- case get_error_condition(Packet) of
- <<"gone">> -> true;
- <<"internal-server-error">> -> true;
- <<"item-not-found">> -> true;
- <<"jid-malformed">> -> true;
- <<"recipient-unavailable">> -> true;
- <<"redirect">> -> true;
- <<"remote-server-not-found">> -> true;
- <<"remote-server-timeout">> -> true;
- <<"service-unavailable">> -> true;
- _ -> false
- end.
-
-get_error_condition(Packet) ->
- case catch get_error_condition2(Packet) of
- {condition, ErrorCondition} -> ErrorCondition;
- {'EXIT', _} -> <<"badformed error stanza">>
- end.
+-spec check_error_kick(stanza_error()) -> boolean().
+check_error_kick(#stanza_error{reason = Reason}) ->
+ case Reason of
+ #gone{} -> true;
+ 'internal-server-error' -> true;
+ 'item-not-found' -> true;
+ 'jid-malformed' -> true;
+ 'recipient-unavailable' -> true;
+ #redirect{} -> true;
+ 'remote-server-not-found' -> true;
+ 'remote-server-timeout' -> true;
+ 'service-unavailable' -> true;
+ _ -> false
+ end;
+check_error_kick(undefined) ->
+ false.
-get_error_condition2(Packet) ->
- #xmlel{children = EEls} = fxml:get_subtag(Packet,
- <<"error">>),
- [Condition] = [Name
- || #xmlel{name = Name,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}],
- children = []}
- <- EEls],
- {condition, Condition}.
+-spec get_error_condition(stanza_error()) -> string().
+get_error_condition(#stanza_error{reason = Reason}) ->
+ case Reason of
+ #gone{} -> "gone";
+ #redirect{} -> "redirect";
+ Atom -> atom_to_list(Atom)
+ end;
+get_error_condition(undefined) ->
+ "undefined".
get_error_text(Error) ->
case fxml:get_subtag_with_xmlns(Error, <<"text">>, ?NS_STANZAS) of
@@ -1414,31 +1271,28 @@ get_error_text(Error) ->
<<"">>
end.
+-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
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])).
+ Condition = get_error_condition(xmpp:get_error(Packet)),
+ str:format(Reason1, [FromNick, Condition]).
+-spec expulse_participant(stanza(), jid(), state(), binary()) ->
+ state().
expulse_participant(Packet, From, StateData, Reason1) ->
Reason2 = make_reason(Packet, From, StateData, Reason1),
NewState = add_user_presence_un(From,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name = <<"status">>,
- attrs = [],
- children =
- [{xmlcdata,
- Reason2}]}]},
+ #presence{type = unavailable,
+ status = xmpp:mk_text(Reason2)},
StateData),
send_new_presence(From, NewState, StateData),
remove_online_user(From, NewState).
+-spec set_affiliation(jid(), affiliation(), state()) -> state().
set_affiliation(JID, Affiliation, StateData) ->
set_affiliation(JID, Affiliation, StateData, <<"">>).
+-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state().
set_affiliation(JID, Affiliation, StateData, Reason) ->
LJID = jid:remove_resource(jid:tolower(JID)),
Affiliations = case Affiliation of
@@ -1450,6 +1304,7 @@ set_affiliation(JID, Affiliation, StateData, Reason) ->
end,
StateData#state{affiliations = Affiliations}.
+-spec get_affiliation(jid(), state()) -> affiliation().
get_affiliation(JID, StateData) ->
{_AccessRoute, _AccessCreate, AccessAdmin,
_AccessPersistent} =
@@ -1490,6 +1345,7 @@ get_affiliation(JID, StateData) ->
_ -> Res
end.
+-spec get_service_affiliation(jid(), state()) -> owner | none.
get_service_affiliation(JID, StateData) ->
{_AccessRoute, _AccessCreate, AccessAdmin,
_AccessPersistent} =
@@ -1501,6 +1357,7 @@ get_service_affiliation(JID, StateData) ->
_ -> none
end.
+-spec set_role(jid(), role(), state()) -> state().
set_role(JID, Role, StateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
@@ -1549,6 +1406,7 @@ set_role(JID, Role, StateData) ->
end,
StateData#state{users = Users, nicks = Nicks}.
+-spec get_role(jid(), state()) -> role().
get_role(JID, StateData) ->
LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.users) of
@@ -1556,6 +1414,7 @@ get_role(JID, StateData) ->
_ -> none
end.
+-spec get_default_role(affiliation(), state()) -> role().
get_default_role(Affiliation, StateData) ->
case Affiliation of
owner -> moderator;
@@ -1574,12 +1433,15 @@ get_default_role(Affiliation, StateData) ->
end
end.
+-spec is_visitor(jid(), state()) -> boolean().
is_visitor(Jid, StateData) ->
get_role(Jid, StateData) =:= visitor.
+-spec is_moderator(jid(), state()) -> boolean().
is_moderator(Jid, StateData) ->
get_role(Jid, StateData) =:= moderator.
+-spec get_max_users(state()) -> non_neg_integer().
get_max_users(StateData) ->
MaxUsers = (StateData#state.config)#config.max_users,
ServiceMaxUsers = get_service_max_users(StateData),
@@ -1587,18 +1449,21 @@ get_max_users(StateData) ->
true -> ServiceMaxUsers
end.
+-spec get_service_max_users(state()) -> pos_integer().
get_service_max_users(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users,
fun(I) when is_integer(I), I>0 -> I end,
?MAX_USERS_DEFAULT).
+-spec get_max_users_admin_threshold(state()) -> pos_integer().
get_max_users_admin_threshold(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users_admin_threshold,
fun(I) when is_integer(I), I>0 -> I end,
5).
+-spec get_user_activity(jid(), state()) -> #activity{}.
get_user_activity(JID, StateData) ->
case treap:lookup(jid:tolower(JID),
StateData#state.activity)
@@ -1619,6 +1484,7 @@ get_user_activity(JID, StateData) ->
presence_shaper = PresenceShaper}
end.
+-spec store_user_activity(jid(), #activity{}, state()) -> state().
store_user_activity(JID, UserActivity, StateData) ->
MinMessageInterval =
trunc(gen_mod:get_module_opt(StateData#state.server_host,
@@ -1680,6 +1546,7 @@ store_user_activity(JID, UserActivity, StateData) ->
end,
StateData1.
+-spec clean_treap(treap:treap(), integer()) -> treap:treap().
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
true -> Treap;
@@ -1691,6 +1558,7 @@ clean_treap(Treap, CleanPriority) ->
end
end.
+-spec prepare_room_queue(state()) -> state().
prepare_room_queue(StateData) ->
case queue:out(StateData#state.room_queue) of
{{value, {message, From}}, _RoomQueue} ->
@@ -1714,6 +1582,7 @@ prepare_room_queue(StateData) ->
{empty, _} -> StateData
end.
+-spec update_online_user(jid(), #user{}, state()) -> state().
update_online_user(JID, #user{nick = Nick} = User, StateData) ->
LJID = jid:tolower(JID),
Nicks1 = case (?DICT):find(LJID, StateData#state.users) of
@@ -1759,14 +1628,17 @@ set_subscriber(JID, Nick, Nodes, StateData) ->
store_room(NewStateData),
NewStateData.
+-spec add_online_user(jid(), binary(), role(), state()) -> state().
add_online_user(JID, Nick, Role, StateData) ->
tab_add_online_user(JID, StateData),
User = #user{jid = JID, nick = Nick, role = Role},
update_online_user(JID, User, StateData).
+-spec remove_online_user(jid(), state()) -> state().
remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, <<"">>).
+-spec remove_online_user(jid(), state(), binary()) -> state().
remove_online_user(JID, StateData, Reason) ->
LJID = jid:tolower(JID),
{ok, #user{nick = Nick}} = (?DICT):find(LJID,
@@ -1784,38 +1656,23 @@ remove_online_user(JID, StateData, Reason) ->
end,
StateData#state{users = Users, nicks = Nicks}.
-filter_presence(#xmlel{name = <<"presence">>,
- attrs = Attrs, children = Els}) ->
- FEls = lists:filter(fun (El) ->
- case El of
- {xmlcdata, _} -> false;
- #xmlel{attrs = Attrs1} ->
- XMLNS = fxml:get_attr_s(<<"xmlns">>,
- Attrs1),
- NS_MUC = ?NS_MUC,
- Size = byte_size(NS_MUC),
- case XMLNS of
- <<NS_MUC:Size/binary, _/binary>> ->
- false;
- _ ->
- true
- end
- end
- end,
- Els),
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = FEls}.
-
-strip_status(#xmlel{name = <<"presence">>,
- attrs = Attrs, children = Els}) ->
- FEls = lists:filter(fun (#xmlel{name = <<"status">>}) ->
- false;
- (_) -> true
- end,
- Els),
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = FEls}.
-
+-spec filter_presence(presence()) -> presence().
+filter_presence(Presence) ->
+ Els = lists:filter(
+ fun(El) ->
+ XMLNS = xmpp:get_ns(El),
+ case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
+ ?NS_MUC -> false;
+ _ -> true
+ end
+ end, xmpp:get_els(Presence)),
+ xmpp:set_els(Presence, Els).
+
+-spec strip_status(presence()) -> presence().
+strip_status(Presence) ->
+ Presence#presence{status = []}.
+
+-spec add_user_presence(jid(), presence(), state()) -> state().
add_user_presence(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
@@ -1826,6 +1683,7 @@ add_user_presence(JID, Presence, StateData) ->
StateData#state.users),
StateData#state{users = Users}.
+-spec add_user_presence_un(jid(), presence(), state()) -> state().
add_user_presence_un(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
@@ -1839,18 +1697,20 @@ add_user_presence_un(JID, Presence, StateData) ->
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
+-spec find_jids_by_nick(binary(), state()) -> [jid()].
find_jids_by_nick(Nick, StateData) ->
Nicks = ?DICT:merge(fun(_, Val, _) -> Val end,
StateData#state.nicks,
StateData#state.subscriber_nicks),
case (?DICT):find(Nick, Nicks) of
- {ok, [User]} -> [jid:make(User)];
- {ok, Users} -> [jid:make(LJID) || LJID <- Users];
- error -> false
+ {ok, [User]} -> [jid:make(User)];
+ {ok, Users} -> [jid:make(LJID) || LJID <- Users];
+ error -> []
end.
%% Find and return the full JID of the user of Nick with
%% highest-priority presence. Return jid record.
+-spec find_jid_by_nick(binary(), state()) -> jid() | false.
find_jid_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [User]} -> jid:make(User);
@@ -1875,6 +1735,8 @@ find_jid_by_nick(Nick, StateData) ->
error -> false
end.
+-spec higher_presence(undefined | presence(),
+ undefined | presence()) -> boolean().
higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
Pri1 = get_priority_from_presence(Pres1),
Pri2 = get_priority_from_presence(Pres2),
@@ -1882,26 +1744,20 @@ higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
higher_presence(Pres1, Pres2) ->
Pres1 > Pres2.
-get_priority_from_presence(PresencePacket) ->
- case fxml:get_subtag(PresencePacket, <<"priority">>) of
- false -> 0;
- SubEl ->
- case catch
- jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
- of
- P when is_integer(P) -> P;
- _ -> 0
- end
+-spec get_priority_from_presence(presence()) -> integer().
+get_priority_from_presence(#presence{priority = Prio}) ->
+ case Prio of
+ undefined -> 0;
+ _ -> Prio
end.
-find_nick_by_jid(Jid, StateData) ->
- [{_, #user{nick = Nick}}] = lists:filter(fun ({_,
- #user{jid = FJid}}) ->
- FJid == Jid
- end,
- (?DICT):to_list(StateData#state.users)),
+-spec find_nick_by_jid(jid(), state()) -> binary().
+find_nick_by_jid(JID, StateData) ->
+ LJID = jid:tolower(JID),
+ {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
Nick.
+-spec is_nick_change(jid(), binary(), state()) -> boolean().
is_nick_change(JID, Nick, StateData) ->
LJID = jid:tolower(JID),
case Nick of
@@ -1912,6 +1768,7 @@ is_nick_change(JID, Nick, StateData) ->
Nick /= OldNick
end.
+-spec nick_collision(jid(), binary(), state()) -> boolean().
nick_collision(User, Nick, StateData) ->
UserOfNick = case find_jid_by_nick(Nick, StateData) of
false ->
@@ -1925,10 +1782,13 @@ nick_collision(User, Nick, StateData) ->
jid:remove_resource(jid:tolower(UserOfNick))
/= jid:remove_resource(jid:tolower(User))).
-add_new_user(From, Nick,
- #xmlel{name = Name, attrs = Attrs, children = Els} = Packet,
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec add_new_user(jid(), binary(), presence() | iq(), state()) ->
+ state() |
+ {error, stanza_error()} |
+ {ignore, state()} |
+ {result, xmpp_element(), state()}.
+add_new_user(From, Nick, Packet, StateData) ->
+ Lang = xmpp:get_lang(Packet),
UserRoomJID = jid:replace_resource(StateData#state.jid, Nick),
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
@@ -1945,7 +1805,7 @@ add_new_user(From, Nick,
fun(I) when is_integer(I), I>0 -> I end,
10),
Collision = nick_collision(From, Nick, StateData),
- IsSubscribeRequest = Name /= <<"presence">>,
+ IsSubscribeRequest = not is_record(Packet, presence),
case {(ServiceAffiliation == owner orelse
((Affiliation == admin orelse Affiliation == owner)
andalso NUsers < MaxAdminUsers)
@@ -1958,72 +1818,66 @@ add_new_user(From, Nick,
of
{false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
Txt = <<"Too many users in this conference">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(Txt, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{false, _, _, _} when NConferences >= MaxConferences ->
Txt = <<"You have joined too many conferences">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(Txt, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{false, _, _, _} ->
- Err = ?ERR_SERVICE_UNAVAILABLE,
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_service_unavailable(),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, _, none} ->
Err = case Affiliation of
outcast ->
ErrText = <<"You have been banned from this room">>,
- ?ERRT_FORBIDDEN(Lang, ErrText);
+ xmpp:err_forbidden(ErrText, Lang);
_ ->
ErrText = <<"Membership is required to enter this room">>,
- ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText)
+ xmpp:err_registration_required(ErrText, Lang)
end,
- ErrPacket = jlib:make_error_reply(Packet, Err),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, true, _, _} ->
ErrText = <<"That nickname is already in use by another occupant">>,
- Err = ?ERRT_CONFLICT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_conflict(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, false, _} ->
ErrText = <<"That nickname is registered by another person">>,
- Err = ?ERRT_CONFLICT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_conflict(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, _, Role} ->
case check_password(ServiceAffiliation, Affiliation,
- Els, From, StateData)
+ Packet, From, StateData)
of
true ->
Nodes = get_subscription_nodes(Packet),
@@ -2035,11 +1889,9 @@ add_new_user(From, Nick,
StateData)),
send_existing_presences(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, StateData)
- end,
+ History = get_history(Nick, Packet, NewState),
+ send_history(From, History, NewState),
+ send_subject(From, StateData),
NewState;
true ->
set_subscriber(From, Nick, Nodes, StateData)
@@ -2053,30 +1905,28 @@ add_new_user(From, Nick,
NewStateData#state{robots = Robots}
end,
if not IsSubscribeRequest -> ResultState;
- true -> {result, subscription_nodes_to_events(Nodes), ResultState}
+ true -> {result, subscribe_result(Packet), ResultState}
end;
nopass ->
ErrText = <<"A password is required to enter this room">>,
- Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_not_authorized(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
captcha_required ->
- SID = fxml:get_attr_s(<<"id">>, Attrs),
+ SID = xmpp:get_id(Packet),
RoomJID = StateData#state.jid,
To = jid:replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
case ejabberd_captcha:create_captcha(SID, RoomJID, To,
Lang, Limiter, From)
of
- {ok, ID, CaptchaEls} ->
- MsgPkt = #xmlel{name = <<"message">>,
- attrs = [{<<"id">>, ID}],
- children = CaptchaEls},
+ {ok, ID, Body, CaptchaEls} ->
+ MsgPkt = #message{id = ID, body = Body,
+ sub_els = CaptchaEls},
Robots = (?DICT):store(From, {Nick, Packet},
StateData#state.robots),
ejabberd_router:route(RoomJID, From, MsgPkt),
@@ -2088,49 +1938,51 @@ add_new_user(From, Nick,
end;
{error, limit} ->
ErrText = <<"Too many CAPTCHA requests">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(
+ UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
_ ->
ErrText = <<"Unable to generate a CAPTCHA">>,
- Err = ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_internal_server_error(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(
+ UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end
end;
_ ->
ErrText = <<"Incorrect password">>,
- Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_not_authorized(ErrText, Lang),
if not IsSubscribeRequest ->
- ejabberd_router:route(UserRoomJID, From, ErrPacket),
+ ejabberd_router:route_error(
+ UserRoomJID, From, Packet, Err),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end
end
end.
-check_password(owner, _Affiliation, _Els, _From,
+-spec check_password(affiliation(), affiliation(),
+ stanza(), jid(), state()) -> boolean() | nopass.
+check_password(owner, _Affiliation, _Packet, _From,
_StateData) ->
%% Don't check pass if user is owner in MUC service (access_admin option)
true;
-check_password(_ServiceAffiliation, Affiliation, Els,
+check_password(_ServiceAffiliation, Affiliation, Packet,
From, StateData) ->
case (StateData#state.config)#config.password_protected
of
false -> check_captcha(Affiliation, From, StateData);
true ->
- Pass = extract_password(Els),
+ Pass = extract_password(Packet),
case Pass of
false -> nopass;
_ ->
@@ -2141,6 +1993,7 @@ check_password(_ServiceAffiliation, Affiliation, Els,
end
end.
+-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required.
check_captcha(Affiliation, From, StateData) ->
case (StateData#state.config)#config.captcha_protected
andalso ejabberd_captcha:is_feature_available()
@@ -2169,108 +2022,55 @@ check_captcha(Affiliation, From, StateData) ->
_ -> true
end.
-extract_password([]) -> false;
-extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC ->
- case fxml:get_subtag(El, <<"password">>) of
- false -> false;
- SubEl -> fxml:get_tag_cdata(SubEl)
- end;
- _ -> extract_password(Els)
- end;
-extract_password([_ | Els]) -> extract_password(Els).
-
-count_stanza_shift(Nick, Els, StateData) ->
- HL = lqueue_to_list(StateData#state.history),
- Since = extract_history(Els, <<"since">>),
- Shift0 = case Since of
- false -> 0;
- _ ->
- Sin = calendar:datetime_to_gregorian_seconds(Since),
- count_seconds_shift(Sin, HL)
- end,
- Seconds = extract_history(Els, <<"seconds">>),
- Shift1 = case Seconds of
- false -> 0;
- _ ->
- Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
- - Seconds,
- count_seconds_shift(Sec, HL)
- end,
- MaxStanzas = extract_history(Els, <<"maxstanzas">>),
- Shift2 = case MaxStanzas of
- false -> 0;
- _ -> count_maxstanzas_shift(MaxStanzas, HL)
- end,
- MaxChars = extract_history(Els, <<"maxchars">>),
- Shift3 = case MaxChars of
- false -> 0;
- _ -> count_maxchars_shift(Nick, MaxChars, HL)
- end,
- lists:max([Shift0, Shift1, Shift2, Shift3]).
-
-count_seconds_shift(Seconds, HistoryList) ->
- lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject,
- TimeStamp, _Size}) ->
- T =
- calendar:datetime_to_gregorian_seconds(TimeStamp),
- if T < Seconds -> 1;
- true -> 0
- end
- end,
- HistoryList)).
-
-count_maxstanzas_shift(MaxStanzas, HistoryList) ->
- S = length(HistoryList) - MaxStanzas,
- if S =< 0 -> 0;
- true -> S
+-spec extract_password(stanza()) -> binary() | false.
+extract_password(Packet) ->
+ case xmpp:get_subtag(Packet, #muc{}) of
+ #muc{password = Password} when is_binary(Password) ->
+ Password;
+ _ ->
+ false
end.
-count_maxchars_shift(Nick, MaxSize, HistoryList) ->
- NLen = byte_size(Nick) + 1,
- Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject,
- _TimeStamp, Size}) ->
- Size + NLen
- end,
- HistoryList),
- calc_shift(MaxSize, Sizes).
-
-calc_shift(MaxSize, Sizes) ->
- Total = lists:sum(Sizes),
- calc_shift(MaxSize, Total, 0, Sizes).
-
-calc_shift(_MaxSize, _Size, Shift, []) -> Shift;
-calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
- if MaxSize >= Size -> Shift;
- true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
+-spec get_history(binary(), stanza(), state()) -> lqueue().
+get_history(Nick, Packet, #state{history = History}) ->
+ case xmpp:get_subtag(Packet, #muc{}) of
+ #muc{history = #muc_history{} = MUCHistory} ->
+ Now = p1_time_compat:timestamp(),
+ Q = History#lqueue.queue,
+ {NewQ, Len} = filter_history(Q, MUCHistory, Now, Nick, queue:new(), 0, 0),
+ History#lqueue{queue = NewQ, len = Len};
+ _ ->
+ History
+ end.
+
+-spec filter_history(?TQUEUE, muc_history(), erlang:timestamp(), binary(),
+ ?TQUEUE, non_neg_integer(), non_neg_integer()) ->
+ {?TQUEUE, non_neg_integer()}.
+filter_history(Queue, #muc_history{since = Since,
+ seconds = Seconds,
+ maxstanzas = MaxStanzas,
+ maxchars = MaxChars} = MUC,
+ Now, Nick, AccQueue, NumStanzas, NumChars) ->
+ case queue:out_r(Queue) of
+ {{value, {_, _, _, TimeStamp, Size} = Elem}, NewQueue} ->
+ NowDiff = timer:now_diff(Now, TimeStamp) div 1000000,
+ Chars = Size + byte_size(Nick) + 1,
+ if (NumStanzas < MaxStanzas) andalso
+ (TimeStamp > Since) andalso
+ (NowDiff =< Seconds) andalso
+ (NumChars + Chars =< MaxChars) ->
+ filter_history(NewQueue, MUC, Now, Nick,
+ queue:in_r(Elem, AccQueue),
+ NumStanzas + 1,
+ NumChars + Chars);
+ true ->
+ {AccQueue, NumStanzas}
+ end;
+ {empty, _} ->
+ {AccQueue, NumStanzas}
end.
-extract_history([], _Type) -> false;
-extract_history([#xmlel{attrs = Attrs} = El | Els],
- Type) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC ->
- AttrVal = fxml:get_path_s(El,
- [{elem, <<"history">>}, {attr, Type}]),
- case Type of
- <<"since">> ->
- case jlib:datetime_string_to_timestamp(AttrVal) of
- undefined -> false;
- TS -> calendar:now_to_universal_time(TS)
- end;
- _ ->
- case catch jlib:binary_to_integer(AttrVal) of
- IntVal when is_integer(IntVal) and (IntVal >= 0) ->
- IntVal;
- _ -> false
- end
- end;
- _ -> extract_history(Els, Type)
- end;
-extract_history([_ | Els], Type) ->
- extract_history(Els, Type).
-
+-spec is_room_overcrowded(state()) -> boolean().
is_room_overcrowded(StateData) ->
MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users_presence,
@@ -2278,22 +2078,27 @@ is_room_overcrowded(StateData) ->
?DEFAULT_MAX_USERS_PRESENCE),
(?DICT):size(StateData#state.users) > MaxUsersPresence.
+-spec presence_broadcast_allowed(jid(), state()) -> boolean().
presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+-spec send_initial_presence(jid(), state(), state()) -> ok.
send_initial_presence(NJID, StateData, OldStateData) ->
send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
+-spec send_update_presence(jid(), state(), state()) -> ok.
send_update_presence(JID, StateData, OldStateData) ->
send_update_presence(JID, <<"">>, StateData, OldStateData).
+-spec send_update_presence(jid(), binary(), state(), state()) -> ok.
send_update_presence(JID, Reason, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
false -> send_update_presence1(JID, Reason, StateData, OldStateData)
end.
+-spec send_update_presence1(jid(), binary(), state(), state()) -> ok.
send_update_presence1(JID, Reason, StateData, OldStateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
@@ -2317,12 +2122,15 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
end,
LJIDs).
+-spec send_new_presence(jid(), state(), state()) -> ok.
send_new_presence(NJID, StateData, OldStateData) ->
send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
+-spec send_new_presence(jid(), binary(), state(), state()) -> ok.
send_new_presence(NJID, Reason, StateData, OldStateData) ->
send_new_presence(NJID, Reason, false, StateData, OldStateData).
+-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
@@ -2330,6 +2138,7 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
OldStateData)
end.
+-spec is_ra_changed(jid() | ljid(), boolean(), state(), state()) -> boolean().
is_ra_changed(_, _IsInitialPresence = true, _, _) ->
false;
is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
@@ -2348,6 +2157,7 @@ is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
(NewRole /= OldRole) or (NewAff /= OldAff)
end.
+-spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok.
send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
LNJID = jid:tolower(NJID),
#user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
@@ -2360,15 +2170,9 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
{Role1, Presence1} =
case presence_broadcast_allowed(NJID, StateData) of
true -> {Role0, Presence0};
- false ->
- {none,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children = []}
- }
+ false -> {none, #presence{type = unavailable}}
end,
Affiliation = get_affiliation(LJID, StateData),
- SAffiliation = affiliation_to_list(Affiliation),
UserList =
case not (presence_broadcast_allowed(NJID, StateData) orelse
presence_broadcast_allowed(NJID, OldStateData)) of
@@ -2379,62 +2183,38 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
end,
lists:foreach(
fun({LUJID, Info}) ->
- {Role, Presence} = if LNJID == LUJID -> {Role0, Presence0};
+ IsSelfPresence = LNJID == LUJID,
+ {Role, Presence} = if IsSelfPresence -> {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">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}]
- end,
- ItemEls = case Reason of
- <<"">> -> [];
- _ ->
- [#xmlel{name = <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata, Reason}]}]
- end,
- StatusEls = status_els(IsInitialPresence, NJID, Info,
- StateData),
- Pres = if Presence == undefined -> #xmlel{name = <<"presence">>};
+ Item0 = #muc_item{affiliation = Affiliation,
+ role = Role},
+ Item1 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false orelse IsSelfPresence of
+ true -> Item0#muc_item{jid = RealJID};
+ false -> Item0
+ end,
+ Item = Item1#muc_item{reason = Reason},
+ StatusCodes = status_codes(IsInitialPresence, IsSelfPresence,
+ StateData),
+ Pres = if Presence == undefined -> #presence{};
true -> Presence
end,
- Packet = fxml:append_subtags(Pres,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs
- =
- ItemAttrs,
- children
- =
- ItemEls}
- | StatusEls]}]),
+ Packet = xmpp:set_subtag(
+ Pres, #muc_user{items = [Item],
+ status_codes = StatusCodes}),
Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
false -> ?NS_MUCSUB_NODES_PRESENCE
end,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
- Type = fxml:get_tag_attr_s(<<"type">>, Packet),
+ Type = xmpp:get_type(Packet),
IsSubscriber = is_subscriber(Info#user.jid, StateData),
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) and
- (IsInitialPresence or (Type == <<"unavailable">>)) ->
+ (IsInitialPresence or (Type == unavailable)) ->
Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node2, StateData);
@@ -2444,12 +2224,14 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
end,
UserList).
+-spec send_existing_presences(jid(), state()) -> ok.
send_existing_presences(ToJID, StateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
false -> send_existing_presences1(ToJID, StateData)
end.
+-spec send_existing_presences1(jid(), state()) -> ok.
send_existing_presences1(ToJID, StateData) ->
LToJID = jid:tolower(ToJID),
{ok, #user{jid = RealToJID, role = Role}} =
@@ -2469,46 +2251,23 @@ send_existing_presences1(ToJID, StateData) ->
{_, false} -> ok;
_ ->
FromAffiliation = get_affiliation(LJID, StateData),
- ItemAttrs = case Role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(FromJID)},
- {<<"affiliation">>,
- affiliation_to_list(FromAffiliation)},
- {<<"role">>,
- role_to_list(FromRole)}];
- _ ->
- [{<<"affiliation">>,
- affiliation_to_list(FromAffiliation)},
- {<<"role">>,
- role_to_list(FromRole)}]
- end,
- Packet = fxml:append_subtags(
- Presence,
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- ItemAttrs,
- children
- =
- []}]}]),
+ Item0 = #muc_item{affiliation = FromAffiliation,
+ role = FromRole},
+ Item = case Role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false of
+ true -> Item0#muc_item{jid = FromJID};
+ false -> Item0
+ end,
+ Packet = xmpp:set_subtag(
+ Presence, #muc_user{items = [Item]}),
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
end
end,
(?DICT):to_list(StateData#state.nicks)).
+-spec set_nick(jid(), binary(), state()) -> state().
set_nick(JID, Nick, State) ->
LJID = jid:tolower(JID),
{ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users),
@@ -2531,6 +2290,7 @@ set_nick(JID, Nick, State) ->
end,
State#state{users = Users, nicks = Nicks}.
+-spec change_nick(jid(), binary(), state()) -> state().
change_nick(JID, Nick, StateData) ->
LJID = jid:tolower(JID),
{ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users),
@@ -2551,6 +2311,7 @@ change_nick(JID, Nick, StateData) ->
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
+-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok.
send_nick_changing(JID, OldNick, StateData,
SendOldUnavailable, SendNewAvailable) ->
{ok,
@@ -2559,104 +2320,48 @@ send_nick_changing(JID, OldNick, StateData,
(?DICT):find(jid:tolower(JID),
StateData#state.users),
Affiliation = get_affiliation(JID, StateData),
- SAffiliation = affiliation_to_list(Affiliation),
- SRole = role_to_list(Role),
- lists:foreach(fun ({_LJID, Info}) when Presence /= undefined ->
- ItemAttrs1 = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole},
- {<<"nick">>, Nick}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole},
- {<<"nick">>, Nick}]
- end,
- ItemAttrs2 = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}]
- end,
- Status110 = case JID == Info#user.jid of
- true ->
- [#xmlel{name = <<"status">>,
- attrs = [{<<"code">>, <<"110">>}]
- }];
- false ->
- []
- end,
- Packet1 = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs1,
- children =
- []},
- #xmlel{name =
- <<"status">>,
- attrs =
- [{<<"code">>,
- <<"303">>}],
- children =
- []}|Status110]}]},
- Packet2 = fxml:append_subtags(Presence,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- ItemAttrs2,
- children
- =
- []}|Status110]}]),
- if SendOldUnavailable ->
- send_wrapped(jid:replace_resource(StateData#state.jid,
- OldNick),
- Info#user.jid, Packet1,
- ?NS_MUCSUB_NODES_PRESENCE,
- StateData);
- true -> ok
+ lists:foreach(
+ fun({LJID, Info}) when Presence /= undefined ->
+ IsSelfPresence = LJID == jid:tolower(JID),
+ Item0 = #muc_item{affiliation = Affiliation, role = Role},
+ Item = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false orelse IsSelfPresence of
+ true -> Item0#muc_item{jid = RealJID};
+ false -> Item0
+ end,
+ Status110 = case IsSelfPresence of
+ true -> [110];
+ false -> []
end,
- if SendNewAvailable ->
- send_wrapped(jid:replace_resource(StateData#state.jid,
- Nick),
- Info#user.jid, Packet2,
- ?NS_MUCSUB_NODES_PRESENCE,
- StateData);
- true -> ok
- end;
- (_) ->
- ok
- end,
+ Packet1 = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{
+ items = [Item#muc_item{nick = Nick}],
+ status_codes = [303|Status110]}]},
+ Packet2 = xmpp:set_subtag(Presence,
+ #muc_user{items = [Item],
+ status_codes = Status110}),
+ if SendOldUnavailable ->
+ send_wrapped(
+ jid:replace_resource(StateData#state.jid, OldNick),
+ Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE,
+ StateData);
+ true -> ok
+ end,
+ if SendNewAvailable ->
+ send_wrapped(
+ jid:replace_resource(StateData#state.jid, Nick),
+ Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE,
+ StateData);
+ true -> ok
+ end;
+ (_) ->
+ ok
+ end,
?DICT:to_list(get_users_and_subscribers(StateData))).
+-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
Users = get_users_and_subscribers(StateData),
@@ -2674,21 +2379,16 @@ maybe_send_affiliation(JID, Affiliation, StateData) ->
true ->
ok; % The new affiliation is published via presence.
false ->
- send_affiliation(LJID, Affiliation, StateData)
+ send_affiliation(JID, Affiliation, StateData)
end.
-send_affiliation(LJID, Affiliation, StateData) ->
- ItemAttrs = [{<<"jid">>, jid:to_string(LJID)},
- {<<"affiliation">>, affiliation_to_list(Affiliation)},
- {<<"role">>, <<"none">>}],
- Message = #xmlel{name = <<"message">>,
- attrs = [{<<"id">>, randoms:get_string()}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs = ItemAttrs}]}]},
+-spec send_affiliation(jid(), affiliation(), state()) -> ok.
+send_affiliation(JID, Affiliation, StateData) ->
+ Item = #muc_item{jid = JID,
+ affiliation = Affiliation,
+ role = none},
+ Message = #message{id = randoms:get_string(),
+ sub_els = [#muc_user{items = [Item]}]},
Users = get_users_and_subscribers(StateData),
Recipients = case (StateData#state.config)#config.anonymous of
true ->
@@ -2703,43 +2403,33 @@ send_affiliation(LJID, Affiliation, StateData) ->
send_wrapped_multiple(StateData#state.jid, Recipients, Message,
?NS_MUCSUB_NODES_AFFILIATIONS, StateData).
-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) -> [].
+-spec status_codes(boolean(), boolean(), state()) -> [pos_integer()].
+status_codes(IsInitialPresence, _IsSelfPresence = true, StateData) ->
+ S0 = [110],
+ case IsInitialPresence of
+ true ->
+ S1 = case StateData#state.just_created of
+ true -> [201|S0];
+ false -> S0
+ end,
+ S2 = case (StateData#state.config)#config.anonymous of
+ true -> S1;
+ false -> [100|S1]
+ end,
+ S3 = case (StateData#state.config)#config.logging of
+ true -> [170|S2];
+ false -> S2
+ end,
+ S3;
+ false -> S0
+ end;
+status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> [].
+-spec lqueue_new(non_neg_integer()) -> lqueue().
lqueue_new(Max) ->
#lqueue{queue = queue:new(), len = 0, max = Max}.
+-spec lqueue_in(term(), lqueue()) -> lqueue().
%% If the message queue limit is set to 0, do not store messages.
lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
%% Otherwise, rotate messages in the queue store.
@@ -2752,76 +2442,71 @@ lqueue_in(Item,
true -> #lqueue{queue = Q2, len = Len + 1, max = Max}
end.
+-spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue().
lqueue_cut(Q, 0) -> Q;
lqueue_cut(Q, N) ->
{_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
+-spec lqueue_to_list(lqueue()) -> list().
lqueue_to_list(#lqueue{queue = Q1}) ->
queue:to_list(Q1).
-
+-spec add_message_to_history(binary(), jid(), message(), state()) -> state().
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
- of
- false -> false;
- _ -> true
- end,
- TimeStamp = p1_time_compat:timestamp(),
- AddrPacket = case (StateData#state.config)#config.anonymous of
- true -> Packet;
- false ->
- Address = #xmlel{name = <<"address">>,
- attrs = [{<<"type">>, <<"ofrom">>},
- {<<"jid">>,
- jid:to_string(FromJID)}],
- children = []},
- Addresses = #xmlel{name = <<"addresses">>,
- attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
- children = [Address]},
- fxml:append_subtags(Packet, [Addresses])
- end,
- TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
- SPacket =
- jlib:replace_from_to(jid:replace_resource(StateData#state.jid,
- FromNick),
- StateData#state.jid, TSPacket),
- Size = element_size(SPacket),
- Q1 = lqueue_in({FromNick, TSPacket, HaveSubject,
- calendar:now_to_universal_time(TimeStamp), Size},
- StateData#state.history),
add_to_log(text, {FromNick, Packet}, StateData),
- StateData#state{history = Q1}.
-
-send_history(JID, Shift, StateData) ->
- lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
- _Size},
- B) ->
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- Nick),
- JID, Packet),
- B or HaveSubject
- end,
- false,
- lists:nthtail(Shift,
- lqueue_to_list(StateData#state.history))).
+ case check_subject(Packet) of
+ false ->
+ TimeStamp = p1_time_compat:timestamp(),
+ AddrPacket = case (StateData#state.config)#config.anonymous of
+ true -> Packet;
+ false ->
+ Addresses = #addresses{
+ list = [#address{type = ofrom,
+ jid = FromJID}]},
+ xmpp:set_subtag(Packet, Addresses)
+ end,
+ TSPacket = xmpp_util:add_delay_info(
+ AddrPacket, StateData#state.jid, TimeStamp),
+ SPacket = xmpp:set_from_to(
+ TSPacket,
+ jid:replace_resource(StateData#state.jid, FromNick),
+ StateData#state.jid),
+ Size = element_size(SPacket),
+ Q1 = lqueue_in({FromNick, TSPacket, false,
+ TimeStamp, Size},
+ StateData#state.history),
+ StateData#state{history = Q1};
+ _ ->
+ StateData
+ end.
-send_subject(_JID, #state{subject_author = <<"">>}) -> ok;
+-spec send_history(jid(), lqueue(), state()) -> boolean().
+send_history(JID, History, StateData) ->
+ lists:foreach(
+ fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) ->
+ ejabberd_router:route(
+ jid:replace_resource(StateData#state.jid, Nick),
+ JID, Packet)
+ end, lqueue_to_list(History)).
+
+-spec send_subject(jid(), state()) -> 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}]}]},
+ Subject = case StateData#state.subject of
+ <<"">> -> [#text{}];
+ Subj -> xmpp:mk_text(Subj)
+ end,
+ Packet = #message{type = groupchat, subject = Subject},
ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID,
Packet).
-check_subject(Packet) ->
- case fxml:get_subtag(Packet, <<"subject">>) of
- false -> false;
- SubjEl -> fxml:get_tag_cdata(SubjEl)
- end.
+-spec check_subject(message()) -> false | binary().
+check_subject(#message{subject = [_|_] = Subj, body = [],
+ thread = undefined}) ->
+ xmpp:get_text(Subj);
+check_subject(_) ->
+ false.
+-spec can_change_subject(role(), boolean(), state()) -> boolean().
can_change_subject(Role, IsSubscriber, StateData) ->
case (StateData#state.config)#config.allow_change_subj
of
@@ -2832,99 +2517,85 @@ can_change_subject(Role, IsSubscriber, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Admin stuff
-process_iq_admin(From, set, Lang, SubEl, StateData) ->
- #xmlel{children = Items} = SubEl,
+-spec process_iq_admin(jid(), iq(), #state{}) -> {error, stanza_error()} |
+ {result, undefined, #state{}} |
+ {result, muc_admin()}.
+process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]},
+ _StateData) ->
+ Txt = <<"No 'item' element found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+process_iq_admin(From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_admin{items = Items}]},
+ StateData) ->
process_admin_items_set(From, Items, Lang, StateData);
-process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case fxml:get_subtag(SubEl, <<"item">>) of
- false ->
- Txt = <<"No 'item' element found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- Item ->
- FAffiliation = get_affiliation(From, StateData),
- FRole = get_role(From, StateData),
- case fxml:get_tag_attr(<<"role">>, Item) of
- false ->
- case fxml:get_tag_attr(<<"affiliation">>, Item) of
- false ->
- Txt = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
- SAffiliation ->
- if (FAffiliation == owner) or
- (FAffiliation == admin) or
- ((FAffiliation == member) and not
- (StateData#state.config)#config.anonymous) ->
- Items = items_with_affiliation(SAffiliation,
- StateData),
- {result, Items, StateData};
- true ->
- ErrText =
- <<"Administrator privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
- {'EXIT', _} ->
- Txt = <<"Incorrect value of 'role' attribute">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- SRole ->
- if FRole == moderator ->
- Items = items_with_role(SRole, StateData),
- {result, Items, StateData};
- true ->
- ErrText = <<"Moderator privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end
- end
- end
- end.
+process_iq_admin(From, #iq{type = get, lang = Lang,
+ sub_els = [#muc_admin{items = [Item]}]},
+ StateData) ->
+ FAffiliation = get_affiliation(From, StateData),
+ FRole = get_role(From, StateData),
+ case Item of
+ #muc_item{role = undefined, affiliation = undefined} ->
+ Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ #muc_item{role = undefined, affiliation = Affiliation} ->
+ if (FAffiliation == owner) or
+ (FAffiliation == admin) or
+ ((FAffiliation == member) and
+ not (StateData#state.config)#config.anonymous) ->
+ Items = items_with_affiliation(Affiliation, StateData),
+ {result, #muc_admin{items = Items}};
+ true ->
+ ErrText = <<"Administrator privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)}
+ end;
+ #muc_item{role = Role} ->
+ if FRole == moderator ->
+ Items = items_with_role(Role, StateData),
+ {result, #muc_admin{items = Items}};
+ true ->
+ ErrText = <<"Moderator privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)}
+ end
+ end;
+process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) ->
+ ErrText = <<"Too many <item/> elements">>,
+ {error, xmpp:err_bad_request(ErrText, Lang)}.
+-spec items_with_role(role(), state()) -> [muc_item()].
items_with_role(SRole, StateData) ->
lists:map(fun ({_, U}) -> user_to_item(U, StateData)
end,
search_role(SRole, StateData)).
+-spec items_with_affiliation(affiliation(), state()) -> [muc_item()].
items_with_affiliation(SAffiliation, StateData) ->
- lists:map(fun ({JID, {Affiliation, Reason}}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- affiliation_to_list(Affiliation)},
- {<<"jid">>, jid:to_string(JID)}],
- children =
- [#xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, Reason}]}]};
- ({JID, Affiliation}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- affiliation_to_list(Affiliation)},
- {<<"jid">>, jid:to_string(JID)}],
- children = []}
- end,
- search_affiliation(SAffiliation, StateData)).
+ lists:map(
+ fun({JID, {Affiliation, Reason}}) ->
+ #muc_item{affiliation = Affiliation, jid = jid:make(JID),
+ reason = Reason};
+ ({JID, Affiliation}) ->
+ #muc_item{affiliation = Affiliation, jid = jid:make(JID)}
+ end,
+ search_affiliation(SAffiliation, StateData)).
+-spec user_to_item(#user{}, state()) -> muc_item().
user_to_item(#user{role = Role, nick = Nick, jid = JID},
StateData) ->
Affiliation = get_affiliation(JID, StateData),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"role">>, role_to_list(Role)},
- {<<"affiliation">>, affiliation_to_list(Affiliation)},
- {<<"nick">>, Nick},
- {<<"jid">>, jid:to_string(JID)}],
- children = []}.
+ #muc_item{role = Role,
+ affiliation = Affiliation,
+ nick = Nick,
+ jid = JID}.
+-spec search_role(role(), state()) -> [{ljid(), #user{}}].
search_role(Role, StateData) ->
lists:filter(fun ({_, #user{role = R}}) -> Role == R
end,
(?DICT):to_list(StateData#state.users)).
+-spec search_affiliation(affiliation(), state()) ->
+ [{ljid(),
+ affiliation() | {affiliation(), binary()}}].
search_affiliation(Affiliation, StateData) ->
lists:filter(fun ({_, A}) ->
case A of
@@ -2934,263 +2605,183 @@ search_affiliation(Affiliation, StateData) ->
end,
(?DICT):to_list(StateData#state.affiliations)).
+-spec process_admin_items_set(jid(), [muc_item()], binary(),
+ #state{}) -> {result, undefined, #state{}} |
+ {error, stanza_error()}.
process_admin_items_set(UJID, Items, Lang, StateData) ->
UAffiliation = get_affiliation(UJID, StateData),
URole = get_role(UJID, StateData),
- case find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData, [])
+ case catch find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData, [])
of
{result, Res} ->
?INFO_MSG("Processing MUC admin query from ~s in "
"room ~s:~n ~p",
[jid:to_string(UJID),
jid:to_string(StateData#state.jid), Res]),
- NSD = lists:foldl(process_item_change(UJID),
- StateData, lists:flatten(Res)),
- store_room(NSD),
- {result, [], NSD};
- Err -> Err
+ case lists:foldl(process_item_change(UJID),
+ StateData, lists:flatten(Res)) of
+ {error, _} = Err ->
+ Err;
+ NSD ->
+ store_room(NSD),
+ {result, undefined, NSD}
+ end;
+ {error, Err} -> {error, Err}
end.
+-spec process_item_change(jid()) -> function().
process_item_change(UJID) ->
- fun(E, SD) ->
- process_item_change(E, SD, UJID)
- end.
-
-process_item_change(E, SD, UJID) ->
- case catch case E of
- {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
- %% If the provided JID does not have username,
- %% forget the affiliation completely
- SD;
- {JID, role, none, Reason} ->
- catch
- send_kickban_presence(UJID, JID,
- Reason,
- <<"307">>,
- SD),
- set_role(JID, none, SD);
- {JID, affiliation, none, Reason} ->
- case (SD#state.config)#config.members_only of
- true ->
- catch
- send_kickban_presence(UJID, JID,
- Reason,
- <<"321">>,
- none,
- SD),
- maybe_send_affiliation(JID, none, SD),
- SD1 = set_affiliation(JID, none, SD),
- set_role(JID, none, SD1);
- _ ->
- SD1 = set_affiliation(JID, none, SD),
- send_update_presence(JID, SD1, SD),
- maybe_send_affiliation(JID, none, SD1),
- SD1
- end;
- {JID, affiliation, outcast, Reason} ->
- catch
- send_kickban_presence(UJID, JID,
- Reason,
- <<"301">>,
- outcast,
- SD),
- maybe_send_affiliation(JID, outcast, SD),
- set_affiliation(JID,
- outcast,
- set_role(JID, none, SD),
- Reason);
- {JID, affiliation, A, Reason}
- 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, SD),
- maybe_send_affiliation(JID, A, SD2),
- SD2;
- {JID, affiliation, member, Reason} ->
- SD1 = set_affiliation(JID, member, SD, Reason),
- SD2 = set_role(JID, participant, SD1),
- send_update_presence(JID, Reason, SD2, SD),
- maybe_send_affiliation(JID, member, SD2),
- SD2;
- {JID, role, Role, Reason} ->
- SD1 = set_role(JID, Role, SD),
- catch
- send_new_presence(JID, Reason, SD1, SD),
- SD1;
- {JID, affiliation, A, _Reason} ->
- SD1 = set_affiliation(JID, A, SD),
- send_update_presence(JID, SD1, SD),
- maybe_send_affiliation(JID, A, SD1),
- SD1
- end
- of
- {'EXIT', ErrReason} ->
- ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", [ErrReason]),
- SD;
- NSD -> NSD
- end.
-
+ fun(_, {error, _} = Err) ->
+ Err;
+ (Item, SD) ->
+ process_item_change(Item, SD, UJID)
+ end.
+
+-type admin_action() :: {jid(), affiliation | role,
+ affiliation() | role(), binary()}.
+
+-spec process_item_change(admin_action(), state(), jid()) -> state() | {error, stanza_error()}.
+process_item_change(Item, SD, UJID) ->
+ try case Item of
+ {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
+ %% If the provided JID does not have username,
+ %% forget the affiliation completely
+ SD;
+ {JID, role, none, Reason} ->
+ send_kickban_presence(UJID, JID, Reason, 307, SD),
+ set_role(JID, none, SD);
+ {JID, affiliation, none, Reason} ->
+ case (SD#state.config)#config.members_only of
+ true ->
+ send_kickban_presence(UJID, JID, Reason, 321, none, SD),
+ maybe_send_affiliation(JID, none, SD),
+ SD1 = set_affiliation(JID, none, SD),
+ set_role(JID, none, SD1);
+ _ ->
+ SD1 = set_affiliation(JID, none, SD),
+ send_update_presence(JID, Reason, SD1, SD),
+ maybe_send_affiliation(JID, none, SD1),
+ SD1
+ end;
+ {JID, affiliation, outcast, Reason} ->
+ send_kickban_presence(UJID, JID, Reason, 301, outcast, SD),
+ maybe_send_affiliation(JID, outcast, SD),
+ set_affiliation(JID, outcast, set_role(JID, none, SD), Reason);
+ {JID, affiliation, A, Reason} 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, SD),
+ maybe_send_affiliation(JID, A, SD2),
+ SD2;
+ {JID, affiliation, member, Reason} ->
+ SD1 = set_affiliation(JID, member, SD, Reason),
+ SD2 = set_role(JID, participant, SD1),
+ send_update_presence(JID, Reason, SD2, SD),
+ maybe_send_affiliation(JID, member, SD2),
+ SD2;
+ {JID, role, Role, Reason} ->
+ SD1 = set_role(JID, Role, SD),
+ send_new_presence(JID, Reason, SD1, SD),
+ SD1;
+ {JID, affiliation, A, _Reason} ->
+ SD1 = set_affiliation(JID, A, SD),
+ send_update_presence(JID, SD1, SD),
+ maybe_send_affiliation(JID, A, SD1),
+ SD1
+ end
+ catch E:R ->
+ ?ERROR_MSG("failed to set item ~p from ~s: ~p",
+ [Item, jid:to_string(UJID),
+ {E, {R, erlang:get_stacktrace()}}]),
+ {error, xmpp:err_internal_server_error()}
+ end.
+
+-spec find_changed_items(jid(), affiliation(), role(),
+ [muc_item()], binary(), state(), [admin_action()]) ->
+ {result, [admin_action()]}.
find_changed_items(_UJID, _UAffiliation, _URole, [],
_Lang, _StateData, Res) ->
{result, Res};
+find_changed_items(_UJID, _UAffiliation, _URole,
+ [#muc_item{jid = undefined, nick = undefined}|_],
+ Lang, _StateData, _Res) ->
+ Txt = <<"Neither 'jid' nor 'nick' attribute found">>,
+ throw({error, xmpp:err_bad_request(Txt, Lang)});
+find_changed_items(_UJID, _UAffiliation, _URole,
+ [#muc_item{role = undefined, affiliation = undefined}|_],
+ Lang, _StateData, _Res) ->
+ Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+ throw({error, xmpp:err_bad_request(Txt, Lang)});
find_changed_items(UJID, UAffiliation, URole,
- [{xmlcdata, _} | Items], Lang, StateData, Res) ->
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData, Res);
-find_changed_items(UJID, UAffiliation, URole,
- [#xmlel{name = <<"item">>, attrs = Attrs} = Item
- | Items],
+ [#muc_item{jid = J, nick = Nick, reason = Reason,
+ role = Role, affiliation = Affiliation}|Items],
Lang, StateData, Res) ->
- TJID = case fxml:get_attr(<<"jid">>, Attrs) of
- {value, S} ->
- case jid:from_string(S) of
- error ->
- ErrText = iolist_to_binary(
- io_lib:format(translate:translate(
- Lang,
- <<"Jabber ID ~s is invalid">>),
- [S])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J -> {value, [J]}
- end;
- _ ->
- case fxml:get_attr(<<"nick">>, Attrs) of
- {value, N} ->
- case find_jids_by_nick(N, StateData) of
- false ->
- ErrText = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Nickname ~s does not exist in the room">>),
- [N])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J -> {value, J}
- end;
- _ ->
- Txt1 = <<"No 'nick' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}
- end
- end,
- case TJID of
- {value, [JID | _] = JIDs} ->
- TAffiliation = get_affiliation(JID, StateData),
- TRole = get_role(JID, StateData),
- case fxml:get_attr(<<"role">>, Attrs) of
- false ->
- case fxml:get_attr(<<"affiliation">>, Attrs) of
- false ->
- Txt2 = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt2)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText1 = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Invalid affiliation: ~s">>),
- [StrAffiliation])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
- SAffiliation ->
- ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA = case can_change_ra(UAffiliation,
- URole,
- TAffiliation,
- TRole, affiliation,
- SAffiliation,
- ServiceAf)
- of
- nothing -> nothing;
- true -> true;
- check_owner ->
- case search_affiliation(owner,
- StateData)
- of
- [{OJID, _}] ->
- jid:remove_resource(OJID)
- /=
- jid:tolower(jid:remove_resource(UJID));
- _ -> true
- end;
- _ -> false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = fxml:get_path_s(Item,
- [{elem, <<"reason">>},
- cdata]),
- MoreRes = [{jid:remove_resource(Jidx),
- affiliation, SAffiliation, Reason}
- || Jidx <- JIDs],
- find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData,
- [MoreRes | Res]);
- false ->
- Txt3 = <<"Changing role/affiliation is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
- {'EXIT', _} ->
- ErrText1 = iolist_to_binary(
- io_lib:format(translate:translate(
- Lang,
- <<"Invalid role: ~s">>),
- [StrRole])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
- SRole ->
- ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA = case can_change_ra(UAffiliation, URole,
- TAffiliation, TRole,
- role, SRole, ServiceAf)
- of
- nothing -> nothing;
- true -> true;
- check_owner ->
- case search_affiliation(owner,
- StateData)
- of
- [{OJID, _}] ->
- jid:remove_resource(OJID)
- /=
- jid:tolower(jid:remove_resource(UJID));
- _ -> true
- end;
- _ -> false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData, Res);
- true ->
- Reason = fxml:get_path_s(Item,
- [{elem, <<"reason">>},
- cdata]),
- MoreRes = [{Jidx, role, SRole, Reason}
- || Jidx <- JIDs],
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData,
- [MoreRes | Res]);
- _ ->
- Txt4 = <<"Changing role/affiliation is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)}
- end
+ [JID | _] = JIDs =
+ if J /= undefined ->
+ [J];
+ Nick /= <<"">> ->
+ case find_jids_by_nick(Nick, StateData) of
+ [] ->
+ ErrText = {<<"Nickname ~s does not exist in the room">>,
+ [Nick]},
+ throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
+ JIDList ->
+ JIDList
end
- end;
- Err -> Err
- end;
-find_changed_items(_UJID, _UAffiliation, _URole, _Items,
- _Lang, _StateData, _Res) ->
- {error, ?ERR_BAD_REQUEST}.
+ end,
+ {RoleOrAff, RoleOrAffValue} = if Role == undefined ->
+ {affiliation, Affiliation};
+ true ->
+ {role, Role}
+ end,
+ TAffiliation = get_affiliation(JID, StateData),
+ TRole = get_role(JID, StateData),
+ ServiceAf = get_service_affiliation(JID, StateData),
+ CanChangeRA = case can_change_ra(UAffiliation,
+ URole,
+ TAffiliation,
+ TRole, RoleOrAff, RoleOrAffValue,
+ ServiceAf) of
+ nothing -> nothing;
+ true -> true;
+ check_owner ->
+ case search_affiliation(owner, StateData) of
+ [{OJID, _}] ->
+ jid:remove_resource(OJID)
+ /=
+ jid:tolower(jid:remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
+ case CanChangeRA of
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ Res);
+ true ->
+ MoreRes = case RoleOrAff of
+ affiliation ->
+ [{jid:remove_resource(Jidx),
+ RoleOrAff, RoleOrAffValue, Reason}
+ || Jidx <- JIDs];
+ role ->
+ [{Jidx, RoleOrAff, RoleOrAffValue, Reason}
+ || Jidx <- JIDs]
+ end,
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ [MoreRes | Res]);
+ false ->
+ Txt = <<"Changing role/affiliation is not allowed">>,
+ throw({error, xmpp:err_not_allowed(Txt, Lang)})
+ end.
+-spec can_change_ra(affiliation(), role(), affiliation(), role(),
+ affiliation, affiliation(), affiliation()) -> boolean();
+ (affiliation(), role(), affiliation(), role(),
+ role, role(), affiliation()) -> boolean().
can_change_ra(_FAffiliation, _FRole, owner, _TRole,
affiliation, owner, owner) ->
%% A room owner tries to add as persistent owner a
@@ -3315,11 +2906,15 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, role, _Value, _ServiceAf) ->
false.
+-spec send_kickban_presence(jid(), jid(), binary(),
+ pos_integer(), state()) -> ok.
send_kickban_presence(UJID, JID, Reason, Code, StateData) ->
NewAffiliation = get_affiliation(JID, StateData),
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData).
+-spec send_kickban_presence(jid(), jid(), binary(), pos_integer(),
+ affiliation(), state()) -> ok.
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData) ->
LJID = jid:tolower(JID),
@@ -3348,77 +2943,51 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
end,
LJIDs).
+-spec send_kickban_presence1(jid(), jid(), binary(), pos_integer(),
+ affiliation(), state()) -> ok.
send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
{ok, #user{jid = RealJID, nick = Nick}} =
(?DICT):find(jid:tolower(UJID),
StateData#state.users),
- SAffiliation = affiliation_to_list(Affiliation),
- BannedJIDString = jid:to_string(RealJID),
ActorNick = get_actor_nick(MJID, StateData),
- lists:foreach(fun ({_LJID, Info}) ->
- JidAttrList = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>, BannedJIDString}];
- false -> []
- end,
- ItemAttrs = [{<<"affiliation">>, SAffiliation},
- {<<"role">>, <<"none">>}]
- ++ JidAttrList,
- ItemEls = case Reason of
- <<"">> -> [];
- _ ->
- [#xmlel{name = <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata, Reason}]}]
- end,
- ItemElsActor = case MJID of
- <<"">> -> [];
- _ -> [#xmlel{name = <<"actor">>,
- attrs =
- [{<<"nick">>, ActorNick}]}]
- end,
- Packet = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs,
- children =
- ItemElsActor ++ ItemEls},
- #xmlel{name =
- <<"status">>,
- attrs =
- [{<<"code">>,
- Code}],
- children =
- []}]}]},
- RoomJIDNick = jid:replace_resource(
- StateData#state.jid, Nick),
- send_wrapped(RoomJIDNick, Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
+ lists:foreach(
+ fun({LJID, Info}) ->
+ IsSelfPresence = jid:tolower(UJID) == LJID,
+ Item0 = #muc_item{affiliation = Affiliation,
+ role = none},
+ Item1 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false orelse IsSelfPresence of
+ true -> Item0#muc_item{jid = RealJID};
+ false -> Item0
+ end,
+ Item2 = Item1#muc_item{reason = Reason},
+ Item = case ActorNick of
+ <<"">> -> Item2;
+ _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}}
+ end,
+ Codes = if IsSelfPresence -> [110, Code];
+ true -> [Code]
+ end,
+ Packet = #presence{type = unavailable,
+ sub_els = [#muc_user{items = [Item],
+ status_codes = Codes}]},
+ RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick),
+ send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
IsSubscriber = is_subscriber(Info#user.jid, StateData),
- IsOccupant = Info#user.last_presence /= undefined,
- if (IsSubscriber and not IsOccupant) ->
- send_wrapped(RoomJIDNick, Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
- true ->
- ok
- end
- end,
+ IsOccupant = Info#user.last_presence /= undefined,
+ if (IsSubscriber and not IsOccupant) ->
+ send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
+ true ->
+ ok
+ end
+ end,
(?DICT):to_list(get_users_and_subscribers(StateData))).
+-spec get_actor_nick(binary() | jid(), state()) -> binary().
get_actor_nick(<<"">>, _StateData) ->
<<"">>;
get_actor_nick(MJID, StateData) ->
@@ -3429,97 +2998,94 @@ get_actor_nick(MJID, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Owner stuff
-
-process_iq_owner(From, set, Lang, SubEl, StateData) ->
+-spec process_iq_owner(jid(), iq(), state()) ->
+ {result, undefined | muc_owner()} |
+ {result, undefined | muc_owner(), state() | stop} |
+ {error, stanza_error()}.
+process_iq_owner(From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_owner{destroy = Destroy,
+ config = Config,
+ items = Items}]},
+ StateData) ->
FAffiliation = get_affiliation(From, StateData),
- case FAffiliation of
- owner ->
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>} = 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">>} ->
- case is_allowed_log_change(XEl, StateData, From) andalso
- is_allowed_persistent_change(XEl, StateData, From)
- andalso
- is_allowed_room_name_desc_limits(XEl, StateData)
- andalso
- is_password_settings_correct(XEl, StateData)
- of
- true -> set_config(XEl, StateData, Lang);
- false -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- [#xmlel{name = <<"destroy">>} = SubEl1] ->
- ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
- [jid:to_string(StateData#state.jid),
- jid:to_string(From)]),
- add_to_log(room_existence, destroyed, StateData),
- destroy_room(SubEl1, StateData);
- Items ->
- process_admin_items_set(From, Items, Lang, StateData)
- end;
- _ ->
- ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ if FAffiliation /= owner ->
+ ErrText = <<"Owner privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)};
+ Destroy /= undefined, Config == undefined, Items == [] ->
+ ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
+ [jid:to_string(StateData#state.jid), jid:to_string(From)]),
+ add_to_log(room_existence, destroyed, StateData),
+ destroy_room(Destroy, StateData);
+ Config /= undefined, Destroy == undefined, Items == [] ->
+ case Config of
+ #xdata{type = cancel} ->
+ {result, undefined};
+ #xdata{type = submit, fields = Fs} ->
+ try muc_roomconfig:decode(Fs) of
+ Options ->
+ case is_allowed_log_change(Options, StateData, From) andalso
+ is_allowed_persistent_change(Options, StateData, From) andalso
+ is_allowed_room_name_desc_limits(Options, StateData) andalso
+ is_password_settings_correct(Options, StateData) of
+ true ->
+ set_config(Options, StateData, Lang);
+ false ->
+ {error, xmpp:err_not_acceptable()}
+ end
+ catch _:{muc_roomconfig, Why} ->
+ Txt = muc_roomconfig:format_error(Why),
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ _ ->
+ Txt = <<"Incorrect data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ Items /= [], Config == undefined, Destroy == undefined ->
+ process_admin_items_set(From, Items, Lang, StateData);
+ true ->
+ {error, xmpp:err_bad_request()}
end;
-process_iq_owner(From, get, Lang, SubEl, StateData) ->
+process_iq_owner(From, #iq{type = get, lang = Lang,
+ sub_els = [#muc_owner{destroy = Destroy,
+ config = Config,
+ items = Items}]},
+ StateData) ->
FAffiliation = get_affiliation(From, StateData),
- case FAffiliation of
- owner ->
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [] -> get_config(Lang, StateData, From);
- [Item] ->
- case fxml:get_tag_attr(<<"affiliation">>, Item) of
- false ->
- Txt = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Invalid affiliation: ~s">>),
- [StrAffiliation])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- SAffiliation ->
- Items = items_with_affiliation(SAffiliation,
- StateData),
- {result, Items, StateData}
- end
- end;
- _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- _ ->
- ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ if FAffiliation /= owner ->
+ ErrText = <<"Owner privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)};
+ Destroy == undefined, Config == undefined ->
+ case Items of
+ [] ->
+ {result,
+ #muc_owner{config = get_config(Lang, StateData, From)}};
+ [#muc_item{affiliation = undefined}] ->
+ Txt = <<"No 'affiliation' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [#muc_item{affiliation = Affiliation}] ->
+ Items = items_with_affiliation(Affiliation, StateData),
+ {result, #muc_owner{items = Items}};
+ [_|_] ->
+ Txt = <<"Too many <item/> elements">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ true ->
+ {error, xmpp:err_bad_request()}
end.
-is_allowed_log_change(XEl, StateData, From) ->
- case lists:keymember(<<"muc#roomconfig_enablelogging">>,
- 1, jlib:parse_xdata_submit(XEl))
- of
- false -> true;
- true ->
- allow ==
- mod_muc_log:check_access_log(StateData#state.server_host,
- From)
+-spec is_allowed_log_change(muc_roomconfig:result(), state(), jid()) -> boolean().
+is_allowed_log_change(Options, StateData, From) ->
+ case proplists:is_defined(enablelogging, Options) of
+ false -> true;
+ true ->
+ allow ==
+ mod_muc_log:check_access_log(StateData#state.server_host,
+ From)
end.
-is_allowed_persistent_change(XEl, StateData, From) ->
- case
- lists:keymember(<<"muc#roomconfig_persistentroom">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
+-spec is_allowed_persistent_change(muc_roomconfig:result(), state(), jid()) -> boolean().
+is_allowed_persistent_change(Options, StateData, From) ->
+ case proplists:is_defined(persistentroom, Options) of
false -> true;
true ->
{_AccessRoute, _AccessCreate, _AccessAdmin,
@@ -3532,103 +3098,46 @@ is_allowed_persistent_change(XEl, StateData, From) ->
%% Check if the Room Name and Room Description defined in the Data Form
%% are conformant to the configured limits
-is_allowed_room_name_desc_limits(XEl, StateData) ->
- IsNameAccepted = case
- lists:keysearch(<<"muc#roomconfig_roomname">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [N]}} ->
- byte_size(N) =<
- gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_room_name,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 -> I
- end, infinity);
- _ -> true
- end,
- IsDescAccepted = case
- lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [D]}} ->
- byte_size(D) =<
- gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_room_desc,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 ->
- I
- end, infinity);
- _ -> true
- end,
- IsNameAccepted and IsDescAccepted.
+-spec is_allowed_room_name_desc_limits(muc_roomconfig:result(), state()) -> boolean().
+is_allowed_room_name_desc_limits(Options, StateData) ->
+ RoomName = proplists:get_value(roomname, Options, <<"">>),
+ RoomDesc = proplists:get_value(roomdesc, Options, <<"">>),
+ MaxRoomName = gen_mod:get_module_opt(
+ StateData#state.server_host,
+ mod_muc, max_room_name,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 -> I
+ end, infinity),
+ MaxRoomDesc = gen_mod:get_module_opt(
+ StateData#state.server_host,
+ mod_muc, max_room_desc,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, infinity),
+ (byte_size(RoomName) =< MaxRoomName)
+ andalso (byte_size(RoomDesc) =< MaxRoomDesc).
%% Return false if:
%% "the password for a password-protected room is blank"
-is_password_settings_correct(XEl, StateData) ->
+-spec is_password_settings_correct(muc_roomconfig:result(), state()) -> boolean().
+is_password_settings_correct(Options, StateData) ->
Config = StateData#state.config,
OldProtected = Config#config.password_protected,
OldPassword = Config#config.password,
- NewProtected = case
- lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>,
- 1, jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [<<"1">>]}} -> true;
- {value, {_, [<<"0">>]}} -> false;
- _ -> undefined
- end,
- NewPassword = case
- lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [P]}} -> P;
- _ -> undefined
- end,
- case {OldProtected, NewProtected, OldPassword,
- NewPassword}
- of
- {true, undefined, <<"">>, undefined} -> false;
- {true, undefined, _, <<"">>} -> false;
- {_, true, <<"">>, undefined} -> false;
- {_, true, _, <<"">>} -> false;
- _ -> true
+ NewProtected = proplists:get_value(passwordprotectedroom, Options),
+ NewPassword = proplists:get_value(roomsecret, Options),
+ case {OldProtected, NewProtected, OldPassword, NewPassword} of
+ {true, undefined, <<"">>, undefined} -> false;
+ {true, undefined, _, <<"">>} -> false;
+ {_, true, <<"">>, undefined} -> false;
+ {_, true, _, <<"">>} -> false;
+ _ -> true
end.
--define(XFIELD(Type, Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
--define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD(<<"boolean">>, Label, Var,
- case Val of
- true -> <<"1">>;
- _ -> <<"0">>
- end)).
-
--define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD(<<"text-single">>, Label, Var, Val)).
-
--define(PRIVATEXFIELD(Label, Var, Val),
- ?XFIELD(<<"text-private">>, Label, Var, Val)).
-
--define(JIDMULTIXFIELD(Label, Var, JIDList),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-multi">>},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, jid:to_string(JID)}]}
- || JID <- JIDList]}).
-
+-spec get_default_room_maxusers(state()) -> non_neg_integer().
get_default_room_maxusers(RoomState) ->
DefRoomOpts =
gen_mod:get_module_opt(RoomState#state.server_host,
@@ -3638,564 +3147,163 @@ get_default_room_maxusers(RoomState) ->
RoomState2 = set_opts(DefRoomOpts, RoomState),
(RoomState2#state.config)#config.max_users.
+-spec get_config(binary(), state(), jid()) -> xdata().
get_config(Lang, StateData, From) ->
- {_AccessRoute, _AccessCreate, _AccessAdmin,
- AccessPersistent} =
+ {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} =
StateData#state.access,
ServiceMaxUsers = get_service_max_users(StateData),
- DefaultRoomMaxUsers =
- get_default_room_maxusers(StateData),
+ DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
Config = StateData#state.config,
- {MaxUsersRoomInteger, MaxUsersRoomString} = case
- get_max_users(StateData)
- of
- N when is_integer(N) ->
- {N,
- jlib:integer_to_binary(N)};
- _ -> {0, <<"none">>}
- end,
- Res = [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Configuration of room ~s">>),
- [jid:to_string(StateData#state.jid)]))}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"hidden">>},
- {<<"var">>, <<"FORM_TYPE">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"http://jabber.org/protocol/muc#roomconfig">>}]}]},
- ?STRINGXFIELD(<<"Room title">>,
- <<"muc#roomconfig_roomname">>, (Config#config.title)),
- ?STRINGXFIELD(<<"Room description">>,
- <<"muc#roomconfig_roomdesc">>,
- (Config#config.description))]
- ++
- case acl:match_rule(StateData#state.server_host,
- AccessPersistent, From)
- of
- allow ->
- [?BOOLXFIELD(<<"Make room persistent">>,
- <<"muc#roomconfig_persistentroom">>,
- (Config#config.persistent))];
- _ -> []
- end
- ++
- [?BOOLXFIELD(<<"Make room public searchable">>,
- <<"muc#roomconfig_publicroom">>,
- (Config#config.public)),
- ?BOOLXFIELD(<<"Make participants list public">>,
- <<"public_list">>, (Config#config.public_list)),
- ?BOOLXFIELD(<<"Make room password protected">>,
- <<"muc#roomconfig_passwordprotectedroom">>,
- (Config#config.password_protected)),
- ?PRIVATEXFIELD(<<"Password">>,
- <<"muc#roomconfig_roomsecret">>,
- case Config#config.password_protected of
- true -> Config#config.password;
- false -> <<"">>
- end),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Maximum Number of Occupants">>)},
- {<<"var">>, <<"muc#roomconfig_maxusers">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, MaxUsersRoomString}]}]
- ++
- if is_integer(ServiceMaxUsers) -> [];
- true ->
- [#xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"No limit">>)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- <<"none">>}]}]}]
- end
- ++
- [#xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- jlib:integer_to_binary(N)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- jlib:integer_to_binary(N)}]}]}
- || N
- <- lists:usort([ServiceMaxUsers,
- DefaultRoomMaxUsers,
- MaxUsersRoomInteger
- | ?MAX_USERS_DEFAULT_LIST]),
- N =< ServiceMaxUsers]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Present real Jabber IDs to">>)},
- {<<"var">>, <<"muc#roomconfig_whois">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- if Config#config.anonymous ->
- <<"moderators">>;
- true -> <<"anyone">>
- end}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"moderators only">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"moderators">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"anyone">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- 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)),
- ?BOOLXFIELD(<<"Make room moderated">>,
- <<"muc#roomconfig_moderatedroom">>,
- (Config#config.moderated)),
- ?BOOLXFIELD(<<"Default users as participants">>,
- <<"members_by_default">>,
- (Config#config.members_by_default)),
- ?BOOLXFIELD(<<"Allow users to change the subject">>,
- <<"muc#roomconfig_changesubject">>,
- (Config#config.allow_change_subj)),
- ?BOOLXFIELD(<<"Allow users to send private messages">>,
- <<"allow_private_messages">>,
- (Config#config.allow_private_messages)),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Allow visitors to send private messages to">>)},
- {<<"var">>,
- <<"allow_private_messages_from_visitors">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- case
- Config#config.allow_private_messages_from_visitors
- of
- anyone -> <<"anyone">>;
- moderators -> <<"moderators">>;
- nobody -> <<"nobody">>
- end}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"nobody">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, <<"nobody">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"moderators only">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"moderators">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"anyone">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"anyone">>}]}]}]},
- ?BOOLXFIELD(<<"Allow users to query other users">>,
- <<"allow_query_users">>,
- (Config#config.allow_query_users)),
- ?BOOLXFIELD(<<"Allow users to send invites">>,
- <<"muc#roomconfig_allowinvites">>,
- (Config#config.allow_user_invites)),
- ?BOOLXFIELD(<<"Allow visitors to send status text in "
- "presence updates">>,
- <<"muc#roomconfig_allowvisitorstatus">>,
- (Config#config.allow_visitor_status)),
- ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
- <<"muc#roomconfig_allowvisitornickchange">>,
- (Config#config.allow_visitor_nickchange)),
- ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
- <<"muc#roomconfig_allowvoicerequests">>,
- (Config#config.allow_voice_requests)),
- ?BOOLXFIELD(<<"Allow subscription">>,
- <<"muc#roomconfig_allow_subscription">>,
- (Config#config.allow_subscription)),
- ?STRINGXFIELD(<<"Minimum interval between voice requests "
- "(in seconds)">>,
- <<"muc#roomconfig_voicerequestmininterval">>,
- (jlib:integer_to_binary(Config#config.voice_request_min_interval)))]
- ++
- case ejabberd_captcha:is_feature_available() of
- true ->
- [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
- <<"captcha_protected">>,
- (Config#config.captcha_protected))];
- false -> []
- end ++
- [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
- <<"muc#roomconfig_captcha_whitelist">>,
- ((?SETS):to_list(Config#config.captcha_whitelist)))]
- ++
- case
- mod_muc_log:check_access_log(StateData#state.server_host,
- From)
- of
- allow ->
- [?BOOLXFIELD(<<"Enable logging">>,
- <<"muc#roomconfig_enablelogging">>,
- (Config#config.logging))];
- _ -> []
- end,
- X = ejabberd_hooks:run_fold(get_room_config,
- StateData#state.server_host,
- Res,
- [StateData, From, Lang]),
- {result,
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "configure room">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children = X}],
- StateData}.
-
-set_config(XEl, StateData, Lang) ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
- _ ->
- case set_xoption(XData, StateData#state.config,
- StateData#state.server_host, Lang) of
- #config{} = Config ->
- Res = change_config(Config, StateData),
- {result, _, NSD} = Res,
- Type = case {(StateData#state.config)#config.logging,
- Config#config.logging}
- of
- {true, false} -> roomconfig_change_disabledlogging;
- {false, true} -> roomconfig_change_enabledlogging;
- {_, _} -> roomconfig_change
- end,
- Users = [{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- (?DICT):to_list(StateData#state.users)],
- add_to_log(Type, Users, NSD),
- Res;
- Err -> Err
- end
+ {MaxUsersRoomInteger, MaxUsersRoomString} =
+ case get_max_users(StateData) of
+ N when is_integer(N) ->
+ {N, N};
+ _ -> {0, none}
+ end,
+ Title = str:format(
+ translate:translate(Lang, <<"Configuration of room ~s">>),
+ [jid:to_string(StateData#state.jid)]),
+ Fs = [{roomname, Config#config.title},
+ {roomdesc, Config#config.description}] ++
+ case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
+ allow -> [{persistentroom, Config#config.persistent}];
+ deny -> []
+ end ++
+ [{publicroom, Config#config.public},
+ {public_list, Config#config.public_list},
+ {passwordprotectedroom, Config#config.password_protected},
+ {roomsecret, case Config#config.password_protected of
+ true -> Config#config.password;
+ false -> <<"">>
+ end},
+ {maxusers, MaxUsersRoomString,
+ [if is_integer(ServiceMaxUsers) -> [];
+ true -> [{<<"No limit">>, <<"none">>}]
+ end] ++ [{integer_to_binary(N), N}
+ || N <- lists:usort([ServiceMaxUsers,
+ DefaultRoomMaxUsers,
+ MaxUsersRoomInteger
+ | ?MAX_USERS_DEFAULT_LIST]),
+ N =< ServiceMaxUsers]},
+ {whois, if Config#config.anonymous -> moderators;
+ true -> anyone
+ end},
+ {presencebroadcast, Config#config.presence_broadcast},
+ {membersonly, Config#config.members_only},
+ {moderatedroom, Config#config.moderated},
+ {members_by_default, Config#config.members_by_default},
+ {changesubject, Config#config.allow_change_subj},
+ {allow_private_messages, Config#config.allow_private_messages},
+ {allow_private_messages_from_visitors,
+ Config#config.allow_private_messages_from_visitors},
+ {allow_query_users, Config#config.allow_query_users},
+ {allowinvites, Config#config.allow_user_invites},
+ {allow_visitor_status, Config#config.allow_visitor_status},
+ {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
+ {allow_voice_requests, Config#config.allow_voice_requests},
+ {allow_subscription, Config#config.allow_subscription},
+ {voice_request_min_interval, Config#config.voice_request_min_interval}]
+ ++
+ case ejabberd_captcha:is_feature_available() of
+ true -> [{captcha_protected, Config#config.captcha_protected}];
+ false -> []
+ end ++
+ [{captcha_whitelist,
+ lists:map(fun jid:make/1, ?SETS:to_list(Config#config.captcha_whitelist))}]
+ ++
+ case mod_muc_log:check_access_log(StateData#state.server_host, From) of
+ allow -> [{enablelogging, Config#config.logging}];
+ deny -> []
+ end,
+ Fields = ejabberd_hooks:run_fold(get_room_config,
+ StateData#state.server_host,
+ Fs,
+ [StateData, From, Lang]),
+ #xdata{type = form, title = Title,
+ fields = muc_roomconfig:encode(
+ Fields, fun(T) -> translate:translate(Lang, T) end)}.
+
+-spec set_config(muc_roomconfig:result(), state(), binary()) ->
+ {error, stanza_error()} | {result, undefined, state()}.
+set_config(Options, StateData, Lang) ->
+ try
+ #config{} = Config = set_config(Options, StateData#state.config,
+ StateData#state.server_host, Lang),
+ {result, _, NSD} = Res = change_config(Config, StateData),
+ Type = case {(StateData#state.config)#config.logging,
+ Config#config.logging}
+ of
+ {true, false} -> roomconfig_change_disabledlogging;
+ {false, true} -> roomconfig_change_enabledlogging;
+ {_, _} -> roomconfig_change
+ end,
+ Users = [{U#user.jid, U#user.nick, U#user.role}
+ || {_, U} <- (?DICT):to_list(StateData#state.users)],
+ add_to_log(Type, Users, NSD),
+ Res
+ catch _:{badmatch, {error, #stanza_error{}} = Err} ->
+ Err
end.
--define(SET_BOOL_XOPT(Opt, Val),
- case Val of
- <<"0">> ->
- set_xoption(Opts, Config#config{Opt = false}, ServerHost, Lang);
- <<"false">> ->
- set_xoption(Opts, Config#config{Opt = false}, ServerHost, Lang);
- <<"1">> -> set_xoption(Opts, Config#config{Opt = true}, ServerHost, Lang);
- <<"true">> ->
- set_xoption(Opts, Config#config{Opt = true}, ServerHost, Lang);
- _ ->
- Txt = <<"Value of '~s' should be boolean">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
- end).
+get_config_opt_name(Pos) ->
+ Fs = [config|record_info(fields, config)],
+ lists:nth(Pos, Fs).
--define(SET_NAT_XOPT(Opt, Val),
- case catch jlib:binary_to_integer(Val) of
- I when is_integer(I), I > 0 ->
- set_xoption(Opts, Config#config{Opt = I}, ServerHost, Lang);
- _ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
- end).
-
--define(SET_STRING_XOPT(Opt, Val),
- set_xoption(Opts, Config#config{Opt = Val}, ServerHost, Lang)).
-
--define(SET_JIDMULTI_XOPT(Opt, Vals),
- begin
- Set = lists:foldl(fun ({U, S, R}, Set1) ->
- (?SETS):add_element({U, S, R}, Set1);
- (#jid{luser = U, lserver = S, lresource = R},
- Set1) ->
- (?SETS):add_element({U, S, R}, Set1);
- (_, Set1) -> Set1
- end,
- (?SETS):empty(), Vals),
- set_xoption(Opts, Config#config{Opt = Set}, ServerHost, Lang)
- end).
-
-set_xoption([], Config, _ServerHost, _Lang) -> Config;
-set_xoption([{<<"muc#roomconfig_roomname">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(title, Val);
-set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(description, Val);
-set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_change_subj, Val);
-set_xoption([{<<"allow_query_users">>, [Val]} | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_query_users, Val);
-set_xoption([{<<"allow_private_messages">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_private_messages, Val);
-set_xoption([{<<"allow_private_messages_from_visitors">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- case Val of
- <<"anyone">> ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors,
- anyone);
- <<"moderators">> ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors,
- moderators);
- <<"nobody">> ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors,
- nobody);
- _ ->
- Txt = <<"Value of 'allow_private_messages_from_visitors' "
- "should be anyone|moderators|nobody">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
-set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_visitor_status, Val);
-set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_visitor_nickchange, Val);
-set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(public, Val);
-set_xoption([{<<"public_list">>, [Val]} | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(public_list, Val);
-set_xoption([{<<"muc#roomconfig_persistentroom">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(persistent, Val);
-set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(moderated, Val);
-set_xoption([{<<"members_by_default">>, [Val]} | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(members_by_default, Val);
-set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(members_only, Val);
-set_xoption([{<<"captcha_protected">>, [Val]} | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(captcha_protected, Val);
-set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_user_invites, Val);
-set_xoption([{<<"muc#roomconfig_allow_subscription">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_subscription, Val);
-set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(password_protected, Val);
-set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(password, Val);
-set_xoption([{<<"anonymous">>, [Val]} | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(anonymous, Val);
-set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
- Config, ServerHost, Lang) ->
- 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 ->
- Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
- "be moderator|participant|visitor">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {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},
- ServerHost, Lang)
- end;
-set_xoption([{<<"muc#roomconfig_allowvoicerequests">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(allow_voice_requests, Val);
-set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>,
- [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_NAT_XOPT(voice_request_min_interval, Val);
-set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- case Val of
- <<"moderators">> ->
- ?SET_BOOL_XOPT(anonymous,
- (iolist_to_binary(integer_to_list(1))));
- <<"anyone">> ->
- ?SET_BOOL_XOPT(anonymous,
- (iolist_to_binary(integer_to_list(0))));
- _ ->
- Txt = <<"Value of 'muc#roomconfig_whois' should be "
- "moderators|anyone">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
-set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- case Val of
- <<"none">> -> ?SET_STRING_XOPT(max_users, none);
- _ -> ?SET_NAT_XOPT(max_users, Val)
- end;
-set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
- | Opts],
- Config, ServerHost, Lang) ->
- ?SET_BOOL_XOPT(logging, Val);
-set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
- Vals}
- | Opts],
- Config, ServerHost, Lang) ->
- JIDs = [jid:from_string(Val) || Val <- Vals],
- ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
-set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) ->
- set_xoption(Opts, Config, ServerHost, Lang);
-set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
- Txt = <<"Unknown option '~s'">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- Err = {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)},
- case ejabberd_hooks:run_fold(set_room_option,
- ServerHost,
- Err,
- [Opt, Vals, Lang]) of
- {error, Reason} ->
- {error, Reason};
- {Pos, Val} ->
- set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang)
- end.
+-spec set_config([muc_roomconfig:property()], #config{},
+ binary(), binary()) -> #config{} | {error, stanza_error()}.
+set_config(Opts, Config, ServerHost, Lang) ->
+ lists:foldl(
+ fun(_, {error, _} = Err) -> Err;
+ ({roomname, Title}, C) -> C#config{title = Title};
+ ({roomdesc, Desc}, C) -> C#config{description = Desc};
+ ({changesubject, V}, C) -> C#config{allow_change_subj = V};
+ ({allow_query_users, V}, C) -> C#config{allow_query_users = V};
+ ({allow_private_messages, V}, C) ->
+ C#config{allow_private_messages = V};
+ ({allow_private_messages_from_visitors, V}, C) ->
+ C#config{allow_private_messages_from_visitors = V};
+ ({allow_visitor_status, V}, C) -> C#config{allow_visitor_status = V};
+ ({allow_visitor_nickchange, V}, C) ->
+ C#config{allow_visitor_nickchange = V};
+ ({publicroom, V}, C) -> C#config{public = V};
+ ({public_list, V}, C) -> C#config{public_list = V};
+ ({persistentroom, V}, C) -> C#config{persistent = V};
+ ({moderatedroom, V}, C) -> C#config{moderated = V};
+ ({members_by_default, V}, C) -> C#config{members_by_default = V};
+ ({membersonly, V}, C) -> C#config{members_only = V};
+ ({captcha_protected, V}, C) -> C#config{captcha_protected = V};
+ ({allowinvites, V}, C) -> C#config{allow_user_invites = V};
+ ({allow_subscription, V}, C) -> C#config{allow_subscription = V};
+ ({passwordprotectedroom, V}, C) -> C#config{password_protected = V};
+ ({roomsecret, V}, C) -> C#config{password = V};
+ ({anonymous, V}, C) -> C#config{anonymous = V};
+ ({presencebroadcast, V}, C) -> C#config{presence_broadcast = V};
+ ({allow_voice_requests, V}, C) -> C#config{allow_voice_requests = V};
+ ({voice_request_min_interval, V}, C) ->
+ C#config{voice_request_min_interval = V};
+ ({whois, moderators}, C) -> C#config{anonymous = true};
+ ({whois, anyone}, C) -> C#config{anonymous = false};
+ ({maxusers, V}, C) -> C#config{max_users = V};
+ ({enablelogging, V}, C) -> C#config{logging = V};
+ ({captcha_whitelist, Js}, C) ->
+ LJIDs = [jid:tolower(J) || J <- Js],
+ C#config{captcha_whitelist = ?SETS:from_list(LJIDs)};
+ ({O, V} = Opt, C) ->
+ case ejabberd_hooks:run_fold(set_room_option,
+ ServerHost,
+ {0, undefined},
+ [Opt, Lang]) of
+ {0, undefined} ->
+ ?ERROR_MSG("set_room_option hook failed for "
+ "option '~s' with value ~p", [O, V]),
+ Txt = {<<"Failed to process option '~s'">>, [O]},
+ {error, xmpp:err_internal_server_error(Txt, Lang)};
+ {Pos, Val} ->
+ setelement(Pos, C, Val)
+ end
+ end, Config, Opts).
+-spec change_config(#config{}, state()) -> {result, undefined, state()}.
change_config(Config, StateData) ->
send_config_change_info(Config, StateData),
NSD = remove_subscriptions(StateData#state{config = Config}),
@@ -4214,57 +3322,59 @@ change_config(Config, StateData) ->
Config#config.members_only}
of
{false, true} ->
- NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
- _ -> {result, [], NSD}
+ NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1};
+ _ -> {result, undefined, NSD}
end.
+-spec send_config_change_info(#config{}, state()) -> ok.
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">>];
+ {false, true} -> [170];
+ {true, false} -> [171];
_ -> []
end
++
case {Old#config.anonymous, New#config.anonymous} of
- {true, false} -> [<<"172">>];
- {false, true} -> [<<"173">>];
+ {true, false} -> [172];
+ {false, true} -> [173];
_ -> []
end
++
case Old#config{anonymous = New#config.anonymous,
+ vcard = New#config.vcard,
logging = New#config.logging} of
New -> [];
- _ -> [<<"104">>]
+ _ -> [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_wrapped_multiple(StateData#state.jid,
+ if Codes /= [] ->
+ Message = #message{type = groupchat,
+ id = randoms:get_string(),
+ sub_els = [#muc_user{status_codes = Codes}]},
+ send_wrapped_multiple(StateData#state.jid,
get_users_and_subscribers(StateData),
- Message,
- ?NS_MUCSUB_NODES_CONFIG,
- StateData).
+ Message,
+ ?NS_MUCSUB_NODES_CONFIG,
+ StateData);
+ true ->
+ ok
+ end.
+-spec remove_nonmembers(state()) -> state().
remove_nonmembers(StateData) ->
lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
Affiliation = get_affiliation(JID, SD),
case Affiliation of
none ->
catch send_kickban_presence(<<"">>, JID, <<"">>,
- <<"322">>, SD),
+ 322, SD),
set_role(JID, none, SD);
_ -> SD
end
end,
StateData, (?DICT):to_list(get_users_and_subscribers(StateData))).
+-spec set_opts([{atom(), any()}], state()) -> state().
set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
NSD = case Opt of
@@ -4403,9 +3513,10 @@ set_opts([{Opt, Val} | Opts], StateData) ->
end,
set_opts(Opts, NSD).
--define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
-
+-define(MAKE_CONFIG_OPT(Opt),
+ {get_config_opt_name(Opt), element(Opt, Config)}).
+-spec make_opts(state()) -> [{atom(), any()}].
make_opts(StateData) ->
Config = StateData#state.config,
Subscribers = (?DICT):fold(
@@ -4414,28 +3525,29 @@ make_opts(StateData) ->
Sub#subscriber.nick,
Sub#subscriber.nodes}|Acc]
end, [], StateData#state.subscribers),
- [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description),
- ?MAKE_CONFIG_OPT(allow_change_subj),
- ?MAKE_CONFIG_OPT(allow_query_users),
- ?MAKE_CONFIG_OPT(allow_private_messages),
- ?MAKE_CONFIG_OPT(allow_private_messages_from_visitors),
- ?MAKE_CONFIG_OPT(allow_visitor_status),
- ?MAKE_CONFIG_OPT(allow_visitor_nickchange),
- ?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(captcha_protected),
- ?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(allow_subscription),
- ?MAKE_CONFIG_OPT(mam),
- ?MAKE_CONFIG_OPT(voice_request_min_interval),
- ?MAKE_CONFIG_OPT(vcard),
+ [?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description),
+ ?MAKE_CONFIG_OPT(#config.allow_change_subj),
+ ?MAKE_CONFIG_OPT(#config.allow_query_users),
+ ?MAKE_CONFIG_OPT(#config.allow_private_messages),
+ ?MAKE_CONFIG_OPT(#config.allow_private_messages_from_visitors),
+ ?MAKE_CONFIG_OPT(#config.allow_visitor_status),
+ ?MAKE_CONFIG_OPT(#config.allow_visitor_nickchange),
+ ?MAKE_CONFIG_OPT(#config.public), ?MAKE_CONFIG_OPT(#config.public_list),
+ ?MAKE_CONFIG_OPT(#config.persistent),
+ ?MAKE_CONFIG_OPT(#config.moderated),
+ ?MAKE_CONFIG_OPT(#config.members_by_default),
+ ?MAKE_CONFIG_OPT(#config.members_only),
+ ?MAKE_CONFIG_OPT(#config.allow_user_invites),
+ ?MAKE_CONFIG_OPT(#config.password_protected),
+ ?MAKE_CONFIG_OPT(#config.captcha_protected),
+ ?MAKE_CONFIG_OPT(#config.password), ?MAKE_CONFIG_OPT(#config.anonymous),
+ ?MAKE_CONFIG_OPT(#config.logging), ?MAKE_CONFIG_OPT(#config.max_users),
+ ?MAKE_CONFIG_OPT(#config.allow_voice_requests),
+ ?MAKE_CONFIG_OPT(#config.allow_subscription),
+ ?MAKE_CONFIG_OPT(#config.mam),
+ ?MAKE_CONFIG_OPT(#config.presence_broadcast),
+ ?MAKE_CONFIG_OPT(#config.voice_request_min_interval),
+ ?MAKE_CONFIG_OPT(#config.vcard),
{captcha_whitelist,
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
{affiliations,
@@ -4444,32 +3556,22 @@ make_opts(StateData) ->
{subject_author, StateData#state.subject_author},
{subscribers, Subscribers}].
+-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
destroy_room(DEl, StateData) ->
- lists:foreach(fun ({_LJID, Info}) ->
- Nick = Info#user.nick,
- ItemAttrs = [{<<"affiliation">>, <<"none">>},
- {<<"role">>, <<"none">>}],
- Packet = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs,
- children =
- []},
- DEl]}]},
- send_wrapped(jid:replace_resource(StateData#state.jid,
- Nick),
- Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_CONFIG, StateData)
- end,
+ Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER},
+ lists:foreach(
+ fun({_LJID, Info}) ->
+ Nick = Info#user.nick,
+ Item = #muc_item{affiliation = none,
+ role = none},
+ Packet = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{items = [Item],
+ destroy = Destroy}]},
+ send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
+ Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_CONFIG, StateData)
+ end,
(?DICT):to_list(get_users_and_subscribers(StateData))),
case (StateData#state.config)#config.persistent of
true ->
@@ -4477,191 +3579,163 @@ destroy_room(DEl, StateData) ->
StateData#state.host, StateData#state.room);
false -> ok
end,
- {result, [], stop}.
+ {result, undefined, stop}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
--define(FEATURE(Var),
- #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}],
- children = []}).
-
-define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
case Opt of
- true -> ?FEATURE(Fiftrue);
- false -> ?FEATURE(Fiffalse)
+ true -> Fiftrue;
+ false -> Fiffalse
end).
-process_iq_disco_info(_From, set, Lang, _StateData) ->
+-spec process_iq_disco_info(jid(), iq(), state()) ->
+ {result, disco_info()} | {error, stanza_error()}.
+process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_info(_From, get, Lang, StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
Config = StateData#state.config,
- {result,
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"conference">>},
- {<<"type">>, <<"text">>},
- {<<"name">>, get_title(StateData)}],
- children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUC}], children = []},
- ?CONFIG_OPT_TO_FEATURE((Config#config.public),
- <<"muc_public">>, <<"muc_hidden">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
- <<"muc_persistent">>, <<"muc_temporary">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
- <<"muc_membersonly">>, <<"muc_open">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
- <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
- <<"muc_moderated">>, <<"muc_unmoderated">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
- <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
- ++ case Config#config.allow_subscription of
- true -> [?FEATURE(?NS_MUCSUB)];
- false -> []
- end
- ++ 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}.
-
--define(RFIELDT(Type, Var, Val),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
--define(RFIELD(Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+ Feats = [?NS_VCARD, ?NS_MUC,
+ ?CONFIG_OPT_TO_FEATURE((Config#config.public),
+ <<"muc_public">>, <<"muc_hidden">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
+ <<"muc_persistent">>, <<"muc_temporary">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
+ <<"muc_membersonly">>, <<"muc_open">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
+ <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
+ <<"muc_moderated">>, <<"muc_unmoderated">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
+ <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
+ ++ case Config#config.allow_subscription of
+ true -> [?NS_MUCSUB];
+ false -> []
+ end
+ ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
+ Config#config.mam} of
+ {true, true} ->
+ [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
+ _ ->
+ []
+ end,
+ {result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)],
+ identities = [#identity{category = <<"conference">>,
+ type = <<"text">>,
+ name = get_title(StateData)}],
+ features = Feats}}.
+-spec iq_disco_info_extras(binary(), state()) -> xdata().
iq_disco_info_extras(Lang, StateData) ->
- Len = (?DICT):size(StateData#state.users),
- RoomDescription =
- (StateData#state.config)#config.description,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children =
- [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>,
- <<"http://jabber.org/protocol/muc#roominfo">>),
- ?RFIELD(<<"Room description">>,
- <<"muc#roominfo_description">>, RoomDescription),
- ?RFIELD(<<"Number of occupants">>,
- <<"muc#roominfo_occupants">>,
- (iolist_to_binary(integer_to_list(Len))))]}].
-
-process_iq_disco_items(_From, set, Lang, _StateData) ->
+ Fs = [{description, (StateData#state.config)#config.description},
+ {occupants, ?DICT:size(StateData#state.users)}],
+ #xdata{type = result,
+ fields = muc_roominfo:encode(
+ Fs, fun(T) -> translate:translate(Lang, T) end)}.
+
+-spec process_iq_disco_items(jid(), iq(), state()) ->
+ {error, stanza_error()} | {result, disco_items()}.
+process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_items(From, get, Lang, StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_items(From, #iq{type = get}, StateData) ->
case (StateData#state.config)#config.public_list of
true ->
- {result, get_mucroom_disco_items(StateData), StateData};
+ {result, get_mucroom_disco_items(StateData)};
_ ->
case is_occupant_or_admin(From, StateData) of
true ->
- {result, get_mucroom_disco_items(StateData), StateData};
+ {result, get_mucroom_disco_items(StateData)};
_ ->
- Txt = <<"Only occupants or administrators can perform this query">>,
- {error, ?ERRT_FORBIDDEN(Lang, Txt)}
+ %% If the list of occupants is private,
+ %% the room MUST return an empty <query/> element
+ %% (http://xmpp.org/extensions/xep-0045.html#disco-roomitems)
+ {result, #disco_items{}}
end
end.
-process_iq_captcha(_From, get, Lang, _SubEl,
- _StateData) ->
+-spec process_iq_captcha(jid(), iq(), state()) -> {error, stanza_error()} |
+ {result, undefined}.
+process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_captcha(_From, set, Lang, SubEl,
- StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+ _StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
- ok -> {result, [], StateData};
+ ok -> {result, undefined};
{error, malformed} ->
Txt = <<"Incorrect CAPTCHA submit">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+ {error, xmpp:err_bad_request(Txt, Lang)};
_ ->
Txt = <<"The CAPTCHA verification has failed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+ {error, xmpp:err_not_allowed(Txt, Lang)}
end.
-process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
+-spec process_iq_vcard(jid(), iq(), state()) ->
+ {result, vcard_temp() | xmlel()} |
+ {result, undefined, state()} |
+ {error, stanza_error()}.
+process_iq_vcard(_From, #iq{type = get}, StateData) ->
#state{config = #config{vcard = VCardRaw}} = StateData,
case fxml_stream:parse_element(VCardRaw) of
- #xmlel{children = VCardEls} ->
- {result, VCardEls, StateData};
+ #xmlel{} = VCard ->
+ {result, VCard};
{error, _} ->
- {result, [], StateData}
+ {error, xmpp:err_item_not_found()}
end;
-process_iq_vcard(From, set, Lang, SubEl, StateData) ->
+process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+ StateData) ->
case get_affiliation(From, StateData) of
owner ->
- VCardRaw = fxml:element_to_binary(SubEl),
+ VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)),
Config = StateData#state.config,
NewConfig = Config#config{vcard = VCardRaw},
change_config(NewConfig, StateData);
_ ->
ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end.
-
-process_iq_mucsub(From, Packet,
+ {error, xmpp:err_forbidden(ErrText, Lang)}
+ end.
+
+-spec process_iq_mucsub(jid(), iq(), state()) ->
+ {error, stanza_error()} |
+ {result, undefined | muc_subscribe(), state()} |
+ {ignore, state()}.
+process_iq_mucsub(_From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_subscribe{}]},
+ #state{config = #config{allow_subscription = false}}) ->
+ {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)};
+process_iq_mucsub(From,
#iq{type = set, lang = Lang,
- sub_el = #xmlel{name = <<"subscribe">>} = SubEl},
- #state{config = Config} = StateData) ->
- case fxml:get_tag_attr_s(<<"nick">>, SubEl) of
- <<"">> ->
- Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>),
- {error, Err};
- Nick when Config#config.allow_subscription ->
- LBareJID = jid:tolower(jid:remove_resource(From)),
- case (?DICT):find(LBareJID, StateData#state.subscribers) of
- {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick ->
- Nodes = get_subscription_nodes(Packet),
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(StateData#state.server_host,
- StateData#state.host,
- From, Nick)} of
- {true, _} ->
- ErrText = <<"That nickname is already in use by another occupant">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- {_, false} ->
- ErrText = <<"That nickname is registered by another person">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ ->
- NewStateData = set_subscriber(From, Nick, Nodes, StateData),
- {result, subscription_nodes_to_events(Nodes), NewStateData}
- end;
- {ok, #subscriber{}} ->
- Nodes = get_subscription_nodes(Packet),
+ sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
+ StateData) ->
+ LBareJID = jid:tolower(jid:remove_resource(From)),
+ case (?DICT):find(LBareJID, StateData#state.subscribers) of
+ {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick ->
+ Nodes = get_subscription_nodes(Packet),
+ case {nick_collision(From, Nick, StateData),
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick)} of
+ {true, _} ->
+ ErrText = <<"That nickname is already in use by another occupant">>,
+ {error, xmpp:err_conflict(ErrText, Lang)};
+ {_, false} ->
+ ErrText = <<"That nickname is registered by another person">>,
+ {error, xmpp:err_conflict(ErrText, Lang)};
+ _ ->
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
- {result, subscription_nodes_to_events(Nodes), NewStateData};
- error ->
- add_new_user(From, Nick, Packet, StateData)
+ {result, subscribe_result(Packet), NewStateData}
end;
- _ ->
- Err = ?ERRT_NOT_ALLOWED(Lang, <<"Subscriptions are not allowed">>),
- {error, Err}
+ {ok, #subscriber{}} ->
+ Nodes = get_subscription_nodes(Packet),
+ NewStateData = set_subscriber(From, Nick, Nodes, StateData),
+ {result, subscribe_result(Packet), NewStateData};
+ error ->
+ add_new_user(From, Nick, Packet, StateData)
end;
-process_iq_mucsub(From, _Packet,
- #iq{type = set,
- sub_el = #xmlel{name = <<"unsubscribe">>}},
+process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
case ?DICT:find(LBareJID, StateData#state.subscribers) of
@@ -4671,31 +3745,28 @@ process_iq_mucsub(From, _Packet,
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData),
- {result, [], NewStateData};
- error ->
- {result, [], StateData}
+ {result, undefined, NewStateData};
+ _ ->
+ {result, undefined, StateData}
end;
-process_iq_mucsub(From, _Packet,
- #iq{type = get, lang = Lang,
- sub_el = #xmlel{name = <<"subscriptions">>}},
+process_iq_mucsub(From, #iq{type = get, lang = Lang,
+ sub_els = [#muc_subscriptions{}]},
StateData) ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
- Subs = dict:fold(
+ JIDs = dict:fold(
fun(_, #subscriber{jid = J}, Acc) ->
- SJID = jid:to_string(J),
- [#xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>, SJID}]}|Acc]
+ [J|Acc]
end, [], StateData#state.subscribers),
- {result, Subs, StateData};
+ {result, #muc_subscriptions{list = JIDs}, StateData};
true ->
Txt = <<"Moderator privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, Txt)}
+ {error, xmpp:err_forbidden(Txt, Lang)}
end;
-process_iq_mucsub(_From, _Packet, #iq{lang = Lang}, _StateData) ->
- Txt = <<"Unrecognized subscription command">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
+process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}.
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
@@ -4705,41 +3776,32 @@ remove_subscriptions(StateData) ->
StateData
end.
-get_subscription_nodes(#xmlel{name = <<"iq">>} = Packet) ->
- case fxml:get_subtag_with_xmlns(Packet, <<"subscribe">>, ?NS_MUCSUB) of
- #xmlel{children = Els} ->
- lists:flatmap(
- fun(#xmlel{name = <<"event">>, attrs = Attrs}) ->
- Node = fxml:get_attr_s(<<"node">>, Attrs),
- case lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
- ?NS_MUCSUB_NODES_MESSAGES,
- ?NS_MUCSUB_NODES_AFFILIATIONS,
- ?NS_MUCSUB_NODES_SUBJECT,
- ?NS_MUCSUB_NODES_CONFIG,
- ?NS_MUCSUB_NODES_PARTICIPANTS]) of
- true ->
- [Node];
- false ->
- []
- end;
- (_) ->
- []
- end, Els);
- false ->
- []
- end;
+-spec get_subscription_nodes(iq()) -> [binary()].
+get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) ->
+ lists:filter(
+ fun(Node) ->
+ lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
+ ?NS_MUCSUB_NODES_MESSAGES,
+ ?NS_MUCSUB_NODES_AFFILIATIONS,
+ ?NS_MUCSUB_NODES_SUBJECT,
+ ?NS_MUCSUB_NODES_CONFIG,
+ ?NS_MUCSUB_NODES_PARTICIPANTS])
+ end, Nodes);
get_subscription_nodes(_) ->
[].
-subscription_nodes_to_events(Nodes) ->
- [#xmlel{name = <<"event">>, attrs = [{<<"node">>, Node}]} || Node <- Nodes].
+-spec subscribe_result(iq()) -> muc_subscribe().
+subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) ->
+ #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}.
+-spec get_title(state()) -> binary().
get_title(StateData) ->
case (StateData#state.config)#config.title of
<<"">> -> StateData#state.room;
Name -> Name
end.
+-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false.
get_roomdesc_reply(JID, StateData, Tail) ->
IsOccupantOrAdmin = is_occupant_or_admin(JID,
StateData),
@@ -4753,378 +3815,132 @@ get_roomdesc_reply(JID, StateData, Tail) ->
true -> false
end.
+-spec get_roomdesc_tail(state(), binary()) -> binary().
get_roomdesc_tail(StateData, Lang) ->
Desc = case (StateData#state.config)#config.public of
true -> <<"">>;
_ -> translate:translate(Lang, <<"private, ">>)
end,
- Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0,
- StateData#state.users),
- <<" (", Desc/binary,
- (iolist_to_binary(integer_to_list(Len)))/binary, ")">>.
+ Len = (?DICT):size(StateData#state.users),
+ <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>.
+-spec get_mucroom_disco_items(state()) -> disco_items().
get_mucroom_disco_items(StateData) ->
- lists:map(fun ({_LJID, Info}) ->
+ Items = lists:map(
+ fun({_LJID, Info}) ->
Nick = Info#user.nick,
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string({StateData#state.room,
- StateData#state.host,
- Nick})},
- {<<"name">>, Nick}],
- children = []}
+ #disco_item{jid = jid:make(StateData#state.room,
+ StateData#state.host,
+ Nick),
+ name = Nick}
end,
- (?DICT):to_list(StateData#state.users)).
+ (?DICT):to_list(StateData#state.users)),
+ #disco_items{items = Items}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Voice request support
-is_voice_request(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_ | _] = Fields ->
- case {lists:keysearch(<<"FORM_TYPE">>, 1,
- Fields),
- lists:keysearch(<<"muc#role">>, 1,
- Fields)}
- of
- {{value,
- {_,
- [<<"http://jabber.org/protocol/muc#request">>]}},
- {value, {_, [<<"participant">>]}}} ->
- true;
- _ -> false
- end;
- _ -> false
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
-
+-spec prepare_request_form(jid(), binary(), binary()) -> message().
prepare_request_form(Requester, Nick, Lang) ->
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"normal">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Voice request">>)}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Either approve or decline the voice "
- "request.">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"http://jabber.org/protocol/muc#request">>}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"muc#role">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"participant">>}]}]},
- ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
- (jid:to_string(Requester))),
- ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>,
- Nick),
- ?BOOLXFIELD(<<"Grant voice to this person?">>,
- <<"muc#request_allow">>,
- (jlib:binary_to_atom(<<"false">>)))]}]}.
-
-send_voice_request(From, StateData) ->
+ Title = translate:translate(Lang, <<"Voice request">>),
+ Instruction = translate:translate(
+ Lang, <<"Either approve or decline the voice request.">>),
+ Fs = muc_request:encode([{role, participant},
+ {jid, Requester},
+ {roomnick, Nick},
+ {request_allow, false}],
+ fun(T) -> translate:translate(Lang, T) end),
+ #message{type = normal,
+ sub_els = [#xdata{type = form,
+ title = Title,
+ instructions = [Instruction],
+ fields = Fs}]}.
+
+-spec send_voice_request(jid(), binary(), state()) -> ok.
+send_voice_request(From, Lang, StateData) ->
Moderators = search_role(moderator, StateData),
FromNick = find_nick_by_jid(From, StateData),
lists:foreach(fun ({_, User}) ->
ejabberd_router:route(
StateData#state.jid, User#user.jid,
- prepare_request_form(From, FromNick, <<"">>))
+ prepare_request_form(From, FromNick, Lang))
end,
Moderators).
-is_voice_approvement(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_ | _] = Fs ->
- case {lists:keysearch(<<"FORM_TYPE">>, 1,
- Fs),
- lists:keysearch(<<"muc#role">>, 1,
- Fs),
- lists:keysearch(<<"muc#request_allow">>,
- 1, Fs)}
- of
- {{value,
- {_,
- [<<"http://jabber.org/protocol/muc#request">>]}},
- {value, {_, [<<"participant">>]}},
- {value, {_, [Flag]}}}
- when Flag == <<"true">>;
- Flag == <<"1">> ->
- true;
- _ -> false
- end;
- _ -> false
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
-
-extract_jid_from_voice_approvement(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) ->
- Fields = case jlib:parse_xdata_submit(El) of
- invalid -> [];
- Res -> Res
- end,
- lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) ->
- case jid:from_string(JIDStr) of
- error -> error;
- J -> {ok, J}
- end;
- (_, Acc) -> Acc
- end,
- error, Fields);
- (_, Acc) -> Acc
- end,
- error, Els).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Invitation support
-is_invitation(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC_USER ->
- case fxml:get_subtag(El, <<"invite">>) of
- false -> false;
- _ -> true
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
-
-process_invitees(Invetees, StateDataIni) ->
- lists:foldl(
- fun(IJID, StateData) ->
- case get_affiliation(IJID, StateData) of
- none ->
- NSD = set_affiliation(IJID, member,
- StateData),
- send_affiliation(IJID, member,
- StateData),
- NSD;
- _ -> StateData
- end
- end,
- StateDataIni,
- Invetees).
-
-check_invitation(From, Packet, Lang, StateData) ->
+-spec check_invitation(jid(), muc_invite(), binary(), state()) -> {error, stanza_error()} | jid().
+check_invitation(From, Invitation, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
- CanInvite =
- (StateData#state.config)#config.allow_user_invites
- orelse
- FAffiliation == admin orelse FAffiliation == owner,
-
- InviteEls = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
- false ->
- Txt1 = <<"No 'x' element found">>,
- throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)});
- XEl ->
- case fxml:get_subtags(XEl, <<"invite">>) of
- false ->
- Txt2 = <<"No 'invite' element found">>,
- throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
- InviteEl1 ->
- InviteEl1
- end
- end,
+ CanInvite = (StateData#state.config)#config.allow_user_invites
+ orelse
+ FAffiliation == admin orelse FAffiliation == owner,
case CanInvite of
- false ->
- Txt3 = <<"Invitations are not allowed in this conference">>,
- throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
- true ->
- process_invitations(From, InviteEls, Lang, StateData)
- end.
-
-process_invitations(From, InviteEls, Lang, StateData) ->
- lists:map(
- fun(InviteEl) ->
- Reason = fxml:get_path_s(InviteEl,
- [{elem, <<"reason">>}, cdata]),
- ContinueEl = case fxml:get_path_s(InviteEl,
- [{elem, <<"continue">>}])
- of
- <<>> -> [];
- Continue1 -> [Continue1]
+ false ->
+ Txt = <<"Invitations are not allowed in this conference">>,
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+ true ->
+ #muc_invite{to = JID, reason = Reason} = Invitation,
+ Invite = Invitation#muc_invite{to = undefined, from = From},
+ Password = case (StateData#state.config)#config.password_protected of
+ true ->
+ (StateData#state.config)#config.password;
+ false ->
+ undefined
end,
- IEl = [#xmlel{name = <<"invite">>,
- attrs = [{<<"from">>, jid:to_string(From)}],
- children =
- [#xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, Reason}]}]
- ++ ContinueEl}],
- PasswdEl = case
- (StateData#state.config)#config.password_protected
- of
- true ->
- [#xmlel{name = <<"password">>, attrs = [],
- children =
- [{xmlcdata,
- (StateData#state.config)#config.password}]}];
- _ -> []
- end,
- Body = #xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- [io_lib:format(
- translate:translate(
- Lang,
- <<"~s invites you to the room ~s">>),
- [jid:to_string(From),
- jid:to_string({StateData#state.room,
- StateData#state.host,
- <<"">>})]),
- case
- (StateData#state.config)#config.password_protected
- of
- true ->
- <<", ",
- (translate:translate(Lang,
- <<"the password is">>))/binary,
- " '",
- ((StateData#state.config)#config.password)/binary,
- "'">>;
- _ -> <<"">>
- end
- ,
- case Reason of
- <<"">> -> <<"">>;
- _ -> <<" (", Reason/binary, ") ">>
- end])}]},
- Msg = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"normal">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children = IEl ++ PasswdEl},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XCONFERENCE},
- {<<"jid">>,
- jid:to_string({StateData#state.room,
- StateData#state.host,
- <<"">>})}],
- children = [{xmlcdata, Reason}]},
- Body]},
- JID = case
- jid:from_string(fxml:get_tag_attr_s(<<"to">>,
- InviteEl))
- of
- error ->
- Txt = <<"Incorrect value of 'to' attribute">>,
- throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
- JID1 -> JID1
- end,
- ejabberd_hooks:run(muc_invite, StateData#state.server_host,
- [StateData#state.jid, StateData#state.config, From, JID, Reason]),
- ejabberd_router:route(StateData#state.jid, JID, Msg),
- JID
- end,
- InviteEls).
+ XUser = #muc_user{password = Password, invites = [Invite]},
+ XConference = #x_conference{jid = jid:make(StateData#state.room,
+ StateData#state.host),
+ reason = Reason},
+ Body = iolist_to_binary(
+ [io_lib:format(
+ translate:translate(
+ Lang,
+ <<"~s invites you to the room ~s">>),
+ [jid:to_string(From),
+ jid:to_string({StateData#state.room,
+ StateData#state.host,
+ <<"">>})]),
+ case (StateData#state.config)#config.password_protected of
+ true ->
+ <<", ",
+ (translate:translate(
+ Lang, <<"the password is">>))/binary,
+ " '",
+ ((StateData#state.config)#config.password)/binary,
+ "'">>;
+ _ -> <<"">>
+ end,
+ case Reason of
+ <<"">> -> <<"">>;
+ _ -> <<" (", Reason/binary, ") ">>
+ end]),
+ Msg = #message{type = normal,
+ body = xmpp:mk_text(Body),
+ sub_els = [XUser, XConference]},
+ ejabberd_router:route(StateData#state.jid, JID, Msg),
+ JID
+ end.
%% Handle a message sent to the room by a non-participant.
%% If it is a decline, send to the inviter.
%% Otherwise, an error message is sent to the sender.
-handle_roommessage_from_nonparticipant(Packet, Lang,
- StateData, From) ->
- case catch check_decline_invitation(Packet) of
- {true, Decline_data} ->
- send_decline_invitation(Decline_data,
- StateData#state.jid, From);
- _ ->
- send_error_only_occupants(Packet, Lang,
- StateData#state.jid, From)
+-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok.
+handle_roommessage_from_nonparticipant(Packet, StateData, From) ->
+ case xmpp:get_subtag(Packet, #muc_user{}) of
+ #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser ->
+ NewDecline = Decline#muc_decline{to = undefined, from = From},
+ NewXUser = XUser#muc_user{decline = NewDecline},
+ NewPacket = xmpp:set_subtag(Packet, NewXUser),
+ ejabberd_router:route(StateData#state.jid, To, NewPacket);
+ _ ->
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err)
end.
-%% Check in the packet is a decline.
-%% If so, also returns the splitted packet.
-%% 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 = 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.
-%% The original stanza must be slightly modified.
-send_decline_invitation({Packet, XEl, DEl, ToJID},
- RoomJID, FromJID) ->
- FromString =
- jid:to_string(jid:remove_resource(FromJID)),
- #xmlel{name = <<"decline">>, attrs = DAttrs,
- children = DEls} =
- DEl,
- DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
- DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
- DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3,
- children = DEls},
- XEl2 = replace_subelement(XEl, DEl2),
- Packet2 = replace_subelement(Packet, XEl2),
- ejabberd_router:route(RoomJID, ToJID, Packet2).
-
-%% 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},
- NewSubEl) ->
- {_, NameNewSubEl, _, _} = NewSubEl,
- SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
- #xmlel{name = Name, attrs = Attrs, children = SubEls2}.
-
-send_error_only_occupants(Packet, Lang, RoomJID, From) ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(RoomJID, From, Err).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
@@ -5145,6 +3961,7 @@ add_to_log(Type, Data, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Users number checking
+-spec tab_add_online_user(jid(), state()) -> ok.
tab_add_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5152,8 +3969,10 @@ tab_add_online_user(JID, StateData) ->
Host = StateData#state.host,
catch ets:insert(muc_online_users,
#muc_online_users{us = US, resource = LResource,
- room = Room, host = Host}).
+ room = Room, host = Host}),
+ ok.
+-spec tab_remove_online_user(jid(), state()) -> ok.
tab_remove_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5161,8 +3980,10 @@ tab_remove_online_user(JID, StateData) ->
Host = StateData#state.host,
catch ets:delete_object(muc_online_users,
#muc_online_users{us = US, resource = LResource,
- room = Room, host = Host}).
+ room = Room, host = Host}),
+ ok.
+-spec tab_count_user(jid()) -> non_neg_integer().
tab_count_user(JID) ->
{LUser, LServer, _} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5173,9 +3994,11 @@ tab_count_user(JID) ->
_ -> 0
end.
+-spec element_size(stanza()) -> non_neg_integer().
element_size(El) ->
- byte_size(fxml:element_to_binary(El)).
+ byte_size(fxml:element_to_binary(xmpp:encode(El, ?NS_CLIENT))).
+-spec store_room(state()) -> ok.
store_room(StateData) ->
if (StateData#state.config)#config.persistent ->
mod_muc:store_room(StateData#state.server_host,
@@ -5185,6 +4008,7 @@ store_room(StateData) ->
ok
end.
+-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
send_wrapped(From, To, Packet, Node, State) ->
LTo = jid:tolower(To),
LBareTo = jid:tolower(jid:remove_resource(To)),
@@ -5196,41 +4020,37 @@ send_wrapped(From, To, Packet, Node, State) ->
if IsOffline ->
case ?DICT:find(LBareTo, State#state.subscribers) of
{ok, #subscriber{nodes = Nodes, jid = JID}} ->
- case lists:member(Node, Nodes) of
- true ->
+ case lists:member(Node, Nodes) of
+ true ->
NewPacket = wrap(From, JID, Packet, Node),
ejabberd_router:route(State#state.jid, JID, NewPacket);
- false ->
- ok
- end;
- _ ->
+ false ->
+ ok
+ end;
+ _ ->
ok
end;
true ->
ejabberd_router:route(From, To, Packet)
end.
+-spec wrap(jid(), jid(), stanza(), binary()) -> message().
wrap(From, To, Packet, Node) ->
- Pkt1 = jlib:replace_from_to(From, To, Packet),
- Pkt2 = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"xmlns">>, Pkt1),
- Pkt3 = Pkt2#xmlel{attrs = [{<<"xmlns">>, <<"jabber:client">>}|Attrs]},
- Item = #xmlel{name = <<"item">>,
- attrs = [{<<"id">>, randoms:get_string()}],
- children = [Pkt3]},
- Items = #xmlel{name = <<"items">>, attrs = [{<<"node">>, Node}],
- children = [Item]},
- Event = #xmlel{name = <<"event">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
- children = [Items]},
- #xmlel{name = <<"message">>, children = [Event]}.
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Multicast
-
+ El = xmpp:encode(xmpp:set_from_to(Packet, From, To)),
+ #message{
+ sub_els = [#ps_event{
+ items = #ps_items{
+ node = Node,
+ items = [#ps_item{
+ id = randoms:get_string(),
+ xml_els = [El]}]}}]}.
+
+%% -spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok.
%% send_multiple(From, Server, Users, Packet) ->
%% JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
%% ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
+-spec send_wrapped_multiple(jid(), [#user{}], stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
lists:foreach(
fun({_, #user{jid = To}}) ->
@@ -5239,10 +4059,6 @@ send_wrapped_multiple(From, Users, Packet, Node, State) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaninful content
-
-has_body_or_subject(Packet) ->
- [] /= lists:dropwhile(fun
- (#xmlel{name = <<"body">>}) -> false;
- (#xmlel{name = <<"subject">>}) -> false;
- (_) -> true
- end, Packet#xmlel.children).
+-spec has_body_or_subject(message()) -> boolean().
+has_body_or_subject(#message{body = Body, subject = Subj}) ->
+ Body /= [] orelse Subj /= [].
diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl
index ff7ec1ebb..4b6be7d06 100644
--- a/src/mod_muc_sql.erl
+++ b/src/mod_muc_sql.erl
@@ -11,13 +11,16 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_muc).
+-behaviour(mod_muc_room).
%% API
-export([init/2, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4,
- import/1, import/2, export/1]).
+ import/3, export/1]).
+-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
+ get_affiliations/3, search_affiliation/4]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("mod_muc.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -127,6 +130,21 @@ set_nick(LServer, Host, From, Nick) ->
end,
ejabberd_sql:sql_transaction(LServer, F).
+set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) ->
+ {error, not_implemented}.
+
+set_affiliations(_ServerHost, _Room, _Host, _Affiliations) ->
+ {error, not_implemented}.
+
+get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) ->
+ {error, not_implemented}.
+
+get_affiliations(_ServerHost, _Room, _Host) ->
+ {error, not_implemented}.
+
+search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
+ {error, not_implemented}.
+
export(_Server) ->
[{muc_room,
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
@@ -158,21 +176,8 @@ export(_Server) ->
end
end}].
-import(_LServer) ->
- [{<<"select name, host, opts from muc_room;">>,
- fun([Name, RoomHost, SOpts]) ->
- Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
- #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} = jid:from_string(J),
- #muc_registered{us_host = {{U, S}, RoomHost},
- nick = Nick}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index df385c28c..fbd2402ee 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -45,16 +45,20 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(state,
{lserver, lservice, access, service_limits}).
+-type state() :: #state{}.
-record(multicastc, {rserver, response, ts}).
%% ts: timestamp (in seconds) when the cache item was last updated
--record(dest, {jid_string, jid_jid, type, full_xml}).
+-record(dest, {jid_string = none :: binary(),
+ jid_jid :: jid(),
+ type :: atom(),
+ full_xml :: address()}).
%% jid_string = string()
%% jid_jid = jid()
@@ -168,10 +172,8 @@ handle_cast(_Msg, State) -> {noreply, State}.
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
-handle_info({route, From, To,
- #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
- State) ->
- case catch handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) of
+handle_info({route, From, To, #iq{} = Packet}, State) ->
+ case catch handle_iq(From, To, Packet, State) of
{'EXIT', Reason} ->
?ERROR_MSG("Error when processing IQ stanza: ~p",
[Reason]);
@@ -179,13 +181,10 @@ handle_info({route, From, To,
end,
{noreply, State};
%% XEP33 allows only 'message' and 'presence' stanza type
-handle_info({route, From, To,
- #xmlel{name = Stanza_type} = Packet},
+handle_info({route, From, To, Packet},
#state{lservice = LServiceS, lserver = LServerS,
access = Access, service_limits = SLimits} =
- State)
- when (Stanza_type == <<"message">>) or
- (Stanza_type == <<"presence">>) ->
+ State) when ?is_stanza(Packet) ->
route_untrusted(LServiceS, LServerS, Access, SLimits,
From, To, Packet),
{noreply, State};
@@ -220,91 +219,58 @@ 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
+handle_iq(From, To, Packet, State) ->
+ try
+ IQ = xmpp:decode_els(Packet),
+ case process_iq(From, IQ, State) of
+ {result, SubEl} ->
+ ejabberd_router:route(To, From, xmpp:make_iq_result(Packet, SubEl));
+ {error, Error} ->
+ ejabberd_router:route_error(To, From, Packet, Error);
+ reply ->
+ LServiceS = jid:to_string(To),
+ case Packet#iq.type of
+ result ->
+ process_iqreply_result(From, LServiceS, IQ);
+ error ->
+ process_iqreply_error(From, LServiceS, IQ)
+ end
+ end
+ catch _:{xmpp_codec, Why} ->
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_bad_request(xmpp:format_error(Why), Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
end.
-process_iq(From,
- #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
- IQ,
- State) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
- children = iq_disco_info(From, Lang, State)}]};
-%% disco#items request
-process_iq(_,
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
- children = []}]};
-%% vCard request
-process_iq(_,
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ,
- _) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_vcard(Lang)}]};
-%% Unknown "set" or "get" request
-process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
- when Type == get; Type == set ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
-%% IQ "result" or "error".
-process_iq(_, reply, _) -> reply;
-%% IQ "result" or "error".
-process_iq(_, _, _) -> ok.
-
--define(FEATURE(Feat),
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}], children = []}).
+-spec process_iq(jid(), iq(), state()) -> {result, xmpp_element()} |
+ {error, stanza_error()} | reply.
+process_iq(From, #iq{type = get, lang = Lang,
+ sub_els = [#disco_info{}]}, State) ->
+ {result, iq_disco_info(From, Lang, State)};
+process_iq(_, #iq{type = get, sub_els = [#disco_items{}]}, _) ->
+ {result, #disco_items{}};
+process_iq(_, #iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]}, _) ->
+ {result, iq_vcard(Lang)};
+process_iq(_, #iq{type = T}, _) when T == set; T == get ->
+ {error, xmpp:err_service_unavailable()};
+process_iq(_, _, _) ->
+ reply.
+
+-define(FEATURE(Feat), Feat).
iq_disco_info(From, Lang, State) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"service">>},
- {<<"type">>, <<"multicast">>},
- {<<"name">>,
- translate:translate(Lang, <<"Multicast">>)}],
- children = []},
- ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_DISCO_ITEMS)),
- ?FEATURE((?NS_VCARD)), ?FEATURE((?NS_ADDRESS))]
- ++ iq_disco_info_extras(From, State).
+ #disco_info{
+ identities = [#identity{category = <<"service">>,
+ type = <<"multicast">>,
+ name = translate:translate(Lang, <<"Multicast">>)}],
+ features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_VCARD, ?NS_ADDRESS],
+ xdata = iq_disco_info_extras(From, State)}.
iq_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_multicast">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd Multicast service">>))/binary,
- "\nCopyright (c) 2002-2016 ProcessOne">>}]}].
+ Desc = translate:translate(Lang, <<"ejabberd Multicast service">>),
+ #vcard_temp{fn = <<"ejabberd/mod_multicast">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>}.
%%%-------------------------
%%% Route
@@ -313,19 +279,14 @@ iq_vcard(Lang) ->
route_trusted(LServiceS, LServerS, FromJID,
Destinations, Packet) ->
Packet_stripped = Packet,
- AAttrs = [{<<"xmlns">>, ?NS_ADDRESS}],
+ AAttrs = [],
Delivereds = [],
- Dests2 = lists:map(fun (D) ->
- DS = jts(D),
- XML = #xmlel{name = <<"address">>,
- attrs =
- [{<<"type">>, <<"bcc">>},
- {<<"jid">>, DS}],
- children = []},
- #dest{jid_string = DS, jid_jid = D,
- type = <<"bcc">>, full_xml = XML}
- end,
- Destinations),
+ Dests2 = lists:map(
+ fun(D) ->
+ #dest{jid_string = jid:to_string(D),
+ jid_jid = D, type = bcc,
+ full_xml = #address{type = bcc, jid = D}}
+ end, Destinations),
Groups = group_dests(Dests2),
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped, AAttrs).
@@ -363,20 +324,19 @@ route_untrusted(LServiceS, LServerS, Access, SLimits,
route_untrusted2(LServiceS, LServerS, Access, SLimits,
FromJID, Packet) ->
ok = check_access(LServerS, Access, FromJID),
- {ok, Packet_stripped, AAttrs, Addresses} =
- strip_addresses_element(Packet),
- {To_deliver, Delivereds} =
- split_addresses_todeliver(Addresses),
+ {ok, Packet_stripped, Addresses} = strip_addresses_element(Packet),
+ {To_deliver, Delivereds} = split_addresses_todeliver(Addresses),
Dests = convert_dest_record(To_deliver),
{Dests2, Not_jids} = split_dests_jid(Dests),
report_not_jid(FromJID, Packet, Not_jids),
- ok = check_limit_dests(SLimits, FromJID, Packet,
- Dests2),
+ ok = check_limit_dests(SLimits, FromJID, Packet, Dests2),
Groups = group_dests(Dests2),
ok = check_relay(FromJID#jid.server, LServerS, Groups),
route_common(LServerS, LServiceS, FromJID, Groups,
- Delivereds, Packet_stripped, AAttrs).
+ Delivereds, Packet_stripped, []).
+-spec route_common(binary(), binary(), jid(), [#group{}],
+ [address()], stanza(), list()) -> any().
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped, AAttrs) ->
Groups2 = look_cached_servers(LServerS, Groups),
@@ -435,52 +395,39 @@ check_access(LServerS, Access, From) ->
%%% Strip 'addresses' XML element
%%%-------------------------
+-spec strip_addresses_element(stanza()) -> {ok, stanza(), [address()]}.
strip_addresses_element(Packet) ->
- case fxml:get_subtag(Packet, <<"addresses">>) of
- #xmlel{name = <<"addresses">>, attrs = AAttrs,
- children = Addresses} ->
- 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, fxml:remove_cdata(Addresses)};
- _ -> throw(ewxmlns)
- end;
- _ -> throw(eadsele)
+ case xmpp:get_subtag(Packet, #addresses{}) of
+ #addresses{list = Addrs} ->
+ PacketStripped = xmpp:remove_subtag(Packet, #addresses{}),
+ {ok, PacketStripped, Addrs};
+ undefined ->
+ throw(eadsele)
end.
%%%-------------------------
%%% Split Addresses
%%%-------------------------
+-spec split_addresses_todeliver([address()]) -> {[address()], [address()]}.
split_addresses_todeliver(Addresses) ->
- lists:partition(fun (XML) ->
- case XML of
- #xmlel{name = <<"address">>, attrs = Attrs} ->
- case fxml:get_attr_s(<<"delivered">>, Attrs) of
- <<"true">> -> false;
- _ ->
- Type = fxml:get_attr_s(<<"type">>,
- Attrs),
- case Type of
- <<"to">> -> true;
- <<"cc">> -> true;
- <<"bcc">> -> true;
- _ -> false
- end
- end;
- _ -> false
- end
- end,
- Addresses).
+ lists:partition(
+ fun(#address{delivered = true}) ->
+ false;
+ (#address{type = Type}) ->
+ case Type of
+ to -> true;
+ cc -> true;
+ bcc -> true;
+ _ -> false
+ end
+ end, Addresses).
%%%-------------------------
%%% Check does not exceed limit of destinations
%%%-------------------------
+-spec check_limit_dests(_, jid(), stanza(), [address()]) -> ok.
check_limit_dests(SLimits, FromJID, Packet,
Addresses) ->
SenderT = sender_type(FromJID),
@@ -497,24 +444,22 @@ check_limit_dests(SLimits, FromJID, Packet,
%%% Convert Destination XML to record
%%%-------------------------
-convert_dest_record(XMLs) ->
- lists:map(fun (XML) ->
- case fxml:get_tag_attr_s(<<"jid">>, XML) of
- <<"">> -> #dest{jid_string = none, full_xml = XML};
- JIDS ->
- Type = fxml:get_tag_attr_s(<<"type">>, XML),
- JIDJ = stj(JIDS),
- #dest{jid_string = JIDS, jid_jid = JIDJ,
- type = Type, full_xml = XML}
- end
- end,
- XMLs).
+-spec convert_dest_record([address()]) -> [#dest{}].
+convert_dest_record(Addrs) ->
+ lists:map(
+ fun(#address{jid = undefined} = Addr) ->
+ #dest{jid_string = none, full_xml = Addr};
+ (#address{jid = JID, type = Type} = Addr) ->
+ #dest{jid_string = jid:to_string(JID), jid_jid = JID,
+ type = Type, full_xml = Addr}
+ end, Addrs).
%%%-------------------------
%%% Split destinations by existence of JID
%%% and send error messages for other dests
%%%-------------------------
+-spec split_dests_jid([#dest{}]) -> {[#dest{}], [#dest{}]}.
split_dests_jid(Dests) ->
lists:partition(fun (Dest) ->
case Dest#dest.jid_string of
@@ -524,8 +469,9 @@ split_dests_jid(Dests) ->
end,
Dests).
+-spec report_not_jid(jid(), stanza(), #dest{}) -> any().
report_not_jid(From, Packet, Dests) ->
- Dests2 = [fxml:element_to_binary(Dest#dest.full_xml)
+ Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.full_xml))
|| Dest <- Dests],
[route_error(From, From, Packet, jid_malformed,
<<"This service can not process the address: ",
@@ -536,6 +482,7 @@ report_not_jid(From, Packet, Dests) ->
%%% Group destinations by their servers
%%%-------------------------
+-spec group_dests([#dest{}]) -> [#group{}].
group_dests(Dests) ->
D = lists:foldl(fun (Dest, Dict) ->
ServerS = (Dest#dest.jid_jid)#jid.server,
@@ -575,18 +522,17 @@ build_other_xml(Dests) ->
lists:foldl(fun (Dest, R) ->
XML = Dest#dest.full_xml,
case Dest#dest.type of
- <<"to">> -> [add_delivered(XML) | R];
- <<"cc">> -> [add_delivered(XML) | R];
- <<"bcc">> -> R;
+ to -> [add_delivered(XML) | R];
+ cc -> [add_delivered(XML) | R];
+ bcc -> R;
_ -> [XML | R]
end
end,
[], Dests).
-add_delivered(#xmlel{name = Name, attrs = Attrs,
- children = Els}) ->
- Attrs2 = [{<<"delivered">>, <<"true">>} | Attrs],
- #xmlel{name = Name, attrs = Attrs2, children = Els}.
+-spec add_delivered(address()) -> address().
+add_delivered(Addr) ->
+ Addr#address{delivered = true}.
%%%-------------------------
%%% Add preliminary packets
@@ -636,7 +582,7 @@ decide_action_group(Group) ->
route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) ->
Dests = case ToDest#dest.type of
- <<"bcc">> -> [];
+ bcc -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
@@ -652,20 +598,20 @@ route_packet_multicast(From, ToS, Packet, AAttrs, Dests,
Addresses)
|| DFragment <- Fragmented_dests].
-route_packet2(From, ToS, Dests, Packet, AAttrs,
+-spec route_packet2(jid(), binary(), [#dest{}], stanza(), list(), [address()]) -> ok.
+route_packet2(From, ToS, Dests, Packet, _AAttrs,
Addresses) ->
- #xmlel{name = T, attrs = A, children = C} = Packet,
- C2 = case append_dests(Dests, Addresses) of
- [] -> C;
- ACs ->
- [#xmlel{name = <<"addresses">>, attrs = AAttrs,
- children = ACs}
- | C]
- end,
- Packet2 = #xmlel{name = T, attrs = A, children = C2},
+ Els = case append_dests(Dests, Addresses) of
+ [] ->
+ xmpp:get_els(Packet);
+ ACs ->
+ [#addresses{list = ACs}|xmpp:get_els(Packet)]
+ end,
+ Packet2 = xmpp:set_els(Packet, Els),
ToJID = stj(ToS),
ejabberd_router:route(From, ToJID, Packet2).
+-spec append_dests([#dest{}], {[address()], [address()]} | [address()]) -> [address()].
append_dests(_Dests, {Others, Addresses}) ->
Addresses++Others;
append_dests([], Addresses) -> Addresses;
@@ -676,12 +622,14 @@ append_dests([Dest | Dests], Addresses) ->
%%% Check relay
%%%-------------------------
+-spec check_relay(binary(), binary(), [#group{}]) -> ok.
check_relay(RS, LS, Gs) ->
case check_relay_required(RS, LS, Gs) of
false -> ok;
true -> throw(edrelay)
end.
+-spec check_relay_required(binary(), binary(), [#group{}]) -> boolean().
check_relay_required(RServer, LServerS, Groups) ->
case lists:suffix(str:tokens(LServerS, <<".">>),
str:tokens(RServer, <<".">>)) of
@@ -689,6 +637,7 @@ check_relay_required(RServer, LServerS, Groups) ->
false -> check_relay_required(LServerS, Groups)
end.
+-spec check_relay_required(binary(), [#group{}]) -> boolean().
check_relay_required(LServerS, Groups) ->
lists:any(fun (Group) -> Group#group.server /= LServerS
end,
@@ -701,19 +650,16 @@ check_relay_required(LServerS, Groups) ->
send_query_info(RServerS, LServiceS) ->
case str:str(RServerS, <<"echo.">>) of
1 -> false;
- _ -> send_query(RServerS, LServiceS, ?NS_DISCO_INFO)
+ _ -> send_query(RServerS, LServiceS, #disco_info{})
end.
send_query_items(RServerS, LServiceS) ->
- send_query(RServerS, LServiceS, ?NS_DISCO_ITEMS).
-
-send_query(RServerS, LServiceS, XMLNS) ->
- Packet = #xmlel{name = <<"iq">>,
- attrs = [{<<"to">>, RServerS}, {<<"type">>, <<"get">>}],
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = []}]},
+ send_query(RServerS, LServiceS, #disco_items{}).
+
+-spec send_query(binary(), binary(), [disco_info()|disco_items()]) -> ok.
+send_query(RServerS, LServiceS, SubEl) ->
+ Packet = #iq{id = randoms:get_string(),
+ type = get, sub_els = [SubEl]},
ejabberd_router:route(stj(LServiceS), stj(RServerS),
Packet).
@@ -733,49 +679,40 @@ process_iqreply_error(From, LServiceS, _Packet) ->
%%% Check protocol support: Receive response: Disco
%%%-------------------------
-process_iqreply_result(From, LServiceS, Packet, State) ->
- #xmlel{name = <<"query">>, attrs = Attrs2,
- children = Els2} =
- 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 ->
- process_discoitems_result(From, LServiceS, Els2)
+-spec process_iqreply_result(jid(), binary(), iq()) -> any().
+process_iqreply_result(From, LServiceS, #iq{sub_els = [SubEl]}) ->
+ case SubEl of
+ #disco_info{} ->
+ process_discoinfo_result(From, LServiceS, SubEl);
+ #disco_items{} ->
+ process_discoitems_result(From, LServiceS, SubEl);
+ _ ->
+ ok
end.
%%%-------------------------
%%% Check protocol support: Receive response: Disco Info
%%%-------------------------
-process_discoinfo_result(From, LServiceS, Els,
- _State) ->
+process_discoinfo_result(From, LServiceS, DiscoInfo) ->
FromS = jts(From),
case search_waiter(FromS, LServiceS, info) of
{found_waiter, Waiter} ->
- process_discoinfo_result2(From, FromS, LServiceS, Els,
+ process_discoinfo_result2(From, FromS, LServiceS, DiscoInfo,
Waiter);
_ -> ok
end.
-process_discoinfo_result2(From, FromS, LServiceS, Els,
+process_discoinfo_result2(From, FromS, LServiceS,
+ #disco_info{features = Feats} = DiscoInfo,
Waiter) ->
- 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),
+ Multicast_support = lists:member(?NS_ADDRESS, Feats),
Group = Waiter#waiter.group,
RServer = Group#group.server,
case Multicast_support of
true ->
SenderT = sender_type(From),
- RLimits = get_limits_xml(Els, SenderT),
+ RLimits = get_limits_xml(DiscoInfo, SenderT),
add_response(RServer, {multicast_supported, FromS, RLimits}),
FromM = Waiter#waiter.sender,
DestsM = Group#group.dests,
@@ -799,90 +736,58 @@ process_discoinfo_result2(From, FromS, LServiceS, Els,
end
end.
-get_limits_xml(Els, SenderT) ->
- LimitOpts = get_limits_els(Els),
+get_limits_xml(DiscoInfo, SenderT) ->
+ LimitOpts = get_limits_els(DiscoInfo),
build_remote_limit_record(LimitOpts, SenderT).
-get_limits_els(Els) ->
- lists:foldl(fun (XML, R) ->
- case XML of
- #xmlel{name = <<"x">>, attrs = Attrs,
- children = SubEls} ->
- case ((?NS_XDATA) ==
- fxml:get_attr_s(<<"xmlns">>, Attrs))
- and
- (<<"result">> ==
- fxml:get_attr_s(<<"type">>, Attrs))
- of
- true -> get_limits_fields(SubEls) ++ R;
- false -> R
- end;
- _ -> R
- end
- end,
- [], Els).
-
-get_limits_fields(Fields) ->
- {Head, Tail} = lists:partition(fun (Field) ->
- case Field of
- #xmlel{name = <<"field">>,
- attrs = Attrs} ->
- (<<"FORM_TYPE">> ==
- fxml:get_attr_s(<<"var">>,
- Attrs))
- and
- (<<"hidden">> ==
- fxml:get_attr_s(<<"type">>,
- Attrs));
- _ -> false
- end
- end,
- Fields),
+-spec get_limits_els(disco_info()) -> [{atom(), integer()}].
+get_limits_els(DiscoInfo) ->
+ lists:flatmap(
+ fun(#xdata{type = result} = X) ->
+ get_limits_fields(X);
+ (_) ->
+ []
+ end, DiscoInfo#disco_info.xdata).
+
+-spec get_limits_fields(xdata()) -> [{atom(), integer()}].
+get_limits_fields(X) ->
+ {Head, Tail} = lists:partition(
+ fun(#xdata_field{var = Var, type = Type}) ->
+ Var == <<"FORM_TYPE">> andalso Type == hidden
+ end, X#xdata.fields),
case Head of
[] -> [];
_ -> get_limits_values(Tail)
end.
-get_limits_values(Values) ->
- lists:foldl(fun (Value, R) ->
- case Value of
- #xmlel{name = <<"field">>, attrs = Attrs,
- children = SubEls} ->
- [#xmlel{name = <<"value">>, children = SubElsV}] =
- SubEls,
- Number = fxml:get_cdata(SubElsV),
- Name = fxml:get_attr_s(<<"var">>, Attrs),
- [{jlib:binary_to_atom(Name),
- jlib:binary_to_integer(Number)}
- | R];
- _ -> R
- end
- end,
- [], Values).
+-spec get_limits_values([xdata_field()]) -> [{atom(), integer()}].
+get_limits_values(Fields) ->
+ lists:flatmap(
+ fun(#xdata_field{var = Name, values = [Number]}) ->
+ try
+ [{binary_to_atom(Name, utf8), binary_to_integer(Number)}]
+ catch _:badarg ->
+ []
+ end;
+ (_) ->
+ []
+ end, Fields).
%%%-------------------------
%%% Check protocol support: Receive response: Disco Items
%%%-------------------------
-process_discoitems_result(From, LServiceS, Els) ->
+process_discoitems_result(From, LServiceS, #disco_items{items = Items}) ->
FromS = jts(From),
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),
+ List = lists:flatmap(
+ fun(#disco_item{jid = #jid{luser = <<"">>,
+ lresource = <<"">>} = J}) ->
+ [J];
+ (_) ->
+ []
+ end, Items),
case List of
[] ->
received_awaiter(FromS, Waiter, LServiceS);
@@ -935,7 +840,7 @@ received_awaiter(JID, Waiter, LServiceS) ->
%%%-------------------------
create_cache() ->
- mnesia:create_table(multicastc,
+ ejabberd_mnesia:create(?MODULE, multicastc,
[{ram_copies, [node()]},
{attributes, record_info(fields, multicastc)}]).
@@ -1109,9 +1014,7 @@ get_limit_value(Name, Default, LimitOpts) ->
false -> {default, Default}
end.
-type_of_stanza(#xmlel{name = <<"message">>}) -> message;
-type_of_stanza(#xmlel{name = <<"presence">>}) ->
- presence.
+type_of_stanza(Stanza) -> element(1, Stanza).
get_limit_number(message, Limits) ->
Limits#limits.message;
@@ -1144,17 +1047,10 @@ fragment_dests(Dests, Limit_number) ->
%% Some parts of code are borrowed from mod_muc_room.erl
-define(RFIELDT(Type, Var, Val),
- #xmlel{name = <<"field">>,
- attrs = [{<<"var">>, Var}, {<<"type">>, Type}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+ #xdata_field{type = Type, var = Var, values = [Val]}).
-define(RFIELDV(Var, Val),
- #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+ #xdata_field{var = Var, values = [Val]}).
iq_disco_info_extras(From, State) ->
SenderT = sender_type(From),
@@ -1162,12 +1058,9 @@ iq_disco_info_extras(From, State) ->
case iq_disco_info_extras2(SenderT, Service_limits) of
[] -> [];
List_limits_xmpp ->
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children =
- [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADDRESS))]
- ++ List_limits_xmpp}]
+ #xdata{type = result,
+ fields = [?RFIELDT(hidden, <<"FORM_TYPE">>, ?NS_ADDRESS)
+ | List_limits_xmpp]}
end.
sender_type(From) ->
@@ -1198,22 +1091,20 @@ to_binary(A) -> list_to_binary(hd(io_lib:format("~p", [A]))).
%%%-------------------------
route_error(From, To, Packet, ErrType, ErrText) ->
- #xmlel{attrs = Attrs} = Packet,
- 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).
+ Lang = xmpp:get_lang(Packet),
+ Err = make_reply(ErrType, Lang, ErrText),
+ ejabberd_router:route_error(From, To, Packet, Err).
make_reply(bad_request, Lang, ErrText) ->
- ?ERRT_BAD_REQUEST(Lang, ErrText);
+ xmpp:err_bad_request(ErrText, Lang);
make_reply(jid_malformed, Lang, ErrText) ->
- ?ERRT_JID_MALFORMED(Lang, ErrText);
+ xmpp:err_jid_malformed(ErrText, Lang);
make_reply(not_acceptable, Lang, ErrText) ->
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText);
+ xmpp:err_not_acceptable(ErrText, Lang);
make_reply(internal_server_error, Lang, ErrText) ->
- ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText);
+ xmpp:err_internal_server_error(ErrText, Lang);
make_reply(forbidden, Lang, ErrText) ->
- ?ERRT_FORBIDDEN(Lang, ErrText).
+ xmpp:err_forbidden(ErrText, Lang).
stj(String) -> jid:from_string(String).
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 1d9417117..42bc46631 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -49,12 +49,13 @@
get_sm_identity/5,
get_sm_items/5,
get_info/5,
- handle_offline_query/3,
+ handle_offline_query/1,
remove_expired_messages/1,
remove_old_messages/2,
remove_user/2,
- import/1,
- import/3,
+ import_info/0,
+ import_start/2,
+ import/5,
export/1,
get_queue_length/2,
count_offline_messages/2,
@@ -73,7 +74,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -90,7 +91,7 @@
-type us() :: {binary(), binary()}.
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #offline_msg{}) -> ok | pass.
+-callback import(#offline_msg{}) -> ok.
-callback store_messages(binary(), us(), [#offline_msg{}],
non_neg_integer(), non_neg_integer()) ->
{atomic, any()}.
@@ -99,10 +100,11 @@
-callback remove_expired_messages(binary()) -> {atomic, any()}.
-callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}.
-callback remove_user(binary(), binary()) -> {atomic, any()}.
--callback read_message_headers(binary(), binary()) -> any().
+-callback read_message_headers(binary(), binary()) ->
+ [{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}].
-callback read_message(binary(), binary(), non_neg_integer()) ->
{ok, #offline_msg{}} | error.
--callback remove_message(binary(), binary(), non_neg_integer()) -> ok.
+-callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}.
-callback read_all_messages(binary(), binary()) -> [#offline_msg{}].
-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
-callback count_messages(binary(), binary()) -> non_neg_integer().
@@ -268,12 +270,10 @@ get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+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];
+ [#identity{category = <<"automation">>,
+ type = <<"message-list">>}|Acc];
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
@@ -282,15 +282,16 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
?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)),
+ Mod = gen_mod:db_mod(S, ?MODULE),
+ Hdrs = Mod:read_message_headers(U, S),
+ BareJID = jid:remove_resource(JID),
Pid ! dont_ask_offline,
{result, lists:map(
- fun({Node, From, _To, _El}) ->
- #xmlel{name = <<"item">>,
- attrs = [{<<"jid">>, BareJID},
- {<<"node">>, Node},
- {<<"name">>, jid:to_string(From)}]}
+ fun({Seq, From, _To, _TS, _El}) ->
+ Node = integer_to_binary(Seq),
+ #disco_item{jid = BareJID,
+ node = Node,
+ name = jid:to_string(From)}
end, Hdrs)};
none ->
{result, []}
@@ -298,59 +299,70 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
get_sm_items(Acc, _From, _To, _Node, _Lang) ->
Acc.
+-spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
+ ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
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)),
+ #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, Lang) ->
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}]}]}]}];
+ [#xdata{type = result,
+ fields = flex_offline:encode(
+ [{number_of_messages, count_offline_messages(U, S)}],
+ fun(T) -> translate:translate(Lang, T) end)}];
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, lang = Lang} = IQ) ->
+-spec handle_offline_query(iq()) -> iq().
+handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1},
+ to = #jid{luser = U2, lserver = S2},
+ lang = Lang,
+ sub_els = [#offline{}]} = IQ)
+ when {U1, S1} /= {U2, S2} ->
Txt = <<"Query to another users is forbidden">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}.
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
+handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From,
+ to = #jid{luser = U, lserver = S} = _To,
+ type = Type, lang = Lang,
+ sub_els = [#offline{} = Offline]} = IQ) ->
+ case {Type, Offline} of
+ {get, #offline{fetch = true, items = [], purge = false}} ->
+ %% TODO: report database errors
+ handle_offline_fetch(From),
+ xmpp:make_iq_result(IQ);
+ {get, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
+ case handle_offline_items_view(From, Items) of
+ true -> xmpp:make_iq_result(IQ);
+ false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
+ end;
+ {set, #offline{fetch = false, items = [], purge = true}} ->
+ case delete_all_msgs(U, S) of
+ {atomic, ok} ->
+ xmpp:make_iq_result(IQ);
+ _Err ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end;
+ {set, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
+ case handle_offline_items_remove(From, Items) of
+ true -> xmpp:make_iq_result(IQ);
+ false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
+ end;
+ _ ->
+ xmpp:make_error(IQ, xmpp:err_bad_request())
+ end;
+handle_offline_query(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
-handle_offline_items_view(JID, #xmlel{children = Items}) ->
+-spec handle_offline_items_view(jid(), [offline_item()]) -> boolean().
+handle_offline_items_view(JID, Items) ->
{U, S, R} = jid:tolower(JID),
- lists:foreach(
- fun(Node) ->
+ lists:foldl(
+ fun(#offline_item{node = Node, action = view}, Acc) ->
case fetch_msg_by_node(JID, Node) of
{ok, OfflineMsg} ->
case offline_msg_to_route(S, OfflineMsg) of
@@ -361,46 +373,28 @@ handle_offline_items_view(JID, #xmlel{children = Items}) ->
Pid ! {route, From, To, NewEl};
none ->
ok
- end;
+ end,
+ Acc or true;
error ->
- ok
+ Acc or false
end;
error ->
- ok
+ Acc or false
end
- end, get_nodes_from_items(Items, <<"view">>)).
+ end, false, Items).
-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">>)).
+-spec handle_offline_items_remove(jid(), [offline_item()]) -> boolean().
+handle_offline_items_remove(JID, Items) ->
+ lists:foldl(
+ fun(#offline_item{node = Node, action = remove}, Acc) ->
+ Acc or remove_msg_by_node(JID, Node)
+ end, false, Items).
-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]}.
+-spec set_offline_tag(message(), binary()) -> message().
+set_offline_tag(Msg, Node) ->
+ xmpp:set_subtag(Msg, #offline{items = [#offline_item{node = Node}]}).
+-spec handle_offline_fetch(jid()) -> ok.
handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
case ejabberd_sm:get_session_pid(U, S, R) of
none ->
@@ -408,12 +402,13 @@ handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
Pid when is_pid(Pid) ->
Pid ! dont_ask_offline,
lists:foreach(
- fun({Node, From, To, El}) ->
+ fun({Node, El}) ->
NewEl = set_offline_tag(El, Node),
- Pid ! {route, From, To, NewEl}
- end, read_message_headers(U, S))
+ Pid ! {route, xmpp:get_from(El), xmpp:get_to(El), NewEl}
+ end, read_messages(U, S))
end.
+-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}.
fetch_msg_by_node(To, Seq) ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I >= 0 ->
@@ -425,33 +420,32 @@ fetch_msg_by_node(To, Seq) ->
error
end.
+-spec remove_msg_by_node(jid(), binary()) -> boolean().
remove_msg_by_node(To, Seq) ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>= 0 ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_message(LUser, LServer, I);
+ Mod:remove_message(LUser, LServer, I),
+ true;
_ ->
- ok
+ false
end.
-need_to_store(LServer, Packet) ->
- case has_offline_tag(Packet) of
+-spec need_to_store(binary(), message()) -> boolean().
+need_to_store(_LServer, #message{type = error}) -> false;
+need_to_store(LServer, #message{type = Type} = Packet) ->
+ case xmpp:has_subtag(Packet, #offline{}) of
false ->
- case {check_store_hint(Packet),
- fxml:get_tag_attr_s(<<"type">>, Packet)} of
- {_Hint, <<"error">>} ->
- false;
- {store, _Type} ->
+ case check_store_hint(Packet) of
+ store ->
true;
- {no_store, _Type} ->
+ no_store ->
false;
- {none, <<"groupchat">>} ->
+ none when Type == headline; Type == groupchat ->
false;
- {none, <<"headline">>} ->
- false;
- {none, _Type} ->
+ none ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
@@ -461,15 +455,16 @@ need_to_store(LServer, Packet) ->
true ->
true;
false ->
- fxml:get_subtag(Packet, <<"body">>) /= false;
+ Packet#message.body /= [];
unless_chat_state ->
- not jlib:is_standalone_chat_state(Packet)
+ not xmpp_util:is_standalone_chat_state(Packet)
end
end;
true ->
false
end.
+-spec store_packet(jid(), jid(), message()) -> ok | stop.
store_packet(From, To, Packet) ->
case need_to_store(To#jid.lserver, Packet) of
true ->
@@ -482,14 +477,14 @@ store_packet(From, To, Packet) ->
ok;
NewPacket ->
TimeStamp = p1_time_compat:timestamp(),
- #xmlel{children = Els} = NewPacket,
- Expire = find_x_expire(TimeStamp, Els),
+ Expire = find_x_expire(TimeStamp, NewPacket),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
- #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp,
- expire = Expire,
- from = From, to = To,
- packet = NewPacket},
+ #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp,
+ expire = Expire,
+ from = From,
+ to = To,
+ packet = NewPacket},
stop
end;
_ -> ok
@@ -497,6 +492,7 @@ store_packet(From, To, Packet) ->
false -> ok
end.
+-spec check_store_hint(message()) -> store | no_store | none.
check_store_hint(Packet) ->
case has_store_hint(Packet) of
true ->
@@ -510,89 +506,43 @@ check_store_hint(Packet) ->
end
end.
+-spec has_store_hint(message()) -> boolean().
has_store_hint(Packet) ->
- fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
+ xmpp:has_subtag(Packet, #hint{type = 'store'}).
+-spec has_no_store_hint(message()) -> boolean().
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.
+ xmpp:has_subtag(Packet, #hint{type = 'no-store'})
+ orelse
+ xmpp:has_subtag(Packet, #hint{type = 'no-storage'}).
%% 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 fxml:get_subtag(El, <<"id">>) of
- false ->
- case fxml:get_subtag(El, <<"offline">>) of
- false -> true;
- _ ->
- ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of
- <<"">> ->
- #xmlel{name = <<"id">>, attrs = [],
- children = []};
- S ->
- #xmlel{name = <<"id">>, attrs = [],
- children = [{xmlcdata, S}]}
- end,
- ejabberd_router:route(To, From,
- #xmlel{name = Name, attrs = Attrs,
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_EVENT}],
- children =
- [ID,
- #xmlel{name
- =
- <<"offline">>,
- attrs
- =
- [],
- children
- =
- []}]}]}),
- true
- end;
- _ -> false
- end
- end.
-
-%% 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 fxml:get_tag_attr_s(<<"xmlns">>, El) of
- ?NS_EVENT -> El;
- _ -> find_x_event(Els)
+-spec check_event(jid(), jid(), message()) -> boolean().
+check_event(From, To, #message{id = ID} = Msg) ->
+ case xmpp:get_subtag(Msg, #xevent{}) of
+ false ->
+ true;
+ #xevent{id = undefined, offline = false} ->
+ true;
+ #xevent{id = undefined, offline = true} ->
+ NewMsg = Msg#message{sub_els = [#xevent{id = ID, offline = true}]},
+ ejabberd_router:route(To, From, xmpp:set_from_to(NewMsg, To, From)),
+ true;
+ _ ->
+ false
end.
-find_x_expire(_, []) -> never;
-find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
- find_x_expire(TimeStamp, Els);
-find_x_expire(TimeStamp, [El | Els]) ->
- case fxml:get_tag_attr_s(<<"xmlns">>, El) of
- ?NS_EXPIRE ->
- Val = fxml:get_tag_attr_s(<<"seconds">>, El),
- case catch jlib:binary_to_integer(Val) of
- {'EXIT', _} -> never;
- Int when Int > 0 ->
- {MegaSecs, Secs, MicroSecs} = TimeStamp,
- S = MegaSecs * 1000000 + Secs + Int,
- MegaSecs1 = S div 1000000,
- Secs1 = S rem 1000000,
- {MegaSecs1, Secs1, MicroSecs};
- _ -> never
- end;
- _ -> find_x_expire(TimeStamp, Els)
+-spec find_x_expire(erlang:timestamp(), message()) -> erlang:timestamp() | never.
+find_x_expire(TimeStamp, Msg) ->
+ case xmpp:get_subtag(Msg, #expire{}) of
+ #expire{seconds = Int} ->
+ {MegaSecs, Secs, MicroSecs} = TimeStamp,
+ S = MegaSecs * 1000000 + Secs + Int,
+ MegaSecs1 = S div 1000000,
+ Secs1 = S rem 1000000,
+ {MegaSecs1, Secs1, MicroSecs};
+ false ->
+ never
end.
resend_offline_messages(User, Server) ->
@@ -601,13 +551,19 @@ resend_offline_messages(User, Server) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:pop_messages(LUser, LServer) of
{ok, Rs} ->
- lists:foreach(fun (R) ->
- ejabberd_sm ! offline_msg_to_route(LServer, R)
- end,
- lists:keysort(#offline_msg.timestamp, Rs));
+ lists:foreach(
+ fun(R) ->
+ case offline_msg_to_route(LServer, R) of
+ error -> ok;
+ RouteMsg -> ejabberd_sm ! RouteMsg
+ end
+ end, lists:keysort(#offline_msg.timestamp, Rs));
_ -> ok
end.
+-spec pop_offline_messages([{route, jid(), jid(), message()}],
+ binary(), binary()) ->
+ [{route, jid(), jid(), message()}].
pop_offline_messages(Ls, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -616,23 +572,26 @@ pop_offline_messages(Ls, User, Server) ->
{ok, Rs} ->
TS = p1_time_compat:timestamp(),
Ls ++
- lists:map(fun (R) ->
- offline_msg_to_route(LServer, R)
- end,
- lists:filter(
- fun(#offline_msg{packet = Pkt} = R) ->
- #xmlel{children = Els} = Pkt,
- Expire = case R#offline_msg.expire of
- undefined ->
- find_x_expire(TS, Els);
- Exp ->
- Exp
- end,
- case Expire of
- never -> true;
- TimeStamp -> TS < TimeStamp
- end
- end, Rs));
+ lists:flatmap(
+ fun(R) ->
+ case offline_msg_to_route(LServer, R) of
+ error -> [];
+ RouteMsg -> [RouteMsg]
+ end
+ end,
+ lists:filter(
+ fun(#offline_msg{packet = Pkt} = R) ->
+ Expire = case R#offline_msg.expire of
+ undefined ->
+ find_x_expire(TS, Pkt);
+ Exp ->
+ Exp
+ end,
+ case Expire of
+ never -> true;
+ TimeStamp -> TS < TimeStamp
+ end
+ end, Rs));
_ ->
Ls
end.
@@ -647,27 +606,26 @@ remove_old_messages(Days, Server) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_old_messages(Days, LServer).
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_user(LUser, LServer),
+ ok.
%% Helper functions:
%% Warn senders that their messages have been discarded:
discard_warn_sender(Msgs) ->
- lists:foreach(fun (#offline_msg{from = From, to = To,
- packet = Packet}) ->
- ErrText = <<"Your contact offline message queue is "
- "full. The message has been discarded.">>,
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Err = jlib:make_error_reply(Packet,
- ?ERRT_RESOURCE_CONSTRAINT(Lang,
- ErrText)),
- ejabberd_router:route(To, From, Err)
- end,
- Msgs).
+ lists:foreach(
+ fun(#offline_msg{from = From, to = To, packet = Packet}) ->
+ ErrText = <<"Your contact offline message queue is "
+ "full. The message has been discarded.">>,
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_resource_constraint(ErrText, Lang),
+ ejabberd_router:route_error(To, From, Packet, Err)
+ end, Msgs).
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"queue">>],
@@ -677,51 +635,61 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
get_offline_els(LUser, LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Hdrs = Mod:read_message_headers(LUser, LServer),
- lists:map(
- fun({_Seq, From, To, Packet}) ->
- jlib:replace_from_to(From, To, Packet)
- end, Hdrs).
+ [Packet || {_Seq, Packet} <- read_messages(LUser, LServer)].
+-spec offline_msg_to_route(binary(), #offline_msg{}) ->
+ {route, jid(), jid(), message()} | error.
offline_msg_to_route(LServer, #offline_msg{} = R) ->
- El = case R#offline_msg.timestamp of
- undefined ->
- R#offline_msg.packet;
- TS ->
- jlib:add_delay_info(R#offline_msg.packet, LServer, TS,
- <<"Offline Storage">>)
- end,
- {route, R#offline_msg.from, R#offline_msg.to, El}.
-
-read_message_headers(LUser, LServer) ->
+ try xmpp:decode(R#offline_msg.packet, ?NS_CLIENT, [ignore_els]) of
+ Pkt ->
+ NewPkt = add_delay_info(Pkt, LServer, R#offline_msg.timestamp),
+ {route, R#offline_msg.from, R#offline_msg.to, NewPkt}
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("failed to decode packet ~p of user ~s: ~s",
+ [R#offline_msg.packet, jid:to_string(R#offline_msg.to),
+ xmpp:format_error(Why)]),
+ error
+ end.
+
+-spec read_messages(binary(), binary()) -> [{binary(), message()}].
+read_messages(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- lists:map(
- fun({Seq, From, To, El}) ->
+ lists:flatmap(
+ fun({Seq, From, To, TS, El}) ->
Node = integer_to_binary(Seq),
- {Node, From, To, El}
+ try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
+ Pkt ->
+ Node = integer_to_binary(Seq),
+ Pkt1 = add_delay_info(Pkt, LServer, TS),
+ Pkt2 = xmpp:set_from_to(Pkt1, From, To),
+ [{Node, Pkt2}]
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("failed to decode packet ~p "
+ "of user ~s: ~s",
+ [El, jid:to_string(To),
+ xmpp:format_error(Why)]),
+ []
+ end
end, Mod:read_message_headers(LUser, LServer)).
format_user_queue(Hdrs) ->
lists:map(
- fun({Seq, From, To, El}) ->
+ fun({Seq, From, To, TS, El}) ->
ID = integer_to_binary(Seq),
FPacket = ejabberd_web_admin:pretty_print_xml(El),
SFrom = jid:to_string(From),
STo = jid:to_string(To),
- Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
- {attr, <<"stamp">>}]),
- Time = case jlib:datetime_string_to_timestamp(Stamp) of
+ Time = case TS of
+ undefined ->
+ Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
+ {attr, <<"stamp">>}]),
+ try xmpp_util:decode_timestamp(Stamp) of
+ {_, _, _} = Now -> format_time(Now)
+ catch _:_ ->
+ <<"">>
+ end;
{_, _, _} = Now ->
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(Now),
- iolist_to_binary(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute,
- Second]));
- _ ->
- <<"">>
+ format_time(Now)
end,
?XE(<<"tr">>,
[?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
@@ -733,6 +701,11 @@ format_user_queue(Hdrs) ->
[?XC(<<"pre">>, FPacket)])])
end, Hdrs).
+format_time(Now) ->
+ {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Now),
+ str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour, Minute, Second]).
+
user_queue(User, Server, Query, Lang) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -743,7 +716,7 @@ user_queue(User, Server, Query, Lang) ->
Hdrs = get_messages_subset(US, Server, HdrsAll),
FMsgs = format_user_queue(Hdrs),
[?XC(<<"h1">>,
- list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>),
+ (str:format(?T(<<"~s's Offline Messages Queue">>),
[us_to_list(US)])))]
++
case Res of
@@ -827,7 +800,7 @@ webadmin_user(Acc, User, Server, Lang) ->
QueueLen = count_offline_messages(jid:nodeprep(User),
jid:nameprep(Server)),
FQueueLen = [?AC(<<"queue/">>,
- (iolist_to_binary(integer_to_list(QueueLen))))],
+ (integer_to_binary(QueueLen)))],
Acc ++
[?XCT(<<"h3">>, <<"Offline Messages:">>)] ++
FQueueLen ++
@@ -835,6 +808,7 @@ webadmin_user(Acc, User, Server, Lang) ->
?INPUTT(<<"submit">>, <<"removealloffline">>,
<<"Remove All Offline Messages">>)].
+-spec delete_all_msgs(binary(), binary()) -> {atomic, any()}.
delete_all_msgs(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -858,23 +832,54 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
Acc.
%% Returns as integer the number of offline messages for a given user
+-spec count_offline_messages(binary(), binary()) -> non_neg_integer().
count_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:count_messages(LUser, LServer).
+-spec add_delay_info(message(), binary(),
+ undefined | erlang:timestamp()) -> message().
+add_delay_info(Packet, _LServer, undefined) ->
+ Packet;
+add_delay_info(Packet, LServer, {_, _, _} = TS) ->
+ xmpp_util:add_delay_info(Packet, jid:make(LServer), TS,
+ <<"Offline storage">>).
+
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"spool">>, 4}].
-import(LServer, DBType, Data) ->
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, []).
+
+import(LServer, {sql, _}, DBType, <<"spool">>,
+ [LUser, XML, _Seq, _TimeStamp]) ->
+ El = 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 = try xmpp_util:decode_timestamp(Stamp) of
+ {MegaSecs, Secs, _} ->
+ {MegaSecs, Secs, 0}
+ catch _:_ ->
+ p1_time_compat:timestamp()
+ end,
+ US = {LUser, LServer},
+ Expire = find_x_expire(TS, El#xmlel.children),
+ Msg = #offline_msg{us = US, packet = El,
+ from = From, to = To,
+ timestamp = TS, expire = Expire},
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
+ Mod:import(Msg).
mod_opt_type(access_max_user_messages) ->
fun acl:shaper_rules_validator/1;
diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl
index 6a1d9e309..fb75f618e 100644
--- a/src/mod_offline_mnesia.erl
+++ b/src/mod_offline_mnesia.erl
@@ -13,9 +13,9 @@
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2,
- remove_all_messages/2, count_messages/2, import/2]).
+ remove_all_messages/2, count_messages/2, import/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_offline.hrl").
-include("logger.hrl").
@@ -25,7 +25,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(offline_msg,
+ ejabberd_mnesia:create(?MODULE, offline_msg,
[{disc_only_copies, [node()]}, {type, bag},
{attributes, record_info(fields, offline_msg)}]),
update_table().
@@ -42,7 +42,11 @@ store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) ->
mnesia:write_lock_table(offline_msg);
true -> ok
end,
- lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
+ lists:foreach(
+ fun(#offline_msg{packet = Pkt} = M) ->
+ El = xmpp:encode(Pkt),
+ mnesia:write(M#offline_msg{packet = El})
+ end, Msgs)
end
end,
mnesia:transaction(F).
@@ -107,9 +111,7 @@ read_message_headers(LUser, LServer) ->
fun(#offline_msg{from = From, to = To, packet = Pkt,
timestamp = TS}) ->
Seq = now_to_integer(TS),
- NewPkt = jlib:add_delay_info(Pkt, LServer, TS,
- <<"Offline Storage">>),
- {Seq, From, To, NewPkt}
+ {Seq, From, To, TS, Pkt}
end, Msgs),
lists:keysort(1, Hdrs).
@@ -127,12 +129,16 @@ read_message(LUser, LServer, I) ->
remove_message(LUser, LServer, I) ->
US = {LUser, LServer},
TS = integer_to_now(I),
- Msgs = mnesia:dirty_match_object(
- offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}),
- lists:foreach(
- fun(Msg) ->
- mnesia:dirty_delete_object(Msg)
- end, Msgs).
+ case mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}) of
+ [] ->
+ {error, notfound};
+ Msgs ->
+ lists:foreach(
+ fun(Msg) ->
+ mnesia:dirty_delete_object(Msg)
+ end, Msgs)
+ end.
read_all_messages(LUser, LServer) ->
US = {LUser, LServer},
@@ -158,7 +164,7 @@ count_messages(LUser, LServer) ->
_ -> 0
end.
-import(_LServer, #offline_msg{} = Msg) ->
+import(#offline_msg{} = Msg) ->
mnesia:dirty_write(Msg).
%%%===================================================================
diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl
index 217e8f828..824abc89c 100644
--- a/src/mod_offline_riak.erl
+++ b/src/mod_offline_riak.erl
@@ -13,9 +13,9 @@
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2,
- remove_all_messages/2, count_messages/2, import/2]).
+ remove_all_messages/2, count_messages/2, import/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_offline.hrl").
%%%===================================================================
@@ -36,9 +36,12 @@ store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) ->
try
lists:foreach(
fun(#offline_msg{us = US,
+ packet = Pkt,
timestamp = TS} = M) ->
+ El = xmpp:encode(Pkt),
ok = ejabberd_riak:put(
- M, offline_msg_schema(),
+ M#offline_msg{packet = El},
+ offline_msg_schema(),
[{i, TS}, {'2i', [{<<"us">>, US}]}])
end, Msgs),
{atomic, ok}
@@ -85,9 +88,7 @@ read_message_headers(LUser, LServer) ->
fun(#offline_msg{from = From, to = To, packet = Pkt,
timestamp = TS}) ->
Seq = now_to_integer(TS),
- NewPkt = jlib:add_delay_info(
- Pkt, LServer, TS, <<"Offline Storage">>),
- {Seq, From, To, NewPkt}
+ {Seq, From, To, TS, Pkt}
end, Rs),
lists:keysort(1, Hdrs);
_Err ->
@@ -132,7 +133,7 @@ count_messages(LUser, LServer) ->
0
end.
-import(_LServer, #offline_msg{us = US, timestamp = TS} = M) ->
+import(#offline_msg{us = US, timestamp = TS} = M) ->
ejabberd_riak:put(M, offline_msg_schema(),
[{i, TS}, {'2i', [{<<"us">>, US}]}]).
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index d9de50e04..e626df425 100644
--- a/src/mod_offline_sql.erl
+++ b/src/mod_offline_sql.erl
@@ -15,10 +15,9 @@
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2,
- remove_all_messages/2, count_messages/2, import/1, import/2,
- export/1]).
+ remove_all_messages/2, count_messages/2, import/1, export/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_offline.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -41,14 +40,14 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
LUser = (M#offline_msg.to)#jid.luser,
From = M#offline_msg.from,
To = M#offline_msg.to,
- Packet =
- jlib:replace_from_to(From, To,
- M#offline_msg.packet),
- NewPacket =
- jlib:add_delay_info(Packet, Host,
- M#offline_msg.timestamp,
- <<"Offline Storage">>),
- XML = fxml:element_to_binary(NewPacket),
+ Packet = xmpp:set_from_to(
+ M#offline_msg.packet, From, To),
+ NewPacket = xmpp_util:add_delay_info(
+ Packet, jid:make(Host),
+ M#offline_msg.timestamp,
+ <<"Offline Storage">>),
+ XML = fxml:element_to_binary(
+ xmpp:encode(NewPacket)),
sql_queries:add_spool_sql(LUser, XML)
end,
Msgs),
@@ -103,8 +102,9 @@ read_message_headers(LUser, LServer) ->
case xml_to_offline_msg(XML) of
{ok, #offline_msg{from = From,
to = To,
+ timestamp = TS,
packet = El}} ->
- [{Seq, From, To, El}];
+ [{Seq, From, To, TS, El}];
_ ->
[]
end
@@ -171,44 +171,29 @@ export(_Server) ->
[{offline_msg,
fun(Host, #offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, from = From, to = To,
- packet = Packet})
+ packet = El})
when LServer == Host ->
- Packet1 = jlib:replace_from_to(From, To, Packet),
- Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
- <<"Offline Storage">>),
- XML = fxml:element_to_binary(Packet2),
- [?SQL("delete from spool where username=%(LUser)s;"),
- ?SQL("insert into spool(username, xml) values ("
- "%(LUser)s, %(XML)s);")];
+ try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
+ Packet ->
+ Packet1 = xmpp:set_from_to(Packet, From, To),
+ Packet2 = xmpp_util:add_delay_info(
+ Packet1, jid:make(LServer),
+ TimeStamp, <<"Offline Storage">>),
+ XML = fxml:element_to_binary(xmpp:encode(Packet2)),
+ [?SQL("delete from spool where username=%(LUser)s;"),
+ ?SQL("insert into spool(username, xml) values ("
+ "%(LUser)s, %(XML)s);")]
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s",
+ [El, LUser, LServer, xmpp:format_error(Why)]),
+ []
+ end;
(_Host, _R) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, xml from spool;">>,
- fun([LUser, XML]) ->
- 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 ->
- p1_time_compat:timestamp()
- end,
- Expire = mod_offline:find_x_expire(TS, El#xmlel.children),
- #offline_msg{us = {LUser, LServer},
- from = From, to = To,
- packet = El,
- timestamp = TS, expire = Expire}
- end}].
-
-import(_, _) ->
- pass.
+import(_) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index d1b3f9322..5e861b7f7 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -36,7 +36,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(SUPERVISOR, ejabberd_sup).
@@ -54,7 +54,7 @@
-export([init/1, terminate/2, handle_call/3,
handle_cast/2, handle_info/2, code_change/3]).
--export([iq_ping/3, user_online/3, user_offline/3,
+-export([iq_ping/1, user_online/3, user_offline/3,
user_send/4, mod_opt_type/1, depends/2]).
-record(state,
@@ -73,10 +73,12 @@ start_link(Host, Opts) ->
gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []).
+-spec start_ping(binary(), jid()) -> ok.
start_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {start_ping, JID}).
+-spec stop_ping(binary(), jid()) -> ok.
stop_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {stop_ping, JID}).
@@ -181,10 +183,7 @@ handle_cast({iq_pong, JID, timeout}, State) ->
handle_cast(_Msg, State) -> {noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) ->
- IQ = #iq{type = get,
- sub_el =
- [#xmlel{name = <<"ping">>,
- attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
+ IQ = #iq{type = get, sub_els = [#ping{}]},
Pid = self(),
F = fun (Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response})
@@ -201,23 +200,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%====================================================================
%% Hook callbacks
%%====================================================================
-iq_ping(_From, _To,
- #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
- case {Type, SubEl} of
- {get, #xmlel{name = <<"ping">>}} ->
- IQ#iq{type = result, sub_el = []};
- _ ->
- Txt = <<"Ping query is incorrect">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end.
-
+-spec iq_ping(iq()) -> iq().
+iq_ping(#iq{type = get, sub_els = [#ping{}]} = IQ) ->
+ xmpp:make_iq_result(IQ);
+iq_ping(#iq{lang = Lang} = IQ) ->
+ Txt = <<"Ping query is incorrect">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
+
+-spec user_online(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
user_online(_SID, JID, _Info) ->
start_ping(JID#jid.lserver, JID).
+-spec user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
user_offline(_SID, JID, _Info) ->
stop_ping(JID#jid.lserver, JID).
+-spec user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
user_send(Packet, _C2SState, JID, _From) ->
start_ping(JID#jid.lserver, JID),
Packet.
@@ -225,6 +223,7 @@ user_send(Packet, _C2SState, JID, _From) ->
%%====================================================================
%% Internal functions
%%====================================================================
+-spec add_timer(jid(), non_neg_integer(), map()) -> map().
add_timer(JID, Interval, Timers) ->
LJID = jid:tolower(JID),
NewTimers = case maps:find(LJID, Timers) of
@@ -237,6 +236,7 @@ add_timer(JID, Interval, Timers) ->
{ping, JID}),
maps:put(LJID, TRef, NewTimers).
+-spec del_timer(jid(), map()) -> map().
del_timer(JID, Timers) ->
LJID = jid:tolower(JID),
case maps:find(LJID, Timers) of
@@ -246,6 +246,7 @@ del_timer(JID, Timers) ->
_ -> Timers
end.
+-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
index e6f2cfbab..955e53f6f 100644
--- a/src/mod_pres_counter.erl
+++ b/src/mod_pres_counter.erl
@@ -33,7 +33,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-record(pres_counter,
{dir, start, count, logged = false}).
@@ -51,28 +51,27 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
+-spec check_packet(allow | deny, binary(), binary(), _,
+ {jid(), jid(), stanza()}, in | out) -> allow | deny.
check_packet(_, _User, Server, _PrivacyList,
- {From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
- case Name of
- <<"presence">> ->
- IsSubscription = case fxml:get_attr_s(<<"type">>, Attrs)
- of
- <<"subscribe">> -> true;
- <<"subscribed">> -> true;
- <<"unsubscribe">> -> true;
- <<"unsubscribed">> -> true;
- _ -> false
- end,
- if IsSubscription ->
- JID = case Dir of
- in -> To;
- out -> From
- end,
- update(Server, JID, Dir);
- true -> allow
- end;
- _ -> allow
- end.
+ {From, To, #presence{type = Type}}, Dir) ->
+ IsSubscription = case Type of
+ subscribe -> true;
+ subscribed -> true;
+ unsubscribe -> true;
+ unsubscribed -> true;
+ _ -> false
+ end,
+ if IsSubscription ->
+ JID = case Dir of
+ in -> To;
+ out -> From
+ end,
+ update(Server, JID, Dir);
+ true -> allow
+ end;
+check_packet(_, _User, _Server, _PrivacyList, _Pkt, _Dir) ->
+ allow.
update(Server, JID, Dir) ->
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count,
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 18ff78371..d6936e1b7 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -31,25 +31,26 @@
-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,
+-export([start/2, stop/1, process_iq/1, export/1, import_info/0,
+ process_iq_set/3, process_iq_get/3, get_user_list/3,
+ check_packet/6, remove_user/2, encode_list_item/1,
is_list_needdb/1, updated_list/3,
- item_to_xml/1, get_user_lists/2, import/3,
+ import_start/2, import_stop/2,
+ item_to_xml/1, get_user_lists/2, import/5,
set_privacy_list/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #privacy{}) -> ok | pass.
--callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error.
+-callback import(#privacy{}) -> ok.
+-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error.
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
--callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}.
+-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}.
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
-callback set_privacy_list(#privacy{}) -> any().
@@ -96,335 +97,293 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVACY).
-process_iq(_From, _To, IQ) ->
- SubEl = IQ#iq.sub_el,
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-
-process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl},
+-spec process_iq(iq()) -> iq().
+process_iq(IQ) ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed()).
+
+-spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined},
+ iq(), userlist()) -> {error, stanza_error()} |
+ {result, xmpp_element() | undefined}.
+process_iq_get(_, #iq{lang = Lang,
+ sub_els = [#privacy_query{default = Default,
+ active = Active}]},
+ _) when Default /= undefined; Active /= undefined ->
+ Txt = <<"Only <list/> element is allowed in this query">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+process_iq_get(_, #iq{from = From, lang = Lang,
+ sub_els = [#privacy_query{lists = Lists}]},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [] -> process_lists_get(LUser, LServer, Active, Lang);
- [#xmlel{name = Name, attrs = Attrs}] ->
- case Name of
- <<"list">> ->
- ListName = fxml:get_attr(<<"name">>, Attrs),
- process_list_get(LUser, LServer, ListName, Lang);
- _ ->
- Txt = <<"Unsupported tag name">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
- end.
+ case Lists of
+ [] ->
+ process_lists_get(LUser, LServer, Active, Lang);
+ [#privacy_list{name = ListName}] ->
+ process_list_get(LUser, LServer, ListName, Lang);
+ _ ->
+ Txt = <<"Too many <list/> elements">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+process_iq_get(Acc, _, _) ->
+ Acc.
+-spec process_lists_get(binary(), binary(), binary(), binary()) ->
+ {error, stanza_error()} | {result, privacy_query()}.
process_lists_get(LUser, LServer, Active, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_lists_get(LUser, LServer) of
- error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- {_Default, []} ->
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]};
- {Default, LItems} ->
- DItems = case Default of
- none -> LItems;
- _ ->
- [#xmlel{name = <<"default">>,
- attrs = [{<<"name">>, Default}], children = []}
- | LItems]
- end,
- ADItems = case Active of
- none -> DItems;
- _ ->
- [#xmlel{name = <<"active">>,
- attrs = [{<<"name">>, Active}], children = []}
- | DItems]
- end,
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
- children = ADItems}]}
+ error ->
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)};
+ {_Default, []} ->
+ {result, #privacy_query{}};
+ {Default, ListNames} ->
+ {result,
+ #privacy_query{active = Active,
+ default = Default,
+ lists = [#privacy_list{name = ListName}
+ || ListName <- ListNames]}}
end.
-process_list_get(LUser, LServer, {value, Name}, Lang) ->
+-spec process_list_get(binary(), binary(), binary(), binary()) ->
+ {error, stanza_error()} | {result, privacy_query()}.
+process_list_get(LUser, LServer, Name, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_list_get(LUser, LServer, Name) of
- error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- not_found -> {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- LItems = lists:map(fun item_to_xml/1, Items),
- {result,
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
- children =
- [#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
- children = LItems}]}]}
- end;
-process_list_get(_LUser, _LServer, false, _Lang) ->
- {error, ?ERR_BAD_REQUEST}.
-
-item_to_xml(Item) ->
- Attrs1 = [{<<"action">>,
- action_to_list(Item#listitem.action)},
- {<<"order">>, order_to_list(Item#listitem.order)}],
- Attrs2 = case Item#listitem.type of
- none -> Attrs1;
- Type ->
- [{<<"type">>, type_to_list(Item#listitem.type)},
- {<<"value">>, value_to_list(Type, Item#listitem.value)}
- | Attrs1]
- end,
- SubEls = case Item#listitem.match_all of
- true -> [];
- false ->
- SE1 = case Item#listitem.match_iq of
- true ->
- [#xmlel{name = <<"iq">>, attrs = [],
- children = []}];
- false -> []
- end,
- SE2 = case Item#listitem.match_message of
- true ->
- [#xmlel{name = <<"message">>, attrs = [],
- children = []}
- | SE1];
- false -> SE1
- end,
- SE3 = case Item#listitem.match_presence_in of
- true ->
- [#xmlel{name = <<"presence-in">>, attrs = [],
- children = []}
- | SE2];
- false -> SE2
- end,
- SE4 = case Item#listitem.match_presence_out of
- true ->
- [#xmlel{name = <<"presence-out">>, attrs = [],
- children = []}
- | SE3];
- false -> SE3
- end,
- SE4
- end,
- #xmlel{name = <<"item">>, attrs = Attrs2,
- children = SubEls}.
-
-action_to_list(Action) ->
- case Action of
- allow -> <<"allow">>;
- deny -> <<"deny">>
+ error ->
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)};
+ not_found ->
+ Txt = <<"No privacy list with this name found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Items ->
+ LItems = lists:map(fun encode_list_item/1, Items),
+ {result,
+ #privacy_query{
+ lists = [#privacy_list{name = Name, items = LItems}]}}
end.
-order_to_list(Order) ->
- iolist_to_binary(integer_to_list(Order)).
-
-type_to_list(Type) ->
- case Type of
- jid -> <<"jid">>;
- group -> <<"group">>;
- subscription -> <<"subscription">>
+-spec item_to_xml(listitem()) -> xmlel().
+item_to_xml(ListItem) ->
+ xmpp:encode(encode_list_item(ListItem)).
+
+-spec encode_list_item(listitem()) -> privacy_item().
+encode_list_item(#listitem{action = Action,
+ order = Order,
+ type = Type,
+ match_all = MatchAll,
+ match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut,
+ value = Value}) ->
+ Item = #privacy_item{action = Action,
+ order = Order,
+ type = case Type of
+ none -> undefined;
+ Type -> Type
+ end,
+ value = encode_value(Type, Value)},
+ case MatchAll of
+ true ->
+ Item;
+ false ->
+ Item#privacy_item{message = MatchMessage,
+ iq = MatchIQ,
+ presence_in = MatchPresenceIn,
+ presence_out = MatchPresenceOut}
end.
-value_to_list(Type, Val) ->
+-spec encode_value(listitem_type(), listitem_value()) -> binary().
+encode_value(Type, Val) ->
case Type of
- jid -> jid:to_string(Val);
- group -> Val;
- subscription ->
- case Val of
- both -> <<"both">>;
- to -> <<"to">>;
- from -> <<"from">>;
- none -> <<"none">>
- end
+ jid -> jid:to_string(Val);
+ group -> Val;
+ subscription ->
+ case Val of
+ both -> <<"both">>;
+ to -> <<"to">>;
+ from -> <<"from">>;
+ none -> <<"none">>
+ end;
+ none -> <<"">>
end.
-list_to_action(S) ->
- case S of
- <<"allow">> -> allow;
- <<"deny">> -> deny
+-spec decode_value(jid | subscription | group | undefined, binary()) ->
+ listitem_value().
+decode_value(Type, Value) ->
+ case Type of
+ jid -> jid:tolower(jid:from_string(Value));
+ subscription ->
+ case Value of
+ <<"from">> -> from;
+ <<"to">> -> to;
+ <<"both">> -> both;
+ <<"none">> -> none
+ end;
+ group when Value /= <<"">> -> Value;
+ undefined -> none
end.
-process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
+-spec process_iq_set({error, stanza_error()} |
+ {result, xmpp_element() | undefined} |
+ {result, xmpp_element() | undefined, userlist()},
+ iq(), #userlist{}) ->
+ {error, stanza_error()} |
+ {result, xmpp_element() | undefined} |
+ {result, xmpp_element() | undefined, userlist()}.
+process_iq_set(_, #iq{from = From, lang = Lang,
+ sub_els = [#privacy_query{default = Default,
+ active = Active,
+ lists = Lists}]},
+ #userlist{} = UserList) ->
#jid{luser = LUser, lserver = LServer} = From,
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [#xmlel{name = Name, attrs = Attrs,
- children = SubEls}] ->
- ListName = fxml:get_attr(<<"name">>, Attrs),
- case Name of
- <<"list">> ->
- process_list_set(LUser, LServer, ListName,
- fxml:remove_cdata(SubEls), Lang);
- <<"active">> ->
- process_active_set(LUser, LServer, ListName);
- <<"default">> ->
- process_default_set(LUser, LServer, ListName, Lang);
- _ ->
- Txt = <<"Unsupported tag name">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- _ -> {error, ?ERR_BAD_REQUEST}
- end.
+ case Lists of
+ [#privacy_list{items = Items, name = ListName}]
+ when Default == undefined, Active == undefined ->
+ process_lists_set(LUser, LServer, ListName, Items, UserList, Lang);
+ [] when Default == undefined, Active /= undefined ->
+ process_active_set(LUser, LServer, Active, Lang);
+ [] when Active == undefined, Default /= undefined ->
+ process_default_set(LUser, LServer, Default, Lang);
+ _ ->
+ Txt = <<"The stanza MUST contain only one <active/> element, "
+ "one <default/> element, or one <list/> element">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+process_iq_set(Acc, _, _) ->
+ Acc.
+-spec process_default_set(binary(), binary(), none | binary(),
+ binary()) -> {error, stanza_error()} | {result, undefined}.
process_default_set(LUser, LServer, Value, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_default_set(LUser, LServer, Value) of
- {atomic, error} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
- {atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND};
- {atomic, ok} -> {result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {atomic, error} ->
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)};
+ {atomic, not_found} ->
+ Txt = <<"No privacy list with this name found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ {atomic, ok} ->
+ {result, undefined};
+ Err ->
+ ?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p",
+ [Value, LUser, LServer, Err]),
+ {error, xmpp:err_internal_server_error()}
end.
-process_active_set(LUser, LServer, {value, Name}) ->
+-spec process_active_set(binary(), binary(), none | binary(), binary()) ->
+ {error, stanza_error()} |
+ {result, undefined, userlist()}.
+process_active_set(_LUser, _LServer, none, _Lang) ->
+ {result, undefined, #userlist{}};
+process_active_set(LUser, LServer, Name, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_active_set(LUser, LServer, Name) of
- error -> {error, ?ERR_ITEM_NOT_FOUND};
- Items ->
- NeedDb = is_list_needdb(Items),
- {result, [],
- #userlist{name = Name, list = Items, needdb = NeedDb}}
- end;
-process_active_set(_LUser, _LServer, false) ->
- {result, [], #userlist{}}.
+ error ->
+ Txt = <<"No privacy list with this name found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ Items ->
+ NeedDb = is_list_needdb(Items),
+ {result, undefined,
+ #userlist{name = Name, list = Items, needdb = NeedDb}}
+ end.
+-spec set_privacy_list(privacy()) -> any().
set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_privacy_list(Privacy).
-process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
- case parse_items(Els) of
- false -> {error, ?ERR_BAD_REQUEST};
- remove ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:remove_privacy_list(LUser, LServer, Name) of
- {atomic, conflict} ->
- Txt = <<"Cannot remove default list">>,
- {error, ?ERRT_CONFLICT(Lang, Txt)};
- {atomic, ok} ->
- ejabberd_sm:route(jid:make(LUser, LServer,
- <<"">>),
- jid:make(LUser, LServer, <<"">>),
- {broadcast, {privacy_list,
- #userlist{name = Name,
- list = []},
- Name}}),
- {result, []};
- _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
- end;
- List ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- case Mod:set_privacy_list(LUser, LServer, Name, List) of
- {atomic, ok} ->
- NeedDb = is_list_needdb(List),
- ejabberd_sm:route(jid:make(LUser, LServer,
- <<"">>),
- jid:make(LUser, LServer, <<"">>),
- {broadcast, {privacy_list,
- #userlist{name = Name,
- list = List,
- needdb = NeedDb},
- Name}}),
- {result, []};
- _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
- end
+-spec process_lists_set(binary(), binary(), binary(), [privacy_item()],
+ #userlist{}, binary()) -> {error, stanza_error()} |
+ {result, undefined}.
+process_lists_set(_LUser, _LServer, Name, [], #userlist{name = Name}, Lang) ->
+ Txt = <<"Cannot remove active list">>,
+ {error, xmpp:err_conflict(Txt, Lang)};
+process_lists_set(LUser, LServer, Name, [], _UserList, Lang) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:remove_privacy_list(LUser, LServer, Name) of
+ {atomic, conflict} ->
+ Txt = <<"Cannot remove default list">>,
+ {error, xmpp:err_conflict(Txt, Lang)};
+ {atomic, not_found} ->
+ Txt = <<"No privacy list with this name found">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+ {atomic, ok} ->
+ ejabberd_sm:route(jid:make(LUser, LServer,
+ <<"">>),
+ jid:make(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = []},
+ Name}}),
+ {result, undefined};
+ Err ->
+ ?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p",
+ [Name, LUser, LServer, Err]),
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
end;
-process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
- {error, ?ERR_BAD_REQUEST}.
-
-parse_items([]) -> remove;
-parse_items(Els) -> parse_items(Els, []).
-
-parse_items([], Res) ->
- lists:keysort(#listitem.order, Res);
-parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
- children = SubEls}
- | Els],
- Res) ->
- 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;
- Val -> Val
- end,
- Order = case catch jlib:binary_to_integer(element(2,
- SOrder))
- of
- {'EXIT', _} -> false;
- IntVal ->
- if IntVal >= 0 -> IntVal;
- true -> false
- end
+process_lists_set(LUser, LServer, Name, Items, _UserList, Lang) ->
+ case catch lists:map(fun decode_item/1, Items) of
+ {error, Why} ->
+ Txt = xmpp:format_error(Why),
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ List ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:set_privacy_list(LUser, LServer, Name, List) of
+ {atomic, ok} ->
+ NeedDb = is_list_needdb(List),
+ ejabberd_sm:route(jid:make(LUser, LServer,
+ <<"">>),
+ jid:make(LUser, LServer, <<"">>),
+ {broadcast, {privacy_list,
+ #userlist{name = Name,
+ list = List,
+ needdb = NeedDb},
+ Name}}),
+ {result, undefined};
+ Err ->
+ ?ERROR_MSG("failed to set privacy list '~s' "
+ "for user ~s@~s: ~p",
+ [Name, LUser, LServer, Err]),
+ Txt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
+ end
+ end.
+
+-spec decode_item(privacy_item()) -> listitem().
+decode_item(#privacy_item{order = Order,
+ action = Action,
+ type = T,
+ value = V,
+ message = MatchMessage,
+ iq = MatchIQ,
+ presence_in = MatchPresenceIn,
+ presence_out = MatchPresenceOut}) ->
+ Value = try decode_value(T, V)
+ catch _:_ ->
+ throw({error, {bad_attr_value, <<"value">>,
+ <<"item">>, ?NS_PRIVACY}})
end,
- if (Action /= false) and (Order /= false) ->
- I1 = #listitem{action = Action, order = Order},
- I2 = case {Type, Value} of
- {{value, T}, {value, V}} ->
- case T of
- <<"jid">> ->
- case jid:from_string(V) of
- error -> false;
- JID ->
- I1#listitem{type = jid,
- value = jid:tolower(JID)}
- end;
- <<"group">> -> I1#listitem{type = group, value = V};
- <<"subscription">> ->
- case V of
- <<"none">> ->
- I1#listitem{type = subscription,
- value = none};
- <<"both">> ->
- I1#listitem{type = subscription,
- value = both};
- <<"from">> ->
- I1#listitem{type = subscription,
- value = from};
- <<"to">> ->
- I1#listitem{type = subscription, value = to};
- _ -> false
- end
- end;
- {{value, _}, false} -> false;
- _ -> I1
- end,
- case I2 of
- false -> false;
- _ ->
- case parse_matches(I2, fxml:remove_cdata(SubEls)) of
- false -> false;
- I3 -> parse_items(Els, [I3 | Res])
- end
- end;
- true -> false
- end;
-parse_items(_, _Res) -> false.
-
-parse_matches(Item, []) ->
- Item#listitem{match_all = true};
-parse_matches(Item, Els) -> parse_matches1(Item, Els).
-
-parse_matches1(Item, []) -> Item;
-parse_matches1(Item,
- [#xmlel{name = <<"message">>} | Els]) ->
- parse_matches1(Item#listitem{match_message = true},
- Els);
-parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) ->
- parse_matches1(Item#listitem{match_iq = true}, Els);
-parse_matches1(Item,
- [#xmlel{name = <<"presence-in">>} | Els]) ->
- parse_matches1(Item#listitem{match_presence_in = true},
- Els);
-parse_matches1(Item,
- [#xmlel{name = <<"presence-out">>} | Els]) ->
- parse_matches1(Item#listitem{match_presence_out = true},
- Els);
-parse_matches1(_Item, [#xmlel{} | _Els]) -> false.
+ Type = case T of
+ undefined -> none;
+ _ -> T
+ end,
+ ListItem = #listitem{order = Order,
+ action = Action,
+ type = Type,
+ value = Value},
+ if not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
+ ListItem#listitem{match_all = true};
+ true ->
+ ListItem#listitem{match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}
+ end.
+-spec is_list_needdb([listitem()]) -> boolean().
is_list_needdb(Items) ->
lists:any(fun (X) ->
case X#listitem.type of
@@ -435,6 +394,7 @@ is_list_needdb(Items) ->
end,
Items).
+-spec get_user_list(userlist(), binary(), binary()) -> userlist().
get_user_list(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -444,6 +404,7 @@ get_user_list(_Acc, User, Server) ->
#userlist{name = Default, list = Items,
needdb = NeedDb}.
+-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
get_user_lists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -453,6 +414,8 @@ get_user_lists(User, Server) ->
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To).
+-spec check_packet(allow | deny, binary(), binary(), userlist(),
+ {jid(), jid(), stanza()}, in | out) -> allow | deny.
check_packet(_, _User, _Server, _UserList,
{#jid{luser = <<"">>, lserver = Server} = _From,
#jid{lserver = Server} = _To, _},
@@ -470,22 +433,16 @@ check_packet(_, _User, _Server, _UserList,
allow;
check_packet(_, User, Server,
#userlist{list = List, needdb = NeedDb},
- {From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) ->
+ {From, To, Packet}, Dir) ->
case List of
[] -> allow;
_ ->
- PType = case PName of
- <<"message">> -> message;
- <<"iq">> -> iq;
- <<"presence">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- %% notification
- <<"">> -> presence;
- <<"unavailable">> -> presence;
- %% subscribe, subscribed, unsubscribe,
- %% unsubscribed, error, probe, or other
- _ -> other
- end
+ PType = case Packet of
+ #message{} -> message;
+ #iq{} -> iq;
+ #presence{type = available} -> presence;
+ #presence{type = unavailable} -> presence;
+ _ -> other
end,
PType2 = case {PType, Dir} of
{message, in} -> message;
@@ -511,6 +468,10 @@ check_packet(_, User, Server,
Groups)
end.
+-spec check_packet_aux([listitem()],
+ message | iq | presence_in | presence_out | other,
+ ljid(), none | both | from | to, [binary()]) ->
+ allow | deny.
%% Ptype = mesage | iq | presence_in | presence_out | other
check_packet_aux([], _PType, _JID, _Subscription,
_Groups) ->
@@ -521,21 +482,18 @@ check_packet_aux([Item | List], PType, JID,
Item,
case is_ptype_match(Item, PType) of
true ->
- case Type of
- none -> Action;
- _ ->
- case is_type_match(Type, Value, JID, Subscription,
- Groups)
- of
- true -> Action;
- false ->
- check_packet_aux(List, PType, JID, Subscription, Groups)
- end
- end;
+ case is_type_match(Type, Value, JID, Subscription, Groups) of
+ true -> Action;
+ false ->
+ check_packet_aux(List, PType, JID, Subscription, Groups)
+ end;
false ->
check_packet_aux(List, PType, JID, Subscription, Groups)
end.
+-spec is_ptype_match(listitem(),
+ message | iq | presence_in | presence_out | other) ->
+ boolean().
is_ptype_match(Item, PType) ->
case Item#listitem.match_all of
true -> true;
@@ -549,6 +507,10 @@ is_ptype_match(Item, PType) ->
end
end.
+-spec is_type_match(none | jid | subscription | group, listitem_value(),
+ ljid(), none | both | from | to, [binary()]) -> boolean().
+is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
+ true;
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
@@ -569,30 +531,122 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
group -> lists:member(Value, Groups)
end.
+-spec remove_user(binary(), binary()) -> any().
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
+-spec updated_list(userlist(), userlist(), userlist()) -> userlist().
updated_list(_, #userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
if OldName == NewName -> New;
true -> Old
end.
+numeric_to_binary(<<0, 0, _/binary>>) ->
+ <<"0">>;
+numeric_to_binary(<<0, _, _:6/binary, T/binary>>) ->
+ Res = lists:foldl(
+ fun(X, Sum) ->
+ Sum*10000 + X
+ end, 0, [X || <<X:16>> <= T]),
+ integer_to_binary(Res).
+
+bool_to_binary(<<0>>) -> <<"0">>;
+bool_to_binary(<<1>>) -> <<"1">>.
+
+prepare_list_data(mysql, [ID|Row]) ->
+ [binary_to_integer(ID)|Row];
+prepare_list_data(pgsql, [<<ID:64>>,
+ SType, SValue, SAction, SOrder, SMatchAll,
+ SMatchIQ, SMatchMessage, SMatchPresenceIn,
+ SMatchPresenceOut]) ->
+ [ID, SType, SValue, SAction,
+ numeric_to_binary(SOrder),
+ bool_to_binary(SMatchAll),
+ bool_to_binary(SMatchIQ),
+ bool_to_binary(SMatchMessage),
+ bool_to_binary(SMatchPresenceIn),
+ bool_to_binary(SMatchPresenceOut)].
+
+prepare_id(mysql, ID) ->
+ binary_to_integer(ID);
+prepare_id(pgsql, <<ID:32>>) ->
+ ID.
+
+import_info() ->
+ [{<<"privacy_default_list">>, 2},
+ {<<"privacy_list_data">>, 10},
+ {<<"privacy_list">>, 4}].
+
+import_start(LServer, DBType) ->
+ ets:new(privacy_default_list_tmp, [private, named_table]),
+ ets:new(privacy_list_data_tmp, [private, named_table, bag]),
+ ets:new(privacy_list_tmp, [private, named_table, bag,
+ {keypos, #privacy.us}]),
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
+
+import(LServer, {sql, _}, _DBType, <<"privacy_default_list">>, [LUser, Name]) ->
+ US = {LUser, LServer},
+ ets:insert(privacy_default_list_tmp, {US, Name}),
+ ok;
+import(LServer, {sql, SQLType}, _DBType, <<"privacy_list_data">>, Row1) ->
+ [ID|Row] = prepare_list_data(SQLType, Row1),
+ case mod_privacy_sql:raw_to_item(Row) of
+ [Item] ->
+ IS = {ID, LServer},
+ ets:insert(privacy_list_data_tmp, {IS, Item}),
+ ok;
+ [] ->
+ ok
+ end;
+import(LServer, {sql, SQLType}, _DBType, <<"privacy_list">>,
+ [LUser, Name, ID, _TimeStamp]) ->
+ US = {LUser, LServer},
+ IS = {prepare_id(SQLType, ID), LServer},
+ Default = case ets:lookup(privacy_default_list_tmp, US) of
+ [{_, Name}] -> Name;
+ _ -> none
+ end,
+ case [Item || {_, Item} <- ets:lookup(privacy_list_data_tmp, IS)] of
+ [_|_] = Items ->
+ Privacy = #privacy{us = {LUser, LServer},
+ default = Default,
+ lists = [{Name, Items}]},
+ ets:insert(privacy_list_tmp, Privacy),
+ ets:delete(privacy_list_data_tmp, IS),
+ ok;
+ _ ->
+ ok
+ end.
+
+import_stop(_LServer, DBType) ->
+ import_next(DBType, ets:first(privacy_list_tmp)),
+ ets:delete(privacy_default_list_tmp),
+ ets:delete(privacy_list_data_tmp),
+ ets:delete(privacy_list_tmp),
+ ok.
+
+import_next(_DBType, '$end_of_table') ->
+ ok;
+import_next(DBType, US) ->
+ [P|_] = Ps = ets:lookup(privacy_list_tmp, US),
+ Lists = lists:flatmap(
+ fun(#privacy{lists = Lists}) ->
+ Lists
+ end, Ps),
+ Privacy = P#privacy{lists = Lists},
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(Privacy),
+ import_next(DBType, ets:next(privacy_list_tmp, US)).
+
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
-
-import(LServer, DBType, Data) ->
- Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
-
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl
index 4026b7f64..eca6f8ecd 100644
--- a/src/mod_privacy_mnesia.erl
+++ b/src/mod_privacy_mnesia.erl
@@ -15,9 +15,9 @@
process_default_set/3, process_active_set/3,
remove_privacy_list/3, set_privacy_list/1,
set_privacy_list/4, get_user_list/2, get_user_lists/2,
- remove_user/2, import/2]).
+ remove_user/2, import/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
-include("logger.hrl").
@@ -25,7 +25,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(privacy,
+ ejabberd_mnesia:create(?MODULE, privacy,
[{disc_copies, [node()]},
{attributes, record_info(fields, privacy)}]),
update_table().
@@ -35,11 +35,7 @@ process_lists_get(LUser, LServer) ->
{'EXIT', _Reason} -> error;
[] -> {none, []};
[#privacy{default = Default, lists = Lists}] ->
- LItems = lists:map(fun ({N, _}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end, Lists),
+ LItems = lists:map(fun ({N, _}) -> N end, Lists),
{Default, LItems}
end.
@@ -54,7 +50,15 @@ process_list_get(LUser, LServer, Name) ->
end
end.
-process_default_set(LUser, LServer, {value, Name}) ->
+process_default_set(LUser, LServer, none) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> ok;
+ [R] -> mnesia:write(R#privacy{default = none})
+ end
+ end,
+ mnesia:transaction(F);
+process_default_set(LUser, LServer, Name) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] -> not_found;
@@ -68,14 +72,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
end
end
end,
- mnesia:transaction(F);
-process_default_set(LUser, LServer, false) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> ok;
- [R] -> mnesia:write(R#privacy{default = none})
- end
- end,
mnesia:transaction(F).
process_active_set(LUser, LServer, Name) ->
@@ -148,7 +144,7 @@ remove_user(LUser, LServer) ->
F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
mnesia:transaction(F).
-import(_LServer, #privacy{} = P) ->
+import(#privacy{} = P) ->
mnesia:dirty_write(P).
%%%===================================================================
diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl
index 0c43e74f9..b96a08cbc 100644
--- a/src/mod_privacy_riak.erl
+++ b/src/mod_privacy_riak.erl
@@ -15,11 +15,11 @@
process_default_set/3, process_active_set/3,
remove_privacy_list/3, set_privacy_list/1,
set_privacy_list/4, get_user_list/2, get_user_lists/2,
- remove_user/2, import/2]).
+ remove_user/2, import/1]).
-export([privacy_schema/0]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
@@ -31,12 +31,7 @@ init(_Host, _Opts) ->
process_lists_get(LUser, LServer) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} ->
- LItems = lists:map(fun ({N, _}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Lists),
+ LItems = lists:map(fun ({N, _}) -> N end, Lists),
{Default, LItems};
{error, notfound} ->
{none, []};
@@ -57,7 +52,15 @@ process_list_get(LUser, LServer, Name) ->
error
end.
-process_default_set(LUser, LServer, {value, Name}) ->
+process_default_set(LUser, LServer, none) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, R} ->
+ ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
+ {error, _} ->
+ ok
+ end};
+process_default_set(LUser, LServer, Name) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} ->
@@ -71,14 +74,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
end;
{error, _} ->
not_found
- end};
-process_default_set(LUser, LServer, false) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, R} ->
- ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
- {error, _} ->
- ok
end}.
process_active_set(LUser, LServer, Name) ->
@@ -150,7 +145,7 @@ get_user_lists(LUser, LServer) ->
remove_user(LUser, LServer) ->
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}.
-import(_LServer, #privacy{} = P) ->
+import(#privacy{} = P) ->
ejabberd_riak:put(P, privacy_schema()).
%%%===================================================================
diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl
index a700db391..1984237c6 100644
--- a/src/mod_privacy_sql.erl
+++ b/src/mod_privacy_sql.erl
@@ -17,7 +17,7 @@
process_default_set/3, process_active_set/3,
remove_privacy_list/3, set_privacy_list/1,
set_privacy_list/4, get_user_list/2, get_user_lists/2,
- remove_user/2, import/1, import/2, export/1]).
+ remove_user/2, import/1, export/1]).
-export([item_to_raw/1, raw_to_item/1,
sql_add_privacy_list/2,
@@ -28,7 +28,7 @@
sql_get_privacy_list_id_t/2,
sql_set_default_privacy_list/2, sql_set_privacy_list/2]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_privacy.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -47,12 +47,7 @@ process_lists_get(LUser, LServer) ->
end,
case catch sql_get_privacy_list_names(LUser, LServer) of
{selected, Names} ->
- LItems = lists:map(fun ({N}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Names),
+ LItems = lists:map(fun ({N}) -> N end, Names),
{Default, LItems};
_ -> error
end.
@@ -69,7 +64,15 @@ process_list_get(LUser, LServer, Name) ->
_ -> error
end.
-process_default_set(LUser, LServer, {value, Name}) ->
+process_default_set(LUser, LServer, none) ->
+ case catch sql_unset_default_privacy_list(LUser,
+ LServer)
+ of
+ {'EXIT', _Reason} -> {atomic, error};
+ {error, _Reason} -> {atomic, error};
+ _ -> {atomic, ok}
+ end;
+process_default_set(LUser, LServer, Name) ->
F = fun () ->
case sql_get_privacy_list_names_t(LUser) of
{selected, []} -> not_found;
@@ -80,15 +83,7 @@ process_default_set(LUser, LServer, {value, Name}) ->
end
end
end,
- sql_queries:sql_transaction(LServer, F);
-process_default_set(LUser, LServer, false) ->
- case catch sql_unset_default_privacy_list(LUser,
- LServer)
- of
- {'EXIT', _Reason} -> {atomic, error};
- {error, _Reason} -> {atomic, error};
- _ -> {atomic, ok}
- end.
+ sql_queries:sql_transaction(LServer, F).
process_active_set(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
@@ -203,7 +198,7 @@ export(Server) ->
[<<"select id from privacy_list order by "
"id desc limit 1;">>]) of
{selected, [<<"id">>], [[I]]} ->
- put(id, jlib:binary_to_integer(I));
+ put(id, binary_to_integer(I));
_ ->
put(id, 0)
end,
@@ -254,37 +249,8 @@ get_id() ->
put(id, ID + 1),
ID + 1.
-import(LServer) ->
- [{<<"select username from privacy_list;">>,
- fun([LUser]) ->
- Default = case sql_get_default_privacy_list_t(LUser) of
- {selected, [<<"name">>], []} ->
- none;
- {selected, [<<"name">>], [[DefName]]} ->
- DefName;
- _ ->
- none
- end,
- {selected, [<<"name">>], Names} =
- sql_get_privacy_list_names_t(LUser),
- Lists = lists:flatmap(
- fun([Name]) ->
- case sql_get_privacy_list_data_t(LUser, Name) of
- {selected, _, RItems} ->
- [{Name,
- lists:map(fun raw_to_item/1,
- RItems)}];
- _ ->
- []
- end
- end, Names),
- #privacy{default = Default,
- us = {LUser, LServer},
- lists = Lists}
- end}].
-
-import(_, _) ->
- pass.
+import(_) ->
+ ok.
%%%===================================================================
%%% Internal functions
@@ -368,9 +334,6 @@ sql_get_privacy_list_id_t(LUser, Name) ->
sql_get_privacy_list_data(LUser, LServer, Name) ->
sql_queries:get_privacy_list_data(LServer, LUser, Name).
-sql_get_privacy_list_data_t(LUser, Name) ->
- sql_queries:get_privacy_list_data_t(LUser, Name).
-
sql_get_privacy_list_data_by_id(ID, LServer) ->
sql_queries:get_privacy_list_data_by_id(LServer, ID).
diff --git a/src/mod_private.erl b/src/mod_private.erl
index f0e4632f6..d11cad36f 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -31,25 +31,22 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_sm_iq/3, import/3,
- remove_user/2, get_data/2, export/1, import/1,
- mod_opt_type/1, set_data/3, depends/2]).
+-export([start/2, stop/1, process_sm_iq/1, import_info/0,
+ remove_user/2, get_data/2, get_data/3, export/1,
+ import/5, import_start/2, mod_opt_type/1, set_data/3, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_private.hrl").
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #private_storage{}) -> ok | pass.
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error.
-callback get_all_data(binary(), binary()) -> [xmlel()].
-
--define(Xmlel_Query(Attrs, Children),
- #xmlel{name = <<"query">>, attrs = Attrs,
- children = Children}).
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -67,111 +64,80 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVATE).
-process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
- when IQ#iq.type == set ->
- case IQ#iq.sub_el of
- #xmlel{name = <<"query">>, children = Xmlels} ->
- case filter_xmlels(Xmlels) of
- [] ->
- Txt = <<"No private data found in this query">>,
- IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]};
- Data ->
- set_data(LUser, LServer, Data),
- IQ#iq{type = result, sub_el = []}
- end;
- _ ->
- Txt = <<"No query found">>,
- IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}
+-spec process_sm_iq(iq()) -> iq().
+process_sm_iq(#iq{type = Type, lang = Lang,
+ from = #jid{luser = LUser, lserver = LServer},
+ to = #jid{luser = LUser, lserver = LServer},
+ sub_els = [#private{xml_els = Els0}]} = IQ) ->
+ case filter_xmlels(Els0) of
+ [] ->
+ Txt = <<"No private data found in this query">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ Data when Type == set ->
+ set_data(LUser, LServer, Data),
+ xmpp:make_iq_result(IQ);
+ Data when Type == get ->
+ StorageEls = get_data(LUser, LServer, Data),
+ xmpp:make_iq_result(IQ, #private{xml_els = StorageEls})
end;
-%%
-process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
- when IQ#iq.type == get ->
- case IQ#iq.sub_el of
- #xmlel{name = <<"query">>, attrs = Attrs,
- children = Xmlels} ->
- case filter_xmlels(Xmlels) of
- [] ->
- Txt = <<"No private data found in this query">>,
- IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]};
- Data ->
- case catch get_data(LUser, LServer, Data) of
- {'EXIT', _Reason} ->
- Txt = <<"Database failure">>,
- IQ#iq{type = error,
- sub_el =
- [IQ#iq.sub_el, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
- Storage_Xmlels ->
- IQ#iq{type = result,
- sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
- end
- end;
- _ ->
- Txt = <<"No query found">>,
- IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}
- end;
-%%
-process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) ->
+process_sm_iq(#iq{lang = Lang} = IQ) ->
Txt = <<"Query to another users is forbidden">>,
- IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERRT_FORBIDDEN(Lang, Txt)]}.
-
-filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
-
-filter_xmlels([], Data) -> lists:reverse(Data);
-filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
- Data) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- <<"">> -> [];
- XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
- end;
-filter_xmlels([_ | Xmlels], Data) ->
- filter_xmlels(Xmlels, Data).
-
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
+
+-spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}].
+filter_xmlels(Els) ->
+ lists:flatmap(
+ fun(#xmlel{} = El) ->
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
+ <<"">> -> [];
+ NS -> [{NS, El}]
+ end
+ end, Els).
+
+-spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
set_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LUser, LServer, Data).
+-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()].
get_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- get_data(LUser, LServer, Data, Mod, []).
-
-get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) ->
- lists:reverse(Storage_Xmlels);
-get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) ->
- case Mod:get_data(LUser, LServer, XmlNS) of
- {ok, Storage_Xmlel} ->
- get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]);
- error ->
- get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels])
- end.
-
+ lists:map(
+ fun({NS, El}) ->
+ case Mod:get_data(LUser, LServer, NS) of
+ {ok, StorageEl} ->
+ StorageEl;
+ error ->
+ El
+ end
+ end, Data).
+
+-spec get_data(binary(), binary()) -> [xmlel()].
get_data(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_all_data(LUser, LServer).
+-spec remove_user(binary(), binary()) -> any().
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(Server, ?MODULE),
Mod:remove_user(LUser, LServer).
+import_info() ->
+ [{<<"private_storage">>, 4}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
+
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
-
-import(LServer, DBType, PD) ->
+import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, PD).
+ Mod:import(LServer, Tab, L).
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl
index 7a852c4f8..42e5ddfd8 100644
--- a/src/mod_private_mnesia.erl
+++ b/src/mod_private_mnesia.erl
@@ -11,9 +11,9 @@
%% API
-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
- import/2]).
+ import/3]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_private.hrl").
-include("logger.hrl").
@@ -21,7 +21,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(private_storage,
+ ejabberd_mnesia:create(?MODULE, private_storage,
[{disc_only_copies, [node()]},
{attributes,
record_info(fields, private_storage)}]),
@@ -72,7 +72,10 @@ remove_user(LUser, LServer) ->
end,
mnesia:transaction(F).
-import(_LServer, #private_storage{} = PS) ->
+import(LServer, <<"private_storage">>,
+ [LUser, XMLNS, XML, _TimeStamp]) ->
+ El = #xmlel{} = fxml_stream:parse_element(XML),
+ PS = #private_storage{usns = {LUser, LServer, XMLNS}, xml = El},
mnesia:dirty_write(PS).
%%%===================================================================
diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl
index 11cfa4770..7b091c4a6 100644
--- a/src/mod_private_riak.erl
+++ b/src/mod_private_riak.erl
@@ -12,9 +12,9 @@
%% API
-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
- import/2]).
+ import/3]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_private.hrl").
%%%===================================================================
@@ -56,7 +56,10 @@ remove_user(LUser, LServer) ->
{atomic, ejabberd_riak:delete_by_index(private_storage,
<<"us">>, {LUser, LServer})}.
-import(_LServer, #private_storage{usns = {LUser, LServer, _}} = PS) ->
+import(LServer, <<"private_storage">>,
+ [LUser, XMLNS, XML, _TimeStamp]) ->
+ El = #xmlel{} = fxml_stream:parse_element(XML),
+ PS = #private_storage{usns = {LUser, LServer, XMLNS}, xml = El},
ejabberd_riak:put(PS, private_storage_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl
index 0e9d1b61e..b459916e4 100644
--- a/src/mod_private_sql.erl
+++ b/src/mod_private_sql.erl
@@ -12,9 +12,9 @@
%% API
-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
- import/1, import/2, export/1]).
+ import/3, export/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_private.hrl").
%%%===================================================================
@@ -77,16 +77,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, namespace, data from private_storage;">>,
- fun([LUser, XMLNS, XML]) ->
- El = #xmlel{} = fxml_stream:parse_element(XML),
- #private_storage{usns = {LUser, LServer, XMLNS},
- xml = El}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl
index af6dacec4..c1ac5a3fc 100644
--- a/src/mod_privilege.erl
+++ b/src/mod_privilege.erl
@@ -1,363 +1,375 @@
-%%%--------------------------------------------------------------------------------------
+%%%-------------------------------------------------------------------
%%% File : mod_privilege.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
-%%% Purpose : This module is an implementation for XEP-0356: Privileged Entity
-%%%--------------------------------------------------------------------------------------
-
+%%% Purpose : XEP-0356: Privileged Entity
+%%%
+%%%
+%%% 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_privilege).
-author('amuhar3@gmail.com').
-protocol({xep, 0356, '0.2.1'}).
--export([advertise_permissions/1, initial_presences/1, process_presence/1,
- process_roster_presence/1, compare_presences/2,
- process_message/4, process_iq/4]).
-
--include("ejabberd_service.hrl").
-
--include("mod_privacy.hrl").
-
-%%%--------------------------------------------------------------------------------------
-%%% Functions to advertise services of allowed permission
-%%%--------------------------------------------------------------------------------------
-
--spec permissions(binary(), binary(), list()) -> xmlel().
-
-permissions(From, To, PrivAccess) ->
- Perms = lists:map(fun({Access, Type}) ->
- ?DEBUG("Advertise service ~s of allowed permission: ~s = ~s~n",
- [To, Access, Type]),
- #xmlel{name = <<"perm">>,
- attrs = [{<<"access">>,
- atom_to_binary(Access,latin1)},
- {<<"type">>, Type}]}
- end, PrivAccess),
- Stanza = #xmlel{name = <<"privilege">>,
- attrs = [{<<"xmlns">> ,?NS_PRIVILEGE}],
- children = Perms},
- Id = randoms:get_string(),
- #xmlel{name = <<"message">>,
- attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
- children = [Stanza]}.
-
-advertise_permissions(#state{privilege_access = []}) -> ok;
-advertise_permissions(StateData) ->
- Stanza =
- permissions(?MYNAME, StateData#state.host, StateData#state.privilege_access),
- ejabberd_service:send_element(StateData, Stanza).
-
-%%%--------------------------------------------------------------------------------------
-%%% Process presences
-%%%--------------------------------------------------------------------------------------
-
-initial_presences(StateData) ->
- Pids = ejabberd_sm:get_all_pids(),
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% API
+-export([start_link/2]).
+-export([start/2, stop/1, mod_opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([component_connected/1, component_disconnected/2,
+ roster_access/2, process_message/3,
+ process_presence_out/4, process_presence_in/5]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-record(state, {server_host = <<"">> :: binary(),
+ permissions = dict:new() :: ?TDICT}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 2000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, PingSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:call(Proc, stop),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+mod_opt_type(roster) -> v_roster();
+mod_opt_type(message) -> v_message();
+mod_opt_type(presence) -> v_presence();
+mod_opt_type(_) ->
+ [roster, message, presence].
+
+depends(_, _) ->
+ [].
+
+-spec component_connected(binary()) -> ok.
+component_connected(Host) ->
lists:foreach(
- fun(Pid) ->
- {User, Server, Resource, PresenceLast} = ejabberd_c2s:get_last_presence(Pid),
- From = #jid{user = User, server = Server, resource = Resource},
- To = jid:from_string(StateData#state.host),
- PacketNew = jlib:replace_from_to(From, To, PresenceLast),
- ejabberd_service:send_element(StateData, PacketNew)
- end, Pids).
-
-%% hook user_send_packet(Packet, C2SState, From, To) -> Packet
-%% for Managed Entity Presence
-process_presence(Pid) ->
- fun(#xmlel{name = <<"presence">>} = Packet, _C2SState, From, _To) ->
- case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
- T when (T == <<"">>) or (T == <<"unavailable">>) ->
- Pid ! {user_presence, Packet, From};
- _ -> ok
- end,
- Packet;
- (Packet, _C2SState, _From, _To) ->
- Packet
- end.
-%% s2s_receive_packet(From, To, Packet) -> ok
-%% for Roster Presence
-%% From subscription "from" or "both"
-process_roster_presence(Pid) ->
- fun(From, To, #xmlel{name = <<"presence">>} = Packet) ->
- case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
- T when (T == <<"">>) or (T == <<"unavailable">>) ->
- Server = To#jid.server,
- User = To#jid.user,
- PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
- Server, #userlist{}, [User, Server]),
- case privacy_check_packet(Server, User, PrivList, From, To, Packet, in) of
- allow ->
- Pid ! {roster_presence, Packet, From};
- _ -> ok
- end,
- ok;
- _ -> ok
- end;
- (_From, _To, _Packet) -> ok
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_connected, Host})
+ end, ?MYHOSTS).
+
+-spec component_disconnected(binary(), binary()) -> ok.
+component_disconnected(Host, _Reason) ->
+ lists:foreach(
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_disconnected, Host})
+ end, ?MYHOSTS).
+
+-spec process_message(jid(), jid(), stanza()) -> stop | ok.
+process_message(#jid{luser = <<"">>, lresource = <<"">>} = From,
+ #jid{lresource = <<"">>} = To,
+ #message{lang = Lang, type = T} = Msg) when T /= error ->
+ Host = From#jid.lserver,
+ ServerHost = To#jid.lserver,
+ Permissions = get_permissions(ServerHost),
+ case dict:find(Host, Permissions) of
+ {ok, Access} ->
+ case proplists:get_value(message, Access, none) of
+ outgoing ->
+ forward_message(From, To, Msg);
+ none ->
+ Txt = <<"Insufficient privilege">>,
+ Err = xmpp:err_forbidden(Txt, Lang),
+ ejabberd_router:route_error(To, From, Msg, Err)
+ end,
+ stop;
+ error ->
+ %% Component is disconnected
+ ok
+ end;
+process_message(_From, _To, _Stanza) ->
+ ok.
+
+-spec roster_access(boolean(), iq()) -> boolean().
+roster_access(true, _) ->
+ true;
+roster_access(false, #iq{from = From, to = To, type = Type}) ->
+ Host = From#jid.lserver,
+ ServerHost = To#jid.lserver,
+ Permissions = get_permissions(ServerHost),
+ case dict:find(Host, Permissions) of
+ {ok, Access} ->
+ Permission = proplists:get_value(roster, Access, none),
+ (Permission == both)
+ orelse (Permission == get andalso Type == get)
+ orelse (Permission == set andalso Type == set);
+ error ->
+ %% Component is disconnected
+ false
end.
-%%%--------------------------------------------------------------------------------------
-%%% Manage Roster
-%%%--------------------------------------------------------------------------------------
-
-process_iq(StateData, FromJID, ToJID, Packet) ->
- IQ = jlib:iq_query_or_response_info(Packet),
- case IQ of
- #iq{xmlns = ?NS_ROSTER} ->
- case (ToJID#jid.luser /= <<"">>) and
- (FromJID#jid.luser == <<"">>) and
- lists:member(ToJID#jid.lserver, ?MYHOSTS) of
- true ->
- AccessType =
- proplists:get_value(roster, StateData#state.privilege_access, none),
- case IQ#iq.type of
- get when (AccessType == <<"both">>) or (AccessType == <<"get">>) ->
- RosterIQ = roster_management(ToJID, FromJID, IQ),
- ejabberd_service:send_element(StateData, RosterIQ);
- set when (AccessType == <<"both">>) or (AccessType == <<"set">>) ->
- %% check if user ToJID exist
- #jid{lserver = Server, luser = User} = ToJID,
- case ejabberd_auth:is_user_exists(User,Server) of
- true ->
- ResIQ = roster_management(ToJID, FromJID, IQ),
- ejabberd_service:send_element(StateData, ResIQ);
- _ -> ok
- end;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
- ejabberd_service:send_element(StateData, Err)
- end;
- _ ->
- ejabberd_router:route(FromJID, ToJID, Packet)
- end;
- #iq{type = Type, id = Id} when (Type == error) or (Type == result) -> % for XEP-0355
- Hook = {iq, Type, Id},
- Host = ToJID#jid.lserver,
- case (ToJID#jid.luser == <<"">>) and
- (FromJID#jid.luser == <<"">>) and
- lists:member(ToJID#jid.lserver, ?MYHOSTS) of
- true ->
- case ets:lookup(hooks_tmp, {Hook, Host}) of
- [{_, Function, _Timestamp}] ->
- catch apply(Function, [Packet]);
- [] ->
- ejabberd_router:route(FromJID, ToJID, Packet)
- end;
- _ ->
- ejabberd_router:route(FromJID, ToJID, Packet)
- end;
- _ ->
- ejabberd_router:route(FromJID, ToJID, Packet)
+-spec process_presence_out(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
+process_presence_out(#presence{type = Type} = Pres, _C2SState,
+ #jid{luser = LUser, lserver = LServer} = From,
+ #jid{luser = LUser, lserver = LServer, lresource = <<"">>})
+ when Type == available; Type == unavailable ->
+ %% Self-presence processing
+ Permissions = get_permissions(LServer),
+ lists:foreach(
+ fun({Host, Access}) ->
+ Permission = proplists:get_value(presence, Access, none),
+ if Permission == roster; Permission == managed_entity ->
+ To = jid:make(Host),
+ ejabberd_router:route(
+ From, To, xmpp:set_from_to(Pres, From, To));
+ true ->
+ ok
+ end
+ end, dict:to_list(Permissions)),
+ Pres;
+process_presence_out(Acc, _, _, _) ->
+ Acc.
+
+-spec process_presence_in(stanza(), ejabberd_c2s:state(),
+ jid(), jid(), jid()) -> stanza().
+process_presence_in(#presence{type = Type} = Pres, _C2SState, _,
+ #jid{luser = U, lserver = S} = From,
+ #jid{luser = LUser, lserver = LServer})
+ when {U, S} /= {LUser, LServer} andalso
+ (Type == available orelse Type == unavailable) ->
+ Permissions = get_permissions(LServer),
+ lists:foreach(
+ fun({Host, Access}) ->
+ case proplists:get_value(presence, Access, none) of
+ roster ->
+ Permission = proplists:get_value(roster, Access, none),
+ if Permission == both; Permission == get ->
+ To = jid:make(Host),
+ ejabberd_router:route(
+ From, To, xmpp:set_from_to(Pres, From, To));
+ true ->
+ ok
+ end;
+ true ->
+ ok
+ end
+ end, dict:to_list(Permissions)),
+ Pres;
+process_presence_in(Acc, _, _, _, _) ->
+ Acc.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([Host, _Opts]) ->
+ ejabberd_hooks:add(component_connected, ?MODULE,
+ component_connected, 50),
+ ejabberd_hooks:add(component_disconnected, ?MODULE,
+ component_disconnected, 50),
+ ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE,
+ process_message, 50),
+ ejabberd_hooks:add(roster_remote_access, Host, ?MODULE,
+ roster_access, 50),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ process_presence_out, 50),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
+ process_presence_in, 50),
+ {ok, #state{server_host = Host}}.
+
+handle_call(get_permissions, _From, State) ->
+ {reply, {ok, State#state.permissions}, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast({component_connected, Host}, State) ->
+ ServerHost = State#state.server_host,
+ From = jid:make(ServerHost),
+ To = jid:make(Host),
+ RosterPerm = get_roster_permission(ServerHost, Host),
+ PresencePerm = get_presence_permission(ServerHost, Host),
+ MessagePerm = get_message_permission(ServerHost, Host),
+ if RosterPerm /= none, PresencePerm /= none, MessagePerm /= none ->
+ Priv = #privilege{perms = [#privilege_perm{access = message,
+ type = MessagePerm},
+ #privilege_perm{access = roster,
+ type = RosterPerm},
+ #privilege_perm{access = presence,
+ type = PresencePerm}]},
+ ?INFO_MSG("Granting permissions to external "
+ "component '~s': roster = ~s, presence = ~s, "
+ "message = ~s",
+ [Host, RosterPerm, PresencePerm, MessagePerm]),
+ Msg = #message{from = From, to = To, sub_els = [Priv]},
+ ejabberd_router:route(From, To, Msg),
+ Permissions = dict:store(Host, [{roster, RosterPerm},
+ {presence, PresencePerm},
+ {message, MessagePerm}],
+ State#state.permissions),
+ {noreply, State#state{permissions = Permissions}};
+ true ->
+ ?INFO_MSG("Granting no permissions to external component '~s'",
+ [Host]),
+ {noreply, State}
+ end;
+handle_cast({component_disconnected, Host}, State) ->
+ Permissions = dict:erase(Host, State#state.permissions),
+ {noreply, State#state{permissions = Permissions}};
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ %% Note: we don't remove component_* hooks because they are global
+ %% and might be registered within a module on another virtual host
+ Host = State#state.server_host,
+ ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE,
+ process_message, 50),
+ ejabberd_hooks:delete(roster_remote_access, Host, ?MODULE,
+ roster_access, 50),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ process_presence_out, 50),
+ ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
+ process_presence_in, 50).
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+get_permissions(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ try gen_server:call(Proc, get_permissions) of
+ {ok, Permissions} ->
+ Permissions
+ catch exit:{noproc, _} ->
+ %% No module is loaded for this virtual host
+ dict:new()
end.
-roster_management(FromJID, ToJID, IQ) ->
- ResIQ = mod_roster:process_iq(FromJID, FromJID, IQ),
- ResXml = jlib:iq_to_xml(ResIQ),
- jlib:replace_from_to(FromJID, ToJID, ResXml).
-
-%%%--------------------------------------------------------------------------------------
-%%% Message permission
-%%%--------------------------------------------------------------------------------------
-
-process_message(StateData, FromJID, ToJID, #xmlel{children = Children} = Packet) ->
- %% if presence was send from service to server,
- case lists:member(ToJID#jid.lserver, ?MYHOSTS) and
- (ToJID#jid.luser == <<"">>) and
- (FromJID#jid.luser == <<"">>) of %% service
- true ->
- %% if stanza contains privilege element
- case Children of
- [#xmlel{name = <<"privilege">>,
- attrs = [{<<"xmlns">>, ?NS_PRIVILEGE}],
- children = [#xmlel{name = <<"forwarded">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}],
- children = Children2}]}] ->
- %% 1 case : privilege service send subscription message
- %% on behalf of the client
- %% 2 case : privilege service send message on behalf
- %% of the client
- case Children2 of
- %% it isn't case of 0356 extension
- [#xmlel{name = <<"presence">>} = Child] ->
- forward_subscribe(StateData, Child, Packet);
- [#xmlel{name = <<"message">>} = Child] -> %% xep-0356
- forward_message(StateData, Child, Packet);
- _ ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"invalid forwarded element">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_service:send_element(StateData, Err)
- end;
- _ ->
- ejabberd_router:route(FromJID, ToJID, Packet)
- end;
-
- _ ->
- ejabberd_router:route(FromJID, ToJID, Packet)
+forward_message(From, To, Msg) ->
+ ServerHost = To#jid.lserver,
+ Lang = xmpp:get_lang(Msg),
+ case xmpp:get_subtag(Msg, #privilege{}) of
+ #privilege{forwarded = #forwarded{xml_els = [SubEl]}} ->
+ try xmpp:decode(SubEl, ?NS_CLIENT, [ignore_els]) of
+ #message{} = NewMsg ->
+ case NewMsg#message.from of
+ #jid{lresource = <<"">>, lserver = ServerHost} ->
+ ejabberd_router:route(
+ xmpp:get_from(NewMsg), xmpp:get_to(NewMsg), NewMsg);
+ _ ->
+ Lang = xmpp:get_lang(Msg),
+ Txt = <<"Invalid 'from' attribute in forwarded message">>,
+ Err = xmpp:err_forbidden(Txt, Lang),
+ ejabberd_router:route_error(To, From, Msg, Err)
+ end;
+ _ ->
+ Txt = <<"Message not found in forwarded payload">>,
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(To, From, Msg, Err)
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(To, From, Msg, Err)
+ end;
+ _ ->
+ Txt = <<"Invalid <forwarded/> element">>,
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(To, From, Msg, Err)
end.
-forward_subscribe(StateData, Presence, Packet) ->
- PrivAccess = StateData#state.privilege_access,
- T = proplists:get_value(roster, PrivAccess, none),
- Type = fxml:get_attr_s(<<"type">>, Presence#xmlel.attrs),
- if
- ((T == <<"both">>) or (T == <<"set">>)) and (Type == <<"subscribe">>) ->
- From = fxml:get_attr_s(<<"from">>, Presence#xmlel.attrs),
- FromJ = jid:from_string(From),
- To = fxml:get_attr_s(<<"to">>, Presence#xmlel.attrs),
- ToJ = case To of
- <<"">> -> error;
- _ -> jid:from_string(To)
- end,
- if
- (ToJ /= error) and (FromJ /= error) ->
- Server = FromJ#jid.lserver,
- User = FromJ#jid.luser,
- case (FromJ#jid.lresource == <<"">>) and
- lists:member(Server, ?MYHOSTS) of
- true ->
- if
- (Server /= ToJ#jid.lserver) or
- (User /= ToJ#jid.luser) ->
- %% 0356 server MUST NOT allow the privileged entity
- %% to do anything that the managed entity could not do
- try_roster_subscribe(Server,User, FromJ, ToJ, Presence);
- true -> %% we don't want presence sent to self
- ok
- end;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
- ejabberd_service:send_element(StateData, Err)
- end;
- true ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Incorrect stanza from/to JID">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_service:send_element(StateData, Err)
- end;
- true ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
- ejabberd_service:send_element(StateData, Err)
+get_roster_permission(ServerHost, Host) ->
+ Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, roster,
+ v_roster(), []),
+ case match_rule(ServerHost, Host, Perms, both) of
+ allow ->
+ both;
+ deny ->
+ Get = match_rule(ServerHost, Host, Perms, get),
+ Set = match_rule(ServerHost, Host, Perms, set),
+ if Get == allow, Set == allow -> both;
+ Get == allow -> get;
+ Set == allow -> set;
+ true -> none
+ end
end.
-forward_message(StateData, Message, Packet) ->
- PrivAccess = StateData#state.privilege_access,
- T = proplists:get_value(message, PrivAccess, none),
- if
- (T == <<"outgoing">>) ->
- From = fxml:get_attr_s(<<"from">>, Message#xmlel.attrs),
- FromJ = jid:from_string(From),
- To = fxml:get_attr_s(<<"to">>, Message#xmlel.attrs),
- ToJ = case To of
- <<"">> -> FromJ;
- _ -> jid:from_string(To)
- end,
- if
- (ToJ /= error) and (FromJ /= error) ->
- Server = FromJ#jid.server,
- User = FromJ#jid.user,
- case (FromJ#jid.lresource == <<"">>) and
- lists:member(Server, ?MYHOSTS) of
- true ->
- %% there are no restriction on to attribute
- PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
- Server, #userlist{},
- [User, Server]),
- check_privacy_route(Server, User, PrivList,
- FromJ, ToJ, Message);
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
- ejabberd_service:send_element(StateData, Err)
- end;
- true ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- Txt = <<"Incorrect stanza from/to JID">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_service:send_element(StateData, Err)
- end;
- true ->
- Err = jlib:make_error_reply(Packet,?ERR_FORBIDDEN),
- ejabberd_service:send_element(StateData, Err)
+get_message_permission(ServerHost, Host) ->
+ Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, message,
+ v_message(), []),
+ case match_rule(ServerHost, Host, Perms, outgoing) of
+ allow -> outgoing;
+ deny -> none
end.
-%%%--------------------------------------------------------------------------------------
-%%% helper functions
-%%%--------------------------------------------------------------------------------------
-
-compare_presences(undefined, _Presence) -> false;
-compare_presences(#xmlel{attrs = Attrs, children = Child},
- #xmlel{attrs = Attrs2, children = Child2}) ->
- Id1 = fxml:get_attr_s(<<"id">>, Attrs),
- Id2 = fxml:get_attr_s(<<"id">>, Attrs2),
- if
- (Id1 /= Id2) ->
- false;
- (Id1 /= <<"">>) and (Id1 == Id2) ->
- true;
- true ->
- case not compare_attrs(Attrs, Attrs2) of
- true -> false;
- _ ->
- compare_elements(Child, Child2)
- end
+get_presence_permission(ServerHost, Host) ->
+ Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, presence,
+ v_presence(), []),
+ case match_rule(ServerHost, Host, Perms, roster) of
+ allow ->
+ roster;
+ deny ->
+ case match_rule(ServerHost, Host, Perms, managed_entity) of
+ allow -> managed_entity;
+ deny -> none
+ end
end.
+match_rule(ServerHost, Host, Perms, Type) ->
+ Access = proplists:get_value(Type, Perms, none),
+ acl:match_rule(ServerHost, Access, jid:make(Host)).
-compare_elements([],[]) -> true;
-compare_elements(Tags1, Tags2) when length(Tags1) == length(Tags2) ->
- compare_tags(Tags1,Tags2);
-compare_elements(_Tags1, _Tags2) -> false.
-
-compare_tags([],[]) -> true;
-compare_tags([{xmlcdata, CData}|Tags1], [{xmlcdata, CData}|Tags2]) ->
- compare_tags(Tags1, Tags2);
-compare_tags([{xmlcdata, _CData1}|_Tags1], [{xmlcdata, _CData2}|_Tags2]) ->
- false;
-compare_tags([#xmlel{} = Stanza1|Tags1], [#xmlel{} = Stanza2|Tags2]) ->
- case (Stanza1#xmlel.name == Stanza2#xmlel.name) and
- compare_attrs(Stanza1#xmlel.attrs, Stanza2#xmlel.attrs) and
- compare_tags(Stanza1#xmlel.children, Stanza2#xmlel.children) of
- true ->
- compare_tags(Tags1,Tags2);
- false ->
- false
+v_roster() ->
+ fun(Props) ->
+ lists:map(
+ fun({both, ACL}) -> {both, acl:access_rules_validator(ACL)};
+ ({get, ACL}) -> {get, acl:access_rules_validator(ACL)};
+ ({set, ACL}) -> {set, acl:access_rules_validator(ACL)}
+ end, Props)
end.
-%% attr() :: {Name, Value}
--spec compare_attrs([attr()], [attr()]) -> boolean().
-compare_attrs([],[]) -> true;
-compare_attrs(Attrs1, Attrs2) when length(Attrs1) == length(Attrs2) ->
- lists:foldl(fun(Attr,Acc) -> lists:member(Attr, Attrs2) and Acc end, true, Attrs1);
-compare_attrs(_Attrs1, _Attrs2) -> false.
-
-%% Check if privacy rules allow this delivery
-%% from ejabberd_c2s.erl
-privacy_check_packet(Server, User, PrivList, From, To, Packet , Dir) ->
- ejabberd_hooks:run_fold(privacy_check_packet,
- Server, allow, [User, Server, PrivList,
- {From, To, Packet}, Dir]).
-
-check_privacy_route(Server, User, PrivList, From, To, Packet) ->
- case privacy_check_packet(Server, User, PrivList, From, To, Packet, out) of
- allow ->
- ejabberd_router:route(From, To, Packet);
- _ -> ok %% who should receive error : service or user?
+v_message() ->
+ fun(Props) ->
+ lists:map(
+ fun({outgoing, ACL}) -> {outgoing, acl:access_rules_validator(ACL)}
+ end, Props)
end.
-try_roster_subscribe(Server,User, From, To, Packet) ->
- 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, From) of
- deny ->
- ok;
- allow ->
- ejabberd_hooks:run(roster_out_subscription, Server,
- [User, Server, To, subscribe]),
- PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
- Server,
- #userlist{},
- [User, Server]),
- check_privacy_route(Server, User, PrivList, From, To, Packet)
+v_presence() ->
+ fun(Props) ->
+ lists:map(
+ fun({managed_entity, ACL}) ->
+ {managed_entity, acl:access_rules_validator(ACL)};
+ ({roster, ACL}) ->
+ {roster, acl:access_rules_validator(ACL)}
+ end, Props)
end.
diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl
index beea35725..2d0d9ae0a 100644
--- a/src/mod_proxy65.erl
+++ b/src/mod_proxy65.erl
@@ -93,12 +93,10 @@ mod_opt_type(auth_type) ->
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(shaper) -> fun acl:shaper_rules_validator/1;
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(access) -> fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hostname) -> fun iolist_to_binary/1;
mod_opt_type(ip) ->
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 7db6f9da2..0f69086e0 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -33,24 +33,17 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
--export([start_link/2, add_listener/2,
+-export([start_link/2, add_listener/2, process_disco_info/1,
+ process_disco_items/1, process_vcard/1, process_bytestreams/1,
transform_module_options/1, delete_listener/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PROCNAME, ejabberd_mod_proxy65_service).
--record(state,
- {myhost = <<"">> :: binary(),
- serverhost = <<"">> :: binary(),
- name = <<"">> :: binary(),
- stream_addr = [] :: [attr()],
- port = 0 :: inet:port_number(),
- ip = {127,0,0,1} :: inet:ip_address(),
- acl = none :: atom()}).
+-record(state, {myhost = <<"">> :: binary()}).
%%%------------------------
%%% gen_server callbacks
@@ -62,34 +55,32 @@ start_link(Host, Opts) ->
[Host, Opts], []).
init([Host, Opts]) ->
- State = parse_options(Host, Opts),
- ejabberd_router:register_route(State#state.myhost, Host),
- {ok, State}.
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
+ ?MODULE, process_bytestreams, IQDisc),
+ ejabberd_router:register_route(MyHost, Host),
+ {ok, #state{myhost = MyHost}}.
terminate(_Reason, #state{myhost = MyHost}) ->
- ejabberd_router:unregister_route(MyHost), ok.
-
-handle_info({route, From, To,
- #xmlel{name = <<"iq">>} = 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);
- _ -> ok
- end,
+ ejabberd_router:unregister_route(MyHost),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS).
+
+handle_info({route, From, To, #iq{} = Packet}, State) ->
+ ejabberd_router:process_iq(From, To, Packet),
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
-handle_call(get_port_ip, _From, State) ->
- {reply, {port_ip, State#state.port, State#state.ip},
- State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -102,185 +93,116 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%------------------------
add_listener(Host, Opts) ->
- State = parse_options(Host, Opts),
NewOpts = [Host | Opts],
- ejabberd_listener:add_listener({State#state.port,
- State#state.ip},
+ ejabberd_listener:add_listener(get_port_ip(Host),
mod_proxy65_stream, NewOpts).
delete_listener(Host) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- {port_ip, Port, IP} = gen_server:call(Proc,
- get_port_ip),
- catch ejabberd_listener:delete_listener({Port, IP},
+ catch ejabberd_listener:delete_listener(get_port_ip(Host),
mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
%%%------------------------
-
-%% disco#info request
-process_iq(_,
- #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} =
- IQ,
- #state{name = Name, serverhost = ServerHost}) ->
- Info = 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(Lang, Name) ++ Info}]};
-%% disco#items request
-process_iq(_,
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
- children = []}]};
-%% vCard request
-process_iq(_,
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ,
- _) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_vcard(Lang)}]};
-%% bytestreams info request
-process_iq(JID,
- #iq{type = get, sub_el = SubEl, lang = Lang,
- xmlns = ?NS_BYTESTREAMS} =
- IQ,
- #state{acl = ACL, stream_addr = StreamAddr,
- serverhost = ServerHost}) ->
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) ->
+ Host = ejabberd_router:host_of_route(To#jid.lserver),
+ Name = gen_mod:get_module_opt(Host, mod_proxy65, name,
+ fun iolist_to_binary/1,
+ <<"SOCKS5 Bytestreams">>),
+ Info = ejabberd_hooks:run_fold(disco_info, Host,
+ [], [Host, ?MODULE, <<"">>, <<"">>]),
+ xmpp:make_iq_result(
+ IQ, #disco_info{xdata = Info,
+ identities = [#identity{category = <<"proxy">>,
+ type = <<"bytestreams">>,
+ name = translate:translate(Lang, Name)}],
+ features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_VCARD, ?NS_BYTESTREAMS]}).
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get} = IQ) ->
+ xmpp:make_iq_result(IQ, #disco_items{}).
+
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{type = get, lang = Lang} = IQ) ->
+ Desc = translate:translate(Lang, <<"ejabberd SOCKS5 Bytestreams module">>),
+ xmpp:make_iq_result(
+ IQ, #vcard_temp{fn = <<"ejabberd/mod_proxy65">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>}).
+
+-spec process_bytestreams(iq()) -> iq().
+process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access,
+ fun acl:access_rules_validator/1,
+ all),
case acl:match_rule(ServerHost, ACL, JID) of
- allow ->
- StreamHostEl = [#xmlel{name = <<"streamhost">>,
- attrs = StreamAddr, children = []}],
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
- children = StreamHostEl}]};
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
+ allow ->
+ StreamHost = get_streamhost(Host, ServerHost),
+ xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
+ deny ->
+ xmpp:make_error(IQ, xmpp:err_forbidden(<<"Denied by ACL">>, Lang))
end;
-%% bytestream activation request
-process_iq(InitiatorJID,
- #iq{type = set, sub_el = SubEl, lang = Lang,
- xmlns = ?NS_BYTESTREAMS} =
- IQ,
- #state{acl = ACL, serverhost = ServerHost}) ->
+process_bytestreams(#iq{type = set, lang = Lang,
+ sub_els = [#bytestreams{sid = SID}]} = IQ)
+ when SID == <<"">> orelse length(SID) > 128 ->
+ Why = {bad_attr_value, <<"sid">>, <<"query">>, ?NS_BYTESTREAMS},
+ Txt = xmpp:format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+process_bytestreams(#iq{type = set, lang = Lang,
+ sub_els = [#bytestreams{activate = undefined}]} = IQ) ->
+ Why = {missing_cdata, <<"">>, <<"activate">>, ?NS_BYTESTREAMS},
+ Txt = xmpp:format_error(Why),
+ xmpp:make_error(IQ, xmpp:err_jid_malformed(Txt, Lang));
+process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
+ sub_els = [#bytestreams{activate = TargetJID,
+ sid = SID}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access,
+ fun acl:access_rules_validator/1,
+ all),
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
- allow ->
- ActivateEl = fxml:get_path_s(SubEl,
- [{elem, <<"activate">>}]),
- SID = fxml:get_tag_attr_s(<<"sid">>, SubEl),
- case catch
- jid:from_string(fxml:get_tag_cdata(ActivateEl))
- of
- TargetJID
- when is_record(TargetJID, jid), SID /= <<"">>,
- byte_size(SID) =< 128, TargetJID /= InitiatorJID ->
- Target =
- jid:to_string(jid:tolower(TargetJID)),
- Initiator =
- 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)
- of
- ok -> IQ#iq{type = result, sub_el = []};
- false ->
- Txt = <<"Failed to activate bytestream">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
- limit ->
- Txt = <<"Too many active bytestreams">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)]};
- conflict ->
- Txt = <<"Bytestream already activated">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_CONFLICT(Lang, Txt)]};
- _ ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- _ ->
- Txt = <<"Malformed JID">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end;
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
- end;
-%% Unknown "set" or "get" request
-process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
- when Type == get; Type == set ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
-%% IQ "result" or "error".
-process_iq(_, _, _) -> ok.
-
+ allow ->
+ Target = jid:to_string(jid:tolower(TargetJID)),
+ Initiator = 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) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ false ->
+ Txt = <<"Failed to activate bytestream">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
+ limit ->
+ Txt = <<"Too many active bytestreams">>,
+ xmpp:make_error(IQ, xmpp:err_resource_constraint(Txt, Lang));
+ conflict ->
+ Txt = <<"Bytestream already activated">>,
+ xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
+ Err ->
+ ?ERROR_MSG("failed to activate bytestream from ~s to ~s: ~p",
+ [Initiator, Target, Err]),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error())
+ end;
+ deny ->
+ Txt = <<"Denied by ACL">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
+ end.
%%%-------------------------
%%% Auxiliary functions.
%%%-------------------------
--define(FEATURE(Feat),
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, Feat}], children = []}).
-
-iq_disco_info(Lang, Name) ->
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"proxy">>},
- {<<"type">>, <<"bytestreams">>},
- {<<"name">>, translate:translate(Lang, Name)}],
- children = []},
- ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)),
- ?FEATURE((?NS_BYTESTREAMS))].
-
-iq_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd SOCKS5 Bytestreams module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
-parse_options(ServerHost, Opts) ->
- MyHost = gen_mod:get_opt_host(ServerHost, Opts,
- <<"proxy.@HOST@">>),
- Port = gen_mod:get_opt(port, Opts,
- fun(P) when is_integer(P), P>0, P<65536 -> P end,
- 7777),
- ACL = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1,
- all),
- Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
- <<"SOCKS5 Bytestreams">>),
- IP = gen_mod:get_opt(ip, Opts,
- fun(S) ->
- {ok, Addr} = inet_parse:address(
- binary_to_list(
- iolist_to_binary(S))),
- Addr
- end, get_my_ip()),
- HostName = gen_mod:get_opt(hostname, Opts,
- fun iolist_to_binary/1,
- jlib:ip_to_list(IP)),
- StreamAddr = [{<<"jid">>, MyHost},
- {<<"host">>, HostName},
- {<<"port">>, jlib:integer_to_binary(Port)}],
- #state{myhost = MyHost, serverhost = ServerHost,
- name = Name, port = Port, ip = IP,
- stream_addr = StreamAddr, acl = ACL}.
-
transform_module_options(Opts) ->
lists:map(
fun({ip, IP}) when is_tuple(IP) ->
@@ -291,6 +213,33 @@ transform_module_options(Opts) ->
Opt
end, Opts).
+-spec get_streamhost(binary(), binary()) -> streamhost().
+get_streamhost(Host, ServerHost) ->
+ {Port, IP} = get_port_ip(ServerHost),
+ HostName = gen_mod:get_module_opt(ServerHost, mod_proxy65, hostname,
+ fun iolist_to_binary/1,
+ jlib:ip_to_list(IP)),
+ #streamhost{jid = jid:make(Host),
+ host = HostName,
+ port = Port}.
+
+-spec get_port_ip(binary()) -> {pos_integer(), inet:ip_address()}.
+get_port_ip(Host) ->
+ Port = gen_mod:get_module_opt(Host, mod_proxy65, port,
+ fun(P) when is_integer(P), P>0, P<65536 ->
+ P
+ end,
+ 7777),
+ IP = gen_mod:get_module_opt(Host, mod_proxy65, ip,
+ fun(S) ->
+ {ok, Addr} = inet_parse:address(
+ binary_to_list(
+ iolist_to_binary(S))),
+ Addr
+ end, get_my_ip()),
+ {Port, IP}.
+
+-spec get_my_ip() -> inet:ip_address().
get_my_ip() ->
{ok, MyHostName} = inet:gethostname(),
case inet:getaddr(MyHostName, inet) of
diff --git a/src/mod_proxy65_sm.erl b/src/mod_proxy65_sm.erl
index d86b06c4b..b1d33b5d9 100644
--- a/src/mod_proxy65_sm.erl
+++ b/src/mod_proxy65_sm.erl
@@ -38,14 +38,12 @@
-record(state, {max_connections = infinity :: non_neg_integer() | infinity}).
--include("jlib.hrl").
-
-record(bytestream,
{sha1 = <<"">> :: binary() | '$1',
target :: pid() | '_',
initiator :: pid() | '_',
active = false :: boolean() | '_',
- jid_i = {<<"">>, <<"">>, <<"">>} :: ljid() | '_'}).
+ jid_i = {<<"">>, <<"">>, <<"">>} :: jid:ljid() | '_'}).
-define(PROCNAME, ejabberd_mod_proxy65_sm).
@@ -64,7 +62,7 @@ start_link(Host, Opts) ->
[]).
init([Opts]) ->
- mnesia:create_table(bytestream,
+ ejabberd_mnesia:create(?MODULE, bytestream,
[{ram_copies, [node()]},
{attributes, record_info(fields, bytestream)}]),
mnesia:add_table_copy(bytestream, node(), ram_copies),
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 31170bc74..717796fec 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -41,8 +41,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("adhoc.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("pubsub.hrl").
-define(STDTREE, <<"tree">>).
@@ -58,7 +57,9 @@
disco_sm_features/5, disco_sm_items/5]).
%% exported iq handlers
--export([iq_sm/3]).
+-export([iq_sm/1, process_disco_info/1, process_disco_items/1,
+ process_pubsub/1, process_pubsub_owner/1, process_vcard/1,
+ process_commands/1]).
%% exports for console debug manual use
-export([create_node/5, create_node/7, delete_node/3,
@@ -68,12 +69,21 @@
tree_action/3, node_action/4, node_call/4]).
%% general helpers for plugins
--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,
+-export([extended_error/2, service_jid/1,
tree/1, tree/2, plugin/2, plugins/1, config/3,
host/1, serverhost/1]).
+%% pubsub#errors
+-export([err_closed_node/0, err_configuration_required/0,
+ err_invalid_jid/0, err_invalid_options/0, err_invalid_payload/0,
+ err_invalid_subid/0, err_item_forbidden/0, err_item_required/0,
+ err_jid_required/0, err_max_items_exceeded/0, err_max_nodes_exceeded/0,
+ err_nodeid_required/0, err_not_in_roster_group/0, err_not_subscribed/0,
+ err_payload_too_big/0, err_payload_required/0,
+ err_pending_subscription/0, err_presence_subscription_required/0,
+ err_subid_required/0, err_too_many_subscriptions/0, err_unsupported/1,
+ err_unsupported_access_model/0]).
+
%% API and gen_server callbacks
-export([start_link/2, start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
@@ -241,7 +251,7 @@ init([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),
+ fun acl:access_rules_validator/1, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
fun(A) when is_boolean(A) -> A end, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts,
@@ -252,13 +262,13 @@ init([ServerHost, 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),
- pubsub_index:init(Host, ServerHost, Opts),
+ [pubsub_index:init(Host, ServerHost, Opts) || gen_mod:db_type(ServerHost, ?MODULE)==mnesia],
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
DefaultModule = plugin(Host, hd(Plugins)),
BaseOptions = DefaultModule:options(),
DefaultNodeCfg = gen_mod:get_opt(default_node_config, Opts,
fun(A) when is_list(A) -> filter_node_options(A, BaseOptions) end, []),
- mnesia:create_table(pubsub_last_item,
+ ejabberd_mnesia:create(?MODULE, pubsub_last_item,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
@@ -295,6 +305,18 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, ServerHost,
?MODULE, remove_user, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
+ ?MODULE, process_pubsub, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
+ ?MODULE, process_pubsub_owner, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
+ ?MODULE, process_commands, IQDisc),
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:add(caps_add, ServerHost,
@@ -487,27 +509,21 @@ send_loop(State) ->
%% disco hooks handling functions
%%
--spec disco_local_identity(Acc :: [xmlel()], _From :: jid(),
- To :: jid(), Node :: <<>> | mod_pubsub:nodeId(),
- Lang :: binary()) -> [xmlel()].
-
+-spec disco_local_identity([identity()], jid(), jid(),
+ binary(), binary()) -> [identity()].
disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of
true ->
- [#xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, <<"pep">>}]}
- | Acc];
+ [#identity{category = <<"pubsub">>, type = <<"pep">>} | Acc];
false ->
Acc
end;
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
--spec disco_local_features(Acc :: [xmlel()], _From :: jid(),
- To :: jid(), Node :: <<>> | mod_pubsub:nodeId(),
- Lang :: binary()) -> [binary(),...].
-
+-spec disco_local_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]} | empty.
disco_local_features(Acc, _From, To, <<>>, _Lang) ->
Host = host(To#jid.lserver),
Feats = case Acc of
@@ -518,88 +534,83 @@ disco_local_features(Acc, _From, To, <<>>, _Lang) ->
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
+-spec disco_local_items({error, stanza_error()} | {result, [disco_item()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [disco_item()]} | empty.
disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc;
disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc.
-%disco_sm_identity(Acc, From, To, Node, Lang)
-% when is_binary(Node) ->
-% disco_sm_identity(Acc, From, To, iolist_to_binary(Node),
-% Lang);
--spec disco_sm_identity(Acc :: empty | [xmlel()], From :: jid(),
- To :: jid(), Node :: mod_pubsub:nodeId(),
- Lang :: binary()) -> [xmlel()].
-
-disco_sm_identity(empty, From, To, Node, Lang) ->
- disco_sm_identity([], From, To, Node, Lang);
+-spec disco_sm_identity([identity()], jid(), jid(),
+ binary(), binary()) -> [identity()].
disco_sm_identity(Acc, From, To, Node, _Lang) ->
disco_identity(jid:tolower(jid:remove_resource(To)), Node, From)
++ Acc.
+-spec disco_identity(binary(), binary(), jid()) -> [identity()].
disco_identity(_Host, <<>>, _From) ->
- [#xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, <<"pep">>}]}];
+ [#identity{category = <<"pubsub">>, type = <<"pep">>}];
disco_identity(Host, Node, From) ->
- Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
- {result, _} ->
- {result, [#xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, <<"pep">>}]},
- #xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, <<"leaf">>}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{<<"name">>, Title}]
- end]}]};
- _ ->
- {result, []}
- end
- end,
+ Action =
+ fun(#pubsub_node{id = Nidx, type = Type,
+ options = Options, owners = O}) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case get_allowed_items_call(Host, Nidx, From, Type,
+ Options, Owners) of
+ {result, _} ->
+ {result, [#identity{category = <<"pubsub">>,
+ type = <<"pep">>},
+ #identity{category = <<"pubsub">>,
+ type = <<"leaf">>,
+ name = case get_option(Options, title) of
+ false -> <<>>;
+ [Title] -> Title
+ end}]};
+ _ ->
+ {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> Result;
_ -> []
end.
--spec disco_sm_features(Acc :: empty | {result, Features::[Feature::binary()]},
- From :: jid(), To :: jid(), Node :: mod_pubsub:nodeId(),
- Lang :: binary()) -> {result, Features::[Feature::binary()]}.
-%disco_sm_features(Acc, From, To, Node, Lang)
-% when is_binary(Node) ->
-% disco_sm_features(Acc, From, To, iolist_to_binary(Node),
-% Lang);
+-spec disco_sm_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) ->
{result,
- OtherFeatures ++
- disco_features(jid:tolower(jid:remove_resource(To)), Node, From)};
+ OtherFeatures ++
+ disco_features(jid:tolower(jid:remove_resource(To)), Node, From)};
disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
+-spec disco_features(ljid(), binary(), jid()) -> [binary()].
disco_features(Host, <<>>, _From) ->
[?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]];
disco_features(Host, Node, From) ->
- Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
- {result, _} -> {result, [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
- _ -> {result, []}
- end
- end,
+ Action =
+ fun(#pubsub_node{id = Nidx, type = Type,
+ options = Options, owners = O}) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case get_allowed_items_call(Host, Nidx, From,
+ Type, Options, Owners) of
+ {result, _} ->
+ {result,
+ [?NS_PUBSUB |
+ [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
+ _ ->
+ {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> Result;
_ -> []
end.
--spec disco_sm_items(Acc :: empty | {result, [xmlel()]}, From :: jid(),
- To :: jid(), Node :: mod_pubsub:nodeId(),
- Lang :: binary()) -> {result, [xmlel()]}.
-%disco_sm_items(Acc, From, To, Node, Lang)
-% when is_binary(Node) ->
-% disco_sm_items(Acc, From, To, iolist_to_binary(Node),
-% Lang);
+-spec disco_sm_items({error, stanza_error()} | {result, [disco_item()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [disco_item()]}.
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) ->
@@ -607,48 +618,48 @@ disco_sm_items({result, OtherItems}, From, To, Node, _Lang) ->
disco_items(jid:tolower(jid:remove_resource(To)), Node, From))};
disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc.
--spec disco_items(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- From :: jid()) -> [xmlel()].
+-spec disco_items(ljid(), binary(), jid()) -> [disco_item()].
disco_items(Host, <<>>, From) ->
- Action = fun (#pubsub_node{nodeid = {_, Node},
- options = Options, type = Type, id = Nidx, owners = O},
- Acc) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
- {result, _} ->
- [#xmlel{name = <<"item">>,
- attrs = [{<<"node">>, (Node)},
- {<<"jid">>, jid:to_string(Host)}
- | case get_option(Options, title) of
- false -> [];
- [Title] -> [{<<"name">>, Title}]
- end]}
- | Acc];
- _ ->
- Acc
- end
- end,
+ Action =
+ fun(#pubsub_node{nodeid = {_, Node}, options = Options,
+ type = Type, id = Nidx, owners = O}, Acc) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case get_allowed_items_call(Host, Nidx, From,
+ Type, Options, Owners) of
+ {result, _} ->
+ [#disco_item{node = Node,
+ jid = jid:make(Host),
+ name = case get_option(Options, title) of
+ false -> <<>>;
+ [Title] -> Title
+ end} | Acc];
+ _ ->
+ Acc
+ end
+ end,
NodeBloc = fun() ->
- {result,
- lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))}
- end,
+ {result,
+ lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))}
+ end,
case transaction(Host, NodeBloc, sync_dirty) of
{result, Items} -> Items;
_ -> []
end;
disco_items(Host, Node, From) ->
- Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
- {result, Items} ->
- {result, [#xmlel{name = <<"item">>,
- attrs = [{<<"jid">>, jid:to_string(Host)},
- {<<"name">>, ItemId}]}
- || #pubsub_item{itemid = {ItemId, _}} <- Items]};
- _ ->
- {result, []}
- end
- end,
+ Action =
+ fun(#pubsub_node{id = Nidx, type = Type,
+ options = Options, owners = O}) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case get_allowed_items_call(Host, Nidx, From,
+ Type, Options, Owners) of
+ {result, Items} ->
+ {result, [#disco_item{jid = jid:make(Host),
+ name = ItemId}
+ || #pubsub_item{itemid = {ItemId, _}} <- Items]};
+ _ ->
+ {result, []}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> Result;
_ -> []
@@ -658,6 +669,7 @@ disco_items(Host, Node, From) ->
%% presence hooks handling functions
%%
+-spec caps_add(jid(), jid(), [binary()]) -> ok.
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
@@ -673,9 +685,11 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID
caps_add(_From, _To, _Feature) ->
ok.
+-spec caps_update(jid(), jid(), [binary()]) -> ok.
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
presence(Host, {presence, U, S, [R], JID}).
+-spec presence_probe(jid(), jid(), pid()) -> ok.
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
presence(S, {presence, JID, Pid}),
presence(S, {presence, U, S, [R], JID});
@@ -695,12 +709,16 @@ presence(ServerHost, Presence) ->
undefined -> init_send_loop(ServerHost);
Pid -> {Pid, undefined}
end,
- SendLoop ! Presence.
+ SendLoop ! Presence,
+ ok.
%% -------
%% subscription hooks handling functions
%%
+-spec out_subscription(
+ binary(), binary(), jid(),
+ subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, subscribed) ->
Owner = jid:make(User, Server, <<>>),
{PUser, PServer, PResource} = jid:tolower(JID),
@@ -713,6 +731,9 @@ out_subscription(User, Server, JID, subscribed) ->
out_subscription(_, _, _, _) ->
true.
+-spec in_subscription(boolean(), binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> true.
in_subscription(_, User, Server, Owner, unsubscribed, _) ->
unsubscribe_user(jid:make(User, Server, <<>>), Owner),
true;
@@ -761,6 +782,7 @@ unsubscribe_user(Host, Entity, Owner) ->
%% user remove hook handling function
%%
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -800,7 +822,8 @@ remove_user(User, Server) ->
Affs)
end,
plugins(Host))
- end).
+ end),
+ ok.
handle_call(server_host, _From, State) ->
{reply, State#state.server_host, State};
@@ -822,9 +845,6 @@ handle_call(stop, _From, State) ->
%% @private
handle_cast(_Msg, State) -> {noreply, State}.
--spec handle_info(_ :: {route, From::jid(), To::jid(), Packet::xmlel()},
- State :: state()) -> {noreply, state()}.
-
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@@ -832,9 +852,12 @@ handle_cast(_Msg, State) -> {noreply, State}.
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
%% @private
-handle_info({route, From, To, Packet},
- #state{server_host = ServerHost, access = Access, plugins = Plugins} = State) ->
- case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of
+handle_info({route, From, To, #iq{} = IQ},
+ State) when To#jid.lresource == <<"">> ->
+ ejabberd_router:process_iq(From, To, IQ),
+ {noreply, State};
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(To#jid.lserver, From, To, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok
end,
@@ -889,6 +912,12 @@ terminate(_Reason,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, ServerHost,
?MODULE, remove_user, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB),
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
undefined ->
@@ -906,187 +935,166 @@ terminate(_Reason,
%% @private
code_change(_OldVsn, State, _Extra) -> {ok, State}.
--spec do_route(ServerHost :: binary(), Access :: atom(),
- Plugins :: [binary(),...], Host :: mod_pubsub:hostPubsub(),
- From :: jid(), To :: jid(), Packet :: xmlel()) -> ok.
-
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
- #xmlel{name = Name, attrs = Attrs} = Packet,
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{from = From, to = To, lang = Lang, type = get,
+ sub_els = [#disco_info{node = Node}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
+ [],
+ [ServerHost, ?MODULE, <<>>, <<>>]),
+ case iq_disco_info(Host, Node, From, Lang) of
+ {result, IQRes} ->
+ xmpp:make_iq_result(IQ, IQRes#disco_info{node = Node, xdata = Info});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end.
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get, from = From, to = To,
+ sub_els = [#disco_items{node = Node} = SubEl]} = IQ) ->
+ Host = To#jid.lserver,
+ case iq_disco_items(Host, Node, From, SubEl#disco_items.rsm) of
+ {result, IQRes} ->
+ xmpp:make_iq_result(IQ, IQRes#disco_items{node = Node});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end.
+
+-spec process_pubsub(iq()) -> iq().
+process_pubsub(#iq{to = To} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ Access = config(ServerHost, access),
+ case iq_pubsub(Host, Access, IQ) of
+ {result, IQRes} ->
+ xmpp:make_iq_result(IQ, IQRes);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end.
+
+-spec process_pubsub_owner(iq()) -> iq().
+process_pubsub_owner(#iq{to = To} = IQ) ->
+ Host = To#jid.lserver,
+ case iq_pubsub_owner(Host, IQ) of
+ {result, IQRes} ->
+ xmpp:make_iq_result(IQ, IQRes);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end.
+
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = get, lang = Lang} = IQ) ->
+ xmpp:make_iq_result(IQ, iq_get_vcard(Lang));
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
+
+-spec process_commands(iq()) -> iq().
+process_commands(#iq{type = set, to = To, from = From,
+ sub_els = [#adhoc_command{} = Request]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ Plugins = config(ServerHost, plugins),
+ Access = config(ServerHost, access),
+ case adhoc_request(Host, ServerHost, From, Request, Access, Plugins) of
+ {error, Error} ->
+ xmpp:make_error(IQ, Error);
+ Response ->
+ xmpp:make_iq_result(
+ IQ, xmpp_util:make_adhoc_response(Request, Response))
+ end;
+process_commands(#iq{type = get, lang = Lang} = IQ) ->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
+
+-spec do_route(binary(), jid(), jid(), stanza()) -> ok.
+do_route(Host, From, To, Packet) ->
case To of
#jid{luser = <<>>, lresource = <<>>} ->
- case Name of
- <<"iq">> ->
- 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 = fxml:get_attr_s(<<"node">>, QAttrs),
- Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
- [],
- [ServerHost, ?MODULE, <<>>, <<>>]),
- Res = case iq_disco_info(Host, Node, From, Lang) of
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = QAttrs,
- children = IQRes ++ Info}]});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ ->
- #xmlel{attrs = QAttrs} = SubEl,
- 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,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = QAttrs,
- children = IQRes}]})
- %{error, Error} ->
- % jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, sub_el = SubEl} = IQ ->
- Res = case iq_pubsub(Host, ServerHost, From, IQType,
- SubEl, Lang, Access, Plugins)
- of
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, lang = Lang, sub_el = SubEl} = IQ ->
- Res = case iq_pubsub_owner(Host, ServerHost, From,
- IQType, SubEl, Lang)
- of
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes});
- {error, Error} ->
- jlib:make_error_reply(Packet, Error)
- end,
- ejabberd_router:route(To, From, Res);
- #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, lang = Lang, sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
- Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of
- {error, Error} ->
- jlib:make_error_reply(Packet, Error);
- {result, IQRes} ->
- jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes})
- end,
- ejabberd_router:route(To, From, Res);
- #iq{} ->
- Err = jlib:make_error_reply(Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
- end;
- <<"message">> ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> ->
+ case Packet of
+ #message{type = T} when T /= error ->
+ case find_authorization_response(Packet) of
+ undefined ->
ok;
- _ ->
- case find_authorization_response(Packet) of
- none ->
- ok;
- invalid ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
- Txt = <<"Incorrect authorization response">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- XFields ->
- handle_authorization_response(Host, From, To, Packet, XFields)
- end
+ {error, Err} ->
+ ejabberd_router:route_error(To, From, Packet, Err);
+ AuthResponse ->
+ handle_authorization_response(
+ Host, From, To, Packet, AuthResponse)
end;
_ ->
- ok
+ Err = xmpp:err_service_unavailable(),
+ ejabberd_router:route_error(To, From, Packet, Err)
end;
_ ->
- 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
+ Err = xmpp:err_item_not_found(),
+ ejabberd_router:route_error(To, From, Packet, Err)
end.
+-spec command_disco_info(binary(), binary(), jid()) -> {result, disco_info()}.
command_disco_info(_Host, ?NS_COMMANDS, _From) ->
- IdentityEl = #xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-list">>}]},
- {result, [IdentityEl]};
+ {result, #disco_info{identities = [#identity{category = <<"automation">>,
+ type = <<"command-list">>}]}};
command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
- IdentityEl = #xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"automation">>},
- {<<"type">>, <<"command-node">>}]},
- FeaturesEl = #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_COMMANDS}]},
- {result, [IdentityEl, FeaturesEl]}.
+ {result, #disco_info{identities = [#identity{category = <<"automation">>,
+ type = <<"command-node">>}],
+ features = [?NS_COMMANDS]}}.
+-spec node_disco_info(binary(), binary(), jid()) -> {result, disco_info()} |
+ {error, stanza_error()}.
node_disco_info(Host, Node, From) ->
node_disco_info(Host, Node, From, true, true).
+-spec node_disco_info(binary(), binary(), jid(), boolean(), boolean()) ->
+ {result, disco_info()} | {error, stanza_error()}.
node_disco_info(Host, Node, _From, _Identity, _Features) ->
- Action = fun (#pubsub_node{type = Type, options = Options}) ->
- NodeType = case get_option(Options, node_type) of
- collection -> <<"collection">>;
- _ -> <<"leaf">>
- end,
- I = #xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, NodeType}]},
- F = [#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_PUBSUB}]}
- | [#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, feature(F)}]}
- || F <- plugin_features(Host, Type)]],
- {result, [I | F]}
- end,
+ Action =
+ fun(#pubsub_node{type = Type, options = Options}) ->
+ NodeType = case get_option(Options, node_type) of
+ collection -> <<"collection">>;
+ _ -> <<"leaf">>
+ end,
+ Is = [#identity{category = <<"pubsub">>, type = NodeType}],
+ Fs = [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, Type)]],
+ {result, #disco_info{identities = Is, features = Fs}}
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> {result, Result};
Other -> Other
end.
+-spec iq_disco_info(binary(), binary(), jid(), binary())
+ -> {result, disco_info()} | {error, stanza_error()}.
iq_disco_info(Host, SNode, From, Lang) ->
[Node | _] = case SNode of
- <<>> -> [<<>>];
- _ -> str:tokens(SNode, <<"!">>)
- end,
- % Node = string_to_node(RealSNode),
+ <<>> -> [<<>>];
+ _ -> str:tokens(SNode, <<"!">>)
+ end,
case Node of
<<>> ->
- {result, [#xmlel{name = <<"identity">>,
- attrs = [{<<"category">>, <<"pubsub">>},
- {<<"type">>, <<"service">>},
- {<<"name">>, translate:translate(Lang, <<"Publish-Subscribe">>)}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_DISCO_INFO}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_DISCO_ITEMS}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_PUBSUB}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_COMMANDS}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}]}]
- ++ [#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, feature(F)}]}
- || F <- features(Host, Node)]};
+ {result,
+ #disco_info{
+ identities = [#identity{
+ category = <<"pubsub">>,
+ type = <<"service">>,
+ name = translate:translate(
+ Lang, <<"Publish-Subscribe">>)}],
+ features = [?NS_DISCO_INFO,
+ ?NS_DISCO_ITEMS,
+ ?NS_PUBSUB,
+ ?NS_COMMANDS,
+ ?NS_VCARD |
+ [feature(F) || F <- features(Host, Node)]]}};
?NS_COMMANDS ->
command_disco_info(Host, Node, From);
?NS_PUBSUB_GET_PENDING ->
@@ -1095,34 +1103,34 @@ iq_disco_info(Host, SNode, From, Lang) ->
node_disco_info(Host, Node, From)
end.
--spec iq_disco_items(Host :: mod_pubsub:host(), Node :: <<>> | mod_pubsub:nodeId(),
- From :: jid(), Rsm :: none | rsm_in()) -> {result, [xmlel()]}.
+-spec iq_disco_items(host(), binary(), jid(), undefined | rsm_set()) ->
+ {result, disco_items()} | {error, stanza_error()}.
iq_disco_items(Host, <<>>, From, _RSM) ->
- {result,
- lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
- Attrs = case get_option(Options, title) of
- false ->
- [{<<"jid">>, Host}
- | nodeAttr(SubNode)];
- Title ->
- [{<<"jid">>, Host},
- {<<"name">>, Title}
- | nodeAttr(SubNode)]
- end,
- #xmlel{name = <<"item">>, attrs = Attrs}
- end,
- tree_action(Host, get_subnodes, [Host, <<>>, From]))};
+ Items =
+ lists:map(
+ fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
+ case get_option(Options, title) of
+ false ->
+ #disco_item{jid = jid:make(Host),
+ node = SubNode};
+ Title ->
+ #disco_item{jid = jid:make(Host),
+ name = Title,
+ node = SubNode}
+ end
+ end, tree_action(Host, get_subnodes, [Host, <<>>, From])),
+ {result, #disco_items{items = Items}};
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
- {result, [#xmlel{name = <<"item">>,
- attrs = [{<<"jid">>, Host},
- {<<"node">>, ?NS_PUBSUB_GET_PENDING},
- {<<"name">>, <<"Get Pending">>}]}]};
+ {result,
+ #disco_items{items = [#disco_item{jid = jid:make(Host),
+ node = ?NS_PUBSUB_GET_PENDING,
+ name = <<"Get Pending">>}]}};
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
- {result, []};
+ {result, #disco_items{}};
iq_disco_items(Host, Item, From, RSM) ->
case str:tokens(Item, <<"!">>) of
[_Node, _ItemId] ->
- {result, []};
+ {result, #disco_items{}};
[Node] ->
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) ->
Owners = node_owners_call(Host, Type, Nidx, O),
@@ -1130,28 +1138,28 @@ iq_disco_items(Host, Item, From, RSM) ->
From, Type, Options, Owners, RSM)
of
{result, R} -> R;
- _ -> {[], none}
+ _ -> {[], undefined}
end,
- Nodes = lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
- Attrs = case get_option(SubOptions, title) of
- false ->
- [{<<"jid">>, Host}
- | nodeAttr(SubNode)];
- Title ->
- [{<<"jid">>, Host},
- {<<"name">>, Title}
- | nodeAttr(SubNode)]
- end,
- #xmlel{name = <<"item">>, attrs = Attrs}
- end,
- tree_call(Host, get_subnodes, [Host, Node, From])),
- Items = lists:map(fun (#pubsub_item{itemid = {RN, _}}) ->
- {result, Name} = node_call(Host, Type, get_item_name, [Host, Node, RN]),
- #xmlel{name = <<"item">>,
- attrs = [{<<"jid">>, Host}, {<<"name">>, Name}]}
- end,
- NodeItems),
- {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
+ Nodes = lists:map(
+ fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
+ case get_option(SubOptions, title) of
+ false ->
+ #disco_item{jid = jid:make(Host),
+ node = SubNode};
+ Title ->
+ #disco_item{jid = jid:make(Host),
+ name = Title,
+ node = SubNode}
+ end
+ end, tree_call(Host, get_subnodes, [Host, Node, From])),
+ Items = lists:map(
+ fun(#pubsub_item{itemid = {RN, _}}) ->
+ {result, Name} = node_call(Host, Type, get_item_name, [Host, Node, RN]),
+ #disco_item{jid = jid:make(Host), name = Name}
+ end, NodeItems),
+ {result,
+ #disco_items{items = Nodes ++ Items,
+ rsm = RsmOut}}
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> {result, Result};
@@ -1159,477 +1167,367 @@ iq_disco_items(Host, Item, From, RSM) ->
end
end.
--spec iq_sm(From :: jid(), To :: jid(), IQ :: iq_request()) -> iq_result() | iq_error().
-iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
- ServerHost = To#jid.lserver,
+-spec iq_sm(iq()) -> iq().
+iq_sm(#iq{to = To, sub_els = [SubEl]} = IQ) ->
LOwner = jid:tolower(jid:remove_resource(To)),
- Res = case XMLNS of
- ?NS_PUBSUB ->
- iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
- ?NS_PUBSUB_OWNER ->
- iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang)
- end,
+ Res = case xmpp:get_ns(SubEl) of
+ ?NS_PUBSUB ->
+ iq_pubsub(LOwner, all, IQ);
+ ?NS_PUBSUB_OWNER ->
+ iq_pubsub_owner(LOwner, IQ)
+ end,
case Res of
- {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
- {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]}
+ {result, IQRes} ->
+ xmpp:make_iq_result(IQ, IQRes);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
end.
+-spec iq_get_vcard(binary()) -> vcard_temp().
iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children = [{xmlcdata,
- <<(translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>))/binary,
- "\nCopyright (c) 2004-2016 ProcessOne">>}]}].
-
--spec iq_pubsub(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(),
- IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary()) ->
- {result, [xmlel()]} | {error, xmlel()}.
-
-iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
- iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)).
-
--spec iq_pubsub(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(),
- IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary(),
- Access :: atom(), Plugins :: [binary(),...]) ->
- {result, [xmlel()]} | {error, xmlel()}.
-
-iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
- #xmlel{children = SubEls} = SubEl,
- case fxml:remove_cdata(SubEls) of
- [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
- 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 fxml:get_attr_s(<<"type">>, Attrs) of
- <<>> -> hd(Plugins);
- T -> T
- end,
- case lists:member(Type, Plugins) of
- false ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"create-nodes">>)};
- true ->
- create_node(Host, ServerHost, Node, From, Type, Access, Config)
- end;
- {set, <<"publish">>} ->
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"item">>, attrs = ItemAttrs,
- children = Payload}] ->
- ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
- PubOpts = case [C || #xmlel{name = <<"publish-options">>,
- children = [C]} <- Rest] of
- [XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- invalid -> [];
- Form -> Form
- end;
- _ -> []
- end,
- publish_item(Host, ServerHost, Node, From, ItemId, Payload, PubOpts, Access);
- [] ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
- _ ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
+ Desc = translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>),
+ #vcard_temp{fn = <<"ejabberd/mod_pubsub">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>}.
+
+-spec iq_pubsub(binary() | ljid(), atom(), iq()) ->
+ {result, pubsub()} | {error, stanza_error()}.
+iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang,
+ sub_els = [SubEl]}) ->
+ case {IQType, SubEl} of
+ {set, #pubsub{create = Node, configure = Configure,
+ _ = undefined}} when is_binary(Node) ->
+ ServerHost = serverhost(Host),
+ Plugins = config(ServerHost, plugins),
+ Config = case Configure of
+ {_, XData} -> decode_node_config(XData, Host, Lang);
+ undefined -> []
+ end,
+ Type = hd(Plugins),
+ case Config of
+ {error, _} = Err ->
+ Err;
+ _ ->
+ create_node(Host, ServerHost, Node, From, Type, Access, Config)
+ end;
+ {set, #pubsub{publish = #ps_publish{node = Node, items = Items},
+ publish_options = XData, _ = undefined}} ->
+ ServerHost = serverhost(Host),
+ case Items of
+ [#ps_item{id = ItemId, xml_els = Payload}] ->
+ case decode_publish_options(XData, Lang) of
+ {error, _} = Err ->
+ Err;
+ PubOpts ->
+ publish_item(Host, ServerHost, Node, From, ItemId,
+ Payload, PubOpts, Access)
end;
- {set, <<"retract">>} ->
- ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of
- <<"1">> -> true;
- <<"true">> -> true;
- _ -> false
- end,
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
- ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
- delete_item(Host, Node, From, ItemId, ForceNotify);
- _ ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}
+ [] ->
+ {error, extended_error(xmpp:err_bad_request(), err_item_required())};
+ _ ->
+ {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}
+ end;
+ {set, #pubsub{retract = #ps_retract{node = Node, notify = Notify, items = Items},
+ _ = undefined}} ->
+ case Items of
+ [#ps_item{id = ItemId}] ->
+ if ItemId /= <<>> ->
+ delete_item(Host, Node, From, ItemId, Notify);
+ true ->
+ {error, extended_error(xmpp:err_bad_request(),
+ err_item_required())}
end;
- {set, <<"subscribe">>} ->
- Config = case Rest of
- [#xmlel{name = <<"options">>, children = C}] -> C;
- _ -> []
- end,
- JID = fxml:get_attr_s(<<"jid">>, Attrs),
- subscribe_node(Host, Node, From, JID, Config);
- {set, <<"unsubscribe">>} ->
- JID = fxml:get_attr_s(<<"jid">>, Attrs),
- SubId = fxml:get_attr_s(<<"subid">>, Attrs),
- unsubscribe_node(Host, Node, From, JID, SubId);
- {get, <<"items">>} ->
- 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 fxml:get_attr_s(<<"id">>, ItemAttrs) of
- <<>> -> Acc;
- ItemId -> [ItemId | Acc]
- end;
- (_, Acc) ->
- Acc
- end,
- [], 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 = fxml:get_attr_s(<<"subid">>, Attrs),
- JID = fxml:get_attr_s(<<"jid">>, Attrs),
- get_options(Host, Node, JID, SubId, Lang);
- {set, <<"options">>} ->
- SubId = fxml:get_attr_s(<<"subid">>, Attrs),
- JID = fxml:get_attr_s(<<"jid">>, Attrs),
- set_options(Host, Node, JID, SubId, Els);
+ [] ->
+ {error, extended_error(xmpp:err_bad_request(), err_item_required())};
_ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}
end;
- Other ->
- ?INFO_MSG("Too many actions: ~p", [Other]),
- {error, ?ERR_BAD_REQUEST}
- end.
-
-
--spec iq_pubsub_owner(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(),
- IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary()) ->
- {result, [xmlel()]} | {error, xmlel()}.
-
-iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
- #xmlel{children = SubEls} = SubEl,
- Action = fxml:remove_cdata(SubEls),
- case Action of
- [#xmlel{name = Name, attrs = Attrs, children = Els}] ->
- Node = fxml:get_attr_s(<<"node">>, Attrs),
- case {IQType, Name} of
- {get, <<"configure">>} ->
- get_configure(Host, ServerHost, Node, From, Lang);
- {set, <<"configure">>} ->
- set_configure(Host, Node, From, Els, Lang);
- {get, <<"default">>} ->
- get_default(Host, Node, From, Lang);
- {set, <<"delete">>} ->
- delete_node(Host, Node, From);
- {set, <<"purge">>} ->
- purge_node(Host, Node, From);
- {get, <<"subscriptions">>} ->
- get_subscriptions(Host, Node, From);
- {set, <<"subscriptions">>} ->
- set_subscriptions(Host, Node, From, fxml:remove_cdata(Els));
- {get, <<"affiliations">>} ->
- get_affiliations(Host, Node, From);
- {set, <<"affiliations">>} ->
- set_affiliations(Host, Node, From, fxml:remove_cdata(Els));
+ {set, #pubsub{subscribe = #ps_subscribe{node = Node, jid = JID},
+ options = Options, _ = undefined}} ->
+ Config = case Options of
+ #ps_options{xdata = XData} ->
+ decode_subscribe_options(XData, Lang);
+ _ ->
+ []
+ end,
+ case Config of
+ {error, _} = Err ->
+ Err;
_ ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ subscribe_node(Host, Node, From, JID, Config)
+ end;
+ {set, #pubsub{unsubscribe = #ps_unsubscribe{node = Node, jid = JID, subid = SubId},
+ _ = undefined}} ->
+ unsubscribe_node(Host, Node, From, JID, SubId);
+ {get, #pubsub{items = #ps_items{node = Node,
+ max_items = MaxItems,
+ subid = SubId,
+ items = Items},
+ rsm = RSM, _ = undefined}} ->
+ ItemIds = [ItemId || #ps_item{id = ItemId} <- Items, ItemId /= <<>>],
+ get_items(Host, Node, From, SubId, MaxItems, ItemIds, RSM);
+ {get, #pubsub{subscriptions = {Node, _}, _ = undefined}} ->
+ Plugins = config(serverhost(Host), plugins),
+ get_subscriptions(Host, Node, From, Plugins);
+ {get, #pubsub{affiliations = {Node, _}, _ = undefined}} ->
+ Plugins = config(serverhost(Host), plugins),
+ get_affiliations(Host, Node, From, Plugins);
+ {get, #pubsub{options = #ps_options{node = Node, subid = SubId, jid = JID},
+ _ = undefined}} ->
+ get_options(Host, Node, JID, SubId, Lang);
+ {set, #pubsub{options = #ps_options{node = Node, subid = SubId,
+ jid = JID, xdata = XData},
+ _ = undefined}} ->
+ case decode_subscribe_options(XData, Lang) of
+ {error, _} = Err ->
+ Err;
+ Config ->
+ set_options(Host, Node, JID, SubId, Config)
end;
+ {set, #pubsub{}} ->
+ {error, xmpp:err_bad_request()};
_ ->
- ?INFO_MSG("Too many actions: ~p", [Action]),
- {error, ?ERR_BAD_REQUEST}
+ {error, xmpp:err_feature_not_implemented()}
end.
-iq_command(Host, ServerHost, From, IQ, Access, Plugins) ->
- case adhoc:parse_request(IQ) of
- Req when is_record(Req, adhoc_request) ->
- case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of
- Resp when is_record(Resp, adhoc_response) ->
- {result, [adhoc:produce_response(Req, Resp)]};
- Error ->
- Error
+-spec iq_pubsub_owner(binary() | ljid(), iq()) -> {result, pubsub_owner() | undefined} |
+ {error, stanza_error()}.
+iq_pubsub_owner(Host, #iq{type = IQType, from = From,
+ lang = Lang, sub_els = [SubEl]}) ->
+ case {IQType, SubEl} of
+ {get, #pubsub_owner{configure = {Node, undefined}, _ = undefined}} ->
+ ServerHost = serverhost(Host),
+ get_configure(Host, ServerHost, Node, From, Lang);
+ {set, #pubsub_owner{configure = {Node, XData}, _ = undefined}} ->
+ case XData of
+ undefined ->
+ {error, xmpp:err_bad_request(<<"No data form found">>, Lang)};
+ #xdata{type = cancel} ->
+ {result, #pubsub_owner{}};
+ #xdata{type = submit} ->
+ case decode_node_config(XData, Host, Lang) of
+ {error, _} = Err ->
+ Err;
+ Config ->
+ set_configure(Host, Node, From, Config, Lang)
+ end;
+ #xdata{} ->
+ {error, xmpp:err_bad_request(<<"Incorrect data form">>, Lang)}
end;
- Err -> Err
+ {get, #pubsub_owner{default = {Node, undefined}, _ = undefined}} ->
+ get_default(Host, Node, From, Lang);
+ {set, #pubsub_owner{delete = {Node, _}, _ = undefined}} ->
+ delete_node(Host, Node, From);
+ {set, #pubsub_owner{purge = Node, _ = undefined}} when Node /= undefined ->
+ purge_node(Host, Node, From);
+ {get, #pubsub_owner{subscriptions = {Node, []}, _ = undefined}} ->
+ get_subscriptions(Host, Node, From);
+ {set, #pubsub_owner{subscriptions = {Node, Subs}, _ = undefined}} ->
+ set_subscriptions(Host, Node, From, Subs);
+ {get, #pubsub_owner{affiliations = {Node, []}, _ = undefined}} ->
+ get_affiliations(Host, Node, From);
+ {set, #pubsub_owner{affiliations = {Node, Affs}, _ = undefined}} ->
+ set_affiliations(Host, Node, From, Affs);
+ {_, #pubsub_owner{}} ->
+ {error, xmpp:err_bad_request()};
+ _ ->
+ {error, xmpp:err_feature_not_implemented()}
end.
-%% @doc <p>Processes an Ad Hoc Command.</p>
+-spec adhoc_request(binary(), binary(), jid(), adhoc_command(),
+ atom(), [binary()]) -> adhoc_command() | {error, stanza_error()}.
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- lang = Lang, action = <<"execute">>,
- xdata = false},
- _Access, Plugins) ->
+ #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang,
+ action = execute, xdata = undefined},
+ _Access, Plugins) ->
send_pending_node_form(Host, Owner, Lang, Plugins);
adhoc_request(Host, _ServerHost, Owner,
- #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- action = <<"execute">>, xdata = XData, lang = Lang},
- _Access, _Plugins) ->
- ParseOptions = case XData of
- #xmlel{name = <<"x">>} = XEl ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- XData2 ->
- case set_xoption(Host, XData2, []) of
- NewOpts when is_list(NewOpts) -> {result, NewOpts};
- Err -> Err
- end
- end;
- _ ->
- Txt = <<"No data form found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end,
- case ParseOptions of
- {result, XForm} ->
- case lists:keysearch(node, 1, XForm) of
- {value, {_, Node}} -> send_pending_auth_events(Host, Node, Owner);
- false -> {error, extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)}
- end;
- Error -> Error
+ #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang,
+ action = execute, xdata = #xdata{} = XData} = Request,
+ _Access, _Plugins) ->
+ case decode_get_pending(XData, Lang) of
+ {error, _} = Err ->
+ Err;
+ Config ->
+ Node = proplists:get_value(node, Config),
+ case send_pending_auth_events(Host, Node, Owner, Lang) of
+ ok ->
+ xmpp_util:make_adhoc_response(
+ Request, #adhoc_command{action = completed});
+ Err ->
+ Err
+ end
end;
adhoc_request(_Host, _ServerHost, _Owner,
- #adhoc_request{action = <<"cancel">>}, _Access,
- _Plugins) ->
- #adhoc_response{status = canceled};
-adhoc_request(Host, ServerHost, Owner,
- #adhoc_request{action = <<>>} = R, Access, Plugins) ->
- adhoc_request(Host, ServerHost, Owner,
- R#adhoc_request{action = <<"execute">>}, Access,
- Plugins);
+ #adhoc_command{action = cancel}, _Access, _Plugins) ->
+ #adhoc_command{status = canceled};
adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
- {error, ?ERR_ITEM_NOT_FOUND}.
+ {error, xmpp:err_item_not_found()}.
-%% @doc <p>Sends the process pending subscriptions XForm for Host to Owner.</p>
+-spec send_pending_node_form(binary(), jid(), binary(),
+ [binary()]) -> adhoc_command() | {error, stanza_error()}.
send_pending_node_form(Host, Owner, _Lang, Plugins) ->
Filter = fun (Type) ->
lists:member(<<"get-pending">>, plugin_features(Host, Type))
end,
case lists:filter(Filter, Plugins) of
[] ->
- Err = extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, <<"get-pending">>),
+ Err = extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('get-pending')),
{error, Err};
Ps ->
- XOpts = [#xmlel{name = <<"option">>, attrs = [],
- children = [#xmlel{name = <<"value">>,
- attrs = [],
- children = [{xmlcdata, Node}]}]}
- || Node <- get_pending_nodes(Host, Owner, Ps)],
- XForm = #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children = [#xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"list-single">>},
- {<<"var">>, <<"pubsub#node">>}],
- children = lists:usort(XOpts)}]},
- #adhoc_response{status = executing,
- defaultaction = <<"execute">>, elements = [XForm]}
+ case get_pending_nodes(Host, Owner, Ps) of
+ {ok, Nodes} ->
+ XForm = #xdata{type = form,
+ fields = pubsub_get_pending:encode(
+ [{node, Nodes}])},
+ #adhoc_command{status = executing, action = execute,
+ xdata = XForm};
+ Err ->
+ Err
+ end
end.
+-spec get_pending_nodes(binary(), jid(), [binary()]) -> {ok, [binary()]} |
+ {error, stanza_error()}.
get_pending_nodes(Host, Owner, Plugins) ->
Tr = fun (Type) ->
case node_call(Host, Type, get_pending_nodes, [Host, Owner]) of
{result, Nodes} -> Nodes;
_ -> []
end
- end,
+ end,
Action = fun() -> {result, lists:flatmap(Tr, Plugins)} end,
case transaction(Host, Action, sync_dirty) of
- {result, Res} -> Res;
+ {result, Res} -> {ok, Res};
Err -> Err
end.
%% @doc <p>Send a subscription approval form to Owner for all pending
%% subscriptions on Host and Node.</p>
-send_pending_auth_events(Host, Node, Owner) ->
+-spec send_pending_auth_events(binary(), binary(), jid(),
+ binary()) -> adhoc_command() | {error, stanza_error()}.
+send_pending_auth_events(Host, Node, Owner, Lang) ->
?DEBUG("Sending pending auth events for ~s on ~s:~s",
- [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 ->
- case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
- {result, owner} -> node_call(Host, Type, get_node_subscriptions, [Nidx]);
- _ -> {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
- end;
- false ->
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, <<"get-pending">>)}
- end
- end,
+ [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 ->
+ case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
+ {result, owner} ->
+ node_call(Host, Type, get_node_subscriptions, [Nidx]);
+ _ ->
+ {error, xmpp:err_forbidden(
+ <<"Owner privileges required">>, Lang)}
+ end;
+ false ->
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('get-pending'))}
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {N, Subs}} ->
- lists:foreach(fun
- ({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J));
- ({J, pending}) -> send_authorization_request(N, jid:make(J));
- (_) -> ok
- end,
- Subs),
- #adhoc_response{};
+ lists:foreach(
+ fun({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J));
+ ({J, pending}) -> send_authorization_request(N, jid:make(J));
+ (_) -> ok
+ end, Subs),
+ #adhoc_command{};
Err ->
Err
end.
%%% authorization handling
-
-send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = Nidx, owners = O},
- Subscriber) ->
+-spec send_authorization_request(#pubsub_node{}, jid()) -> ok.
+send_authorization_request(#pubsub_node{nodeid = {Host, Node},
+ type = Type, id = Nidx, owners = O},
+ Subscriber) ->
+ %% TODO: pass lang to this function
Lang = <<"en">>,
- Stanza = #xmlel{name = <<"message">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang, <<"PubSub subscriber request">>)}]},
- #xmlel{name = <<"instructions">>,
- attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Choose whether to approve this entity's "
- "subscription.">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"pubsub#node">>},
- {<<"type">>,
- <<"text-single">>},
- {<<"label">>, translate:translate(Lang, <<"Node ID">>)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, Node}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>,
- <<"pubsub#subscriber_jid">>},
- {<<"type">>, <<"jid-single">>},
- {<<"label">>,
- translate:translate(Lang, <<"Subscriber Address">>)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, jid:to_string(Subscriber)}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>,
- <<"pubsub#allow">>},
- {<<"type">>, <<"boolean">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Allow this Jabber ID to subscribe to "
- "this pubsub node?">>)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata, <<"false">>}]}]}]}]},
- lists:foreach(fun (Owner) ->
- ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza)
- end,
- node_owners_action(Host, Type, Nidx, O)).
+ Fs = pubsub_subscribe_authorization:encode(
+ [{node, Node},
+ {subscriber_jid, Subscriber},
+ {allow, false}],
+ fun(T) -> translate:translate(Lang, T) end),
+ X = #xdata{type = form,
+ title = translate:translate(
+ Lang, <<"PubSub subscriber request">>),
+ instructions = [translate:translate(
+ Lang,
+ <<"Choose whether to approve this entity's "
+ "subscription.">>)],
+ fields = Fs},
+ Stanza = #message{sub_els = [X]},
+ lists:foreach(
+ fun (Owner) ->
+ ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza)
+ end, node_owners_action(Host, Type, Nidx, O)).
+-spec find_authorization_response(message()) -> undefined |
+ pubsub_subscribe_authorization:result() |
+ {error, stanza_error()}.
find_authorization_response(Packet) ->
- #xmlel{children = Els} = Packet,
- XData1 = lists:map(fun
- (#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) ->
- case fxml:get_attr_s(<<"xmlns">>, XAttrs) of
- ?NS_XDATA ->
- case fxml:get_attr_s(<<"type">>, XAttrs) of
- <<"cancel">> -> none;
- _ -> jlib:parse_xdata_submit(XEl)
- end;
- _ ->
- none
- end;
- (_) ->
- none
- end,
- fxml:remove_cdata(Els)),
- XData = lists:filter(fun (E) -> E /= none end, XData1),
- case XData of
- [invalid] ->
- invalid;
- [] ->
- none;
- [XFields] when is_list(XFields) ->
- ?DEBUG("XFields: ~p", [XFields]),
- case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of
- {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields;
- _ -> invalid
- end
+ case xmpp:get_subtag(Packet, #xdata{}) of
+ #xdata{type = cancel} ->
+ undefined;
+ #xdata{type = submit, fields = Fs} ->
+ try pubsub_subscribe_authorization:decode(Fs) of
+ Result -> Result
+ catch _:{pubsub_subscribe_authorization, Why} ->
+ Lang = xmpp:get_lang(Packet),
+ Txt = pubsub_subscribe_authorization:format_error(Why),
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ #xdata{} ->
+ {error, xmpp:err_bad_request()};
+ false ->
+ undefined
end.
%% @doc Send a message to JID with the supplied Subscription
+-spec send_authorization_approval(binary(), jid(), binary(), subscribed | none) -> ok.
send_authorization_approval(Host, JID, SNode, Subscription) ->
- SubAttrs = case Subscription of
- %{S, SID} ->
- % [{<<"subscription">>, subscription_to_string(S)},
- % {<<"subid">>, SID}];
- S ->
- [{<<"subscription">>, subscription_to_string(S)}]
- end,
- Stanza = event_stanza(<<"subscription">>,
- [{<<"jid">>, jid:to_string(JID)}
- | nodeAttr(SNode)]
- ++ SubAttrs),
+ Event = #ps_event{subscription =
+ #ps_subscription{jid = JID,
+ node = SNode,
+ type = Subscription}},
+ Stanza = #message{sub_els = [Event]},
ejabberd_router:route(service_jid(Host), JID, Stanza).
-handle_authorization_response(Host, From, To, Packet, XFields) ->
- Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
- case {lists:keysearch(<<"pubsub#node">>, 1, XFields),
- lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields),
- lists:keysearch(<<"pubsub#allow">>, 1, XFields)}
- of
- {{value, {_, [Node]}},
- {value, {_, [SSubscriber]}},
- {value, {_, [SAllow]}}} ->
- FromLJID = jid:tolower(jid:remove_resource(From)),
- Subscriber = jid:from_string(SSubscriber),
- Allow = case SAllow of
- <<"1">> -> true;
- <<"true">> -> true;
- _ -> false
- end,
- Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O}) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case lists:member(FromLJID, Owners) of
- true ->
- {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
- update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
- false ->
- {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {error, Error} ->
- Err = jlib:make_error_reply(Packet, Error),
- ejabberd_router:route(To, From, Err);
- {result, {_, _NewSubscription}} ->
- %% XXX: notify about subscription state change, section 12.11
- ok;
- _ ->
- Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err)
- end;
+-spec handle_authorization_response(binary(), jid(), jid(), message(),
+ pubsub_subscribe_authorization:result()) -> ok.
+handle_authorization_response(Host, From, To, Packet, Response) ->
+ Node = proplists:get_value(node, Response),
+ Subscriber = proplists:get_value(subscriber_jid, Response),
+ Allow = proplists:get_value(allow, Response),
+ Lang = xmpp:get_lang(Packet),
+ FromLJID = jid:tolower(jid:remove_resource(From)),
+ Action =
+ fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case lists:member(FromLJID, Owners) of
+ true ->
+ {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
+ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
+ false ->
+ {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {error, Error} ->
+ ejabberd_router:route_error(To, From, Packet, Error);
+ {result, {_, _NewSubscription}} ->
+ %% XXX: notify about subscription state change, section 12.11
+ ok;
_ ->
- Txt = <<"Incorrect data form">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
- ejabberd_router:route(To, From, Err)
+ Err = xmpp:err_internal_server_error(),
+ ejabberd_router:route_error(To, From, Packet, Err)
end.
+-spec update_auth(binary(), binary(), _, _, jid() | error, boolean(), _) ->
+ {result, ok} | {error, stanza_error()}.
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
Sub= lists:filter(fun
({pending, _}) -> true;
@@ -1647,68 +1545,9 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
{result, ok};
_ ->
Txt = <<"No pending subscriptions found">>,
- {error, ?ERRT_UNEXPECTED_REQUEST(?MYLANG, Txt)}
+ {error, xmpp:err_unexpected_request(Txt, ?MYLANG)}
end.
--define(XFIELD(Type, Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
--define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD(<<"boolean">>, Label, Var,
- case Val of
- true -> <<"1">>;
- _ -> <<"0">>
- end)).
-
--define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD(<<"text-single">>, Label, Var, Val)).
-
--define(STRINGMXFIELD(Label, Var, Vals),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"text-multi">>},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, V}]}
- || V <- Vals]}).
-
--define(XFIELDOPT(Type, Label, Var, Val, Opts),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = [#xmlel{name = <<"option">>, attrs = [],
- children = [#xmlel{name = <<"value">>,
- attrs = [],
- children = [{xmlcdata, Opt}]}]}
- || Opt <- Opts]
- ++
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
--define(LISTXFIELD(Label, Var, Val, Opts),
- ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)).
-
--define(LISTMXFIELD(Label, Var, Vals, Opts),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"list-multi">>},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = [#xmlel{name = <<"option">>, attrs = [],
- children = [#xmlel{name = <<"value">>,
- attrs = [],
- children = [{xmlcdata, Opt}]}]}
- || Opt <- Opts]
- ++
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}
- || Val <- Vals]}).
-
%% @doc <p>Create new pubsub nodes</p>
%%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p>
%%<ul>
@@ -1726,120 +1565,88 @@ 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(Host :: mod_pubsub:host(), ServerHost :: binary(),
- Node :: <<>> | mod_pubsub:nodeId(), Owner :: jid(),
- Type :: binary()) -> {result, [xmlel(),...]} | {error, xmlel()}.
+-spec create_node(host(), binary(), binary(), jid(),
+ binary()) -> {result, pubsub()} | {error, stanza_error()}.
create_node(Host, ServerHost, Node, Owner, Type) ->
create_node(Host, ServerHost, Node, Owner, Type, all, []).
--spec create_node(Host :: mod_pubsub:host(), ServerHost :: binary(),
- Node :: <<>> | mod_pubsub:nodeId(), Owner :: jid(),
- Type :: binary(), Access :: atom(), Configuration :: [xmlel()]) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
-
+-spec create_node(host(), binary(), binary(), jid(), binary(),
+ atom(), [{binary(), [binary()]}]) -> {result, pubsub()} | {error, stanza_error()}.
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of
true ->
Node = randoms:get_string(),
case create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) of
{result, _} ->
- {result, [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"create">>,
- attrs = nodeAttr(Node)}]}]};
+ {result, #pubsub{create = Node}};
Error ->
Error
end;
false ->
- {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"nodeid-required">>)}
+ {error, extended_error(xmpp:err_not_acceptable(), err_nodeid_required())}
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- ParseOptions = case fxml:remove_cdata(Configuration) of
- [] ->
- {result, node_options(Host, Type)};
- [#xmlel{name = <<"x">>} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
- XData ->
- case set_xoption(Host, XData, node_options(Host, Type)) of
- NewOpts when is_list(NewOpts) -> {result, NewOpts};
- Err -> Err
- end
+ NodeOptions = merge_config(Configuration, node_options(Host, Type)),
+ CreateNode =
+ fun() ->
+ Parent = case node_call(Host, Type, node_to_path, [Node]) of
+ {result, [Node]} ->
+ <<>>;
+ {result, Path} ->
+ element(2, node_call(Host, Type, path_to_node,
+ [lists:sublist(Path, length(Path)-1)]))
+ end,
+ Parents = case Parent of
+ <<>> -> [];
+ _ -> [Parent]
+ end,
+ case node_call(Host, Type, create_node_permission,
+ [Host, ServerHost, Node, Parent, Owner, Access]) of
+ {result, true} ->
+ case tree_call(Host, create_node,
+ [Host, Node, Type, Owner, NodeOptions, Parents])
+ of
+ {ok, Nidx} ->
+ SubsByDepth = get_node_subs_by_depth(Host, Node, Owner),
+ case node_call(Host, Type, create_node, [Nidx, Owner]) of
+ {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
+ Error -> Error
+ end;
+ {error, {virtual, Nidx}} ->
+ case node_call(Host, Type, create_node, [Nidx, Owner]) of
+ {result, Result} -> {result, {Nidx, [], Result}};
+ Error -> Error
+ end;
+ Error ->
+ Error
+ end;
+ _ ->
+ Txt = <<"You're not allowed to create nodes">>,
+ {error, xmpp:err_forbidden(Txt, ?MYLANG)}
+ end
+ end,
+ Reply = #pubsub{create = Node},
+ case transaction(Host, CreateNode, transaction) of
+ {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
+ broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
+ ejabberd_hooks:run(pubsub_create_node, ServerHost,
+ [ServerHost, Host, Node, Nidx, NodeOptions]),
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
end;
- _ ->
- ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
- Txt = <<"No data form found">>,
- {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
- end,
- case ParseOptions of
- {result, NodeOptions} ->
- CreateNode = fun () ->
- Parent = case node_call(Host, Type, node_to_path, [Node]) of
- {result, [Node]} ->
- <<>>;
- {result, Path} ->
- element(2, node_call(Host, Type, path_to_node, [lists:sublist(Path, length(Path)-1)]))
- end,
- Parents = case Parent of
- <<>> -> [];
- _ -> [Parent]
- end,
- case node_call(Host, Type, create_node_permission,
- [Host, ServerHost, Node, Parent, Owner, Access])
- of
- {result, true} ->
- case tree_call(Host, create_node,
- [Host, Node, Type, Owner, NodeOptions, Parents])
- of
- {ok, Nidx} ->
- SubsByDepth = get_node_subs_by_depth(Host, Node, Owner),
- case node_call(Host, Type, create_node, [Nidx, Owner]) of
- {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
- Error -> Error
- end;
- {error, {virtual, Nidx}} ->
- case node_call(Host, Type, create_node, [Nidx, Owner]) of
- {result, Result} -> {result, {Nidx, [], Result}};
- Error -> Error
- end;
- Error ->
- Error
- end;
- _ ->
- Txt1 = <<"You're not allowed to create nodes">>,
- {error, ?ERRT_FORBIDDEN(?MYLANG, Txt1)}
- end
- end,
- Reply = [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"create">>,
- attrs = nodeAttr(Node)}]}],
- case transaction(Host, CreateNode, transaction) of
- {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
- broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
- ejabberd_hooks:run(pubsub_create_node, ServerHost,
- [ServerHost, Host, Node, Nidx, NodeOptions]),
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- {result, {Nidx, _SubsByDepth, Result}} ->
- ejabberd_hooks:run(pubsub_create_node, ServerHost,
- [ServerHost, Host, Node, Nidx, NodeOptions]),
- case Result of
- default -> {result, Reply};
- _ -> {result, Result}
- end;
- Error ->
- %% in case we change transaction to sync_dirty...
- %% node_call(Host, Type, delete_node, [Host, Node]),
- %% tree_call(Host, delete_node, [Host, Node]),
- Error
+ {result, {Nidx, _SubsByDepth, Result}} ->
+ ejabberd_hooks:run(pubsub_create_node, ServerHost,
+ [ServerHost, Host, Node, Nidx, NodeOptions]),
+ case Result of
+ default -> {result, Reply};
+ _ -> {result, Result}
end;
Error ->
+ %% in case we change transaction to sync_dirty...
+ %% node_call(Host, Type, delete_node, [Host, Node]),
+ %% tree_call(Host, delete_node, [Host, Node]),
Error
end.
@@ -1850,10 +1657,9 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
%%<li>The node is the root collection node, which cannot be deleted.</li>
%%<li>The specified node does not exist.</li>
%%</ul>
--spec delete_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- Owner :: jid()) -> {result, [xmlel(),...]} | {error, xmlel()}.
+-spec delete_node(host(), binary(), jid()) -> {result, pubsub_owner()} | {error, stanza_error()}.
delete_node(_Host, <<>>, _Owner) ->
- {error, ?ERRT_NOT_ALLOWED(?MYLANG, <<"No node specified">>)};
+ {error, xmpp:err_not_allowed(<<"No node specified">>, ?MYLANG)};
delete_node(Host, Node, Owner) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
@@ -1865,10 +1671,10 @@ delete_node(Host, Node, Owner) ->
Error -> Error
end;
_ ->
- {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
+ {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)}
end
end,
- Reply = [],
+ Reply = undefined,
ServerHost = serverhost(Host),
case transaction(Host, Node, Action, transaction) of
{result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} ->
@@ -1927,16 +1733,15 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not support subscriptions.</li>
%%<li>The node does not exist.</li>
%%</ul>
--spec subscribe_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- From :: jid(), JID :: binary(), Configuration :: [xmlel()]) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
+-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
+ {result, pubsub()} | {error, stanza_error()}.
subscribe_node(Host, Node, From, JID, Configuration) ->
SubModule = subscription_plugin(Host),
SubOpts = case SubModule:parse_options_xform(Configuration) of
{result, GoodSubOpts} -> GoodSubOpts;
_ -> invalid
end,
- Subscriber = string_to_ljid(JID),
+ Subscriber = jid:tolower(JID),
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx, owners = O}) ->
Features = plugin_features(Host, Type),
SubscribeFeature = lists:member(<<"subscribe">>, Features),
@@ -1962,21 +1767,21 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
true
end,
if not SubscribeFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscribe">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('subscribe'))};
not SubscribeConfig ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscribe">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('subscribe'))};
HasOptions andalso not OptionsFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('subscription-options'))};
SubOpts == invalid ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
+ {error, extended_error(xmpp:err_bad_request(),
+ err_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">>)};
+ {error, extended_error(xmpp:err_not_allowed(),
+ err_closed_node())};
true ->
Owners = node_owners_call(Host, Type, Nidx, O),
{PS, RG} = get_presence_and_roster_permissions(Host, Subscriber,
@@ -1987,19 +1792,13 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
end
end,
Reply = fun (Subscription) ->
- SubAttrs = case Subscription of
- {subscribed, SubId} ->
- [{<<"subscription">>, subscription_to_string(subscribed)},
- {<<"subid">>, SubId}, {<<"node">>, Node}];
- Other ->
- [{<<"subscription">>, subscription_to_string(Other)},
- {<<"node">>, Node}]
- end,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>, jid:to_string(Subscriber)}
- | SubAttrs]}]}]
+ Sub = case Subscription of
+ {subscribed, SubId} ->
+ #ps_subscription{type = subscribed, subid = SubId};
+ Other ->
+ #ps_subscription{type = Other}
+ end,
+ #pubsub{subscription = Sub#ps_subscription{jid = Subscriber, node = Node}}
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, subscribed, SubId, send_last}}} ->
@@ -2038,14 +1837,10 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
%%<li>The node does not exist.</li>
%%<li>The request specifies a subscription ID that is not valid or current.</li>
%%</ul>
--spec unsubscribe_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- From :: jid(), JID :: binary() | ljid(),
- SubId :: mod_pubsub:subId()) ->
- {result, []} | {error, xmlel()}.
-
-unsubscribe_node(Host, Node, From, JID, SubId) when is_binary(JID) ->
- unsubscribe_node(Host, Node, From, string_to_ljid(JID), SubId);
-unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
+-spec unsubscribe_node(host(), binary(), jid(), jid(), binary()) ->
+ {result, undefined} | {error, stanza_error()}.
+unsubscribe_node(Host, Node, From, JID, SubId) ->
+ Subscriber = jid:tolower(JID),
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
node_call(Host, Type, unsubscribe_node, [Nidx, From, Subscriber, SubId])
end,
@@ -2053,9 +1848,8 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
{result, {_, default}} ->
ServerHost = serverhost(Host),
ejabberd_hooks:run(pubsub_unsubscribe_node, ServerHost,
- [ServerHost, Host, Node, Subscriber, SubId]),
- {result, []};
- % {result, {_, Result}} -> {result, Result};
+ [ServerHost, Host, Node, Subscriber, SubId]),
+ {result, undefined};
Error -> Error
end.
@@ -2070,12 +1864,8 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li>
%%<li>The request does not match the node configuration.</li>
%%</ul>
--spec publish_item(Host :: mod_pubsub:host(), ServerHost :: binary(),
- Node :: mod_pubsub:nodeId(), Publisher :: jid(),
- ItemId :: <<>> | mod_pubsub:itemId(),
- Payload :: mod_pubsub:payload()) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
-
+-spec publish_item(host(), binary(), binary(), jid(), binary(),
+ [xmlel()]) -> {result, pubsub()} | {error, stanza_error()}.
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all).
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) ->
@@ -2092,34 +1882,31 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
PayloadSize = byte_size(term_to_binary(Payload)) - 2,
PayloadMaxSize = get_option(Options, max_payload_size),
if not PublishFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"publish">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported(publish))};
PayloadSize > PayloadMaxSize ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)};
+ {error, extended_error(xmpp:err_not_acceptable(),
+ err_payload_too_big())};
(PayloadCount == 0) and (Payload == []) ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)};
+ {error, extended_error(xmpp:err_bad_request(),
+ err_payload_required())};
(PayloadCount > 1) or (PayloadCount == 0) ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)};
+ {error, extended_error(xmpp:err_bad_request(),
+ err_invalid_payload())};
(DeliverPayloads == false) and (PersistItems == false) and
(PayloadSize > 0) ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)};
+ {error, extended_error(xmpp:err_bad_request(),
+ err_item_forbidden())};
((DeliverPayloads == true) or (PersistItems == true)) and (PayloadSize == 0) ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
+ {error, extended_error(xmpp:err_bad_request(),
+ err_item_required())};
true ->
node_call(Host, Type, publish_item,
[Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts])
end
end,
- Reply = [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"item">>,
- attrs = itemAttr(ItemId)}]}]}],
+ Reply = #pubsub{publish = #ps_publish{node = Node,
+ items = [#ps_item{id = ItemId}]}},
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, Broadcast, Removed}}} ->
Nidx = TNode#pubsub_node.id,
@@ -2161,29 +1948,20 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
{result, Reply};
{result, {_, Result}} ->
{result, Result};
- {error, _} = Error ->
- case is_item_not_found(Error) of
+ {error, #stanza_error{reason = 'item-not-found'}} ->
+ Type = select_type(ServerHost, Host, Node),
+ case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
true ->
- Type = select_type(ServerHost, Host, Node),
- case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
- true ->
- case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"create">>,
- attrs = [{<<"node">>, NewNode}]}]}]} ->
+ case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of
+ {result, #pubsub{create = NewNode}} ->
publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
- Payload, PubOpts, Access);
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
- end;
- false ->
- Txt = <<"Automatic node creation is not enabled">>,
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, Txt)}
+ Payload, PubOpts, Access);
+ _ ->
+ {error, xmpp:err_item_not_found()}
end;
false ->
- Error
+ Txt = <<"Automatic node creation is not enabled">>,
+ {error, xmpp:err_item_not_found(Txt, ?MYLANG)}
end;
Error ->
Error
@@ -2200,14 +1978,12 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
%%<li>The node does not support persistent items.</li>
%%<li>The service does not support the deletion of items.</li>
%%</ul>
--spec delete_item(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- Publisher :: jid(), ItemId :: mod_pubsub:itemId()) ->
- {result, []} | {error, xmlel()}.
-
+-spec delete_item(host(), binary(), jid(), binary()) -> {result, undefined} |
+ {error, stanza_error()}.
delete_item(Host, Node, Publisher, ItemId) ->
delete_item(Host, Node, Publisher, ItemId, false).
delete_item(_, <<>>, _, _, _) ->
- {error, extended_error(?ERR_BAD_REQUEST, <<"node-required">>)};
+ {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())};
delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
Features = plugin_features(Host, Type),
@@ -2218,16 +1994,16 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
%% %% Request does not specify an item
%% {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
not PersistentFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('persistent-items'))};
not DeleteFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"delete-items">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('delete-items'))};
true ->
node_call(Host, Type, delete_item, [Nidx, Publisher, PublishModel, ItemId])
end
end,
- Reply = [],
+ Reply = undefined,
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, broadcast}}} ->
Nidx = TNode#pubsub_node.id,
@@ -2258,10 +2034,8 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
%%<li>The node is not configured to persist items.</li>
%%<li>The specified node does not exist.</li>
%%</ul>
--spec purge_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- Owner :: jid()) ->
- {result, []} | {error, xmlel()}.
-
+-spec purge_node(mod_pubsub:host(), binary(), jid()) -> {result, undefined} |
+ {error, stanza_error()}.
purge_node(Host, Node, Owner) ->
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
Features = plugin_features(Host, Type),
@@ -2269,18 +2043,18 @@ purge_node(Host, Node, Owner) ->
PersistentFeature = lists:member(<<"persistent-items">>, Features),
PersistentConfig = get_option(Options, persist_items),
if not PurgeFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"purge-nodes">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('purge-nodes'))};
not PersistentFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('persistent-items'))};
not PersistentConfig ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('persistent-items'))};
true -> node_call(Host, Type, purge_node, [Nidx, Owner])
end
end,
- Reply = [],
+ Reply = undefined,
case transaction(Host, Node, Action, sync_dirty) of
{result, {TNode, {Result, broadcast}}} ->
Nidx = TNode#pubsub_node.id,
@@ -2305,76 +2079,62 @@ purge_node(Host, Node, Owner) ->
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
--spec get_items(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- From :: jid(), SubId :: mod_pubsub:subId(),
- SMaxItems :: binary(), ItemIds :: [mod_pubsub:itemId()],
- Rsm :: none | rsm_in()) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
-
+-spec get_items(host(), binary(), jid(), binary(),
+ binary(), [binary()], undefined | rsm_set()) ->
+ {result, pubsub()} | {error, stanza_error()}.
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
- MaxItems = if SMaxItems == <<>> ->
- case get_max_items_node(Host) of
- undefined -> ?MAXITEMS;
- Max -> Max
- end;
- true ->
- case catch jlib:binary_to_integer(SMaxItems) of
- {'EXIT', _} ->
- Txt = <<"Value of 'max_items' should be integer">>,
- {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
- Val -> Val
- end
- end,
- case MaxItems of
- {error, Error} ->
- {error, Error};
- _ ->
- Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx, owners = O}) ->
- Features = plugin_features(Host, Type),
- RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
- PersistentFeature = lists:member(<<"persistent-items">>, Features),
- AccessModel = get_option(Options, access_model),
- AllowedGroups = get_option(Options, roster_groups_allowed, []),
- if not RetreiveFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-items">>)};
- not PersistentFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)};
- true ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners,
- AccessModel, AllowedGroups),
- node_call(Host, Type, get_items,
- [Nidx, From, AccessModel, PS, RG, SubId, RSM])
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, {Items, RsmOut}}} ->
- SendItems = case ItemIds of
- [] ->
- Items;
- _ ->
- lists:filter(fun (#pubsub_item{itemid = {ItemId, _}}) ->
- lists:member(ItemId, ItemIds)
- end,
- Items)
- end,
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children =
- [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = itemsEls(lists:sublist(SendItems, MaxItems))}
- | jlib:rsm_encode(RsmOut)]}]};
- Error ->
- Error
- end
+ MaxItems = if SMaxItems == undefined ->
+ case get_max_items_node(Host) of
+ undefined -> ?MAXITEMS;
+ Max -> Max
+ end;
+ true ->
+ SMaxItems
+ end,
+ Action =
+ fun(#pubsub_node{options = Options, type = Type,
+ id = Nidx, owners = O}) ->
+ Features = plugin_features(Host, Type),
+ RetreiveFeature = lists:member(<<"retrieve-items">>, Features),
+ PersistentFeature = lists:member(<<"persistent-items">>, Features),
+ AccessModel = get_option(Options, access_model),
+ AllowedGroups = get_option(Options, roster_groups_allowed, []),
+ if not RetreiveFeature ->
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('retrieve-items'))};
+ not PersistentFeature ->
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('persistent-items'))};
+ true ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ {PS, RG} = get_presence_and_roster_permissions(
+ Host, From, Owners, AccessModel, AllowedGroups),
+ node_call(Host, Type, get_items,
+ [Nidx, From, AccessModel, PS, RG, SubId, RSM])
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, {Items, RsmOut}}} ->
+ SendItems = case ItemIds of
+ [] ->
+ Items;
+ _ ->
+ lists:filter(
+ fun(#pubsub_item{itemid = {ItemId, _}}) ->
+ lists:member(ItemId, ItemIds)
+ end, Items)
+ end,
+ {result,
+ #pubsub{items = #ps_items{node = Node,
+ items = itemsEls(lists:sublist(SendItems, MaxItems))},
+ rsm = RsmOut}};
+ Error ->
+ Error
end.
get_items(Host, Node) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
- node_call(Host, Type, get_items, [Nidx, service_jid(Host), none])
+ node_call(Host, Type, get_items, [Nidx, service_jid(Host), undefined])
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, {Items, _}}} -> Items;
@@ -2391,7 +2151,7 @@ get_item(Host, Node, ItemId) ->
end.
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) ->
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, none) of
+ case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, undefined) of
{result, {Items, _RSM}} -> {result, Items};
Error -> Error
end.
@@ -2407,7 +2167,7 @@ get_last_item(Host, Type, Nidx, LJID) ->
LastItem -> LastItem
end.
get_last_item(Host, Type, Nidx, LJID, mnesia) ->
- case node_action(Host, Type, get_items, [Nidx, LJID, none]) of
+ case node_action(Host, Type, get_items, [Nidx, LJID, undefined]) of
{result, {[LastItem|_], _}} -> LastItem;
_ -> undefined
end;
@@ -2422,7 +2182,7 @@ get_last_item(_Host, _Type, _Nidx, _LJID, _) ->
get_last_items(Host, Type, Nidx, LJID, Number) ->
get_last_items(Host, Type, Nidx, LJID, Number, gen_mod:db_type(serverhost(Host), ?MODULE)).
get_last_items(Host, Type, Nidx, LJID, Number, mnesia) ->
- case node_action(Host, Type, get_items, [Nidx, LJID, none]) of
+ case node_action(Host, Type, get_items, [Nidx, LJID, undefined]) of
{result, {Items, _}} -> lists:sublist(Items, Number);
_ -> []
end;
@@ -2473,227 +2233,200 @@ 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(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- JID :: jid(), Plugins :: [binary()]) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
-
+-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
+ {result, pubsub()} | {error, stanza_error()}.
get_affiliations(Host, Node, JID, Plugins) when is_list(Plugins) ->
- Result = lists:foldl( fun (Type, {Status, Acc}) ->
- Features = plugin_features(Host, Type),
- RetrieveFeature = lists:member(<<"retrieve-affiliations">>, Features),
- if not RetrieveFeature ->
- {{error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, <<"retrieve-affiliations">>)},
- Acc};
- true ->
- {result, Affs} = node_action(Host, Type,
- get_entity_affiliations,
- [Host, JID]),
- {Status, [Affs | Acc]}
- end
- end,
- {ok, []}, Plugins),
+ Result =
+ lists:foldl(
+ fun(Type, {Status, Acc}) ->
+ Features = plugin_features(Host, Type),
+ RetrieveFeature = lists:member(<<"retrieve-affiliations">>, Features),
+ if not RetrieveFeature ->
+ {{error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('retrieve-affiliations'))},
+ Acc};
+ true ->
+ {result, Affs} = node_action(Host, Type,
+ get_entity_affiliations,
+ [Host, JID]),
+ {Status, [Affs | Acc]}
+ end
+ end,
+ {ok, []}, Plugins),
case Result of
{ok, Affs} ->
- Entities = lists:flatmap(fun
- ({_, none}) ->
- [];
- ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) ->
- if (Node == <<>>) or (Node == NodeId) ->
- [#xmlel{name = <<"affiliation">>,
- attrs = [{<<"affiliation">>, affiliation_to_string(Aff)}
- | nodeAttr(NodeId)]}];
- true ->
- []
- end;
- (_) ->
- []
- end,
- lists:usort(lists:flatten(Affs))),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"affiliations">>, attrs = [],
- children = Entities}]}]};
+ Entities = lists:flatmap(
+ fun({_, none}) ->
+ [];
+ ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) ->
+ if (Node == <<>>) or (Node == NodeId) ->
+ [#ps_affiliation{node = NodeId,
+ type = Aff}];
+ true ->
+ []
+ end;
+ (_) ->
+ []
+ end, lists:usort(lists:flatten(Affs))),
+ {result, #pubsub{affiliations = {<<>>, Entities}}};
{Error, _} ->
Error
end.
--spec get_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- JID :: jid()) ->
- {result, [xmlel(),...]} | {error, xmlel()}.
-
+-spec get_affiliations(host(), binary(), jid()) ->
+ {result, pubsub_owner()} | {error, stanza_error()}.
get_affiliations(Host, Node, JID) ->
- Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
- Features = plugin_features(Host, Type),
- RetrieveFeature = lists:member(<<"modify-affiliations">>, Features),
- {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]),
- if not RetrieveFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"modify-affiliations">>)};
- Affiliation /= owner ->
- {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
- true ->
- node_call(Host, Type, get_node_affiliations, [Nidx])
- end
- end,
+ Action =
+ fun(#pubsub_node{type = Type, id = Nidx}) ->
+ Features = plugin_features(Host, Type),
+ RetrieveFeature = lists:member(<<"modify-affiliations">>, Features),
+ {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]),
+ if not RetrieveFeature ->
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('modify-affiliations'))};
+ Affiliation /= owner ->
+ {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)};
+ true ->
+ node_call(Host, Type, get_node_affiliations, [Nidx])
+ end
+ end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, []}} ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, xmpp:err_item_not_found()};
{result, {_, Affs}} ->
- Entities = lists:flatmap(fun
- ({_, none}) ->
- [];
- ({AJID, Aff}) ->
- [#xmlel{name = <<"affiliation">>,
- attrs = [{<<"jid">>, jid:to_string(AJID)},
- {<<"affiliation">>, affiliation_to_string(Aff)}]}]
- end,
- Affs),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
- children = [#xmlel{name = <<"affiliations">>,
- attrs = nodeAttr(Node), children = Entities}]}]};
+ Entities = lists:flatmap(
+ fun({_, none}) ->
+ [];
+ ({AJID, Aff}) ->
+ [#ps_affiliation{jid = AJID, type = Aff}]
+ end, Affs),
+ {result, #pubsub_owner{affiliations = {Node, Entities}}};
Error ->
Error
end.
--spec set_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(),
- From :: jid(), EntitiesEls :: [xmlel()]) ->
- {result, []} | {error, xmlel()}.
-
-set_affiliations(Host, Node, From, EntitiesEls) ->
+-spec set_affiliations(host(), binary(), jid(), [ps_affiliation()]) ->
+ {result, undefined} | {error, stanza_error()}.
+set_affiliations(Host, Node, From, Affs) ->
Owner = jid:tolower(jid:remove_resource(From)),
- Entities = lists:foldl(fun
- (_, error) ->
- error;
- (El, Acc) ->
- case El of
- #xmlel{name = <<"affiliation">>, attrs = 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 -> [{jid:tolower(JID), Affiliation} | Acc]
- end
- end
- end,
- [], EntitiesEls),
- case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O} = N) ->
- Owners = node_owners_call(Host, Type, Nidx, O),
- case lists:member(Owner, Owners) of
- true ->
- OwnerJID = jid:make(Owner),
- FilteredEntities = case Owners of
- [Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
- _ -> Entities
+ Action =
+ fun(#pubsub_node{type = Type, id = Nidx, owners = O} = N) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case lists:member(Owner, Owners) of
+ true ->
+ OwnerJID = jid:make(Owner),
+ FilteredAffs =
+ case Owners of
+ [Owner] ->
+ [Aff || Aff <- Affs,
+ Aff#ps_affiliation.jid /= OwnerJID];
+ _ ->
+ Affs
end,
- lists:foreach(fun ({JID, Affiliation}) ->
- node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]),
- case Affiliation of
- owner ->
- NewOwner = jid:tolower(jid:remove_resource(JID)),
- NewOwners = [NewOwner | Owners],
- tree_call(Host,
+ lists:foreach(
+ fun(#ps_affiliation{jid = JID, type = Affiliation}) ->
+ node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]),
+ case Affiliation of
+ owner ->
+ NewOwner = jid:tolower(jid:remove_resource(JID)),
+ NewOwners = [NewOwner | Owners],
+ tree_call(Host,
set_node,
[N#pubsub_node{owners = NewOwners}]);
- none ->
- OldOwner = jid:tolower(jid:remove_resource(JID)),
- case lists:member(OldOwner, Owners) of
- true ->
- NewOwners = Owners -- [OldOwner],
- tree_call(Host,
+ none ->
+ OldOwner = jid:tolower(jid:remove_resource(JID)),
+ case lists:member(OldOwner, Owners) of
+ true ->
+ NewOwners = Owners -- [OldOwner],
+ tree_call(Host,
set_node,
[N#pubsub_node{owners = NewOwners}]);
- _ ->
- ok
- end;
- _ ->
- ok
- end
- end,
- FilteredEntities),
- {result, []};
- _ ->
- {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end
+ end, FilteredAffs),
+ {result, undefined};
+ _ ->
+ {error, xmpp:err_forbidden(
+ <<"Owner privileges required">>, ?MYLANG)}
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
+-spec get_options(binary(), binary(), jid(), binary(), binary()) ->
+ {result, xdata()} | {error, stanza_error()}.
get_options(Host, Node, JID, SubId, Lang) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of
true ->
get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type);
false ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)}
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('subscription-options'))}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_Node, XForm}} -> {result, [XForm]};
+ {result, {_Node, XForm}} -> {result, XForm};
Error -> Error
end.
+-spec get_options_helper(binary(), jid(), binary(), binary(), _, binary(),
+ binary()) -> {result, pubsub()} | {error, stanza_error()}.
get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type) ->
- Subscriber = string_to_ljid(JID),
+ Subscriber = jid:tolower(JID),
{result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed],
case {SubId, SubIds} of
{_, []} ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)};
+ {error, extended_error(xmpp:err_not_acceptable(),
+ err_not_subscribed())};
{<<>>, [SID]} ->
read_sub(Host, Node, Nidx, Subscriber, SID, Lang);
{<<>>, _} ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
+ {error, extended_error(xmpp:err_not_acceptable(),
+ err_subid_required())};
{_, _} ->
ValidSubId = lists:member(SubId, SubIds),
if ValidSubId ->
read_sub(Host, Node, Nidx, Subscriber, SubId, Lang);
true ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}
+ {error, extended_error(xmpp:err_not_acceptable(),
+ err_invalid_subid())}
end
end.
+-spec read_sub(binary(), binary(), nodeIdx(), ljid(), binary(), binary()) -> {result, pubsub()}.
read_sub(Host, Node, Nidx, Subscriber, SubId, Lang) ->
SubModule = subscription_plugin(Host),
- Children = case SubModule:get_subscription(Subscriber, Nidx, SubId) of
- {error, notfound} ->
- [];
- {result, #pubsub_subscription{options = Options}} ->
- {result, XdataEl} = SubModule:get_options_xform(Lang, Options),
- [XdataEl]
- end,
- OptionsEl = #xmlel{name = <<"options">>,
- attrs = [{<<"jid">>, jid:to_string(Subscriber)},
- {<<"subid">>, SubId}
- | nodeAttr(Node)],
- children = Children},
- PubsubEl = #xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [OptionsEl]},
- {result, PubsubEl}.
-
+ XData = case SubModule:get_subscription(Subscriber, Nidx, SubId) of
+ {error, notfound} ->
+ undefined;
+ {result, #pubsub_subscription{options = Options}} ->
+ {result, X} = SubModule:get_options_xform(Lang, Options),
+ X
+ end,
+ {result, #pubsub{options = #ps_options{jid = jid:make(Subscriber),
+ subid = SubId,
+ node = Node,
+ xdata = XData}}}.
+
+-spec set_options(binary(), binary(), jid(), binary(),
+ [{binary(), [binary()]}]) ->
+ {result, undefined} | {error, stanza_error()}.
set_options(Host, Node, JID, SubId, Configuration) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of
true ->
set_options_helper(Host, Configuration, JID, Nidx, SubId, Type);
false ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)}
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('subscription-options'))}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2701,56 +2434,53 @@ set_options(Host, Node, JID, SubId, Configuration) ->
Error -> Error
end.
+-spec set_options_helper(binary(), [{binary(), [binary()]}], jid(),
+ nodeIdx(), binary(), binary()) ->
+ {result, undefined} | {error, stanza_error()}.
set_options_helper(Host, Configuration, JID, Nidx, SubId, Type) ->
SubModule = subscription_plugin(Host),
SubOpts = case SubModule:parse_options_xform(Configuration) of
{result, GoodSubOpts} -> GoodSubOpts;
_ -> invalid
end,
- Subscriber = string_to_ljid(JID),
+ Subscriber = jid:tolower(JID),
{result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed],
case {SubId, SubIds} of
{_, []} ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)};
+ {error, extended_error(xmpp:err_not_acceptable(), err_not_subscribed())};
{<<>>, [SID]} ->
write_sub(Host, Nidx, Subscriber, SID, SubOpts);
{<<>>, _} ->
- {error,
- extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
+ {error, extended_error(xmpp:err_not_acceptable(), err_subid_required())};
{_, _} ->
write_sub(Host, Nidx, Subscriber, SubId, SubOpts)
end.
+-spec write_sub(binary(), nodeIdx(), ljid(), binary(), _) -> {result, undefined} |
+ {error, stanza_error()}.
write_sub(_Host, _Nidx, _Subscriber, _SubId, invalid) ->
- {error,
- extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)};
+ {error, extended_error(xmpp:err_bad_request(), err_invalid_options())};
write_sub(_Host, _Nidx, _Subscriber, _SubId, []) ->
- {result, []};
+ {result, undefined};
write_sub(Host, Nidx, Subscriber, SubId, Options) ->
SubModule = subscription_plugin(Host),
case SubModule:set_subscription(Subscriber, Nidx, SubId, Options) of
- {result, _} -> {result, []};
- {error, _} -> {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}
+ {result, _} -> {result, undefined};
+ {error, _} -> {error, extended_error(xmpp:err_not_acceptable(),
+ err_invalid_subid())}
end.
-%% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response}
-%% Host = host()
-%% Node = pubsubNode()
-%% JID = jid()
-%% Plugins = [Plugin::string()]
-%% Reason = stanzaError()
-%% Response = [pubsubIQResponse()]
%% @doc <p>Return the list of subscriptions as an XMPP response.</p>
+-spec get_subscriptions(host(), binary(), jid(), [binary()]) ->
+ {result, pubsub()} | {error, stanza_error()}.
get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
Result = lists:foldl(fun (Type, {Status, Acc}) ->
Features = plugin_features(Host, Type),
RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features),
if not RetrieveFeature ->
- {{error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, <<"retrieve-subscriptions">>)},
+ {{error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('retrieve-subscriptions'))},
Acc};
true ->
Subscriber = jid:remove_resource(JID),
@@ -2769,14 +2499,9 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
({#pubsub_node{nodeid = {_, SubsNode}}, Sub}) ->
case Node of
<<>> ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"subscription">>, subscription_to_string(Sub)}
- | nodeAttr(SubsNode)]}];
+ [#ps_subscription{node = SubsNode, type = Sub}];
SubsNode ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"subscription">>, subscription_to_string(Sub)}]}];
+ [#ps_subscription{type = Sub}];
_ ->
[]
end;
@@ -2785,88 +2510,65 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) ->
({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}) ->
case Node of
<<>> ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(SubJID)},
- {<<"subid">>, SubId},
- {<<"subscription">>, subscription_to_string(Sub)}
- | nodeAttr(SubsNode)]}];
+ [#ps_subscription{jid = SubJID,
+ subid = SubId,
+ type = Sub,
+ node = SubsNode}];
SubsNode ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(SubJID)},
- {<<"subid">>, SubId},
- {<<"subscription">>, subscription_to_string(Sub)}]}];
+ [#ps_subscription{jid = SubJID,
+ subid = SubId,
+ type = Sub}];
_ ->
[]
end;
({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}) ->
case Node of
<<>> ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(SubJID)},
- {<<"subscription">>, subscription_to_string(Sub)}
- | nodeAttr(SubsNode)]}];
+ [#ps_subscription{jid = SubJID,
+ type = Sub,
+ node = SubsNode}];
SubsNode ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(SubJID)},
- {<<"subscription">>, subscription_to_string(Sub)}]}];
+ [#ps_subscription{jid = SubJID, type = Sub}];
_ ->
[]
end
end,
lists:usort(lists:flatten(Subs))),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"subscriptions">>, attrs = [],
- children = Entities}]}]};
+ {result, #pubsub{subscriptions = {<<>>, Entities}}};
{Error, _} ->
Error
end.
+-spec get_subscriptions(host(), binary(), jid()) -> {result, pubsub_owner()} |
+ {error, stanza_error()}.
get_subscriptions(Host, Node, JID) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
Features = plugin_features(Host, Type),
RetrieveFeature = lists:member(<<"manage-subscriptions">>, Features),
{result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]),
if not RetrieveFeature ->
- {error,
- extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"manage-subscriptions">>)};
+ {error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('manage-subscriptions'))};
Affiliation /= owner ->
- {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
+ {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)};
true ->
node_call(Host, Type, get_node_subscriptions, [Nidx])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Subs}} ->
- Entities = lists:flatmap(fun
- ({_, none}) ->
- [];
- ({_, pending, _}) ->
- [];
- ({AJID, Sub}) ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(AJID)},
- {<<"subscription">>, subscription_to_string(Sub)}]}];
+ Entities =
+ lists:flatmap(
+ fun({_, none}) ->
+ [];
+ ({_, pending, _}) ->
+ [];
+ ({AJID, Sub}) ->
+ [#ps_subscription{jid = AJID, type = Sub}];
({AJID, Sub, SubId}) ->
- [#xmlel{name = <<"subscription">>,
- attrs =
- [{<<"jid">>, jid:to_string(AJID)},
- {<<"subscription">>, subscription_to_string(Sub)},
- {<<"subid">>, SubId}]}]
- end,
- Subs),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
- children = [#xmlel{name = <<"subscriptions">>,
- attrs = nodeAttr(Node),
- children = Entities}]}]};
+ [#ps_subscription{jid = AJID, type = Sub, subid = SubId}]
+ end, Subs),
+ {result, #pubsub_owner{subscriptions = {Node, Entities}}};
Error ->
Error
end.
@@ -2894,74 +2596,57 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
get_subscriptions_for_send_last(_Host, _PType, _, _JID, _LJID, _BJID) ->
[].
-set_subscriptions(Host, Node, From, EntitiesEls) ->
+-spec set_subscriptions(host(), binary(), jid(), [ps_subscription()]) ->
+ {result, undefined} | {error, stanza_error()}.
+set_subscriptions(Host, Node, From, Entities) ->
Owner = jid:tolower(jid:remove_resource(From)),
- Entities = lists:foldl(fun
- (_, error) ->
- error;
- (El, Acc) ->
- case El of
- #xmlel{name = <<"subscription">>, attrs = 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 -> [{jid:tolower(JID), Sub, SubId} | Acc]
- end
- end
- end,
- [], EntitiesEls),
- case Entities of
- error ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- Notify = fun (JID, Sub, _SubId) ->
- Stanza = event_stanza(
- [#xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>, jid:to_string(JID)},
- {<<"subscription">>, subscription_to_string(Sub)}
- | nodeAttr(Node)]}]),
- 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),
- case lists:member(Owner, Owners) of
- true ->
- Result = lists:foldl(fun ({JID, Sub, SubId}, Acc) ->
- case
- node_call(Host, Type,
- set_subscriptions,
- [Nidx, JID, Sub, SubId])
- of
- {error, Err} ->
- [{error, Err} | Acc];
- _ ->
- Notify(JID, Sub, SubId),
- Acc
- end
- end,
- [], Entities),
- case Result of
- [] -> {result, []};
- [{error, E}|_] -> {error, E}
- end;
- _ ->
- {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
- end
- end,
- case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Result}} -> {result, Result};
- Other -> Other
- end
+ Notify = fun(#ps_subscription{jid = JID, type = Sub}) ->
+ Stanza = #message{
+ sub_els = [#ps_event{
+ subscription = #ps_subscription{
+ jid = JID,
+ type = Sub,
+ node = Node}}]},
+ ejabberd_router:route(service_jid(Host), JID, Stanza)
+ end,
+ Action =
+ fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
+ Owners = node_owners_call(Host, Type, Nidx, O),
+ case lists:member(Owner, Owners) of
+ true ->
+ Result =
+ lists:foldl(
+ fun(_, {error, _} = Err) ->
+ Err;
+ (#ps_subscription{jid = JID, type = Sub,
+ subid = SubId} = Entity, _) ->
+ case node_call(Host, Type,
+ set_subscriptions,
+ [Nidx, JID, Sub, SubId]) of
+ {error, _} = Err ->
+ Err;
+ _ ->
+ Notify(Entity)
+ end
+ end, ok, Entities),
+ case Result of
+ ok -> {result, undefined};
+ {error, _} = Err -> Err
+ end;
+ _ ->
+ {error, xmpp:err_forbidden(
+ <<"Owner privileges required">>, ?MYLANG)}
+
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+ Other -> Other
end.
--spec get_presence_and_roster_permissions(Host :: mod_pubsub:host(),
- From :: ljid(), Owners :: [ljid(),...],
- AccessModel :: mod_pubsub:accessModel(),
- AllowedGroups :: [binary()]) ->
- {PresenceSubscription::boolean(),
- RosterGroup::boolean()}.
-
+-spec get_presence_and_roster_permissions(
+ host(), ljid(), [ljid()], accessModel(),
+ [binary()]) -> {boolean(), boolean()}.
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
if (AccessModel == presence) or (AccessModel == roster) ->
case Host of
@@ -2993,36 +2678,10 @@ get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, A
get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
get_roster_info(OwnerUser, OwnerServer, jid:tolower(JID), AllowedGroups).
-string_to_affiliation(<<"owner">>) -> owner;
-string_to_affiliation(<<"publisher">>) -> publisher;
-string_to_affiliation(<<"publish-only">>) -> publish_only;
-string_to_affiliation(<<"member">>) -> member;
-string_to_affiliation(<<"outcast">>) -> outcast;
-string_to_affiliation(<<"none">>) -> none;
-string_to_affiliation(_) -> false.
-
-string_to_subscription(<<"subscribed">>) -> subscribed;
-string_to_subscription(<<"pending">>) -> pending;
-string_to_subscription(<<"unconfigured">>) -> unconfigured;
-string_to_subscription(<<"none">>) -> none;
-string_to_subscription(_) -> false.
-
-affiliation_to_string(owner) -> <<"owner">>;
-affiliation_to_string(publisher) -> <<"publisher">>;
-affiliation_to_string(publish_only) -> <<"publish-only">>;
-affiliation_to_string(member) -> <<"member">>;
-affiliation_to_string(outcast) -> <<"outcast">>;
-affiliation_to_string(_) -> <<"none">>.
-
-subscription_to_string(subscribed) -> <<"subscribed">>;
-subscription_to_string(pending) -> <<"pending">>;
-subscription_to_string(unconfigured) -> <<"unconfigured">>;
-subscription_to_string(_) -> <<"none">>.
-
--spec service_jid(Host :: mod_pubsub:host()) -> jid().
+-spec service_jid(jid() | ljid() | binary()) -> jid().
service_jid(#jid{} = Jid) -> Jid;
service_jid({U, S, R}) -> jid:make(U, S, R);
-service_jid(Host) -> jid:make(<<>>, Host, <<>>).
+service_jid(Host) -> jid:make(Host).
%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean()
%% LJID = jid()
@@ -3053,7 +2712,7 @@ sub_option_can_deliver(_, _, {deliver, false}) -> false;
sub_option_can_deliver(_, _, {expire, When}) -> p1_time_compat:timestamp() < When;
sub_option_can_deliver(_, _, _) -> true.
--spec presence_can_deliver(Entity :: ljid(), _ :: boolean()) -> boolean().
+-spec presence_can_deliver(ljid(), boolean()) -> boolean().
presence_can_deliver(_, false) ->
true;
presence_can_deliver({User, Server, Resource}, true) ->
@@ -3074,10 +2733,7 @@ presence_can_deliver({User, Server, Resource}, true) ->
false, Ss)
end.
--spec state_can_deliver(Entity::ljid(),
- SubOptions :: mod_pubsub:subOptions() | []) ->
- [ljid()].
-
+-spec state_can_deliver(ljid(), subOptions() | []) -> [ljid()].
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
state_can_deliver({U, S, R}, SubOptions) ->
case lists:keysearch(show_values, 1, SubOptions) of
@@ -3097,10 +2753,7 @@ state_can_deliver({U, S, R}, SubOptions) ->
[], Resources)
end.
--spec get_resource_state(Entity :: ljid(), ShowValues :: [binary()],
- JIDs :: [ljid()]) ->
- [ljid()].
-
+-spec get_resource_state(ljid(), [binary()], [ljid()]) -> [ljid()].
get_resource_state({U, S, R}, ShowValues, JIDs) ->
case ejabberd_sm:get_session_pid(U, S, R) of
none ->
@@ -3119,8 +2772,7 @@ get_resource_state({U, S, R}, ShowValues, JIDs) ->
end
end.
--spec payload_xmlelements(Payload :: mod_pubsub:payload()) ->
- Count :: non_neg_integer().
+-spec payload_xmlelements([xmlel()]) -> non_neg_integer().
payload_xmlelements(Payload) ->
payload_xmlelements(Payload, 0).
@@ -3134,63 +2786,55 @@ 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">>, jid:to_string(ModifUSR)},
- {<<"stamp">>, <<T_string/binary, Tz_string/binary>>}],
- children = [{xmlcdata, <<>>}]}];
+ [#delay{stamp = ModifNow, from = jid:make(ModifUSR)}];
_ ->
[]
end,
- BaseStanza = event_stanza_with_els([#xmlel{name = <<"items">>,
- attrs = nodeAttr(Node),
- children = itemsEls(Items)}],
- MoreEls),
+ BaseStanza = #message{
+ sub_els = [#ps_event{items = #ps_items{
+ node = Node,
+ items = itemsEls(Items)}}
+ | MoreEls]},
NotificationType = get_option(Options, notification_type, headline),
add_message_type(BaseStanza, NotificationType).
-event_stanza(Els) ->
- event_stanza_with_els(Els, []).
-event_stanza_with_els(Els, MoreEls) ->
- #xmlel{name = <<"message">>, attrs = [],
- children = [#xmlel{name = <<"event">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
- children = Els}
- | MoreEls]}.
-
-event_stanza(Event, EvAttr) ->
- event_stanza_with_els([#xmlel{name = Event, attrs = EvAttr}], []).
-
%%%%%% broadcast functions
broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payload, Removed) ->
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
- Content = case get_option(NodeOptions, deliver_payloads) of
- 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 = Attrs,
- children = Content}]}]),
+ EventItem0 = case get_option(NodeOptions, deliver_payloads) of
+ true -> #ps_item{xml_els = Payload, id = ItemId};
+ false -> #ps_item{id = ItemId}
+ end,
+ EventItem = case get_option(NodeOptions, itemreply, none) of
+ owner -> %% owner not supported
+ EventItem0;
+ publisher ->
+ EventItem0#ps_item{
+ publisher = jid:to_string(From)};
+ none ->
+ EventItem0
+ end,
+ Stanza = #message{
+ sub_els =
+ [#ps_event{items =
+ #ps_items{node = Node,
+ items = [EventItem]}}]},
broadcast_stanza(Host, From, Node, Nidx, Type,
- NodeOptions, SubsByDepth, items, Stanza, true),
+ NodeOptions, SubsByDepth, items, Stanza, true),
case Removed of
[] ->
ok;
_ ->
case get_option(NodeOptions, notify_retract) of
true ->
- RetractStanza = event_stanza(
- [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]),
+ RetractStanza = #message{
+ sub_els =
+ [#ps_event{
+ items = #ps_items{
+ node = Node,
+ retract = Removed}}]},
broadcast_stanza(Host, Node, Nidx, Type,
NodeOptions, SubsByDepth,
items, RetractStanza, true);
@@ -3212,9 +2856,12 @@ broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, ForceNotif
true ->
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
- Stanza = event_stanza(
- [#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]),
+ Stanza = #message{
+ sub_els =
+ [#ps_event{
+ items = #ps_items{
+ node = Node,
+ retract = ItemIds}}]},
broadcast_stanza(Host, Node, Nidx, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
{result, true};
@@ -3230,8 +2877,7 @@ broadcast_purge_node(Host, Node, Nidx, Type, NodeOptions) ->
true ->
case get_collection_subscriptions(Host, Node) of
SubsByDepth when is_list(SubsByDepth) ->
- Stanza = event_stanza(
- [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]),
+ Stanza = #message{sub_els = [#ps_event{purge = Node}]},
broadcast_stanza(Host, Node, Nidx, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -3249,8 +2895,7 @@ broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
[] ->
{result, false};
_ ->
- Stanza = event_stanza(
- [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]),
+ Stanza = #message{sub_els = [#ps_event{delete = {Node, <<>>}}]},
broadcast_stanza(Host, Node, Nidx, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true}
@@ -3262,7 +2907,7 @@ broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
broadcast_created_node(_, _, _, _, _, []) ->
{result, false};
broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
- Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]),
+ Stanza = #message{sub_els = [#ps_event{create = Node}]},
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
{result, true}.
@@ -3273,13 +2918,15 @@ broadcast_config_notification(Host, Node, Nidx, Type, NodeOptions, Lang) ->
SubsByDepth when is_list(SubsByDepth) ->
Content = case get_option(NodeOptions, deliver_payloads) of
true ->
- [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children = get_configure_xfields(Type, NodeOptions, Lang, [])}];
+ #xdata{type = result,
+ fields = get_configure_xfields(
+ Type, NodeOptions, Lang, [])};
false ->
- []
+ undefined
end,
- Stanza = event_stanza(
- [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]),
+ Stanza = #message{
+ sub_els = [#ps_event{
+ configuration = {Node, Content}}]},
broadcast_stanza(Host, Node, Nidx, Type,
NodeOptions, SubsByDepth, nodes, Stanza, false),
{result, true};
@@ -3371,8 +3018,9 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
ejabberd_c2s:broadcast(C2SPid,
{pep_message, <<((Node))/binary, "+notify">>},
_Sender = jid:make(LUser, LServer, <<"">>),
- _StanzaToSend = add_extended_headers(Stanza,
- _ReplyTo = extended_headers([jid:to_string(Publisher)])));
+ _StanzaToSend = add_extended_headers(
+ Stanza,
+ _ReplyTo = extended_headers([Publisher])));
_ ->
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza])
end;
@@ -3438,9 +3086,11 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
JIDSubs.
+-spec user_resources(binary(), binary()) -> [binary()].
user_resources(User, Server) ->
ejabberd_sm:get_user_resources(User, Server).
+-spec user_resource(binary(), binary(), binary()) -> binary().
user_resource(User, Server, <<>>) ->
case user_resources(User, Server) of
[R | _] -> R;
@@ -3450,26 +3100,19 @@ user_resource(_, _, Resource) ->
Resource.
%%%%%%% Configuration handling
-
+-spec get_configure(host(), binary(), binary(), jid(),
+ binary()) -> {error, stanza_error()} | {result, pubsub_owner()}.
get_configure(Host, ServerHost, Node, From, Lang) ->
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
case node_call(Host, Type, get_affiliation, [Nidx, From]) of
{result, owner} ->
Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
- children =
- [#xmlel{name = <<"configure">>,
- attrs = nodeAttr(Node),
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children =
- get_configure_xfields(Type, Options, Lang, Groups)}]}]}]};
+ Fs = get_configure_xfields(Type, Options, Lang, Groups),
+ {result, #pubsub_owner{
+ configure =
+ {Node, #xdata{type = form, fields = Fs}}}};
_ ->
- {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
+ {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -3477,20 +3120,14 @@ get_configure(Host, ServerHost, Node, From, Lang) ->
Other -> Other
end.
+-spec get_default(host(), binary(), jid(), binary()) -> {result, pubsub_owner()}.
get_default(Host, Node, _From, Lang) ->
Type = select_type(Host, Host, Node),
Options = node_options(Host, Type),
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
- children =
- [#xmlel{name = <<"default">>, attrs = [],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA},
- {<<"type">>, <<"form">>}],
- children = get_configure_xfields(Type, Options, Lang, [])}]}]}]}.
+ Fs = get_configure_xfields(Type, Options, Lang, []),
+ {result, #pubsub_owner{default = {<<>>, #xdata{type = form, fields = Fs}}}}.
+-spec match_option(#pubsub_node{} | [{atom(), any()}], atom(), any()) -> boolean().
match_option(Node, Var, Val) when is_record(Node, pubsub_node) ->
match_option(Node#pubsub_node.options, Var, Val);
match_option(Options, Var, Val) when is_list(Options) ->
@@ -3498,21 +3135,26 @@ match_option(Options, Var, Val) when is_list(Options) ->
match_option(_, _, _) ->
false.
+-spec get_option([{atom(), any()}], atom()) -> any().
get_option([], _) -> false;
get_option(Options, Var) -> get_option(Options, Var, false).
+-spec get_option([{atom(), any()}], atom(), any()) -> any().
get_option(Options, Var, Def) ->
case lists:keysearch(Var, 1, Options) of
{value, {_Val, Ret}} -> Ret;
_ -> Def
end.
+-spec node_options(host(), binary()) -> [{atom(), any()}].
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.
+
+-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
node_plugin_options(Host, Type) ->
Module = plugin(Host, Type),
case catch Module:options() of
@@ -3522,12 +3164,15 @@ node_plugin_options(Host, Type) ->
Result ->
Result
end.
+
+-spec filter_node_options([{atom(), any()}], [{atom(), any()}]) -> [{atom(), any()}].
filter_node_options(Options, BaseOptions) ->
lists:foldl(fun({Key, Val}, Acc) ->
DefaultValue = proplists:get_value(Key, Options, Val),
[{Key, DefaultValue}|Acc]
end, [], BaseOptions).
+-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
node_owners_action(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
sql ->
@@ -3541,6 +3186,7 @@ node_owners_action(Host, Type, Nidx, []) ->
node_owners_action(_Host, _Type, _Nidx, Owners) ->
Owners.
+-spec node_owners_call(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
node_owners_call(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
sql ->
@@ -3565,6 +3211,7 @@ node_owners_call(_Host, _Type, _Nidx, Owners) ->
%% @todo In practice, the current data structure means that we cannot manage
%% millions of items on a given node. This should be addressed in a new
%% version.
+-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
max_items(Host, Options) ->
case get_option(Options, persist_items) of
true ->
@@ -3585,83 +3232,17 @@ max_items(Host, Options) ->
end
end.
--define(BOOL_CONFIG_FIELD(Label, Var),
- ?BOOLXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (get_option(Options, Var)))).
-
--define(STRING_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (get_option(Options, Var, <<>>)))).
-
--define(INTEGER_CONFIG_FIELD(Label, Var),
- ?STRINGXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (jlib:integer_to_binary(get_option(Options, Var))))).
-
--define(JLIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (jid:to_string(get_option(Options, Var))),
- [jid:to_string(O) || O <- Opts])).
-
--define(ALIST_CONFIG_FIELD(Label, Var, Opts),
- ?LISTXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (atom_to_binary(get_option(Options, Var), latin1)),
- [atom_to_binary(O, latin1) || O <- Opts])).
-
--define(LISTM_CONFIG_FIELD(Label, Var, Opts),
- ?LISTMXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- (get_option(Options, Var)), Opts)).
-
--define(NLIST_CONFIG_FIELD(Label, Var),
- ?STRINGMXFIELD(Label,
- <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
- get_option(Options, Var, []))).
-
+-spec get_configure_xfields(_, pubsub_node_config:result(),
+ binary(), [binary()]) -> [xdata_field()].
get_configure_xfields(_Type, Options, Lang, Groups) ->
- [?XFIELD(<<"hidden">>, <<>>, <<"FORM_TYPE">>, (?NS_PUBSUB_NODE_CONFIG)),
- ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>,
- deliver_payloads),
- ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>,
- deliver_notifications),
- ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuration changes">>,
- notify_config),
- ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is deleted">>,
- notify_delete),
- ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed from the node">>,
- notify_retract),
- ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>,
- persist_items),
- ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>,
- title),
- ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>,
- max_items),
- ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>,
- subscribe),
- ?ALIST_CONFIG_FIELD(<<"Specify the access model">>,
- access_model, [open, authorize, presence, roster, whitelist]),
- ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>,
- roster_groups_allowed, Groups),
- ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>,
- publish_model, [publishers, subscribers, open]),
- ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher goes offline">>,
- purge_offline),
- ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>,
- notification_type, [headline, normal]),
- ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>,
- max_payload_size),
- ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>,
- send_last_published_item, [never, on_sub, on_sub_and_presence]),
- ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
- presence_based_delivery),
- ?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
- collection),
- ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>,
- itemreply, [none, owner, publisher])].
+ pubsub_node_config:encode(
+ lists:map(
+ fun({roster_groups_allowed, Value}) ->
+ {roster_groups_allowed, Value, Groups};
+ (Opt) ->
+ Opt
+ end, Options),
+ fun(Txt) -> translate:translate(Lang, Txt) end).
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3671,180 +3252,134 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%<li>The node has no configuration options.</li>
%%<li>The specified node does not exist.</li>
%%</ul>
-set_configure(Host, Node, From, Els, Lang) ->
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>} = XEl] ->
- case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of
- {?NS_XDATA, <<"cancel">>} ->
- {result, []};
- {?NS_XDATA, <<"submit">>} ->
- Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx} = N) ->
- case node_call(Host, Type, get_affiliation, [Nidx, From]) of
- {result, owner} ->
- case jlib:parse_xdata_submit(XEl) of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- XData ->
- OldOpts = case Options of
- [] -> node_options(Host, Type);
- _ -> Options
- end,
- case set_xoption(Host, XData, OldOpts) of
- NewOpts when is_list(NewOpts) ->
- case tree_call(Host,
- set_node,
- [N#pubsub_node{options = NewOpts}])
- of
- {result, Nidx} -> {result, ok};
- ok -> {result, ok};
- Err -> Err
- end;
- Error ->
- Error
- end
- end;
- _ ->
- Txt = <<"You're not an owner">>,
- {error, ?ERRT_FORBIDDEN(Lang, Txt)}
- end
- end,
- case transaction(Host, Node, Action, transaction) of
- {result, {TNode, ok}} ->
- Nidx = TNode#pubsub_node.id,
- Type = TNode#pubsub_node.type,
- Options = TNode#pubsub_node.options,
- broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
- {result, []};
- Other ->
- Other
- end;
- _ ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- _ ->
- Txt = <<"No data form found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end.
-
-add_opt(Key, Value, Opts) ->
- [{Key, Value} | lists:keydelete(Key, 1, Opts)].
-
--define(SET_BOOL_XOPT(Opt, Val),
- BoolVal = case Val of
- <<"0">> -> false;
- <<"1">> -> true;
- <<"false">> -> false;
- <<"true">> -> true;
- _ -> error
- end,
- case BoolVal of
- error ->
- Txt = <<"Value of '~s' should be boolean">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)};
- _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
- end).
-
--define(SET_STRING_XOPT(Opt, Val),
- set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-
--define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
- case catch jlib:binary_to_integer(Val) of
- IVal when is_integer(IVal), IVal >= Min ->
- if (Max =:= undefined) orelse (IVal =< Max) ->
- set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
- true ->
- Txt = <<"Incorrect value of '~s'">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
- end;
- _ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
- end).
+-spec set_configure(host(), binary(), jid(), [{binary(), [binary()]}],
+ binary()) -> {result, undefined} | {error, stanza_error()}.
+set_configure(_Host, <<>>, _From, _Config, _Lang) ->
+ {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())};
+set_configure(Host, Node, From, Config, Lang) ->
+ Action =
+ fun(#pubsub_node{options = Options, type = Type, id = Nidx} = N) ->
+ case node_call(Host, Type, get_affiliation, [Nidx, From]) of
+ {result, owner} ->
+ OldOpts = case Options of
+ [] -> node_options(Host, Type);
+ _ -> Options
+ end,
+ NewOpts = merge_config(Config, OldOpts),
+ case tree_call(Host,
+ set_node,
+ [N#pubsub_node{options = NewOpts}]) of
+ {result, Nidx} -> {result, ok};
+ ok -> {result, ok};
+ Err -> Err
+ end;
+ _ ->
+ {error, xmpp:err_forbidden(
+ <<"Owner privileges required">>, Lang)}
+ end
+ end,
+ case transaction(Host, Node, Action, transaction) of
+ {result, {TNode, ok}} ->
+ Nidx = TNode#pubsub_node.id,
+ Type = TNode#pubsub_node.type,
+ Options = TNode#pubsub_node.options,
+ broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
+ {result, undefined};
+ Other ->
+ Other
+ end.
+
+-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()].
+merge_config(Config1, Config2) ->
+ lists:foldl(
+ fun({Opt, Val}, Acc) ->
+ lists:keystore(Opt, 1, Acc, {Opt, Val})
+ end, Config2, Config1).
+
+-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
+ pubsub_node_config:result() |
+ {error, stanza_error()}.
+decode_node_config(undefined, _, _) ->
+ [];
+decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
+ try
+ Config = pubsub_node_config:decode(Fs),
+ Max = get_max_items_node(Host),
+ case {check_opt_range(max_items, Config, Max),
+ check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of
+ {true, true} ->
+ Config;
+ {true, false} ->
+ erlang:error(
+ {pubsub_node_config,
+ {bad_var_value, <<"pubsub#max_payload_size">>,
+ ?NS_PUBSUB_NODE_CONFIG}});
+ {false, _} ->
+ erlang:error(
+ {pubsub_node_config,
+ {bad_var_value, <<"pubsub#max_items">>,
+ ?NS_PUBSUB_NODE_CONFIG}})
+ end
+ catch _:{pubsub_node_config, Why} ->
+ Txt = pubsub_node_config:format_error(Why),
+ {error, xmpp:err_resource_constraint(Txt, Lang)}
+ end.
+
+-spec decode_subscribe_options(undefined | xdata(), binary()) ->
+ pubsub_subscribe_options:result() |
+ {error, stanza_error()}.
+decode_subscribe_options(undefined, _) ->
+ [];
+decode_subscribe_options(#xdata{fields = Fs}, Lang) ->
+ try pubsub_subscribe_options:decode(Fs)
+ catch _:{pubsub_subscribe_options, Why} ->
+ Txt = pubsub_subscribe_options:format_error(Why),
+ {error, xmpp:err_resource_constraint(Txt, Lang)}
+ end.
+
+-spec decode_publish_options(undefined | xdata(), binary()) ->
+ pubsub_publish_options:result() |
+ {error, stanza_error()}.
+decode_publish_options(undefined, _) ->
+ [];
+decode_publish_options(#xdata{fields = Fs}, Lang) ->
+ try pubsub_publish_options:decode(Fs)
+ catch _:{pubsub_publish_options, Why} ->
+ Txt = pubsub_publish_options:format_error(Why),
+ {error, xmpp:err_resource_constraint(Txt, Lang)}
+ end.
+
+-spec decode_get_pending(xdata(), binary()) ->
+ pubsub_get_pending:result() |
+ {error, stanza_error()}.
+decode_get_pending(#xdata{fields = Fs}, Lang) ->
+ try pubsub_get_pending:decode(Fs)
+ catch _:{pubsub_get_pending, Why} ->
+ Txt = pubsub_get_pending:format_error(Why),
+ {error, xmpp:err_resource_constraint(Txt, Lang)}
+ end;
+decode_get_pending(undefined, Lang) ->
+ {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}.
--define(SET_ALIST_XOPT(Opt, Val, Vals),
- case lists:member(Val, [atom_to_binary(V, latin1) || V <- Vals]) of
- true ->
- set_xoption(Host, Opts, add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
- false ->
- Txt = <<"Incorrect value of '~s'">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
- end).
-
--define(SET_LIST_XOPT(Opt, Val),
- set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-
-set_xoption(_Host, [], NewOpts) -> NewOpts;
-set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], NewOpts) ->
- set_xoption(Host, Opts, NewOpts);
-set_xoption(Host, [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], NewOpts) ->
- ?SET_LIST_XOPT(roster_groups_allowed, Value);
-set_xoption(Host, [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(deliver_payloads, Val);
-set_xoption(Host, [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(deliver_notifications, Val);
-set_xoption(Host, [{<<"pubsub#notify_config">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(notify_config, Val);
-set_xoption(Host, [{<<"pubsub#notify_delete">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(notify_delete, Val);
-set_xoption(Host, [{<<"pubsub#notify_retract">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(notify_retract, Val);
-set_xoption(Host, [{<<"pubsub#persist_items">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(persist_items, Val);
-set_xoption(Host, [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) ->
- MaxItems = get_max_items_node(Host),
- ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems);
-set_xoption(Host, [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(subscribe, Val);
-set_xoption(Host, [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
-set_xoption(Host, [{<<"pubsub#publish_model">>, [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
-set_xoption(Host, [{<<"pubsub#notification_type">>, [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]);
-set_xoption(Host, [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
-set_xoption(Host, [{<<"pubsub#max_payload_size">>, [Val]} | Opts], NewOpts) ->
- ?SET_INTEGER_XOPT(max_payload_size, Val, 0, (?MAX_PAYLOAD_SIZE));
-set_xoption(Host, [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], NewOpts) ->
- ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
-set_xoption(Host, [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(presence_based_delivery, Val);
-set_xoption(Host, [{<<"pubsub#purge_offline">>, [Val]} | Opts], NewOpts) ->
- ?SET_BOOL_XOPT(purge_offline, Val);
-set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], NewOpts) ->
- ?SET_STRING_XOPT(title, Value);
-set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], NewOpts) ->
- ?SET_STRING_XOPT(type, Value);
-set_xoption(Host, [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) ->
- ?SET_STRING_XOPT(body_xslt, Value);
-set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
- % NewValue = [string_to_node(V) || V <- Value],
- ?SET_LIST_XOPT(collection, Value);
-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).
+-spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean().
+check_opt_range(Opt, Opts, Max) ->
+ Val = proplists:get_value(Opt, Opts, Max),
+ Val =< Max.
+-spec get_max_items_node(host()) -> undefined | non_neg_integer().
get_max_items_node(Host) ->
config(Host, max_items_node, undefined).
+-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer().
get_max_subscriptions_node(Host) ->
config(Host, max_subscriptions_node, undefined).
%%%% last item cache handling
-
+-spec is_last_item_cache_enabled(host()) -> boolean().
is_last_item_cache_enabled(Host) ->
config(Host, last_item_cache, false).
+-spec set_cached_item(host(), nodeIdx(), binary(), binary(), [xmlel()]) -> ok.
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
@@ -3855,6 +3390,7 @@ set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
_ -> ok
end.
+-spec unset_cached_item(host(), nodeIdx()) -> ok.
unset_cached_item({_, ServerHost, _}, Nidx) ->
unset_cached_item(ServerHost, Nidx);
unset_cached_item(Host, Nidx) ->
@@ -3863,9 +3399,7 @@ unset_cached_item(Host, Nidx) ->
_ -> ok
end.
--spec get_cached_item(Host :: mod_pubsub:host(), Nidx :: mod_pubsub:nodeIdx()) ->
- undefined | mod_pubsub:pubsubItem().
-
+-spec get_cached_item(host(), nodeIdx()) -> undefined | pubsubItem().
get_cached_item({_, ServerHost, _}, Nidx) ->
get_cached_item(ServerHost, Nidx);
get_cached_item(Host, Nidx) ->
@@ -3886,21 +3420,24 @@ get_cached_item(Host, Nidx) ->
end.
%%%% plugin handling
-
+-spec host(binary()) -> binary().
host(ServerHost) ->
config(ServerHost, host, <<"pubsub.", ServerHost/binary>>).
+-spec serverhost(host()) -> binary().
serverhost({_U, ServerHost, _R})->
serverhost(ServerHost);
serverhost(Host) ->
ejabberd_router:host_of_route(Host).
+-spec tree(host()) -> atom().
tree(Host) ->
case config(Host, nodetree) of
undefined -> tree(Host, ?STDTREE);
Tree -> Tree
end.
+-spec tree(host(), binary() | atom()) -> atom().
tree(_Host, <<"virtual">>) ->
nodetree_virtual; % special case, virtual does not use any backend
tree(Host, Name) ->
@@ -3910,6 +3447,7 @@ tree(Host, Name) ->
_ -> Name
end.
+-spec plugin(host(), binary() | atom()) -> atom().
plugin(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"node_", Name/binary>>);
@@ -3917,6 +3455,7 @@ plugin(Host, Name) ->
_ -> Name
end.
+-spec plugins(host()) -> [binary()].
plugins(Host) ->
case config(Host, plugins) of
undefined -> [?STDNODE];
@@ -3924,6 +3463,9 @@ plugins(Host) ->
Plugins -> Plugins
end.
+-spec subscription_plugin(host()) -> pubsub_subscription |
+ pubsub_subscription_sql |
+ none.
subscription_plugin(Host) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> pubsub_subscription;
@@ -3931,9 +3473,11 @@ subscription_plugin(Host) ->
_ -> none
end.
+-spec config(binary(), any()) -> any().
config(ServerHost, Key) ->
config(ServerHost, Key, undefined).
+-spec config(host(), any(), any()) -> any().
config({_User, Host, _Resource}, Key, Default) ->
config(Host, Key, Default);
config(ServerHost, Key, Default) ->
@@ -3942,6 +3486,7 @@ config(ServerHost, Key, Default) ->
_ -> Default
end.
+-spec select_type(binary(), host(), binary(), binary()) -> binary().
select_type(ServerHost, Host, Node, Type) ->
SelectedType = case Host of
{_User, _Server, _Resource} ->
@@ -3958,36 +3503,39 @@ select_type(ServerHost, Host, Node, Type) ->
false -> hd(ConfiguredTypes)
end.
+-spec select_type(binary(), host(), binary()) -> binary().
select_type(ServerHost, Host, Node) ->
select_type(ServerHost, Host, Node, hd(plugins(Host))).
+-spec feature(binary()) -> binary().
feature(<<"rsm">>) -> ?NS_RSM;
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
+-spec features() -> [binary()].
features() ->
[% see plugin "access-authorize", % OPTIONAL
- <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree
- <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep
- <<"access-whitelist">>, % OPTIONAL
- <<"collections">>, % RECOMMENDED
- <<"config-node">>, % RECOMMENDED
- <<"create-and-configure">>, % RECOMMENDED
- <<"item-ids">>, % RECOMMENDED
- <<"last-published">>, % RECOMMENDED
- <<"member-affiliation">>, % RECOMMENDED
- <<"presence-notifications">>, % OPTIONAL
- <<"presence-subscribe">>, % RECOMMENDED
- <<"publisher-affiliation">>, % RECOMMENDED
- <<"publish-only-affiliation">>, % OPTIONAL
- <<"retrieve-default">>,
- <<"shim">>]. % RECOMMENDED
+ <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree
+ <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep
+ <<"access-whitelist">>, % OPTIONAL
+ <<"collections">>, % RECOMMENDED
+ <<"config-node">>, % RECOMMENDED
+ <<"create-and-configure">>, % RECOMMENDED
+ <<"item-ids">>, % RECOMMENDED
+ <<"last-published">>, % RECOMMENDED
+ <<"member-affiliation">>, % RECOMMENDED
+ <<"presence-notifications">>, % OPTIONAL
+ <<"presence-subscribe">>, % RECOMMENDED
+ <<"publisher-affiliation">>, % RECOMMENDED
+ <<"publish-only-affiliation">>, % OPTIONAL
+ <<"retrieve-default">>,
+ <<"shim">>]. % RECOMMENDED
% see plugin "retrieve-items", % RECOMMENDED
% see plugin "retrieve-subscriptions", % RECOMMENDED
% see plugin "subscribe", % REQUIRED
% see plugin "subscription-options", % OPTIONAL
% see plugin "subscription-notifications" % OPTIONAL
-
+-spec plugin_features(binary(), binary()) -> [binary()].
plugin_features(Host, Type) ->
Module = plugin(Host, Type),
case catch Module:features() of
@@ -3995,6 +3543,7 @@ plugin_features(Host, Type) ->
Result -> Result
end.
+-spec features(binary(), binary()) -> [binary()].
features(Host, <<>>) ->
lists:usort(lists:foldl(fun (Plugin, Acc) ->
Acc ++ plugin_features(Host, Plugin)
@@ -4030,11 +3579,13 @@ tree_action(Host, Function, Args) ->
Result;
{aborted, Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ ErrTxt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(ErrTxt, ?MYLANG)}
end;
Other ->
?ERROR_MSG("unsupported backend: ~p~n", [Other]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ ErrTxt = <<"Database failure">>,
+ {error, xmpp:err_internal_server_error(ErrTxt, ?MYLANG)}
end.
%% @doc <p>node plugin call.</p>
@@ -4090,7 +3641,7 @@ transaction(Host, Fun, Trans) ->
transaction_retry(Host, ServerHost, Fun, Trans, DBType, Retry).
transaction_retry(_Host, _ServerHost, _Fun, _Trans, _DBType, 0) ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) ->
Res = case DBType of
mnesia ->
@@ -4115,75 +3666,126 @@ transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) ->
{error, Error};
{aborted, Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
{'EXIT', {timeout, _} = Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [Reason]),
transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count - 1);
{'EXIT', Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
Other ->
?ERROR_MSG("transaction return internal error: ~p~n", [Other]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}
end.
%%%% helpers
%% Add pubsub-specific error element
-extended_error(Error, Ext) ->
- extended_error(Error, Ext, [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]).
-
-extended_error(Error, unsupported, Feature) ->
- %% Give a uniq identifier
- extended_error(Error, <<"unsupported">>,
- [{<<"xmlns">>, ?NS_PUBSUB_ERRORS},
- {<<"feature">>, Feature}]);
-extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtAttrs) ->
- #xmlel{name = Error, attrs = Attrs,
- children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}.
-
-is_item_not_found({error, ErrEl}) ->
- case fxml:get_subtag_with_xmlns(
- ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
- #xmlel{} -> true;
- _ -> false
- end.
+-spec extended_error(stanza_error(), ps_error()) -> stanza_error().
+extended_error(StanzaErr, PubSubErr) ->
+ StanzaErr#stanza_error{sub_els = [PubSubErr]}.
-string_to_ljid(JID) ->
- case jid:from_string(JID) of
- error ->
- {<<>>, <<>>, <<>>};
- J ->
- case jid:tolower(J) of
- error -> {<<>>, <<>>, <<>>};
- J1 -> J1
- end
- end.
+-spec err_closed_node() -> ps_error().
+err_closed_node() ->
+ #ps_error{type = 'closed-node'}.
+
+-spec err_configuration_required() -> ps_error().
+err_configuration_required() ->
+ #ps_error{type = 'configuration-required'}.
+
+-spec err_invalid_jid() -> ps_error().
+err_invalid_jid() ->
+ #ps_error{type = 'invalid-jid'}.
+
+-spec err_invalid_options() -> ps_error().
+err_invalid_options() ->
+ #ps_error{type = 'invalid-options'}.
+
+-spec err_invalid_payload() -> ps_error().
+err_invalid_payload() ->
+ #ps_error{type = 'invalid-payload'}.
+
+-spec err_invalid_subid() -> ps_error().
+err_invalid_subid() ->
+ #ps_error{type = 'invalid-subid'}.
+
+-spec err_item_forbidden() -> ps_error().
+err_item_forbidden() ->
+ #ps_error{type = 'item-forbidden'}.
+
+-spec err_item_required() -> ps_error().
+err_item_required() ->
+ #ps_error{type = 'item-required'}.
+
+-spec err_jid_required() -> ps_error().
+err_jid_required() ->
+ #ps_error{type = 'jid-required'}.
+
+-spec err_max_items_exceeded() -> ps_error().
+err_max_items_exceeded() ->
+ #ps_error{type = 'max-items-exceeded'}.
+
+-spec err_max_nodes_exceeded() -> ps_error().
+err_max_nodes_exceeded() ->
+ #ps_error{type = 'max-nodes-exceeded'}.
+
+-spec err_nodeid_required() -> ps_error().
+err_nodeid_required() ->
+ #ps_error{type = 'nodeid-required'}.
+
+-spec err_not_in_roster_group() -> ps_error().
+err_not_in_roster_group() ->
+ #ps_error{type = 'not-in-roster-group'}.
+
+-spec err_not_subscribed() -> ps_error().
+err_not_subscribed() ->
+ #ps_error{type = 'not-subscribed'}.
+
+-spec err_payload_too_big() -> ps_error().
+err_payload_too_big() ->
+ #ps_error{type = 'payload-too-big'}.
+
+-spec err_payload_required() -> ps_error().
+err_payload_required() ->
+ #ps_error{type = 'payload-required'}.
+
+-spec err_pending_subscription() -> ps_error().
+err_pending_subscription() ->
+ #ps_error{type = 'pending-subscription'}.
+
+-spec err_presence_subscription_required() -> ps_error().
+err_presence_subscription_required() ->
+ #ps_error{type = 'presence-subscription-required'}.
+
+-spec err_subid_required() -> ps_error().
+err_subid_required() ->
+ #ps_error{type = 'subid-required'}.
+
+-spec err_too_many_subscriptions() -> ps_error().
+err_too_many_subscriptions() ->
+ #ps_error{type = 'too-many-subscriptions'}.
+
+-spec err_unsupported(ps_feature()) -> ps_error().
+err_unsupported(Feature) ->
+ #ps_error{type = 'unsupported', feature = Feature}.
+
+-spec err_unsupported_access_model() -> ps_error().
+err_unsupported_access_model() ->
+ #ps_error{type = 'unsupported-access-model'}.
-spec uniqid() -> mod_pubsub:itemId().
uniqid() ->
{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].
+ (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
+-spec itemsEls([#pubsub_item{}]) -> [ps_item()].
itemsEls(Items) ->
- [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
- || #pubsub_item{itemid = {ItemId, _}, payload = Payload} <- Items].
+ [#ps_item{id = ItemId, xml_els = Payload}
+ || #pubsub_item{itemid = {ItemId, _}, payload = Payload} <- Items].
--spec add_message_type(Message :: xmlel(), Type :: atom()) -> xmlel().
-
-add_message_type(Message, normal) -> Message;
-add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, Type) ->
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, jlib:atom_to_binary(Type)} | Attrs],
- children = Els};
-add_message_type(XmlEl, _Type) ->
- XmlEl.
+-spec add_message_type(message(), message_type()) -> message().
+add_message_type(#message{} = Message, Type) ->
+ Message#message{type = Type}.
%% Place of <headers/> changed at the bottom of the stanza
%% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid
@@ -4191,40 +3793,34 @@ add_message_type(XmlEl, _Type) ->
%% "[SHIM Headers] SHOULD be included after the event notification information
%% (i.e., as the last child of the <message/> stanza)".
-add_shim_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls).
-
-add_extended_headers(Stanza, HeaderEls) ->
- add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, HeaderEls).
+-spec add_shim_headers(stanza(), [{binary(), binary()}]) -> stanza().
+add_shim_headers(Stanza, Headers) ->
+ xmpp:set_subtag(Stanza, #shim{headers = Headers}).
-add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, HeaderName, HeaderNS, HeaderEls) ->
- HeaderEl = #xmlel{name = HeaderName,
- attrs = [{<<"xmlns">>, HeaderNS}],
- children = HeaderEls},
- #xmlel{name = Name, attrs = Attrs,
- children = lists:append(Els, [HeaderEl])}.
+-spec add_extended_headers(stanza(), [address()]) -> stanza().
+add_extended_headers(Stanza, Addrs) ->
+ xmpp:set_subtag(Stanza, #addresses{list = Addrs}).
+-spec subid_shim([binary()]) -> [{binary(), binary()}].
subid_shim(SubIds) ->
- [#xmlel{name = <<"header">>,
- attrs = [{<<"name">>, <<"SubId">>}],
- children = [{xmlcdata, SubId}]}
- || SubId <- SubIds].
+ [{<<"SubId">>, SubId} || SubId <- SubIds].
%% The argument is a list of Jids because this function could be used
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
+-spec extended_headers([jid()]) -> [address()].
extended_headers(Jids) ->
- [#xmlel{name = <<"address">>,
- attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}]}
- || Jid <- Jids].
+ [#address{type = replyto, jid = Jid} || Jid <- Jids].
+-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
on_user_offline(_, JID, _) ->
{User, Server, Resource} = jid:tolower(JID),
case user_resources(User, Server) of
[] -> purge_offline({User, Server, Resource});
- _ -> true
+ _ -> ok
end.
+-spec purge_offline(ljid()) -> ok.
purge_offline(LJID) ->
Host = host(element(2, LJID)),
Plugins = plugins(Host),
@@ -4232,8 +3828,8 @@ purge_offline(LJID) ->
Features = plugin_features(Host, Type),
case lists:member(<<"retrieve-affiliations">>, plugin_features(Host, Type)) of
false ->
- {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
- unsupported, <<"retrieve-affiliations">>)},
+ {{error, extended_error(xmpp:err_feature_not_implemented(),
+ err_unsupported('retrieve-affiliations'))},
Acc};
true ->
Items = lists:member(<<"retract-items">>, Features)
@@ -4267,6 +3863,7 @@ purge_offline(LJID) ->
?DEBUG("on_user_offline ~p", [Error])
end.
+-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
purge_offline(Host, LJID, Node) ->
Nidx = Node#pubsub_node.id,
Type = Node#pubsub_node.type,
@@ -4301,8 +3898,7 @@ purge_offline(Host, LJID, Node) ->
Error
end.
-mod_opt_type(access_createnode) ->
- fun (A) when is_atom(A) -> A end;
+mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(ignore_pep_from_offline) ->
diff --git a/src/mod_register.erl b/src/mod_register.erl
index 45cd78fef..b96ebecbd 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -35,14 +35,13 @@
-export([start/2, stop/1, stream_feature_register/2,
unauthenticated_iq_register/4, try_register/5,
- process_iq/3, send_registration_notifications/3,
+ process_iq/1, send_registration_notifications/3,
transform_options/1, transform_module_options/1,
mod_opt_type/1, opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -55,7 +54,7 @@ start(Host, Opts) ->
stream_feature_register, 50),
ejabberd_hooks:add(c2s_unauthenticated_iq, Host,
?MODULE, unauthenticated_iq_register, 50),
- mnesia:create_table(mod_register_ip,
+ ejabberd_mnesia:create(?MODULE, mod_register_ip,
[{ram_copies, [node()]}, {local_content, true},
{attributes, [key, value]}]),
mnesia:add_table_copy(mod_register_ip, node(),
@@ -75,333 +74,228 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
+-spec stream_feature_register([xmpp_element()], binary()) -> [xmpp_element()].
stream_feature_register(Acc, Host) ->
AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
fun(A) -> A end,
all),
- case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
+ case (AF /= none) and lists:keymember(sasl_mechanisms, 1, Acc) of
true ->
- [#xmlel{name = <<"register">>,
- attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}],
- children = []}
- | Acc];
+ [#feature_register{}|Acc];
false ->
Acc
end.
+-spec unauthenticated_iq_register(empty | iq(), binary(), iq(),
+ {inet:ip_address(), non_neg_integer()}) ->
+ empty | iq().
unauthenticated_iq_register(_Acc, Server,
- #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
+ #iq{sub_els = [#register{}]} = IQ, IP) ->
Address = case IP of
{A, _Port} -> A;
_ -> undefined
end,
- ResIQ = process_iq(jid:make(<<"">>, <<"">>,
- <<"">>),
- jid:make(<<"">>, Server, <<"">>), IQ, Address),
- Res1 = jlib:replace_from_to(jid:make(<<"">>,
- Server, <<"">>),
- jid:make(<<"">>, <<"">>, <<"">>),
- jlib:iq_to_xml(ResIQ)),
- jlib:remove_attr(<<"to">>, Res1);
+ ResIQ = process_iq(xmpp:set_from_to(IQ, jid:make(<<>>), jid:make(Server)),
+ Address),
+ xmpp:set_from_to(ResIQ, jid:make(Server), undefined);
unauthenticated_iq_register(Acc, _Server, _IQ, _IP) ->
Acc.
-process_iq(From, To, IQ) ->
- process_iq(From, To, IQ, jid:tolower(From)).
-
-process_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} =
- IQ,
- Source) ->
- IsCaptchaEnabled = case
- gen_mod:get_module_opt(To#jid.lserver, ?MODULE,
- captcha_protected,
- fun(B) when is_boolean(B) -> B end,
- false)
- of
- true -> true;
- _ -> false
- end,
- case Type of
- set ->
- 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) -> A end,
- all),
- AllowRemove = allow ==
- acl:match_rule(Server, Access, From),
- if (UTag /= false) and (RTag /= false) and
- AllowRemove ->
- 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 = fxml:get_tag_cdata(PTag),
- case ejabberd_auth:remove_user(User, Server,
- Password)
- of
- ok -> IQ#iq{type = result, sub_el = []};
- %% TODO FIXME: This piece of
- %% code does not work since
- %% the code have been changed
- %% to allow several auth
- %% modules. lists:foreach can
- %% only return ok:
- not_allowed ->
- Txt = <<"Removal is not allowed">>,
- IQ#iq{type = error,
- sub_el = [SubEl,
- ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- not_exists ->
- Txt = <<"No such user">>,
- IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
- Err ->
- ?ERROR_MSG("failed to remove user ~s@~s: ~p",
- [User, Server, Err]),
- IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERR_INTERNAL_SERVER_ERROR]}
- end;
- true ->
- Txt = <<"No password in this query">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end
- end;
- (UTag == false) and (RTag /= false) and AllowRemove ->
- case From of
- #jid{user = User, lserver = Server,
- resource = Resource} ->
- ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
- id = ID, sub_el = []},
- ejabberd_router:route(jid:make(User, Server,
- Resource),
- jid:make(User, Server,
- Resource),
- jlib:iq_to_xml(ResIQ)),
- ejabberd_auth:remove_user(User, Server),
- ignore;
- _ ->
- Txt = <<"The query is only allowed from local users">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
- end;
- (UTag /= false) and (PTag /= false) ->
- 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);
- IsCaptchaEnabled ->
- case ejabberd_captcha:process_reply(SubEl) of
- ok ->
- case process_xdata_submit(SubEl) of
- {ok, User, Password} ->
- try_register_or_set_password(User, Server,
- Password, From, IQ,
- SubEl, Source, Lang,
- true);
- _ ->
- Txt = <<"Incorrect data form">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end;
- {error, malformed} ->
- Txt = <<"Incorrect CAPTCHA submit">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
- _ ->
- ErrText = <<"The CAPTCHA verification has failed">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, ErrText)]}
- end;
- true ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
- end;
- get ->
- {IsRegistered, UsernameSubels, QuerySubels} = case From
- of
- #jid{user = User,
- lserver =
- Server} ->
- case
- ejabberd_auth:is_user_exists(User,
- Server)
- of
- true ->
- {true,
- [{xmlcdata,
- User}],
- [#xmlel{name
- =
- <<"registered">>,
- attrs
- =
- [],
- children
- =
- []}]};
- false ->
- {false,
- [{xmlcdata,
- User}],
- []}
- end;
- _ -> {false, [], []}
- end,
- if IsCaptchaEnabled and not IsRegistered ->
- TopInstrEl = #xmlel{name = <<"instructions">>,
- attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need a client that supports x:data "
- "and CAPTCHA to register">>)}]},
- InstrEl = #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Choose a username and password to register "
- "with this server">>)}]},
- UField = #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-single">>},
- {<<"label">>,
- translate:translate(Lang, <<"User">>)},
- {<<"var">>, <<"username">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- PField = #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"text-private">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Password">>)},
- {<<"var">>, <<"password">>}],
- children =
- [#xmlel{name = <<"required">>, attrs = [],
- children = []}]},
- case ejabberd_captcha:create_captcha_x(ID, To, Lang,
- Source,
- [InstrEl, UField,
- PField])
- of
- {ok, CaptchaEls} ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_REGISTER}],
- children =
- [TopInstrEl | CaptchaEls]}]};
- {error, limit} ->
- ErrText = <<"Too many CAPTCHA requests">>,
- IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)]};
- _Err ->
- ErrText = <<"Unable to generate a CAPTCHA">>,
- IQ#iq{type = error,
- sub_el =
- [SubEl,
- ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)]}
- end;
- true ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_REGISTER}],
- children =
- [#xmlel{name = <<"instructions">>,
- attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Choose a username and password to register "
- "with this server">>)}]},
- #xmlel{name = <<"username">>,
- attrs = [],
- children = UsernameSubels},
- #xmlel{name = <<"password">>,
- attrs = [], children = []}
- | QuerySubels]}]}
- end
+process_iq(#iq{from = From} = IQ) ->
+ process_iq(IQ, jid:tolower(From)).
+
+process_iq(#iq{from = From, to = To} = IQ, Source) ->
+ IsCaptchaEnabled =
+ case gen_mod:get_module_opt(To#jid.lserver, ?MODULE,
+ captcha_protected,
+ fun(B) when is_boolean(B) -> B end,
+ false) of
+ true -> true;
+ false -> false
+ end,
+ Server = To#jid.lserver,
+ Access = gen_mod:get_module_opt(Server, ?MODULE, access,
+ fun(A) -> A end, all),
+ AllowRemove = allow == acl:match_rule(Server, Access, From),
+ process_iq(IQ, Source, IsCaptchaEnabled, AllowRemove).
+
+process_iq(#iq{type = set, lang = Lang,
+ sub_els = [#register{remove = true}]} = IQ,
+ _Source, _IsCaptchaEnabled, _AllowRemove = false) ->
+ Txt = <<"Denied by ACL">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
+process_iq(#iq{type = set, lang = Lang, to = To, from = From,
+ sub_els = [#register{remove = true,
+ username = User,
+ password = Password}]} = IQ,
+ _Source, _IsCaptchaEnabled, _AllowRemove = true) ->
+ Server = To#jid.lserver,
+ if is_binary(User) ->
+ case From of
+ #jid{user = User, lserver = Server} ->
+ ejabberd_auth:remove_user(User, Server),
+ xmpp:make_iq_result(IQ);
+ _ ->
+ if is_binary(Password) ->
+ ejabberd_auth:remove_user(User, Server, Password),
+ xmpp:make_iq_result(IQ);
+ true ->
+ Txt = <<"No 'password' found in this query">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end
+ end;
+ true ->
+ case From of
+ #jid{luser = LUser, lserver = Server} ->
+ ResIQ = xmpp:make_iq_result(IQ),
+ ejabberd_router:route(From, From, ResIQ),
+ ejabberd_auth:remove_user(LUser, Server),
+ ignore;
+ _ ->
+ Txt = <<"The query is only allowed from local users">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
+ end
+ end;
+process_iq(#iq{type = set, to = To,
+ sub_els = [#register{username = User,
+ password = Password}]} = IQ,
+ Source, IsCaptchaEnabled, _AllowRemove) when is_binary(User),
+ is_binary(Password) ->
+ Server = To#jid.lserver,
+ try_register_or_set_password(
+ User, Server, Password, IQ, Source, not IsCaptchaEnabled);
+process_iq(#iq{type = set, to = To,
+ lang = Lang, sub_els = [#register{xdata = #xdata{} = X}]} = IQ,
+ Source, true, _AllowRemove) ->
+ Server = To#jid.lserver,
+ case ejabberd_captcha:process_reply(X) of
+ ok ->
+ case process_xdata_submit(X) of
+ {ok, User, Password} ->
+ try_register_or_set_password(
+ User, Server, Password, IQ, Source, true);
+ _ ->
+ Txt = <<"Incorrect data form">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end;
+ {error, malformed} ->
+ Txt = <<"Incorrect CAPTCHA submit">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ _ ->
+ ErrText = <<"The CAPTCHA verification has failed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(ErrText, Lang))
+ end;
+process_iq(#iq{type = set} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove) ->
+ xmpp:make_error(IQ, xmpp:err_bad_request());
+process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
+ Source, IsCaptchaEnabled, _AllowRemove) ->
+ Server = To#jid.lserver,
+ {IsRegistered, Username} =
+ case From of
+ #jid{user = User, lserver = Server} ->
+ case ejabberd_auth:is_user_exists(User, Server) of
+ true ->
+ {true, User};
+ false ->
+ {false, User}
+ end;
+ _ ->
+ {false, <<"">>}
+ end,
+ Instr = translate:translate(
+ Lang, <<"Choose a username and password to register "
+ "with this server">>),
+ if IsCaptchaEnabled and not IsRegistered ->
+ TopInstr = translate:translate(
+ Lang, <<"You need a client that supports x:data "
+ "and CAPTCHA to register">>),
+ UField = #xdata_field{type = 'text-single',
+ label = translate:translate(Lang, <<"User">>),
+ var = <<"username">>,
+ required = true},
+ PField = #xdata_field{type = 'text-private',
+ label = translate:translate(Lang, <<"Password">>),
+ var = <<"password">>,
+ required = true},
+ X = #xdata{type = form, instructions = [Instr],
+ fields = [UField, PField]},
+ case ejabberd_captcha:create_captcha_x(ID, To, Lang, Source, X) of
+ {ok, CaptchaEls} ->
+ xmpp:make_iq_result(
+ IQ, #register{instructions = TopInstr,
+ sub_els = CaptchaEls});
+ {error, limit} ->
+ ErrText = <<"Too many CAPTCHA requests">>,
+ xmpp:make_error(
+ IQ, xmpp:err_resource_constraint(ErrText, Lang));
+ _Err ->
+ ErrText = <<"Unable to generate a CAPTCHA">>,
+ xmpp:make_error(
+ IQ, xmpp:err_internal_server_error(ErrText, Lang))
+ end;
+ true ->
+ xmpp:make_iq_result(
+ IQ,
+ #register{instructions = Instr,
+ username = Username,
+ password = <<"">>,
+ registered = IsRegistered})
end.
try_register_or_set_password(User, Server, Password,
- From, IQ, SubEl, Source, Lang, CaptchaSucceed) ->
+ #iq{from = From, lang = Lang} = IQ,
+ Source, CaptchaSucceed) ->
case From of
- #jid{user = User, lserver = Server} ->
- try_set_password(User, Server, Password, IQ, SubEl,
- Lang);
- _ when CaptchaSucceed ->
- case check_from(From, Server) of
- allow ->
- case try_register(User, Server, Password, Source, Lang)
- of
- ok -> IQ#iq{type = result, sub_el = []};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end;
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
- end;
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ #jid{user = User, lserver = Server} ->
+ try_set_password(User, Server, Password, IQ);
+ _ when CaptchaSucceed ->
+ case check_from(From, Server) of
+ allow ->
+ case try_register(User, Server, Password, Source, Lang) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
+ end;
+ deny ->
+ Txt = <<"Denied by ACL">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
+ end;
+ _ ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed())
end.
%% @doc Try to change password and return IQ response
-try_set_password(User, Server, Password, IQ, SubEl,
- Lang) ->
+try_set_password(User, Server, Password, #iq{lang = Lang} = IQ) ->
case is_strong_password(Server, Password) of
true ->
- case ejabberd_auth:set_password(User, Server, Password)
- of
- ok -> IQ#iq{type = result, sub_el = []};
+ case ejabberd_auth:set_password(User, Server, Password) of
+ ok ->
+ xmpp:make_iq_result(IQ);
{error, empty_password} ->
Txt = <<"Empty password">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
{error, not_allowed} ->
Txt = <<"Changing password is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
{error, invalid_jid} ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_JID_MALFORMED]};
+ xmpp:make_error(IQ, xmpp:err_jid_malformed());
Err ->
?ERROR_MSG("failed to register user ~s@~s: ~p",
[User, Server, Err]),
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ xmpp:make_error(IQ, xmpp:err_internal_server_error())
end;
error_preparing_password ->
ErrText = <<"The password contains unacceptable characters">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]};
+ xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang));
false ->
ErrText = <<"The password is too weak">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
+ xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang))
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case jid:is_nodename(User) of
- false -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Malformed username">>)};
+ false -> {error, xmpp:err_bad_request(<<"Malformed username">>, Lang)};
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
@@ -411,8 +305,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
- {deny, _} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
- {_, deny} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
+ {_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@@ -432,35 +326,35 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case Error of
{atomic, exists} ->
Txt = <<"User already exists">>,
- {error, ?ERRT_CONFLICT(Lang, Txt)};
+ {error, xmpp:err_conflict(Txt, Lang)};
{error, invalid_jid} ->
- {error, ?ERR_JID_MALFORMED};
+ {error, xmpp:err_jid_malformed()};
{error, not_allowed} ->
- {error, ?ERR_NOT_ALLOWED};
+ {error, xmpp:err_not_allowed()};
{error, too_many_users} ->
Txt = <<"Too many users registered">>,
- {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)};
+ {error, xmpp:err_resource_constraint(Txt, Lang)};
{error, _} ->
?ERROR_MSG("failed to register user "
"~s@~s: ~p",
[User, Server, Error]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, xmpp:err_internal_server_error()}
end
end;
error_preparing_password ->
remove_timeout(Source),
ErrText = <<"The password contains unacceptable characters">>,
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
+ {error, xmpp:err_not_acceptable(ErrText, Lang)};
false ->
remove_timeout(Source),
ErrText = <<"The password is too weak">>,
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
+ {error, xmpp:err_not_acceptable(ErrText, Lang)}
end;
false ->
ErrText =
<<"Users are not allowed to register accounts "
"so quickly">>,
- {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)}
+ {error, xmpp:err_resource_constraint(ErrText, Lang)}
end
end
end.
@@ -479,20 +373,10 @@ send_welcome_message(JID) ->
of
{<<"">>, <<"">>} -> ok;
{Subj, Body} ->
- ejabberd_router:route(jid:make(<<"">>, Host,
- <<"">>),
- JID,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"normal">>}],
- children =
- [#xmlel{name = <<"subject">>,
- attrs = [],
- children =
- [{xmlcdata, Subj}]},
- #xmlel{name = <<"body">>,
- attrs = [],
- children =
- [{xmlcdata, Body}]}]});
+ ejabberd_router:route(
+ jid:make(Host), JID,
+ #message{subject = xmpp:mk_text(Subj),
+ body = xmpp:mk_text(Body)});
_ -> ok
end.
@@ -507,7 +391,7 @@ send_registration_notifications(Mod, UJID, Source) ->
[] -> ok;
JIDs when is_list(JIDs) ->
Body =
- iolist_to_binary(io_lib:format("[~s] The account ~s was registered from "
+ (str:format("[~s] The account ~s was registered from "
"IP address ~s on node ~w using ~p.",
[get_time_string(),
jid:to_string(UJID),
@@ -516,13 +400,9 @@ send_registration_notifications(Mod, UJID, Source) ->
lists:foreach(
fun(JID) ->
ejabberd_router:route(
- jid:make(<<"">>, Host, <<"">>),
- JID,
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"chat">>}],
- children = [#xmlel{name = <<"body">>,
- attrs = [],
- children = [{xmlcdata,Body}]}]})
+ jid:make(Host), JID,
+ #message{type = chat,
+ body = xmpp:mk_text(Body)})
end, JIDs)
end.
@@ -633,17 +513,11 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) ->
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Y, Mo, D, H, Mi, S]).
-process_xdata_submit(El) ->
- case fxml:get_subtag(El, <<"x">>) of
- false -> error;
- Xdata ->
- Fields = jlib:parse_xdata_submit(Xdata),
- case catch {proplists:get_value(<<"username">>, Fields),
- proplists:get_value(<<"password">>, Fields)}
- of
- {[User | _], [Pass | _]} -> {ok, User, Pass};
- _ -> error
- end
+process_xdata_submit(X) ->
+ case {xmpp_util:get_xdata_values(<<"username">>, X),
+ xmpp_util:get_xdata_values(<<"password">>, X)} of
+ {[User], [Pass]} -> {ok, User, Pass};
+ _ -> error
end.
is_strong_password(Server, Password) ->
@@ -738,14 +612,11 @@ check_ip_access(undefined, _IPAccess) ->
check_ip_access(IPAddress, IPAccess) ->
acl:match_rule(global, IPAccess, IPAddress).
-mod_opt_type(access) ->
- fun acl:access_rules_validator/1;
-mod_opt_type(access_from) ->
- fun (A) when is_atom(A) -> A end;
+mod_opt_type(access) -> fun acl:access_rules_validator/1;
+mod_opt_type(access_from) -> fun acl:access_rules_validator/1;
mod_opt_type(captcha_protected) ->
fun (B) when is_boolean(B) -> B end;
-mod_opt_type(ip_access) ->
- fun acl:access_rules_validator/1;
+mod_opt_type(ip_access) -> fun acl:access_rules_validator/1;
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;
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index 76de1677f..20b370fb9 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -60,7 +60,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -334,7 +334,7 @@ build_captcha_li_list2(Lang, IP) ->
case ejabberd_captcha:create_captcha(SID, From, To,
Lang, IP, Args)
of
- {ok, Id, _} ->
+ {ok, Id, _, _} ->
{_, {CImg, CText, CId, CKey}} =
ejabberd_captcha:build_captcha_html(Id, Lang),
[?XE(<<"li">>,
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index a75041bc7..89578571c 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -41,20 +41,21 @@
-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/1, export/1,
+ import_info/0, process_local_iq/1, get_user_roster/2,
+ import/5, get_subscription_lists/3, get_roster/2,
+ import_start/2, import_stop/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,
+ get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
- mod_opt_type/1, set_roster/1, depends/2]).
+ mod_opt_type/1, set_roster/1, del_roster/3, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_roster.hrl").
@@ -65,7 +66,7 @@
-export_type([subscription/0]).
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #roster{} | #roster_version{}) -> ok | pass.
+-callback import(binary(), binary(), #roster{} | [binary()]) -> ok.
-callback read_roster_version(binary(), binary()) -> binary() | error.
-callback write_roster_version(binary(), binary(), boolean(), binary()) -> any().
-callback get_roster(binary(), binary()) -> [#roster{}].
@@ -139,25 +140,60 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
-process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) ->
- process_iq_manager(From, To, IQ);
-
-process_iq(From, To, IQ) ->
- #iq{sub_el = SubEl, lang = Lang} = IQ,
- #jid{lserver = LServer} = From,
- case lists:member(LServer, ?MYHOSTS) of
- true -> process_local_iq(From, To, IQ);
- _ ->
- Txt = <<"The query is only allowed from local users">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}
+process_iq(#iq{from = #jid{luser = U, lserver = S},
+ to = #jid{luser = U, lserver = S}} = IQ) ->
+ process_local_iq(IQ);
+process_iq(#iq{lang = Lang, to = To} = IQ) ->
+ case ejabberd_hooks:run_fold(roster_remote_access,
+ To#jid.lserver, false, [IQ]) of
+ false ->
+ Txt = <<"Query to another users is forbidden">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
+ true ->
+ process_local_iq(IQ)
end.
-process_local_iq(From, To, #iq{type = Type} = IQ) ->
- case Type of
- set -> try_process_iq_set(From, To, IQ);
- get -> process_iq_get(From, To, IQ)
- end.
+process_local_iq(#iq{type = set,lang = Lang,
+ sub_els = [#roster_query{
+ items = [#roster_item{ask = Ask}]}]} = IQ)
+ when Ask /= undefined ->
+ Txt = <<"Possessing 'ask' attribute is not allowed by RFC6121">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+process_local_iq(#iq{type = set, from = From, lang = Lang,
+ sub_els = [#roster_query{
+ items = [#roster_item{} = Item]}]} = IQ) ->
+ case has_duplicated_groups(Item#roster_item.groups) of
+ true ->
+ Txt = <<"Duplicated groups are not allowed by RFC6121">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ false ->
+ #jid{server = Server} = From,
+ Access = gen_mod:get_module_opt(Server, ?MODULE,
+ access, fun(A) -> A end, all),
+ case acl:match_rule(Server, Access, From) of
+ deny ->
+ Txt = <<"Denied by ACL">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+ allow ->
+ process_iq_set(IQ)
+ end
+ end;
+process_local_iq(#iq{type = set, lang = Lang,
+ sub_els = [#roster_query{items = [_|_]}]} = IQ) ->
+ Txt = <<"Multiple <item/> elements are not allowed by RFC6121">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+process_local_iq(#iq{type = get, lang = Lang,
+ sub_els = [#roster_query{items = Items}]} = IQ) ->
+ case Items of
+ [] ->
+ process_iq_get(IQ);
+ [_|_] ->
+ Txt = <<"The query must not contain <item/> elements">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
+ end;
+process_local_iq(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
roster_hash(Items) ->
p1_sha:sha(term_to_binary(lists:sort([R#roster{groups =
@@ -176,13 +212,11 @@ roster_version_on_db(Host) ->
false).
%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled.
+-spec get_versioning_feature([xmpp_element()], binary()) -> [xmpp_element()].
get_versioning_feature(Acc, Host) ->
case roster_versioning_enabled(Host) of
true ->
- Feature = #xmlel{name = <<"ver">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}],
- children = []},
- [Feature | Acc];
+ [#rosterver_feature{}|Acc];
false -> []
end.
@@ -221,84 +255,64 @@ write_roster_version(LUser, LServer, InTransaction) ->
%% - roster versioning is not used by the client OR
%% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR
%% - the roster version from client don't match current version.
-process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
- LUser = From#jid.luser,
- LServer = From#jid.lserver,
+process_iq_get(#iq{to = To, lang = Lang,
+ sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) ->
+ LUser = To#jid.luser,
+ LServer = To#jid.lserver,
US = {LUser, LServer},
- try {ItemsToSend, VersionToSend} = case
- {fxml:get_tag_attr(<<"ver">>, SubEl),
- roster_versioning_enabled(LServer),
- roster_version_on_db(LServer)}
- of
- {{value, RequestedVersion}, true,
- true} ->
- case read_roster_version(LUser,
- LServer)
- of
- error ->
- RosterVersion =
- write_roster_version(LUser,
- LServer),
- {lists:map(fun item_to_xml/1,
- ejabberd_hooks:run_fold(roster_get,
- To#jid.lserver,
- [],
- [US])),
- RosterVersion};
- RequestedVersion ->
- {false, false};
- NewVersion ->
- {lists:map(fun item_to_xml/1,
- ejabberd_hooks:run_fold(roster_get,
- To#jid.lserver,
- [],
- [US])),
- NewVersion}
- end;
- {{value, RequestedVersion}, true,
- false} ->
- RosterItems =
- ejabberd_hooks:run_fold(roster_get,
- To#jid.lserver,
- [],
- [US]),
- case roster_hash(RosterItems) of
- RequestedVersion ->
- {false, false};
- New ->
- {lists:map(fun item_to_xml/1,
- RosterItems),
- New}
- end;
- _ ->
- {lists:map(fun item_to_xml/1,
- ejabberd_hooks:run_fold(roster_get,
- To#jid.lserver,
- [],
- [US])),
- false}
- end,
- IQ#iq{type = result,
- sub_el =
- case {ItemsToSend, VersionToSend} of
- {false, false} -> [];
- {Items, false} ->
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER}],
- children = Items}];
- {Items, Version} ->
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>, ?NS_ROSTER},
- {<<"ver">>, Version}],
- children = Items}]
- end}
- catch
- _:_ ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ try {ItemsToSend, VersionToSend} =
+ case {roster_versioning_enabled(LServer),
+ roster_version_on_db(LServer)} of
+ {true, true} when RequestedVersion /= undefined ->
+ case read_roster_version(LUser, LServer) of
+ error ->
+ RosterVersion = write_roster_version(LUser, LServer),
+ {lists:map(fun encode_item/1,
+ ejabberd_hooks:run_fold(
+ roster_get, To#jid.lserver, [], [US])),
+ RosterVersion};
+ RequestedVersion ->
+ {false, false};
+ NewVersion ->
+ {lists:map(fun encode_item/1,
+ ejabberd_hooks:run_fold(
+ roster_get, To#jid.lserver, [], [US])),
+ NewVersion}
+ end;
+ {true, false} when RequestedVersion /= undefined ->
+ RosterItems = ejabberd_hooks:run_fold(
+ roster_get, To#jid.lserver, [], [US]),
+ case roster_hash(RosterItems) of
+ RequestedVersion ->
+ {false, false};
+ New ->
+ {lists:map(fun encode_item/1, RosterItems), New}
+ end;
+ _ ->
+ {lists:map(fun encode_item/1,
+ ejabberd_hooks:run_fold(
+ roster_get, To#jid.lserver, [], [US])),
+ false}
+ end,
+ xmpp:make_iq_result(
+ IQ,
+ case {ItemsToSend, VersionToSend} of
+ {false, false} ->
+ undefined;
+ {Items, false} ->
+ #roster_query{items = Items};
+ {Items, Version} ->
+ #roster_query{items = Items,
+ ver = Version}
+ end)
+ catch E:R ->
+ ?ERROR_MSG("failed to process roster get for ~s: ~p",
+ [jid:to_string(To), {E, {R, erlang:get_stacktrace()}}]),
+ Txt = <<"Roster module has failed">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
+-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}].
get_user_roster(Acc, {LUser, LServer}) ->
Items = get_roster(LUser, LServer),
lists:filter(fun (#roster{subscription = none,
@@ -317,147 +331,91 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
transaction(
LServer,
fun() ->
- roster_subscribe_t(LUser, LServer, LJID, Item)
+ update_roster_t(LUser, LServer, LJID, Item)
end).
-item_to_xml(Item) ->
- Attrs1 = [{<<"jid">>,
- jid:to_string(Item#roster.jid)}],
- Attrs2 = case Item#roster.name of
- <<"">> -> Attrs1;
- Name -> [{<<"name">>, Name} | Attrs1]
- end,
- Attrs3 = case Item#roster.subscription of
- none -> [{<<"subscription">>, <<"none">>} | Attrs2];
- from -> [{<<"subscription">>, <<"from">>} | Attrs2];
- to -> [{<<"subscription">>, <<"to">>} | Attrs2];
- both -> [{<<"subscription">>, <<"both">>} | Attrs2];
- remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
- end,
- Attrs4 = case ask_to_pending(Item#roster.ask) of
- out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
- both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
- _ -> Attrs3
- end,
- SubEls1 = lists:map(fun (G) ->
- #xmlel{name = <<"group">>, attrs = [],
- children = [{xmlcdata, G}]}
- end,
- Item#roster.groups),
- SubEls = SubEls1 ++ Item#roster.xs,
- #xmlel{name = <<"item">>, attrs = Attrs4,
- children = SubEls}.
+del_roster(LUser, LServer, LJID) ->
+ transaction(
+ LServer,
+ fun() ->
+ del_roster_t(LUser, LServer, LJID)
+ end).
+
+encode_item(Item) ->
+ #roster_item{jid = jid:make(Item#roster.jid),
+ name = Item#roster.name,
+ subscription = Item#roster.subscription,
+ ask = case ask_to_pending(Item#roster.ask) of
+ out -> subscribe;
+ both -> subscribe;
+ _ -> undefined
+ end,
+ groups = Item#roster.groups}.
+
+decode_item(#roster_item{subscription = remove} = Item, R, _) ->
+ R#roster{jid = jid:tolower(Item#roster_item.jid),
+ name = <<"">>,
+ subscription = remove,
+ ask = none,
+ groups = [],
+ askmessage = <<"">>,
+ xs = []};
+decode_item(Item, R, Managed) ->
+ R#roster{jid = jid:tolower(Item#roster_item.jid),
+ name = Item#roster_item.name,
+ subscription = case Item#roster_item.subscription of
+ Sub when Managed -> Sub;
+ _ -> R#roster.subscription
+ end,
+ groups = Item#roster_item.groups}.
get_roster_by_jid_t(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_roster_by_jid(LUser, LServer, LJID).
-try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
- #jid{server = Server} = From,
- Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
- case acl:match_rule(Server, Access, From) of
- deny ->
- Txt = <<"Denied by ACL">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- allow ->
- process_iq_set(From, To, IQ)
+process_iq_set(#iq{from = From, to = To,
+ sub_els = [#roster_query{items = QueryItems}]} = IQ) ->
+ #jid{user = User, luser = LUser, lserver = LServer} = To,
+ Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
+ F = fun () ->
+ lists:map(
+ fun(#roster_item{jid = JID1} = QueryItem) ->
+ LJID = jid:tolower(JID1),
+ Item = get_roster_by_jid_t(LUser, LServer, LJID),
+ Item2 = decode_item(QueryItem, Item, Managed),
+ Item3 = ejabberd_hooks:run_fold(roster_process_item,
+ LServer, Item2,
+ [LServer]),
+ case Item3#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, LJID);
+ _ -> update_roster_t(LUser, LServer, LJID, Item3)
+ end,
+ case roster_version_on_db(LServer) of
+ true -> write_roster_version_t(LUser, LServer);
+ false -> ok
+ end,
+ {Item, Item3}
+ end, QueryItems)
+ end,
+ case transaction(LServer, F) of
+ {atomic, ItemPairs} ->
+ lists:foreach(
+ fun({OldItem, Item}) ->
+ push_item(User, LServer, To, Item),
+ case Item#roster.subscription of
+ remove ->
+ send_unsubscribing_presence(To, OldItem);
+ _ ->
+ ok
+ end
+ end, ItemPairs),
+ xmpp:make_iq_result(IQ);
+ E ->
+ ?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p",
+ [xmpp:pp(IQ), E]),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error())
end.
-process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
- #xmlel{children = Els} = SubEl,
- Managed = is_managed_from_id(Id),
- lists:foreach(fun (El) -> process_item_set(From, To, El, Managed)
- end,
- Els),
- IQ#iq{type = result, sub_el = []}.
-
-process_item_set(From, To,
- #xmlel{attrs = Attrs, children = Els}, Managed) ->
- JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
- Attrs)),
- #jid{user = User, luser = LUser, lserver = LServer} =
- From,
- case JID1 of
- error -> ok;
- _ ->
- LJID = jid:tolower(JID1),
- F = fun () ->
- Item = get_roster_by_jid_t(LUser, LServer, LJID),
- Item1 = process_item_attrs_managed(Item, Attrs, Managed),
- Item2 = process_item_els(Item1, Els),
- Item3 = ejabberd_hooks:run_fold(roster_process_item,
- LServer, Item2,
- [LServer]),
- case Item3#roster.subscription of
- remove -> del_roster_t(LUser, LServer, LJID);
- _ -> update_roster_t(LUser, LServer, LJID, Item3)
- end,
- send_itemset_to_managers(From, Item3, Managed),
- case roster_version_on_db(LServer) of
- true -> write_roster_version_t(LUser, LServer);
- false -> ok
- end,
- {Item, Item3}
- end,
- case transaction(LServer, F) of
- {atomic, {OldItem, Item}} ->
- push_item(User, LServer, To, Item),
- case Item#roster.subscription of
- remove ->
- send_unsubscribing_presence(From, OldItem), ok;
- _ -> ok
- end;
- E ->
- ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
- end
- end;
-process_item_set(_From, _To, _, _Managed) -> ok.
-
-process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
- case Attr of
- <<"jid">> ->
- case jid:from_string(Val) of
- error -> process_item_attrs(Item, Attrs);
- JID1 ->
- JID = {JID1#jid.luser, JID1#jid.lserver,
- JID1#jid.lresource},
- process_item_attrs(Item#roster{jid = JID}, Attrs)
- end;
- <<"name">> ->
- process_item_attrs(Item#roster{name = Val}, Attrs);
- <<"subscription">> ->
- case Val of
- <<"remove">> ->
- process_item_attrs(Item#roster{subscription = remove},
- Attrs);
- _ -> process_item_attrs(Item, Attrs)
- end;
- <<"ask">> -> process_item_attrs(Item, Attrs);
- _ -> process_item_attrs(Item, Attrs)
- end;
-process_item_attrs(Item, []) -> Item.
-
-process_item_els(Item,
- [#xmlel{name = Name, attrs = Attrs, children = SEls}
- | Els]) ->
- case Name of
- <<"group">> ->
- Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
- process_item_els(Item#roster{groups = Groups}, Els);
- _ ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- <<"">> -> process_item_els(Item, Els);
- _ ->
- XEls = [#xmlel{name = Name, attrs = Attrs,
- children = SEls}
- | Item#roster.xs],
- process_item_els(Item#roster{xs = XEls}, Els)
- end
- end;
-process_item_els(Item, [{xmlcdata, _} | Els]) ->
- process_item_els(Item, Els);
-process_item_els(Item, []) -> Item.
-
push_item(User, Server, From, Item) ->
ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
jid:make(User, Server, <<"">>),
@@ -480,21 +438,19 @@ push_item(User, Server, Resource, From, Item) ->
push_item(User, Server, Resource, From, Item,
RosterVersion) ->
- ExtraAttrs = case RosterVersion of
- not_found -> [];
- _ -> [{<<"ver">>, RosterVersion}]
- end,
- ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
+ Ver = case RosterVersion of
+ not_found -> undefined;
+ _ -> RosterVersion
+ end,
+ ResIQ = #iq{type = set,
%% @doc Roster push, calculate and include the version attribute.
%% TODO: don't push to those who didn't load roster
id = <<"push", (randoms:get_string())/binary>>,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
- children = [item_to_xml(Item)]}]},
+ sub_els = [#roster_query{ver = Ver,
+ items = [encode_item(Item)]}]},
ejabberd_router:route(From,
jid:make(User, Server, Resource),
- jlib:iq_to_xml(ResIQ)).
+ ResIQ).
push_item_version(Server, User, From, Item,
RosterVersion) ->
@@ -504,6 +460,8 @@ push_item_version(Server, User, From, Item,
end,
ejabberd_sm:get_user_resources(User, Server)).
+-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
+ -> {[ljid()], [ljid()]}.
get_subscription_lists(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -536,10 +494,16 @@ transaction(LServer, F) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:transaction(LServer, F).
+-spec in_subscription(boolean(), binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> boolean().
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type,
Reason).
+-spec out_subscription(
+ binary(), binary(), jid(),
+ subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, <<"">>).
@@ -583,8 +547,7 @@ process_subscription(Direction, User, Server, JID1,
{Subscription, Pending} ->
NewItem = Item#roster{subscription = Subscription,
ask = Pending,
- askmessage =
- iolist_to_binary(AskMessage)},
+ askmessage = AskMessage},
roster_subscribe_t(LUser, LServer, LJID, NewItem),
case roster_version_on_db(LServer) of
true -> write_roster_version_t(LUser, LServer);
@@ -598,16 +561,8 @@ process_subscription(Direction, User, Server, JID1,
case AutoReply of
none -> ok;
_ ->
- T = case AutoReply of
- subscribed -> <<"subscribed">>;
- unsubscribed -> <<"unsubscribed">>
- end,
- ejabberd_router:route(jid:make(User, Server,
- <<"">>),
- JID1,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, T}],
- children = []})
+ ejabberd_router:route(jid:make(User, Server, <<"">>),
+ JID1, #presence{type = AutoReply})
end,
case Push of
{push, Item} ->
@@ -739,12 +694,14 @@ in_auto_reply(from, out, unsubscribe) -> unsubscribed;
in_auto_reply(both, none, unsubscribe) -> unsubscribed;
in_auto_reply(_, _, _) -> none.
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_user(LUser, LServer),
+ ok.
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
@@ -769,35 +726,29 @@ send_unsubscribing_presence(From, Item) ->
_ -> false
end,
if IsTo ->
- send_presence_type(jid:remove_resource(From),
- jid:make(Item#roster.jid),
- <<"unsubscribe">>);
+ ejabberd_router:route(jid:remove_resource(From),
+ jid:make(Item#roster.jid),
+ #presence{type = unsubscribe});
true -> ok
end,
if IsFrom ->
- send_presence_type(jid:remove_resource(From),
- jid:make(Item#roster.jid),
- <<"unsubscribed">>);
+ ejabberd_router:route(jid:remove_resource(From),
+ jid:make(Item#roster.jid),
+ #presence{type = unsubscribed});
true -> ok
end,
ok.
-send_presence_type(From, To, Type) ->
- ejabberd_router:route(From, To,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, Type}], children = []}).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-set_items(User, Server, SubEl) ->
- #xmlel{children = Els} = SubEl,
+-spec set_items(binary(), binary(), roster_query()) -> any().
+set_items(User, Server, #roster_query{items = Items}) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
F = fun () ->
- lists:foreach(fun (El) ->
- process_item_set_t(LUser, LServer, El)
- end,
- Els)
+ lists:foreach(fun (Item) ->
+ process_item_set_t(LUser, LServer, Item)
+ end, Items)
end,
transaction(LServer, F).
@@ -809,65 +760,19 @@ del_roster_t(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:del_roster(LUser, LServer, LJID).
-process_item_set_t(LUser, LServer,
- #xmlel{attrs = Attrs, children = Els}) ->
- JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
- Attrs)),
- case JID1 of
- error -> ok;
- _ ->
- JID = {JID1#jid.user, JID1#jid.server,
- JID1#jid.resource},
- LJID = {JID1#jid.luser, JID1#jid.lserver,
- JID1#jid.lresource},
- Item = #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = JID},
- Item1 = process_item_attrs_ws(Item, Attrs),
- Item2 = process_item_els(Item1, Els),
- case Item2#roster.subscription of
- remove -> del_roster_t(LUser, LServer, LJID);
- _ -> update_roster_t(LUser, LServer, LJID, Item2)
- end
+process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
+ JID = {JID1#jid.user, JID1#jid.server, <<>>},
+ LJID = {JID1#jid.luser, JID1#jid.lserver, <<>>},
+ Item = #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = JID},
+ Item2 = decode_item(QueryItem, Item, _Managed = true),
+ case Item2#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, LJID);
+ _ -> update_roster_t(LUser, LServer, LJID, Item2)
end;
process_item_set_t(_LUser, _LServer, _) -> ok.
-process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
- case Attr of
- <<"jid">> ->
- case jid:from_string(Val) of
- error -> process_item_attrs_ws(Item, Attrs);
- JID1 ->
- JID = {JID1#jid.luser, JID1#jid.lserver,
- JID1#jid.lresource},
- process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
- end;
- <<"name">> ->
- process_item_attrs_ws(Item#roster{name = Val}, Attrs);
- <<"subscription">> ->
- case Val of
- <<"remove">> ->
- process_item_attrs_ws(Item#roster{subscription =
- remove},
- Attrs);
- <<"none">> ->
- process_item_attrs_ws(Item#roster{subscription = none},
- Attrs);
- <<"both">> ->
- process_item_attrs_ws(Item#roster{subscription = both},
- Attrs);
- <<"from">> ->
- process_item_attrs_ws(Item#roster{subscription = from},
- Attrs);
- <<"to">> ->
- process_item_attrs_ws(Item#roster{subscription = to},
- Attrs);
- _ -> process_item_attrs_ws(Item, Attrs)
- end;
- <<"ask">> -> process_item_attrs_ws(Item, Attrs);
- _ -> process_item_attrs_ws(Item, Attrs)
- end;
-process_item_attrs_ws(Item, []) -> Item.
-
+-spec get_in_pending_subscriptions([presence()], binary(), binary()) -> [presence()].
get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
@@ -876,31 +781,18 @@ get_in_pending_subscriptions(Ls, User, Server) ->
get_in_pending_subscriptions(Ls, User, Server, Mod) ->
JID = jid:make(User, Server, <<"">>),
Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
- Ls ++ lists:map(fun (R) ->
- Message = R#roster.askmessage,
- Status = if is_binary(Message) -> (Message);
- true -> <<"">>
- end,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"from">>,
- jid:to_string(R#roster.jid)},
- {<<"to">>, jid:to_string(JID)},
- {<<"type">>, <<"subscribe">>}],
- children =
- [#xmlel{name = <<"status">>,
- attrs = [],
- children =
- [{xmlcdata, Status}]}]}
- end,
- lists:filter(fun (R) ->
- case R#roster.ask of
- in -> true;
- both -> true;
- _ -> false
- end
- end,
- Result)).
+ Ls ++ lists:flatmap(
+ fun(#roster{ask = Ask} = R) when Ask == in; Ask == both ->
+ Message = R#roster.askmessage,
+ Status = if is_binary(Message) -> (Message);
+ true -> <<"">>
+ end,
+ [#presence{from = R#roster.jid, to = JID,
+ type = subscribe,
+ status = xmpp:mk_text(Status)}];
+ (_) ->
+ []
+ end, Result).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -910,6 +802,8 @@ read_subscription_and_groups(User, Server, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:read_subscription_and_groups(LUser, LServer, LJID).
+-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), [binary()]}.
get_jid_info(_, User, Server, JID) ->
LJID = jid:tolower(JID),
case read_subscription_and_groups(User, Server, LJID) of
@@ -1070,10 +964,7 @@ user_roster_parse_query(User, Server, Items, Query) ->
user_roster_subscribe_jid(User, Server, JID) ->
out_subscription(User, Server, JID, subscribe),
UJID = jid:make(User, Server, <<"">>),
- ejabberd_router:route(UJID, JID,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"subscribe">>}],
- children = []}).
+ ejabberd_router:route(UJID, JID, #presence{type = subscribe}).
user_roster_item_parse_query(User, Server, Items,
Query) ->
@@ -1089,12 +980,7 @@ user_roster_item_parse_query(User, Server, Items,
subscribed),
UJID = jid:make(User, Server, <<"">>),
ejabberd_router:route(UJID, JID1,
- #xmlel{name =
- <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"subscribed">>}],
- children = []}),
+ #presence{type = subscribed}),
throw(submitted);
false ->
case lists:keysearch(<<"remove",
@@ -1102,29 +988,17 @@ user_roster_item_parse_query(User, Server, Items,
1, Query)
of
{value, _} ->
- UJID = jid:make(User, Server,
- <<"">>),
- process_iq_set(UJID, UJID,
- #iq{type = set,
- sub_el =
- #xmlel{name =
- <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_ROSTER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- [{<<"jid">>,
- jid:to_string(JID)},
- {<<"subscription">>,
- <<"remove">>}],
- children
- =
- []}]}}),
+ UJID = jid:make(User, Server),
+ RosterItem = #roster_item{
+ jid = jid:make(JID),
+ subscription = remove},
+ process_iq_set(
+ #iq{type = set,
+ from = UJID,
+ to = UJID,
+ id = randoms:get_string(),
+ sub_els = [#roster_query{
+ items = [RosterItem]}]}),
throw(submitted);
false -> ok
end
@@ -1141,101 +1015,42 @@ webadmin_user(Acc, _User, _Server, Lang) ->
[?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% Implement XEP-0321 Remote Roster Management
-
-process_iq_manager(From, To, IQ) ->
- %% Check what access is allowed for From to To
- MatchDomain = From#jid.lserver,
- case is_domain_managed(MatchDomain, To#jid.lserver) of
- true ->
- process_iq_manager2(MatchDomain, To, IQ);
- false ->
- #iq{sub_el = SubEl, lang = Lang} = IQ,
- Txt = <<"Roster management is not allowed from this domain">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
- end.
-
-process_iq_manager2(MatchDomain, To, IQ) ->
- %% If IQ is SET, filter the input IQ
- IQFiltered = maybe_filter_request(MatchDomain, IQ),
- %% Call the standard function with reversed JIDs
- IdInitial = IQFiltered#iq.id,
- ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}),
- %% Filter the output IQ
- filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
-
-is_domain_managed(ContactHost, UserHost) ->
- Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers,
- fun(B) when is_list(B) -> B end,
- []),
- lists:member(ContactHost, Managers).
-
-maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
- filter_stanza(MatchDomain, IQ);
-maybe_filter_request(_MatchDomain, IQ) ->
- IQ.
-
-filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) ->
- IQ;
-filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
- #iq{sub_el = SubElFiltered} = IQRes =
- filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
- IQRes#iq{sub_el = [SubElFiltered]};
-filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
- #xmlel{name = Type, attrs = Attrs, children = Items} = SubEl,
- ItemsFiltered = lists:filter(
- fun(Item) ->
- is_item_of_domain(MatchDomain, Item) end, Items),
- SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered},
- IQ#iq{sub_el = SubElFiltered}.
-
-is_item_of_domain(MatchDomain, #xmlel{} = El) ->
- lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs);
-is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
- false.
-
-is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) ->
- case jid:from_string(JIDString) of
- JID when JID#jid.lserver == MatchDomain -> true;
- _ -> false
- end;
-is_jid_of_domain(_, _) ->
- false.
-
-process_item_attrs_managed(Item, Attrs, true) ->
- process_item_attrs_ws(Item, Attrs);
-process_item_attrs_managed(Item, _Attrs, false) ->
- process_item_attrs(Item, _Attrs).
-
-send_itemset_to_managers(_From, _Item, true) ->
- ok;
-send_itemset_to_managers(From, Item, false) ->
- {_, UserHost} = Item#roster.us,
- {_ContactUser, ContactHost, _ContactResource} = Item#roster.jid,
- %% Check if the component is an allowed manager
- IsManager = is_domain_managed(ContactHost, UserHost),
- case IsManager of
- true -> push_item(<<"">>, ContactHost, <<"">>, From, Item);
- false -> ok
- end.
-
-is_managed_from_id(<<"roster-remotely-managed">>) ->
- true;
-is_managed_from_id(_Id) ->
- false.
+has_duplicated_groups(Groups) ->
+ GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]),
+ not (length(GroupsPrep) == length(Groups)).
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"roster_version">>, 2},
+ {<<"rostergroups">>, 3},
+ {<<"rosterusers">>, 10}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ ets:new(rostergroups_tmp, [private, named_table, bag]),
+ Mod:init(LServer, []),
+ ok.
-import(LServer, DBType, R) ->
+import_stop(_LServer, _DBType) ->
+ ets:delete(rostergroups_tmp),
+ ok.
+
+import(LServer, {sql, _}, _DBType, <<"rostergroups">>, [LUser, SJID, Group]) ->
+ LJID = jid:tolower(jid:from_string(SJID)),
+ ets:insert(rostergroups_tmp, {{LUser, LServer, LJID}, Group}),
+ ok;
+import(LServer, {sql, _}, DBType, <<"rosterusers">>, Row) ->
+ I = mod_roster_sql:raw_to_record(LServer, lists:sublist(Row, 9)),
+ Groups = [G || {_, G} <- ets:lookup(rostergroups_tmp, I#roster.usj)],
+ RosterItem = I#roster{groups = Groups},
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, <<"rosterusers">>, RosterItem);
+import(LServer, {sql, _}, DBType, <<"roster_version">>, [LUser, Ver]) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, R).
+ Mod:import(LServer, <<"roster_version">>, [LUser, Ver]).
mod_opt_type(access) ->
fun acl:access_rules_validator/1;
diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl
index ddfa34d68..04bdf72e7 100644
--- a/src/mod_roster_mnesia.erl
+++ b/src/mod_roster_mnesia.erl
@@ -15,9 +15,8 @@
get_roster/2, get_roster_by_jid/3, get_only_items/2,
roster_subscribe/4, get_roster_by_jid_with_groups/3,
remove_user/2, update_roster/4, del_roster/3, transaction/2,
- read_subscription_and_groups/3, import/2]).
+ read_subscription_and_groups/3, import/3, create_roster/1]).
--include("jlib.hrl").
-include("mod_roster.hrl").
-include("logger.hrl").
@@ -25,10 +24,10 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(roster,
+ ejabberd_mnesia:create(?MODULE, roster,
[{disc_copies, [node()]},
{attributes, record_info(fields, roster)}]),
- mnesia:create_table(roster_version,
+ ejabberd_mnesia:create(?MODULE, roster_version,
[{disc_copies, [node()]},
{attributes,
record_info(fields, roster_version)}]),
@@ -104,9 +103,13 @@ read_subscription_and_groups(LUser, LServer, LJID) ->
transaction(_LServer, F) ->
mnesia:transaction(F).
-import(_LServer, #roster{} = R) ->
+create_roster(RItem) ->
+ mnesia:dirty_write(RItem).
+
+import(_LServer, <<"rosterusers">>, #roster{} = R) ->
mnesia:dirty_write(R);
-import(_LServer, #roster_version{} = RV) ->
+import(LServer, <<"roster_version">>, [LUser, Ver]) ->
+ RV = #roster_version{us = {LUser, LServer}, version = Ver},
mnesia:dirty_write(RV).
%%%===================================================================
diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl
index 38e873827..40992d77d 100644
--- a/src/mod_roster_riak.erl
+++ b/src/mod_roster_riak.erl
@@ -13,12 +13,11 @@
%% API
-export([init/2, read_roster_version/2, write_roster_version/4,
- get_roster/2, get_roster_by_jid/3,
+ get_roster/2, get_roster_by_jid/3, create_roster/1,
roster_subscribe/4, get_roster_by_jid_with_groups/3,
remove_user/2, update_roster/4, del_roster/3, transaction/2,
- read_subscription_and_groups/3, get_only_items/2, import/2]).
+ read_subscription_and_groups/3, get_only_items/2, import/3]).
--include("jlib.hrl").
-include("mod_roster.hrl").
%%%===================================================================
@@ -97,10 +96,17 @@ read_subscription_and_groups(LUser, LServer, LJID) ->
error
end.
-import(_LServer, #roster{us = {LUser, LServer}} = R) ->
- ejabberd_riak:put(R, roster_schema(),
+create_roster(#roster{us = {LUser, LServer}} = RItem) ->
+ ejabberd_riak:put(
+ RItem, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+import(_LServer, <<"rosterusers">>, RosterItem) ->
+ {LUser, LServer} = RosterItem#roster.us,
+ ejabberd_riak:put(RosterItem, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
-import(_LServer, #roster_version{} = RV) ->
+import(LServer, <<"roster_version">>, [LUser, Ver]) ->
+ RV = #roster_version{us = {LUser, LServer}, version = Ver},
ejabberd_riak:put(RV, roster_version_schema()).
%%%===================================================================
diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl
index 61f59a990..2fc6b112e 100644
--- a/src/mod_roster_sql.erl
+++ b/src/mod_roster_sql.erl
@@ -18,9 +18,8 @@
roster_subscribe/4, get_roster_by_jid_with_groups/3,
remove_user/2, update_roster/4, del_roster/3, transaction/2,
read_subscription_and_groups/3, get_only_items/2,
- import/1, import/2, export/1]).
+ import/3, export/1, raw_to_record/2]).
--include("jlib.hrl").
-include("mod_roster.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -186,27 +185,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, type from rosterusers;">>,
- fun([LUser, JID|_] = Row) ->
- Item = raw_to_record(LServer, Row),
- Username = ejabberd_sql:escape(LUser),
- SJID = ejabberd_sql:escape(JID),
- {selected, _, Rows} =
- ejabberd_sql:sql_query_t(
- [<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]),
- Groups = [Grp || [Grp] <- Rows],
- Item#roster{groups = Groups}
- end},
- {<<"select username, version from roster_version;">>,
- fun([LUser, Ver]) ->
- #roster_version{us = {LUser, LServer}, version = Ver}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl
index ae264bbc9..ea7768bca 100644
--- a/src/mod_service_log.erl
+++ b/src/mod_service_log.erl
@@ -35,7 +35,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
start(Host, _Opts) ->
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@@ -54,17 +54,18 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
+-spec log_user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
log_user_send(Packet, _C2SState, From, To) ->
log_packet(From, To, Packet, From#jid.lserver),
Packet.
+-spec log_user_receive(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
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},
- Host) ->
+-spec log_packet(jid(), jid(), stanza(), binary()) -> ok.
+log_packet(From, To, Packet, Host) ->
Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers,
fun(L) ->
lists:map(
@@ -76,22 +77,11 @@ log_packet(From, To,
end
end, L)
end, []),
- ServerJID = #jid{user = <<"">>, server = Host,
- resource = <<"">>, luser = <<"">>, lserver = Host,
- lresource = <<"">>},
- NewAttrs =
- jlib:replace_from_to_attrs(jid:to_string(From),
- jid:to_string(To), Attrs),
- FixedPacket = #xmlel{name = Name, attrs = NewAttrs,
- children = Els},
+ ServerJID = jid:make(Host),
+ FixedPacket = xmpp:set_from_to(Packet, From, To),
lists:foreach(fun (Logger) ->
ejabberd_router:route(ServerJID,
- #jid{user = <<"">>,
- server = Logger,
- resource = <<"">>,
- luser = <<"">>,
- lserver = Logger,
- lresource = <<"">>},
+ jid:make(Logger),
#xmlel{name = <<"route">>,
attrs = [],
children =
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index b472e1aab..8ef0f41b5 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -29,10 +29,10 @@
-behaviour(gen_mod).
--export([start/2, stop/1, item_to_xml/1, export/1,
- import/1, webadmin_menu/3, webadmin_page/3,
+-export([start/2, stop/1, export/1,
+ import_info/0, webadmin_menu/3, webadmin_page/3,
get_user_roster/2, get_subscription_lists/3,
- get_jid_info/4, import/3, process_item/2,
+ get_jid_info/4, import/5, process_item/2, import_start/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,
@@ -44,7 +44,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_roster.hrl").
@@ -56,7 +56,7 @@
-type group_options() :: [{atom(), any()}].
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass.
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback list_groups(binary()) -> [binary()].
-callback groups_with_opts(binary()) -> [{binary(), group_options()}].
-callback create_group(binary(), binary(), group_options()) -> {atomic, any()}.
@@ -135,6 +135,7 @@ stop(Host) ->
depends(_Host, _Opts) ->
[].
+-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}].
get_user_roster(Items, US) ->
{U, S} = US,
DisplayedGroups = get_user_displayed_groups(US),
@@ -172,56 +173,37 @@ get_user_roster(Items, US) ->
end
end,
SRUsers, Items),
- ModVcard = get_vcard_module(S),
SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
us = US, jid = {U1, S1, <<"">>},
- name = get_rosteritem_name(ModVcard, U1, S1),
+ name = get_rosteritem_name(U1, S1),
subscription = both, ask = none, groups = GroupNames}
|| {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
-get_vcard_module(Server) ->
- Modules = gen_mod:loaded_modules(Server),
- [M
- || M <- Modules,
- (M == mod_vcard) or (M == mod_vcard_ldap)].
-
-get_rosteritem_name([], _, _) -> <<"">>;
-get_rosteritem_name([ModVcard], U, S) ->
- From = jid:make(<<"">>, S, jlib:atom_to_binary(?MODULE)),
- To = jid:make(U, S, <<"">>),
- case lists:member(To#jid.lserver, ?MYHOSTS) of
+get_rosteritem_name(U, S) ->
+ case gen_mod:is_loaded(S, mod_vcard) of
true ->
- IQ = {iq, <<"">>, get, <<"vcard-temp">>, <<"">>,
- #xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, <<"vcard-temp">>}],
- children = []}},
- IQ_Vcard = ModVcard:process_sm_iq(From, To, IQ),
- case catch get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el) of
- {'EXIT', Err} ->
- ?ERROR_MSG("Error found when trying to get the "
- "vCard of ~s@~s in ~p:~n ~p",
- [U, S, ModVcard, Err]),
- <<"">>;
- NickName ->
- NickName
- end;
+ SubEls = mod_vcard:get_vcard(U, S),
+ get_rosteritem_name_vcard(SubEls);
false ->
<<"">>
end.
-get_rosteritem_name_vcard([]) -> <<"">>;
-get_rosteritem_name_vcard([Vcard]) ->
+-spec get_rosteritem_name_vcard([xmlel()]) -> binary().
+get_rosteritem_name_vcard([Vcard|_]) ->
case fxml:get_path_s(Vcard,
[{elem, <<"NICKNAME">>}, cdata])
of
<<"">> ->
fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
Nickname -> Nickname
- end.
+ end;
+get_rosteritem_name_vcard(_) ->
+ <<"">>.
%% This function rewrites the roster entries when moving or renaming
%% them in the user contact list.
+-spec process_item(#roster{}, binary()) -> #roster{}.
process_item(RosterItem, Host) ->
USFrom = {UserFrom, ServerFrom} = RosterItem#roster.us,
{UserTo, ServerTo, ResourceTo} = RosterItem#roster.jid,
@@ -305,17 +287,15 @@ set_new_rosteritems(UserFrom, ServerFrom, UserTo,
RIFrom.
set_item(User, Server, Resource, Item) ->
- ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
- id = <<"push", (randoms:get_string())/binary>>,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER}],
- children = [mod_roster:item_to_xml(Item)]}]},
- ejabberd_router:route(jid:make(User, Server,
- Resource),
- jid:make(<<"">>, Server, <<"">>),
- jlib:iq_to_xml(ResIQ)).
-
+ ResIQ = #iq{type = set, id = <<"push", (randoms:get_string())/binary>>,
+ sub_els = [#roster_query{
+ items = [mod_roster:encode_item(Item)]}]},
+ ejabberd_router:route(jid:make(User, Server, Resource),
+ jid:make(Server),
+ ResIQ).
+
+-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
+ -> {[ljid()], [ljid()]}.
get_subscription_lists({F, T}, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -328,6 +308,8 @@ get_subscription_lists({F, T}, User, Server) ->
SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
+-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), [binary()]}.
get_jid_info({Subscription, Groups}, User, Server,
JID) ->
LUser = jid:nodeprep(User),
@@ -356,10 +338,16 @@ get_jid_info({Subscription, Groups}, User, Server,
error -> {Subscription, Groups}
end.
+-spec in_subscription(boolean(), binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> boolean().
in_subscription(Acc, User, Server, JID, Type,
_Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
+-spec out_subscription(
+ binary(), binary(), jid(),
+ subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(UserFrom, ServerFrom, JIDTo,
unsubscribed) ->
#jid{luser = UserTo, lserver = ServerTo} = JIDTo,
@@ -574,13 +562,13 @@ add_user_to_group(Host, US, Group) ->
{LUser, LServer} = US,
case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
- GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
MoreGroupOpts = case LUser of
<<"@all@">> -> [{all_users, true}];
<<"@online@">> -> [{online_users, true}];
_ -> []
end,
- (?MODULE):set_group_opts(Host, Group,
+ mod_shared_roster:set_group_opts(Host, Group,
GroupOpts ++ MoreGroupOpts);
nomatch ->
DisplayedToGroups = displayed_to_groups(Group, Host),
@@ -612,7 +600,7 @@ remove_user_from_group(Host, US, Group) ->
{LUser, LServer} = US,
case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
- GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
NewGroupOpts = case LUser of
<<"@all@">> ->
lists:filter(fun (X) -> X /= {all_users, true}
@@ -623,7 +611,7 @@ remove_user_from_group(Host, US, Group) ->
end,
GroupOpts)
end,
- (?MODULE):set_group_opts(Host, Group, NewGroupOpts);
+ mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts);
nomatch ->
Mod = gen_mod:db_mod(Host, ?MODULE),
Result = Mod:remove_user_from_group(Host, US, Group),
@@ -653,12 +641,15 @@ broadcast_members_to_user(LUser, LServer, Group, Host, Subscription) ->
broadcast_subscription(U, S, {LUser, LServer, <<"">>}, Subscription)
end, Members).
+-spec register_user(binary(), binary()) -> ok.
register_user(User, Server) ->
Groups = get_user_groups({User, Server}),
[push_user_to_displayed(User, Server, Group, Server,
both, displayed_to_groups(Group, Server))
- || Group <- Groups].
+ || Group <- Groups],
+ ok.
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
push_user_to_members(User, Server, remove).
@@ -730,13 +721,9 @@ displayed_to_groups(GroupName, LServer) ->
[Name || {Name, _} <- Gs].
push_item(User, Server, Item) ->
- Stanza = jlib:iq_to_xml(#iq{type = set,
- xmlns = ?NS_ROSTER,
- id = <<"push", (randoms:get_string())/binary>>,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_ROSTER}],
- children = [item_to_xml(Item)]}]}),
+ Stanza = #iq{type = set, id = <<"push", (randoms:get_string())/binary>>,
+ sub_els = [#roster_query{
+ items = [mod_roster:encode_item(Item)]}]},
lists:foreach(fun (Resource) ->
JID = jid:make(User, Server, Resource),
ejabberd_router:route(jid:remove_resource(JID), JID, Stanza)
@@ -752,38 +739,7 @@ push_roster_item(User, Server, ContactU, ContactS,
groups = [GroupName]},
push_item(User, Server, Item).
-item_to_xml(Item) ->
- Attrs1 = [{<<"jid">>,
- jid:to_string(Item#roster.jid)}],
- Attrs2 = case Item#roster.name of
- <<"">> -> Attrs1;
- Name -> [{<<"name">>, Name} | Attrs1]
- end,
- Attrs3 = case Item#roster.subscription of
- none -> [{<<"subscription">>, <<"none">>} | Attrs2];
- from -> [{<<"subscription">>, <<"from">>} | Attrs2];
- to -> [{<<"subscription">>, <<"to">>} | Attrs2];
- both -> [{<<"subscription">>, <<"both">>} | Attrs2];
- remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
- end,
- Attrs4 = case ask_to_pending(Item#roster.ask) of
- out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
- both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
- _ -> Attrs3
- end,
- SubEls1 = lists:map(fun (G) ->
- #xmlel{name = <<"group">>, attrs = [],
- children = [{xmlcdata, G}]}
- end,
- Item#roster.groups),
- SubEls = SubEls1 ++ Item#roster.xs,
- #xmlel{name = <<"item">>, attrs = Attrs4,
- children = SubEls}.
-
-ask_to_pending(subscribe) -> out;
-ask_to_pending(unsubscribe) -> none;
-ask_to_pending(Ask) -> Ask.
-
+-spec user_available(jid()) -> ok.
user_available(New) ->
LUser = New#jid.luser,
LServer = New#jid.lserver,
@@ -807,6 +763,7 @@ user_available(New) ->
_ -> ok
end.
+-spec unset_presence(binary(), binary(), binary(), binary()) -> ok.
unset_presence(LUser, LServer, Resource, Status) ->
Resources = ejabberd_sm:get_user_resources(LUser,
LServer),
@@ -850,7 +807,7 @@ webadmin_page(Acc, _, _) -> Acc.
list_shared_roster_groups(Host, Query, Lang) ->
Res = list_sr_groups_parse_query(Host, Query),
- SRGroups = (?MODULE):list_groups(Host),
+ SRGroups = mod_shared_roster:list_groups(Host),
FGroups = (?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
(lists:map(fun (Group) ->
@@ -901,15 +858,15 @@ list_sr_groups_parse_query(Host, Query) ->
list_sr_groups_parse_addnew(Host, Query) ->
case lists:keysearch(<<"namenew">>, 1, Query) of
{value, {_, Group}} when Group /= <<"">> ->
- (?MODULE):create_group(Host, Group), ok;
+ mod_shared_roster:create_group(Host, Group), ok;
_ -> error
end.
list_sr_groups_parse_delete(Host, Query) ->
- SRGroups = (?MODULE):list_groups(Host),
+ SRGroups = mod_shared_roster:list_groups(Host),
lists:foreach(fun (Group) ->
case lists:member({<<"selected">>, Group}, Query) of
- true -> (?MODULE):delete_group(Host, Group);
+ true -> mod_shared_roster:delete_group(Host, Group);
_ -> ok
end
end,
@@ -919,14 +876,14 @@ list_sr_groups_parse_delete(Host, Query) ->
shared_roster_group(Host, Group, Query, Lang) ->
Res = shared_roster_group_parse_query(Host, Group,
Query),
- GroupOpts = (?MODULE):get_group_opts(Host, Group),
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
Name = get_opt(GroupOpts, name, <<"">>),
Description = get_opt(GroupOpts, description, <<"">>),
AllUsers = get_opt(GroupOpts, all_users, false),
OnlineUsers = get_opt(GroupOpts, online_users, false),
DisplayedGroups = get_opt(GroupOpts, displayed_groups,
[]),
- Members = (?MODULE):get_group_explicit_users(Host,
+ Members = mod_shared_roster:get_group_explicit_users(Host,
Group),
FMembers = iolist_to_binary(
[if AllUsers -> <<"@all@\n">>;
@@ -950,21 +907,21 @@ shared_roster_group(Host, Group, Query, Lang) ->
[?XCT(<<"td">>, <<"Description:">>),
?XE(<<"td">>,
[?TEXTAREA(<<"description">>,
- jlib:integer_to_binary(lists:max([3,
+ integer_to_binary(lists:max([3,
DescNL])),
<<"20">>, Description)])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, <<"Members:">>),
?XE(<<"td">>,
[?TEXTAREA(<<"members">>,
- jlib:integer_to_binary(lists:max([3,
+ integer_to_binary(lists:max([3,
byte_size(FMembers)])),
<<"20">>, FMembers)])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, <<"Displayed Groups:">>),
?XE(<<"td">>,
[?TEXTAREA(<<"dispgroups">>,
- jlib:integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
+ integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
<<"20">>,
list_to_binary(FDisplayedGroups))])])])])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
@@ -1003,7 +960,7 @@ shared_roster_group_parse_query(Host, Group, Query) ->
DispGroupsOpt = if DispGroups == [] -> [];
true -> [{displayed_groups, DispGroups}]
end,
- OldMembers = (?MODULE):get_group_explicit_users(Host,
+ OldMembers = mod_shared_roster:get_group_explicit_users(Host,
Group),
SJIDs = str:tokens(SMembers, <<", \r\n">>),
NewMembers = lists:foldl(fun (_SJID, error) -> error;
@@ -1040,7 +997,7 @@ shared_roster_group_parse_query(Host, Group, Query) ->
RemovedDisplayedGroups = CurrentDisplayedGroups -- DispGroups,
displayed_groups_update(OldMembers, RemovedDisplayedGroups, remove),
displayed_groups_update(OldMembers, AddedDisplayedGroups, both),
- (?MODULE):set_group_opts(Host, Group,
+ mod_shared_roster:set_group_opts(Host, Group,
NameOpt ++
DispGroupsOpt ++
DescriptionOpt ++
@@ -1050,13 +1007,13 @@ shared_roster_group_parse_query(Host, Group, Query) ->
AddedMembers = NewMembers -- OldMembers,
RemovedMembers = OldMembers -- NewMembers,
lists:foreach(fun (US) ->
- (?MODULE):remove_user_from_group(Host,
+ mod_shared_roster:remove_user_from_group(Host,
US,
Group)
end,
RemovedMembers),
lists:foreach(fun (US) ->
- (?MODULE):add_user_to_group(Host, US,
+ mod_shared_roster:add_user_to_group(Host, US,
Group)
end,
AddedMembers),
@@ -1115,13 +1072,16 @@ export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"sr_group">>, 3}, {<<"sr_user">>, 3}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, Data) ->
+import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
+ Mod:import(LServer, Tab, L).
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index 22f50d302..97ead9f3d 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -45,7 +45,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_roster.hrl").
-include("eldap.hrl").
@@ -111,6 +111,7 @@ depends(_Host, _Opts) ->
%%--------------------------------------------------------------------
%% Hooks
%%--------------------------------------------------------------------
+-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}].
get_user_roster(Items, {U, S} = US) ->
SRUsers = get_user_to_groups_map(US, true),
{NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item,
@@ -143,6 +144,7 @@ get_user_roster(Items, {U, S} = US) ->
%% This function in use to rewrite the roster entries when moving or renaming
%% them in the user contact list.
+-spec process_item(#roster{}, binary()) -> #roster{}.
process_item(RosterItem, _Host) ->
USFrom = RosterItem#roster.us,
{User, Server, _Resource} = RosterItem#roster.jid,
@@ -158,6 +160,8 @@ process_item(RosterItem, _Host) ->
_ -> RosterItem#roster{subscription = both, ask = none}
end.
+-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
+ -> {[ljid()], [ljid()]}.
get_subscription_lists({F, T}, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -170,6 +174,8 @@ get_subscription_lists({F, T}, User, Server) ->
SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
+-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), [binary()]}.
get_jid_info({Subscription, Groups}, User, Server,
JID) ->
LUser = jid:nodeprep(User),
@@ -187,10 +193,16 @@ get_jid_info({Subscription, Groups}, User, Server,
error -> {Subscription, Groups}
end.
+-spec in_subscription(boolean(), binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> boolean().
in_subscription(Acc, User, Server, JID, Type,
_Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
+-spec out_subscription(
+ binary(), binary(), jid(),
+ subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type,
false).
diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl
index ca2e55e2f..ed4525041 100644
--- a/src/mod_shared_roster_mnesia.erl
+++ b/src/mod_shared_roster_mnesia.erl
@@ -15,21 +15,21 @@
delete_group/2, get_group_opts/2, set_group_opts/3,
get_user_groups/2, get_group_explicit_users/2,
get_user_displayed_groups/3, is_user_in_group/3,
- add_user_to_group/3, remove_user_from_group/3, import/2]).
+ add_user_to_group/3, remove_user_from_group/3, import/3]).
--include("jlib.hrl").
-include("mod_roster.hrl").
-include("mod_shared_roster.hrl").
-include("logger.hrl").
+-include("xmpp.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(sr_group,
+ ejabberd_mnesia:create(?MODULE, sr_group,
[{disc_copies, [node()]},
{attributes, record_info(fields, sr_group)}]),
- mnesia:create_table(sr_user,
+ ejabberd_mnesia:create(?MODULE, sr_user,
[{disc_copies, [node()]}, {type, bag},
{attributes, record_info(fields, sr_user)}]),
update_tables(),
@@ -119,10 +119,14 @@ remove_user_from_group(Host, US, Group) ->
F = fun () -> mnesia:delete_object(R) end,
mnesia:transaction(F).
-import(_LServer, #sr_group{} = G) ->
+import(LServer, <<"sr_group">>, [Group, SOpts, _TimeStamp]) ->
+ G = #sr_group{group_host = {Group, LServer},
+ opts = ejabberd_sql:decode_term(SOpts)},
mnesia:dirty_write(G);
-import(_LServer, #sr_user{} = U) ->
- mnesia:dirty_write(U).
+import(LServer, <<"sr_user">>, [SJID, Group, _TimeStamp]) ->
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
+ User = #sr_user{us = {U, S}, group_host = {Group, LServer}},
+ mnesia:dirty_write(User).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_shared_roster_riak.erl b/src/mod_shared_roster_riak.erl
index 0df35e37d..bdb750981 100644
--- a/src/mod_shared_roster_riak.erl
+++ b/src/mod_shared_roster_riak.erl
@@ -15,11 +15,11 @@
delete_group/2, get_group_opts/2, set_group_opts/3,
get_user_groups/2, get_group_explicit_users/2,
get_user_displayed_groups/3, is_user_in_group/3,
- add_user_to_group/3, remove_user_from_group/3, import/2]).
+ add_user_to_group/3, remove_user_from_group/3, import/3]).
--include("jlib.hrl").
-include("mod_roster.hrl").
-include("mod_shared_roster.hrl").
+-include("xmpp.hrl").
%%%===================================================================
%%% API
@@ -121,13 +121,17 @@ add_user_to_group(Host, US, Group) ->
remove_user_from_group(Host, US, Group) ->
{atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})}.
-import(_LServer, #sr_group{group_host = {_, Host}} = G) ->
- ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
-import(_LServer, #sr_user{us = US, group_host = {Group, Host}} = User) ->
+import(LServer, <<"sr_group">>, [Group, SOpts, _TimeStamp]) ->
+ G = #sr_group{group_host = {Group, LServer},
+ opts = ejabberd_sql:decode_term(SOpts)},
+ ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, LServer}]}]);
+import(LServer, <<"sr_user">>, [SJID, Group|_]) ->
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
+ User = #sr_user{us = {U, S}, group_host = {Group, LServer}},
ejabberd_riak:put(User, sr_user_schema(),
- [{i, {US, {Group, Host}}},
- {'2i', [{<<"us">>, US},
- {<<"group_host">>, {Group, Host}}]}]).
+ [{i, {{U, S}, {Group, LServer}}},
+ {'2i', [{<<"us">>, {U, S}},
+ {<<"group_host">>, {Group, LServer}}]}]).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl
index 2b186fd0b..9f723f839 100644
--- a/src/mod_shared_roster_sql.erl
+++ b/src/mod_shared_roster_sql.erl
@@ -17,10 +17,10 @@
delete_group/2, get_group_opts/2, set_group_opts/3,
get_user_groups/2, get_group_explicit_users/2,
get_user_displayed_groups/3, is_user_in_group/3,
- add_user_to_group/3, remove_user_from_group/3, import/1,
- import/2, export/1]).
+ add_user_to_group/3, remove_user_from_group/3, import/3,
+ export/1]).
--include("jlib.hrl").
+-include("jid.hrl").
-include("mod_roster.hrl").
-include("mod_shared_roster.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -177,20 +177,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select name, opts from sr_group;">>,
- fun([Group, SOpts]) ->
- #sr_group{group_host = {Group, LServer},
- opts = ejabberd_sql:decode_term(SOpts)}
- end},
- {<<"select jid, grp from sr_user;">>,
- fun([SJID, Group]) ->
- #jid{luser = U, lserver = S} = jid:from_string(SJID),
- #sr_user{us = {U, S}, group_host = {Group, LServer}}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_sic.erl b/src/mod_sic.erl
index 49b65a0ee..4bb4eb9eb 100644
--- a/src/mod_sic.erl
+++ b/src/mod_sic.erl
@@ -31,73 +31,66 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3,
- process_sm_iq/3, mod_opt_type/1, depends/2]).
+-export([start/2, stop/1, process_local_iq/1,
+ process_sm_iq/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
-
--define(NS_SIC, <<"urn:xmpp:sic:0">>).
+-include("xmpp.hrl").
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_SIC, ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_SIC, ?MODULE, process_sm_iq, IQDisc).
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_0,
+ ?MODULE, process_local_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_0,
+ ?MODULE, process_sm_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_1,
+ ?MODULE, process_local_iq, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_1,
+ ?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_SIC),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
- ?NS_SIC).
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC_0),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC_0),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC_1),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC_1).
depends(_Host, _Opts) ->
[].
-process_local_iq(#jid{user = User, server = Server,
- resource = Resource},
- _To, #iq{type = get, sub_el = _SubEl} = IQ) ->
+process_local_iq(#iq{from = #jid{user = User, server = Server,
+ resource = Resource},
+ type = get} = IQ) ->
get_ip({User, Server, Resource}, IQ);
-process_local_iq(_From, _To,
- #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
-process_sm_iq(#jid{user = User, server = Server,
- resource = Resource},
- #jid{user = User, server = Server},
- #iq{type = get, sub_el = _SubEl} = IQ) ->
+process_sm_iq(#iq{from = #jid{user = User, server = Server,
+ resource = Resource},
+ to = #jid{user = User, server = Server},
+ type = get} = IQ) ->
get_ip({User, Server, Resource}, IQ);
-process_sm_iq(_From, _To,
- #iq{type = get, sub_el = SubEl, lang = Lang} = IQ) ->
+process_sm_iq(#iq{type = get, lang = Lang} = IQ) ->
Txt = <<"Query to another users is forbidden">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]};
-process_sm_iq(_From, _To,
- #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
+process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
get_ip({User, Server, Resource},
- #iq{lang = Lang,
- sub_el =
- #xmlel{name = Name, attrs = Attrs} = SubEl} =
- IQ) ->
+ #iq{lang = Lang, sub_els = [#sic{xmlns = NS}]} = IQ) ->
case ejabberd_sm:get_user_ip(User, Server, Resource) of
- {IP, _} when is_tuple(IP) ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = Name, attrs = Attrs,
- children =
- [{xmlcdata,
- iolist_to_binary(jlib:ip_to_list(IP))}]}]};
- _ ->
- Txt = <<"User session not found">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
+ {IP, Port} when is_tuple(IP) ->
+ Result = case NS of
+ ?NS_SIC_0 -> #sic{ip = IP, xmlns = NS};
+ ?NS_SIC_1 -> #sic{ip = IP, port = Port, xmlns = NS}
+ end,
+ xmpp:make_iq_result(IQ, Result);
+ _ ->
+ Txt = <<"User session not found">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
end.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl
index 04ae55ae7..06daf0810 100644
--- a/src/mod_sip_proxy.erl
+++ b/src/mod_sip_proxy.erl
@@ -299,7 +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),
- TS = list_to_binary(integer_to_list(p1_time_compat:system_time(seconds))),
+ TS = (integer_to_binary(p1_time_compat:system_time(seconds))),
Sign = make_sign(TS, Hdrs),
User = <<TS/binary, $-, Sign/binary>>,
NewRR_URI = RR_URI#uri{user = User},
@@ -339,7 +339,7 @@ make_sign(TS, Hdrs) ->
is_signed_by_me(TS_Sign, Hdrs) ->
try
[TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
- TS = list_to_integer(binary_to_list(TSBin)),
+ TS = (binary_to_integer(TSBin)),
NowTS = p1_time_compat:system_time(seconds),
true = (NowTS - TS) =< ?SIGN_LIFETIME,
Sign == make_sign(TSBin, Hdrs)
diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl
index fcaa42365..4ae8e077b 100644
--- a/src/mod_sip_registrar.erl
+++ b/src/mod_sip_registrar.erl
@@ -179,7 +179,7 @@ ping(SIPSocket) ->
%%%===================================================================
init([]) ->
update_table(),
- mnesia:create_table(sip_session,
+ ejabberd_mnesia:create(?MODULE, sip_session,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, sip_session)}]),
@@ -355,7 +355,7 @@ min_expires() ->
60.
to_integer(Bin, Min, Max) ->
- case catch list_to_integer(binary_to_list(Bin)) of
+ case catch (binary_to_integer(Bin)) of
N when N >= Min, N =< Max ->
{ok, N};
_ ->
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index 66bbb5b5b..e43409e06 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -31,65 +31,39 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3,
- mod_opt_type/1, depends/2]).
+-export([start/2, stop/1, process_iq/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
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_STATS, ?MODULE, process_local_iq, IQDisc).
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_STATS,
+ ?MODULE, process_iq, IQDisc).
stop(Host) ->
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_STATS).
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_STATS).
depends(_Host, _Opts) ->
[].
-process_local_iq(_From, To,
- #iq{id = _ID, type = Type, xmlns = XMLNS,
- sub_el = SubEl, lang = Lang} =
- IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- #xmlel{children = Els} = SubEl,
- Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
- <<"/">>),
- Names = get_names(Els, []),
- case get_local_stats(To#jid.server, Node, Names, Lang) of
- {result, Res} ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, XMLNS}],
- children = Res}]};
- {error, Error} ->
- IQ#iq{type = error, sub_el = [SubEl, Error]}
- end
+process_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_iq(#iq{type = get, to = To, lang = Lang,
+ sub_els = [#stats{} = Stats]} = IQ) ->
+ Node = str:tokens(Stats#stats.node, <<"/">>),
+ Names = [Name || #stat{name = Name} <- Stats#stats.list],
+ case get_local_stats(To#jid.server, Node, Names, Lang) of
+ {result, List} ->
+ xmpp:make_iq_result(IQ, Stats#stats{list = List});
+ {error, Error} ->
+ xmpp:make_error(IQ, Error)
end.
-get_names([], Res) -> Res;
-get_names([#xmlel{name = <<"stat">>, attrs = Attrs}
- | Els],
- Res) ->
- Name = fxml:get_attr_s(<<"name">>, Attrs),
- case Name of
- <<"">> -> get_names(Els, Res);
- _ -> get_names(Els, [Name | Res])
- end;
-get_names([_ | Els], Res) -> get_names(Els, Res).
-
--define(STAT(Name),
- #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
- children = []}).
+-define(STAT(Name), #stat{name = Name}).
get_local_stats(_Server, [], [], _Lang) ->
{result,
@@ -115,7 +89,7 @@ get_local_stats(_Server, [<<"running nodes">>, ENode],
case search_running_node(ENode) of
false ->
Txt = <<"No running node found">>,
- {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
+ {error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
{result,
lists:map(fun (Name) -> get_node_stat(Node, Name) end,
@@ -123,29 +97,21 @@ get_local_stats(_Server, [<<"running nodes">>, ENode],
end;
get_local_stats(_Server, _, _, Lang) ->
Txt = <<"No statistics found for this item">>,
- {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}.
+ {error, xmpp:err_feature_not_implemented(Txt, Lang)}.
--define(STATVAL(Val, Unit),
- #xmlel{name = <<"stat">>,
- attrs =
- [{<<"name">>, Name}, {<<"units">>, Unit},
- {<<"value">>, Val}],
- children = []}).
+-define(STATVAL(Val, Unit), #stat{name = Name, units = Unit, value = Val}).
-define(STATERR(Code, Desc),
- #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
- children =
- [#xmlel{name = <<"error">>,
- attrs = [{<<"code">>, Code}],
- children = [{xmlcdata, Desc}]}]}).
+ #stat{name = Name,
+ error = #stat_error{code = Code, reason = Desc}}).
get_local_stat(Server, [], Name)
when Name == <<"users/online">> ->
case catch ejabberd_sm:get_vh_session_list(Server) of
{'EXIT', _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Users ->
- ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))),
+ ?STATVAL((integer_to_binary(length(Users))),
<<"users">>)
end;
get_local_stat(Server, [], Name)
@@ -154,15 +120,15 @@ get_local_stat(Server, [], Name)
ejabberd_auth:get_vh_registered_users_number(Server)
of
{'EXIT', _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
NUsers ->
- ?STATVAL((iolist_to_binary(integer_to_list(NUsers))),
+ ?STATVAL((integer_to_binary(NUsers)),
<<"users">>)
end;
get_local_stat(_Server, [], Name)
when Name == <<"users/all-hosts/online">> ->
Users = ejabberd_sm:connected_users_number(),
- ?STATVAL((iolist_to_binary(integer_to_list(Users))), <<"users">>);
+ ?STATVAL((integer_to_binary(Users)), <<"users">>);
get_local_stat(_Server, [], Name)
when Name == <<"users/all-hosts/total">> ->
NumUsers = lists:foldl(fun (Host, Total) ->
@@ -170,10 +136,10 @@ get_local_stat(_Server, [], Name)
+ Total
end,
0, ?MYHOSTS),
- ?STATVAL((iolist_to_binary(integer_to_list(NumUsers))),
+ ?STATVAL((integer_to_binary(NumUsers)),
<<"users">>);
get_local_stat(_Server, _, Name) ->
- ?STATERR(<<"404">>, <<"Not Found">>).
+ ?STATERR(404, <<"Not Found">>).
get_node_stat(Node, Name)
when Name == <<"time/uptime">> ->
@@ -181,11 +147,9 @@ get_node_stat(Node, Name)
[wall_clock])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
CPUTime ->
- ?STATVAL(list_to_binary(
- io_lib:format("~.3f",
- [element(1, CPUTime) / 1000])),
+ ?STATVAL(str:format("~.3f", [element(1, CPUTime) / 1000]),
<<"seconds">>)
end;
get_node_stat(Node, Name)
@@ -193,11 +157,9 @@ get_node_stat(Node, Name)
case catch ejabberd_cluster:call(Node, erlang, statistics, [runtime])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
RunTime ->
- ?STATVAL(list_to_binary(
- io_lib:format("~.3f",
- [element(1, RunTime) / 1000])),
+ ?STATVAL(str:format("~.3f", [element(1, RunTime) / 1000]),
<<"seconds">>)
end;
get_node_stat(Node, Name)
@@ -206,9 +168,9 @@ get_node_stat(Node, Name)
dirty_get_my_sessions_list, [])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Users ->
- ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))),
+ ?STATVAL((integer_to_binary(length(Users))),
<<"users">>)
end;
get_node_stat(Node, Name)
@@ -217,9 +179,9 @@ get_node_stat(Node, Name)
[transaction_commits])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Transactions ->
- ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ ?STATVAL((integer_to_binary(Transactions)),
<<"transactions">>)
end;
get_node_stat(Node, Name)
@@ -228,9 +190,9 @@ get_node_stat(Node, Name)
[transaction_failures])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Transactions ->
- ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ ?STATVAL((integer_to_binary(Transactions)),
<<"transactions">>)
end;
get_node_stat(Node, Name)
@@ -239,9 +201,9 @@ get_node_stat(Node, Name)
[transaction_restarts])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Transactions ->
- ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ ?STATVAL((integer_to_binary(Transactions)),
<<"transactions">>)
end;
get_node_stat(Node, Name)
@@ -250,13 +212,13 @@ get_node_stat(Node, Name)
[transaction_log_writes])
of
{badrpc, _Reason} ->
- ?STATERR(<<"500">>, <<"Internal Server Error">>);
+ ?STATERR(500, <<"Internal Server Error">>);
Transactions ->
- ?STATVAL((iolist_to_binary(integer_to_list(Transactions))),
+ ?STATVAL((integer_to_binary(Transactions)),
<<"transactions">>)
end;
get_node_stat(_, Name) ->
- ?STATERR(<<"404">>, <<"Not Found">>).
+ ?STATERR(404, <<"Not Found">>).
search_running_node(SNode) ->
search_running_node(SNode,
diff --git a/src/mod_time.erl b/src/mod_time.erl
index 90296f3d8..0aeb6831c 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -32,13 +32,13 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3,
+-export([start/2, stop/1, process_local_iq/1,
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -50,41 +50,18 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_TIME).
-process_local_iq(_From, _To,
- #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- 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 =
- calendar:datetime_to_gregorian_seconds(Now_local) -
- calendar:datetime_to_gregorian_seconds(Now_universal),
- {Hd, Md, _} =
- calendar:seconds_to_time(abs(Seconds_diff)),
- {_, TZO_diff} = jlib:timestamp_to_iso({{0, 1, 1},
- {0, 0, 0}},
- {sign(Seconds_diff), {Hd, Md}}),
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"time">>,
- attrs = [{<<"xmlns">>, ?NS_TIME}],
- children =
- [#xmlel{name = <<"tzo">>, attrs = [],
- children = [{xmlcdata, TZO_diff}]},
- #xmlel{name = <<"utc">>, attrs = [],
- children =
- [{xmlcdata,
- <<UTC/binary,
- UTC_diff/binary>>}]}]}]}
- end.
-
-sign(N) when N < 0 -> <<"-">>;
-sign(_) -> <<"+">>.
+process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq(#iq{type = get} = IQ) ->
+ Now = p1_time_compat:timestamp(),
+ Now_universal = calendar:now_to_universal_time(Now),
+ Now_local = calendar:universal_time_to_local_time(Now_universal),
+ Seconds_diff =
+ calendar:datetime_to_gregorian_seconds(Now_local) -
+ calendar:datetime_to_gregorian_seconds(Now_universal),
+ {Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)),
+ xmpp:make_iq_result(IQ, #time{tzo = {Hd, Md}, utc = Now}).
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index aca9d7462..843281ef8 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -33,13 +33,15 @@
-behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5,
- process_local_iq/3, process_sm_iq/3, string2lower/1,
- remove_user/2, export/1, import/1, import/3, depends/2,
- mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
+ process_local_iq/1, process_sm_iq/1, string2lower/1,
+ remove_user/2, export/1, import_info/0, import/5, import_start/2,
+ depends/2, process_search/1, process_vcard/1, get_vcard/2,
+ disco_items/5, disco_features/5, disco_identity/5,
+ decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
@@ -47,13 +49,17 @@
-define(PROCNAME, ejabberd_mod_vcard).
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
+-callback stop(binary()) -> any().
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback get_vcard(binary(), binary()) -> [xmlel()] | error.
-callback set_vcard(binary(), binary(),
xmlel(), #vcard_search{}) -> {atomic, any()}.
+-callback search_fields(binary()) -> [{binary(), binary()}].
+-callback search_reported(binary()) -> [{binary(), binary()}].
-callback search(binary(), [{binary(), [binary()]}], boolean(),
- infinity | pos_integer()) -> [binary()].
+ infinity | pos_integer()) -> [{binary(), binary()}].
-callback remove_user(binary(), binary()) -> {atomic, any()}.
+-callback is_search_supported(binary()) -> boolean().
start(Host, Opts) ->
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
@@ -68,11 +74,30 @@ start(Host, Opts) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
- MyHost = gen_mod:get_opt_host(Host, Opts,
- <<"vjud.@HOST@">>),
+ MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts,
fun(B) when is_boolean(B) -> B end,
false),
+ if Search ->
+ ejabberd_hooks:add(
+ disco_local_items, MyHost, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(
+ disco_local_features, MyHost, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(
+ disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc);
+ true ->
+ ok
+ end,
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost, Host, Search])).
@@ -81,18 +106,35 @@ init(Host, ServerHost, Search) ->
false -> loop(Host, ServerHost);
_ ->
ejabberd_router:register_route(Host, ServerHost),
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ case Mod:is_search_supported(ServerHost) of
+ false ->
+ ?WARNING_MSG("vcard search functionality is "
+ "not implemented for ~s backend",
+ [gen_mod:db_type(ServerHost, ?MODULE)]);
+ true ->
+ ejabberd_router:register_route(Host, ServerHost)
+ end,
loop(Host, ServerHost)
end.
loop(Host, ServerHost) ->
receive
{route, From, To, Packet} ->
- case catch do_route(ServerHost, From, To, Packet) of
+ case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok
end,
loop(Host, ServerHost);
- stop -> ejabberd_router:unregister_route(Host), ok;
+ stop ->
+ ejabberd_router:unregister_route(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),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SEARCH),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO);
_ -> loop(Host, ServerHost)
end.
@@ -105,10 +147,22 @@ stop(Host) ->
?NS_VCARD),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:stop(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
+do_route(From, To, #xmlel{name = <<"iq">>} = El) ->
+ ejabberd_router:process_iq(From, To, El);
+do_route(From, To, #iq{} = IQ) ->
+ ejabberd_router:process_iq(From, To, IQ);
+do_route(_, _, _) ->
+ ok.
+
+-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | empty | {result, [binary()]}.
get_sm_features({error, _Error} = Acc, _From, _To,
_Node, _Lang) ->
Acc;
@@ -123,67 +177,118 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
_ -> Acc
end.
-process_local_iq(_From, _To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children =
- [#xmlel{name = <<"FN">>, attrs = [],
- children =
- [{xmlcdata, <<"ejabberd">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Erlang Jabber Server">>))/binary,
- "\nCopyright (c) 2002-2016 ProcessOne">>}]},
- #xmlel{name = <<"BDAY">>, attrs = [],
- children =
- [{xmlcdata, <<"2002-11-16">>}]}]}]}
+-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel().
+%% Tell gen_iq_handler not to decode vcard elements
+decode_iq_subel(El) ->
+ case xmpp:get_ns(El) of
+ ?NS_VCARD -> xmpp:encode(El);
+ _ -> xmpp:decode(El)
end.
-process_sm_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- #jid{user = User, lserver = LServer} = From,
- case lists:member(LServer, ?MYHOSTS) of
- true ->
- set_vcard(User, LServer, SubEl),
- IQ#iq{type = result, sub_el = []};
- false ->
- Txt = <<"The query is only allowed from local users">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
- end;
- get ->
- #jid{luser = LUser, lserver = LServer} = To,
- case get_vcard(LUser, LServer) of
- error ->
- Txt = <<"Database failure">>,
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
- [] ->
- IQ#iq{type = result,
- sub_el = [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = []}]};
- Els -> IQ#iq{type = result, sub_el = Els}
- end
+-spec process_local_iq(iq()) -> iq().
+process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
+ Desc = translate:translate(Lang, <<"Erlang Jabber Server">>),
+ xmpp:make_iq_result(
+ IQ, #vcard_temp{fn = <<"ejabberd">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>,
+ bday = <<"2002-11-16">>}).
+
+-spec process_sm_iq(iq()) -> iq().
+process_sm_iq(#iq{type = set, lang = Lang, from = From,
+ sub_els = [SubEl]} = IQ) ->
+ #jid{user = User, lserver = LServer} = From,
+ case lists:member(LServer, ?MYHOSTS) of
+ true ->
+ set_vcard(User, LServer, SubEl),
+ xmpp:make_iq_result(IQ);
+ false ->
+ Txt = <<"The query is only allowed from local users">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
+ end;
+process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
+ #jid{luser = LUser, lserver = LServer} = To,
+ case get_vcard(LUser, LServer) of
+ error ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
+ [] ->
+ xmpp:make_iq_result(IQ, #vcard_temp{});
+ Els ->
+ IQ#iq{type = result, to = From, from = To, sub_els = Els}
end.
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{type = get, lang = Lang} = IQ) ->
+ Desc = translate:translate(Lang, <<"ejabberd vCard module">>),
+ xmpp:make_iq_result(
+ IQ, #vcard_temp{fn = <<"ejabberd/mod_vcard">>,
+ url = ?EJABBERD_URI,
+ desc = <<Desc/binary, $\n, ?COPYRIGHT>>}).
+
+-spec process_search(iq()) -> iq().
+process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ xmpp:make_iq_result(IQ, mk_search_form(To, ServerHost, Lang));
+process_search(#iq{type = set, to = To, lang = Lang,
+ sub_els = [#search{xdata = #xdata{type = submit,
+ fields = Fs}}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ ResultXData = search_result(Lang, To, ServerHost, Fs),
+ xmpp:make_iq_result(IQ, #search{xdata = ResultXData});
+process_search(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Incorrect data form">>,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
+
+-spec disco_items({error, stanza_error()} | {result, [disco_item()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [disco_item()]}.
+disco_items(empty, _From, _To, <<"">>, _Lang) ->
+ {result, []};
+disco_items(empty, _From, _To, _Node, Lang) ->
+ {error, xmpp:err_item_not_found(<<"No services available">>, Lang)};
+disco_items(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
+disco_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+ Acc;
+disco_features(Acc, _From, _To, <<"">>, _Lang) ->
+ Features = case Acc of
+ {result, Fs} -> Fs;
+ empty -> []
+ end,
+ {result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_VCARD, ?NS_SEARCH | Features]};
+disco_features(empty, _From, _To, _Node, Lang) ->
+ Txt = <<"No features available">>,
+ {error, xmpp:err_item_not_found(Txt, Lang)};
+disco_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+-spec disco_identity([identity()], jid(), jid(),
+ binary(), binary()) -> [identity()].
+disco_identity(Acc, _From, _To, <<"">>, Lang) ->
+ [#identity{category = <<"directory">>,
+ type = <<"user">>,
+ name = translate:translate(Lang, <<"vCard User Search">>)}|Acc];
+disco_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+-spec get_vcard(binary(), binary()) -> [xmlel()] | error.
get_vcard(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_vcard(LUser, LServer).
+-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
make_vcard_search(User, LUser, LServer, VCARD) ->
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
Family = fxml:get_path_s(VCARD,
@@ -250,6 +355,7 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
orgunit = OrgUnit,
lorgunit = LOrgUnit}.
+-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok.
set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of
error ->
@@ -262,307 +368,64 @@ set_vcard(User, LServer, VCARD) ->
[LUser, LServer, VCARD])
end.
+-spec string2lower(binary()) -> binary().
string2lower(String) ->
case stringprep:tolower(String) of
Lower when is_binary(Lower) -> Lower;
error -> str:to_lower(String)
end.
--define(TLFIELD(Type, Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
--define(FORM(JID),
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "search">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search users in ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Fill in the form to search for any matching "
- "Jabber User (Add * to the end of field "
- "to match substring)">>)}]},
- ?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>),
- ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
- ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
- ?TLFIELD(<<"text-single">>, <<"Middle Name">>,
- <<"middle">>),
- ?TLFIELD(<<"text-single">>, <<"Family Name">>,
- <<"last">>),
- ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
- ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
- ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
- ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
- ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
- ?TLFIELD(<<"text-single">>, <<"Organization Name">>,
- <<"orgname">>),
- ?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
- <<"orgunit">>)]}]).
-
-do_route(ServerHost, From, To, Packet) ->
- #jid{user = User, resource = Resource} = To,
- if (User /= <<"">>) or (Resource /= <<"">>) ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- true ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
- sub_el = SubEl} ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Txt = <<"Data form not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Txt = <<"Incorrect data form">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children =
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XDATA},
- {<<"type">>,
- <<"result">>}],
- children
- =
- search_result(Lang,
- To,
- ServerHost,
- XData)}]}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ))
- end
- end;
- get ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children = ?FORM(To)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
- [],
- [ServerHost, ?MODULE,
- <<"">>, <<"">>]),
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_INFO}],
- children =
- [#xmlel{name =
- <<"identity">>,
- attrs =
- [{<<"category">>,
- <<"directory">>},
- {<<"type">>,
- <<"user">>},
- {<<"name">>,
- translate:translate(Lang,
- <<"vCard User Search">>)}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_DISCO_INFO}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_SEARCH}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_VCARD}],
- children = []}]
- ++ Info}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_ITEMS}],
- children = []}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
- end.
-
-iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd vCard module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
-find_xdata_el(#xmlel{children = SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
- children = SubEls}
- | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- #xmlel{name = Name, attrs = Attrs, children = SubEls};
- _ -> find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
-
--define(LFIELD(Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
-search_result(Lang, JID, ServerHost, Data) ->
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search Results for ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"reported">>, attrs = [],
- children =
- [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
- <<"jid">>),
- ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
- ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
- ?TLFIELD(<<"text-single">>, <<"Middle Name">>,
- <<"middle">>),
- ?TLFIELD(<<"text-single">>, <<"Family Name">>,
- <<"last">>),
- ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
- ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
- ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
- ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
- ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
- ?TLFIELD(<<"text-single">>, <<"Organization Name">>,
- <<"orgname">>),
- ?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
- <<"orgunit">>)]}]
- ++
- lists:map(fun (R) -> record_to_item(ServerHost, R) end,
- search(ServerHost, Data)).
-
--define(FIELD(Var, Val),
- #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
-record_to_item(LServer,
- [Username, FN, Family, Given, Middle, Nickname, BDay,
- CTRY, Locality, EMail, OrgName, OrgUnit]) ->
- #xmlel{name = <<"item">>, attrs = [],
- children =
- [?FIELD(<<"jid">>,
- <<Username/binary, "@", LServer/binary>>),
- ?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family),
- ?FIELD(<<"first">>, Given),
- ?FIELD(<<"middle">>, Middle),
- ?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay),
- ?FIELD(<<"ctry">>, CTRY),
- ?FIELD(<<"locality">>, Locality),
- ?FIELD(<<"email">>, EMail),
- ?FIELD(<<"orgname">>, OrgName),
- ?FIELD(<<"orgunit">>, OrgUnit)]};
-record_to_item(_LServer, #vcard_search{} = R) ->
- {User, Server} = R#vcard_search.user,
- #xmlel{name = <<"item">>, attrs = [],
- children =
- [?FIELD(<<"jid">>, <<User/binary, "@", Server/binary>>),
- ?FIELD(<<"fn">>, (R#vcard_search.fn)),
- ?FIELD(<<"last">>, (R#vcard_search.family)),
- ?FIELD(<<"first">>, (R#vcard_search.given)),
- ?FIELD(<<"middle">>, (R#vcard_search.middle)),
- ?FIELD(<<"nick">>, (R#vcard_search.nickname)),
- ?FIELD(<<"bday">>, (R#vcard_search.bday)),
- ?FIELD(<<"ctry">>, (R#vcard_search.ctry)),
- ?FIELD(<<"locality">>, (R#vcard_search.locality)),
- ?FIELD(<<"email">>, (R#vcard_search.email)),
- ?FIELD(<<"orgname">>, (R#vcard_search.orgname)),
- ?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
-
-search(LServer, Data) ->
+-spec mk_tfield(binary(), binary(), binary()) -> xdata_field().
+mk_tfield(Label, Var, Lang) ->
+ #xdata_field{type = 'text-single',
+ label = translate:translate(Lang, Label),
+ var = Var}.
+
+-spec mk_field(binary(), binary()) -> xdata_field().
+mk_field(Var, Val) ->
+ #xdata_field{var = Var, values = [Val]}.
+
+-spec mk_search_form(jid(), binary(), binary()) -> search().
+mk_search_form(JID, ServerHost, Lang) ->
+ Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
+ (jid:to_string(JID))/binary>>,
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ SearchFields = Mod:search_fields(ServerHost),
+ Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields],
+ X = #xdata{type = form,
+ title = Title,
+ instructions =
+ [translate:translate(
+ Lang,
+ <<"Fill in the form to search for any matching "
+ "Jabber User (Add * to the end of field "
+ "to match substring)">>)],
+ fields = Fs},
+ #search{instructions =
+ translate:translate(
+ Lang, <<"You need an x:data capable client to search">>),
+ xdata = X}.
+
+-spec search_result(binary(), jid(), binary(), [xdata_field()]) -> xdata().
+search_result(Lang, JID, ServerHost, XFields) ->
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ Reported = [mk_tfield(Label, Var, Lang) ||
+ {Label, Var} <- Mod:search_reported(ServerHost)],
+ #xdata{type = result,
+ title = <<(translate:translate(Lang,
+ <<"Search Results for ">>))/binary,
+ (jid:to_string(JID))/binary>>,
+ reported = Reported,
+ items = lists:map(fun (Item) -> item_to_field(Item) end,
+ search(ServerHost, XFields))}.
+
+-spec item_to_field([{binary(), binary()}]) -> [xdata_field()].
+item_to_field(Items) ->
+ [mk_field(Var, Value) || {Var, Value} <- Items].
+
+-spec search(binary(), [xdata_field()]) -> [binary()].
+search(LServer, XFields) ->
+ Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields],
Mod = gen_mod:db_mod(LServer, ?MODULE),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
fun(B) when is_boolean(B) -> B end,
@@ -576,23 +439,27 @@ search(LServer, Data) ->
Mod:search(LServer, Data, AllowReturnAll, MaxMatch).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec remove_user(binary(), binary()) -> any().
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
-export(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:export(LServer).
+import_info() ->
+ [{<<"vcard">>, 3}, {<<"vcard_search">>, 24}].
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, VCard) ->
+import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, VCard).
+ Mod:import(LServer, Tab, L).
+
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index a0ad305a9..47504c39d 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -1,53 +1,33 @@
-%%%----------------------------------------------------------------------
-%%% File : mod_vcard_ldap.erl
-%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : Support for VCards from LDAP storage.
-%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
%%%
-%%%
-%%% 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.
-%%%
-%%%---------------------u-------------------------------------------------
-
+%%% @end
+%%% Created : 29 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
-module(mod_vcard_ldap).
-behaviour(ejabberd_config).
--author('alexey@process-one.net').
-
-behaviour(gen_server).
+-behaviour(mod_vcard).
--behaviour(gen_mod).
-
-%% gen_server callbacks.
--export([init/1, handle_info/2, handle_call/3,
- handle_cast/2, terminate/2, code_change/3]).
+%% API
+-export([start_link/2]).
+-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4,
+ remove_user/2, import/3, search_fields/1, search_reported/1,
+ mod_opt_type/1, opt_type/1]).
+-export([is_search_supported/1]).
--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,
- mod_opt_type/1, opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
-include("eldap.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PROCNAME, ejabberd_mod_vcard_ldap).
@@ -74,59 +54,14 @@
deref_aliases = never :: never | searching | finding | always,
matches = 0 :: non_neg_integer()}).
--define(VCARD_MAP,
- [{<<"NICKNAME">>, <<"%u">>, []},
- {<<"FN">>, <<"%s">>, [<<"displayName">>]},
- {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
- {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
- {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
- {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
- {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
- {<<"CTRY">>, <<"%s">>, [<<"c">>]},
- {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
- {<<"STREET">>, <<"%s">>, [<<"street">>]},
- {<<"REGION">>, <<"%s">>, [<<"st">>]},
- {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
- {<<"TITLE">>, <<"%s">>, [<<"title">>]},
- {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
- {<<"DESC">>, <<"%s">>, [<<"description">>]},
- {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
- {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
- {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
- {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
- {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
-
--define(SEARCH_FIELDS,
- [{<<"User">>, <<"%u">>},
- {<<"Full Name">>, <<"displayName">>},
- {<<"Given Name">>, <<"givenName">>},
- {<<"Middle Name">>, <<"initials">>},
- {<<"Family Name">>, <<"sn">>},
- {<<"Nickname">>, <<"%u">>},
- {<<"Birthday">>, <<"birthDay">>},
- {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
- {<<"Email">>, <<"mail">>},
- {<<"Organization Name">>, <<"o">>},
- {<<"Organization Unit">>, <<"ou">>}]).
-
--define(SEARCH_REPORTED,
- [{<<"Full Name">>, <<"FN">>},
- {<<"Given Name">>, <<"FIRST">>},
- {<<"Middle Name">>, <<"MIDDLE">>},
- {<<"Family Name">>, <<"LAST">>},
- {<<"Nickname">>, <<"NICK">>},
- {<<"Birthday">>, <<"BDAY">>},
- {<<"Country">>, <<"CTRY">>},
- {<<"City">>, <<"LOCALITY">>},
- {<<"Email">>, <<"EMAIL">>},
- {<<"Organization Name">>, <<"ORGNAME">>},
- {<<"Organization Unit">>, <<"ORGUNIT">>}]).
-
-handle_cast(_Request, State) -> {noreply, State}.
-
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-start(Host, Opts) ->
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+init(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 1000, worker, [?MODULE]},
@@ -138,141 +73,131 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-depends(_Host, _Opts) ->
- [].
-
-terminate(_Reason, State) ->
- Host = State#state.serverhost,
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
- ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
- ?NS_VCARD),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
- get_sm_features, 50),
- case State#state.search of
- true ->
- ejabberd_router:unregister_route(State#state.myhost);
- _ -> ok
+is_search_supported(_LServer) ->
+ true.
+
+get_vcard(LUser, LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ VCardMap = State#state.vcard_map,
+ case find_ldap_user(LUser, State) of
+ #eldap_entry{attributes = Attributes} ->
+ VCard = ldap_attributes_to_vcard(Attributes, VCardMap,
+ {LUser, LServer}),
+ [xmpp:encode(VCard)];
+ _ ->
+ []
end.
-start_link(Host, Opts) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
+set_vcard(_LUser, _LServer, _VCard, _VCardSearch) ->
+ {atomic, not_implemented}.
+
+search_fields(LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ State#state.search_fields.
+search_reported(LServer) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ State#state.search_reported.
+
+search(LServer, Data, _AllowReturnAll, MaxMatch) ->
+ {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME),
+ Base = State#state.base,
+ SearchFilter = State#state.search_filter,
+ Eldap_ID = State#state.eldap_id,
+ UIDs = State#state.uids,
+ ReportedAttrs = State#state.search_reported_attrs,
+ Filter = eldap:'and'([SearchFilter,
+ eldap_utils:make_filter(Data, UIDs)]),
+ case eldap_pool:search(Eldap_ID,
+ [{base, Base}, {filter, Filter}, {limit, MaxMatch},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ReportedAttrs}])
+ of
+ #eldap_search_result{entries = E} ->
+ search_items(E, State);
+ _ ->
+ []
+ end.
+
+search_items(Entries, State) ->
+ LServer = State#state.serverhost,
+ SearchReported = State#state.search_reported,
+ VCardMap = State#state.vcard_map,
+ UIDs = State#state.uids,
+ Attributes = lists:map(fun (E) ->
+ #eldap_entry{attributes = Attrs} = E, Attrs
+ end,
+ Entries),
+ lists:flatmap(
+ fun(Attrs) ->
+ case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
+ {U, UIDAttrFormat} ->
+ case eldap_utils:get_user_part(U, UIDAttrFormat) of
+ {ok, Username} ->
+ case ejabberd_auth:is_user_exists(Username,
+ LServer) of
+ true ->
+ RFields = lists:map(
+ fun({_, VCardName}) ->
+ {VCardName,
+ map_vcard_attr(VCardName,
+ Attrs,
+ VCardMap,
+ {Username,
+ ?MYNAME})}
+ end,
+ SearchReported),
+ J = <<Username/binary, $@, LServer/binary>>,
+ [{<<"jid">>, J} | RFields];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end;
+ <<"">> ->
+ []
+ end
+ end, Attributes).
+
+remove_user(_User, _Server) ->
+ {atomic, not_implemented}.
+
+import(_, _, _) ->
+ pass.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
init([Host, Opts]) ->
State = parse_options(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_VCARD, ?MODULE, process_local_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
- get_sm_features, 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),
- case State#state.search of
- true ->
- ejabberd_router:register_route(State#state.myhost, Host);
- _ -> ok
- end,
{ok, State}.
-handle_info({route, From, To, Packet}, State) ->
- case catch do_route(State, From, To, Packet) of
- Pid when is_pid(Pid) -> ok;
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err)
- end,
- {noreply, State};
-handle_info(_Info, State) -> {noreply, State}.
-
-get_sm_features({error, _Error} = Acc, _From, _To,
- _Node, _Lang) ->
- Acc;
-get_sm_features(Acc, _From, _To, Node, _Lang) ->
- case Node of
- <<"">> ->
- case Acc of
- {result, Features} -> {result, [?NS_VCARD | Features]};
- empty -> {result, [?NS_VCARD]}
- end;
- _ -> Acc
- end.
+handle_call(get_state, _From, State) ->
+ {reply, {ok, State}, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
-process_local_iq(_From, _To,
- #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children =
- [#xmlel{name = <<"FN">>, attrs = [],
- children =
- [{xmlcdata, <<"ejabberd">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Erlang Jabber Server">>))/binary,
- "\nCopyright (c) 2002-2016 ProcessOne">>}]},
- #xmlel{name = <<"BDAY">>, attrs = [],
- children =
- [{xmlcdata, <<"2002-11-16">>}]}]}]}
- end.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
-process_sm_iq(_From, #jid{lserver = LServer} = To,
- #iq{sub_el = SubEl} = IQ) ->
- case catch process_vcard_ldap(To, IQ, LServer) of
- {'EXIT', _} ->
- IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
- Other -> Other
- end.
+handle_info(_Info, State) ->
+ {noreply, State}.
-process_vcard_ldap(To, IQ, Server) ->
- {ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
- #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ,
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- #jid{luser = LUser} = To,
- LServer = State#state.serverhost,
- case ejabberd_auth:is_user_exists(LUser, LServer) of
- true ->
- VCardMap = State#state.vcard_map,
- case find_ldap_user(LUser, State) of
- #eldap_entry{attributes = Attributes} ->
- Vcard = ldap_attributes_to_vcard(Attributes, VCardMap,
- {LUser, LServer}),
- IQ#iq{type = result, sub_el = Vcard};
- _ -> IQ#iq{type = result, sub_el = []}
- end;
- _ -> IQ#iq{type = result, sub_el = []}
- end
- end.
+terminate(_Reason, _State) ->
+ ok.
-handle_call(get_state, _From, State) ->
- {reply, {ok, State}, State};
-handle_call(stop, _From, State) ->
- {stop, normal, ok, State};
-handle_call(_Request, _From, State) ->
- {reply, bad_request, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
find_ldap_user(User, State) ->
Base = State#state.base,
RFC2254_Filter = State#state.user_filter,
@@ -300,412 +225,48 @@ ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
UD)}
end,
VCardMap),
- Elts = [ldap_attribute_to_vcard(vCard, Attr)
- || Attr <- Attrs],
- NElts = [ldap_attribute_to_vcard(vCardN, Attr)
- || Attr <- Attrs],
- OElts = [ldap_attribute_to_vcard(vCardO, Attr)
- || Attr <- Attrs],
- AElts = [ldap_attribute_to_vcard(vCardA, Attr)
- || Attr <- Attrs],
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children =
- lists:append([X || X <- Elts, X /= none],
- [#xmlel{name = <<"N">>, attrs = [],
- children = [X || X <- NElts, X /= none]},
- #xmlel{name = <<"ORG">>, attrs = [],
- children = [X || X <- OElts, X /= none]},
- #xmlel{name = <<"ADR">>, attrs = [],
- children =
- [X || X <- AElts, X /= none]}])}].
-
-ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
- #xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard,
- {<<"nickname">>, Value}) ->
- #xmlel{name = <<"NICKNAME">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
- #xmlel{name = <<"TITLE">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
- #xmlel{name = <<"BDAY">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
- #xmlel{name = <<"DESC">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
- #xmlel{name = <<"ROLE">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
- #xmlel{name = <<"TEL">>, attrs = [],
- children =
- [#xmlel{name = <<"VOICE">>, attrs = [], children = []},
- #xmlel{name = <<"WORK">>, attrs = [], children = []},
- #xmlel{name = <<"NUMBER">>, attrs = [],
- children = [{xmlcdata, Value}]}]};
-ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) ->
- #xmlel{name = <<"EMAIL">>, attrs = [],
- children =
- [#xmlel{name = <<"INTERNET">>, attrs = [],
- children = []},
- #xmlel{name = <<"PREF">>, attrs = [], children = []},
- #xmlel{name = <<"USERID">>, attrs = [],
- children = [{xmlcdata, Value}]}]};
-ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) ->
- #xmlel{name = <<"PHOTO">>, attrs = [],
- children =
- [#xmlel{name = <<"TYPE">>, attrs = [],
- children = [{xmlcdata, <<"image/jpeg">>}]},
- #xmlel{name = <<"BINVAL">>, attrs = [],
- children = [{xmlcdata, jlib:encode_base64(Value)}]}]};
-ldap_attribute_to_vcard(vCardN,
- {<<"family">>, Value}) ->
- #xmlel{name = <<"FAMILY">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
- #xmlel{name = <<"GIVEN">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardN,
- {<<"middle">>, Value}) ->
- #xmlel{name = <<"MIDDLE">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardO,
- {<<"orgname">>, Value}) ->
- #xmlel{name = <<"ORGNAME">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardO,
- {<<"orgunit">>, Value}) ->
- #xmlel{name = <<"ORGUNIT">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardA,
- {<<"locality">>, Value}) ->
- #xmlel{name = <<"LOCALITY">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardA,
- {<<"street">>, Value}) ->
- #xmlel{name = <<"STREET">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
- #xmlel{name = <<"CTRY">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardA,
- {<<"region">>, Value}) ->
- #xmlel{name = <<"REGION">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
- #xmlel{name = <<"PCODE">>, attrs = [],
- children = [{xmlcdata, Value}]};
-ldap_attribute_to_vcard(_, _) -> none.
-
--define(TLFIELD(Type, Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
--define(FORM(JID, SearchFields),
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "search">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search users in ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Fill in fields to search for any matching "
- "Jabber User">>)}]}]
- ++
- lists:map(fun ({X, Y}) ->
- ?TLFIELD(<<"text-single">>, X, Y)
- end,
- SearchFields)}]).
-
-do_route(State, From, To, Packet) ->
- spawn(?MODULE, route, [State, From, To, Packet]).
-
-route(State, From, To, Packet) ->
- #jid{user = User, resource = Resource} = To,
- ServerHost = State#state.serverhost,
- if (User /= <<"">>) or (Resource /= <<"">>) ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err);
- true ->
- IQ = jlib:iq_query_info(Packet),
- case IQ of
- #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
- sub_el = SubEl} ->
- case Type of
- set ->
- XDataEl = find_xdata_el(SubEl),
- case XDataEl of
- false ->
- Txt = <<"Data form not found">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- XData = jlib:parse_xdata_submit(XDataEl),
- case XData of
- invalid ->
- Txt = <<"Incorrect data form">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- _ ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children =
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_XDATA},
- {<<"type">>,
- <<"result">>}],
- children
- =
- search_result(Lang,
- To,
- State,
- XData)}]}]},
- ejabberd_router:route(To, From,
- jlib:iq_to_xml(ResIQ))
- end
- end;
- get ->
- SearchFields = State#state.search_fields,
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_SEARCH}],
- children =
- ?FORM(To, SearchFields)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
- [],
- [ServerHost, ?MODULE,
- <<"">>, <<"">>]),
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_INFO}],
- children =
- [#xmlel{name =
- <<"identity">>,
- attrs =
- [{<<"category">>,
- <<"directory">>},
- {<<"type">>,
- <<"user">>},
- {<<"name">>,
- translate:translate(Lang,
- <<"vCard User Search">>)}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_SEARCH}],
- children = []},
- #xmlel{name =
- <<"feature">>,
- attrs =
- [{<<"var">>,
- ?NS_VCARD}],
- children = []}]
- ++ Info}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
- ejabberd_router:route(To, From, Err);
- get ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_DISCO_ITEMS}],
- children = []}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
- end;
- #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
- ResIQ = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"vCard">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD}],
- children = iq_get_vcard(Lang)}]},
- ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
- _ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(To, From, Err)
- end
- end.
-
-iq_get_vcard(Lang) ->
- [#xmlel{name = <<"FN">>, attrs = [],
- children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
- #xmlel{name = <<"URL">>, attrs = [],
- children = [{xmlcdata, ?EJABBERD_URI}]},
- #xmlel{name = <<"DESC">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"ejabberd vCard module">>))/binary,
- "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
--define(LFIELD(Label, Var),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children = []}).
-
-search_result(Lang, JID, State, Data) ->
- SearchReported = State#state.search_reported,
- Header = [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- <<(translate:translate(Lang,
- <<"Search Results for ">>))/binary,
- (jid:to_string(JID))/binary>>}]},
- #xmlel{name = <<"reported">>, attrs = [],
- children =
- [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
- <<"jid">>)]
- ++
- lists:map(fun ({Name, Value}) ->
- ?TLFIELD(<<"text-single">>, Name,
- Value)
- end,
- SearchReported)}],
- case search(State, Data) of
- error -> Header;
- Result -> Header ++ Result
- end.
-
--define(FIELD(Var, Val),
- #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
-search(State, Data) ->
- Base = State#state.base,
- SearchFilter = State#state.search_filter,
- Eldap_ID = State#state.eldap_id,
- UIDs = State#state.uids,
- Limit = State#state.matches,
- ReportedAttrs = State#state.search_reported_attrs,
- Filter = eldap:'and'([SearchFilter,
- eldap_utils:make_filter(Data, UIDs)]),
- case eldap_pool:search(Eldap_ID,
- [{base, Base}, {filter, Filter}, {limit, Limit},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ReportedAttrs}])
- of
- #eldap_search_result{entries = E} ->
- search_items(E, State);
- _ -> error
+ lists:foldl(fun ldap_attribute_to_vcard/2, #vcard_temp{}, Attrs).
+
+-spec ldap_attribute_to_vcard({binary(), binary()}, vcard_temp()) -> vcard_temp().
+ldap_attribute_to_vcard({Attr, Value}, V) ->
+ Ts = V#vcard_temp.tel,
+ Es = V#vcard_temp.email,
+ N = case V#vcard_temp.n of
+ undefined -> #vcard_name{};
+ _ -> V#vcard_temp.n
+ end,
+ O = case V#vcard_temp.org of
+ undefined -> #vcard_org{};
+ _ -> V#vcard_temp.org
+ end,
+ A = case V#vcard_temp.adr of
+ [] -> #vcard_adr{};
+ As -> hd(As)
+ end,
+ case Attr of
+ <<"fn">> -> V#vcard_temp{fn = Value};
+ <<"nickname">> -> V#vcard_temp{nickname = Value};
+ <<"title">> -> V#vcard_temp{title = Value};
+ <<"bday">> -> V#vcard_temp{bday = Value};
+ <<"url">> -> V#vcard_temp{url = Value};
+ <<"desc">> -> V#vcard_temp{desc = Value};
+ <<"role">> -> V#vcard_temp{role = Value};
+ <<"tel">> -> V#vcard_temp{tel = [#vcard_tel{number = Value}|Ts]};
+ <<"email">> -> V#vcard_temp{email = [#vcard_email{userid = Value}|Es]};
+ <<"photo">> -> V#vcard_temp{photo = #vcard_photo{binval = Value}};
+ <<"family">> -> V#vcard_temp{n = N#vcard_name{family = Value}};
+ <<"given">> -> V#vcard_temp{n = N#vcard_name{given = Value}};
+ <<"middle">> -> V#vcard_temp{n = N#vcard_name{middle = Value}};
+ <<"orgname">> -> V#vcard_temp{org = O#vcard_org{name = Value}};
+ <<"orgunit">> -> V#vcard_temp{org = O#vcard_org{units = [Value]}};
+ <<"locality">> -> V#vcard_temp{adr = [A#vcard_adr{locality = Value}]};
+ <<"street">> -> V#vcard_temp{adr = [A#vcard_adr{street = Value}]};
+ <<"ctry">> -> V#vcard_temp{adr = [A#vcard_adr{ctry = Value}]};
+ <<"region">> -> V#vcard_temp{adr = [A#vcard_adr{region = Value}]};
+ <<"pcode">> -> V#vcard_temp{adr = [A#vcard_adr{pcode = Value}]};
+ _ -> V
end.
-search_items(Entries, State) ->
- LServer = State#state.serverhost,
- SearchReported = State#state.search_reported,
- VCardMap = State#state.vcard_map,
- UIDs = State#state.uids,
- Attributes = lists:map(fun (E) ->
- #eldap_entry{attributes = Attrs} = E, Attrs
- end,
- Entries),
- lists:flatmap(fun (Attrs) ->
- case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- {U, UIDAttrFormat} ->
- case eldap_utils:get_user_part(U, UIDAttrFormat)
- of
- {ok, Username} ->
- case
- ejabberd_auth:is_user_exists(Username,
- LServer)
- of
- true ->
- RFields = lists:map(fun ({_,
- VCardName}) ->
- {VCardName,
- map_vcard_attr(VCardName,
- Attrs,
- VCardMap,
- {Username,
- ?MYNAME})}
- end,
- SearchReported),
- Result = [?FIELD(<<"jid">>,
- <<Username/binary,
- "@",
- LServer/binary>>)]
- ++
- [?FIELD(Name, Value)
- || {Name, Value}
- <- RFields],
- [#xmlel{name = <<"item">>,
- attrs = [],
- children = Result}];
- _ -> []
- end;
- _ -> []
- end;
- <<"">> -> []
- end
- end,
- Attributes).
-
-remove_user(_User) -> true.
-
-%%%-----------------------
-%%% Auxiliary functions.
-%%%-----------------------
-
map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
Res = lists:filter(fun ({Name, _, _}) ->
eldap_utils:case_insensitive_match(Name,
@@ -725,19 +286,54 @@ process_pattern(Str, {User, Domain}, AttrValues) ->
[{<<"%u">>, User}, {<<"%d">>, Domain}] ++
[{<<"%s">>, V, 1} || V <- AttrValues]).
-find_xdata_el(#xmlel{children = SubEls}) ->
- find_xdata_el1(SubEls).
-
-find_xdata_el1([]) -> false;
-find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
- children = SubEls}
- | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- #xmlel{name = Name, attrs = Attrs, children = SubEls};
- _ -> find_xdata_el1(Els)
- end;
-find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
+default_vcard_map() ->
+ [{<<"NICKNAME">>, <<"%u">>, []},
+ {<<"FN">>, <<"%s">>, [<<"displayName">>]},
+ {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
+ {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
+ {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
+ {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
+ {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
+ {<<"CTRY">>, <<"%s">>, [<<"c">>]},
+ {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
+ {<<"STREET">>, <<"%s">>, [<<"street">>]},
+ {<<"REGION">>, <<"%s">>, [<<"st">>]},
+ {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
+ {<<"TITLE">>, <<"%s">>, [<<"title">>]},
+ {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
+ {<<"DESC">>, <<"%s">>, [<<"description">>]},
+ {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
+ {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
+ {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
+ {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
+ {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
+
+default_search_fields() ->
+ [{<<"User">>, <<"%u">>},
+ {<<"Full Name">>, <<"displayName">>},
+ {<<"Given Name">>, <<"givenName">>},
+ {<<"Middle Name">>, <<"initials">>},
+ {<<"Family Name">>, <<"sn">>},
+ {<<"Nickname">>, <<"%u">>},
+ {<<"Birthday">>, <<"birthDay">>},
+ {<<"Country">>, <<"c">>},
+ {<<"City">>, <<"l">>},
+ {<<"Email">>, <<"mail">>},
+ {<<"Organization Name">>, <<"o">>},
+ {<<"Organization Unit">>, <<"ou">>}].
+
+default_search_reported() ->
+ [{<<"Full Name">>, <<"FN">>},
+ {<<"Given Name">>, <<"FIRST">>},
+ {<<"Middle Name">>, <<"MIDDLE">>},
+ {<<"Family Name">>, <<"LAST">>},
+ {<<"Nickname">>, <<"NICK">>},
+ {<<"Birthday">>, <<"BDAY">>},
+ {<<"Country">>, <<"CTRY">>},
+ {<<"City">>, <<"LOCALITY">>},
+ {<<"Email">>, <<"EMAIL">>},
+ {<<"Organization Name">>, <<"ORGNAME">>},
+ {<<"Organization Unit">>, <<"ORGUNIT">>}].
parse_options(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
@@ -784,19 +380,19 @@ parse_options(Host, Opts) ->
[iolist_to_binary(E)
|| E <- L]}
end, Ls)
- end, ?VCARD_MAP),
+ end, default_vcard_map()),
SearchFields = gen_mod:get_opt(ldap_search_fields, Opts,
fun(Ls) ->
[{iolist_to_binary(S),
iolist_to_binary(P)}
|| {S, P} <- Ls]
- end, ?SEARCH_FIELDS),
+ end, default_search_fields()),
SearchReported = gen_mod:get_opt(ldap_search_reported, Opts,
fun(Ls) ->
[{iolist_to_binary(S),
iolist_to_binary(P)}
|| {S, P} <- Ls]
- end, ?SEARCH_REPORTED),
+ end, default_search_reported()),
UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
VCardMapAttrs = lists:usort(lists:append([A
|| {_, _, A} <- VCardMap])
@@ -834,27 +430,11 @@ parse_options(Host, Opts) ->
search_reported_attrs = SearchReportedAttrs,
matches = Matches}.
-transform_module_options(Opts) ->
- lists:map(
- fun({ldap_vcard_map, Map}) ->
- NewMap = lists:map(
- fun({Field, Pattern, Attrs}) ->
- {Field, [{Pattern, Attrs}]};
- (Opt) ->
- Opt
- end, Map),
- {ldap_vcard_map, NewMap};
- (Opt) ->
- Opt
- end, Opts).
-
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) ->
@@ -882,12 +462,6 @@ mod_opt_type(ldap_vcard_map) ->
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;
@@ -926,9 +500,9 @@ mod_opt_type(ldap_tls_verify) ->
(false) -> false
end;
mod_opt_type(_) ->
- [host, iqdisc, ldap_filter, ldap_search_fields,
+ [ldap_filter, ldap_search_fields,
ldap_search_reported, ldap_uids, ldap_vcard_map,
- matches, search, deref_aliases, ldap_backups, ldap_base,
+ 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,
diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl
index 781a135c8..40ea36381 100644
--- a/src/mod_vcard_mnesia.erl
+++ b/src/mod_vcard_mnesia.erl
@@ -11,10 +11,12 @@
-behaviour(mod_vcard).
%% API
--export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]).
+-export([init/2, stop/1, import/3, get_vcard/2, set_vcard/4, search/4,
+ search_fields/1, search_reported/1, remove_user/2]).
+-export([is_search_supported/1]).
-include("ejabberd.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_vcard.hrl").
-include("logger.hrl").
@@ -22,10 +24,10 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(vcard,
+ ejabberd_mnesia:create(?MODULE, vcard,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, vcard)}]),
- mnesia:create_table(vcard_search,
+ ejabberd_mnesia:create(?MODULE, vcard_search,
[{disc_copies, [node()]},
{attributes,
record_info(fields, vcard_search)}]),
@@ -43,6 +45,12 @@ init(_Host, _Opts) ->
mnesia:add_table_index(vcard_search, lorgname),
mnesia:add_table_index(vcard_search, lorgunit).
+stop(_Host) ->
+ ok.
+
+is_search_supported(_ServerHost) ->
+ true.
+
get_vcard(LUser, LServer) ->
US = {LUser, LServer},
F = fun () -> mnesia:read({vcard, US}) end,
@@ -71,15 +79,44 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]), [];
Rs ->
+ Fields = lists:map(fun record_to_item/1, Rs),
case MaxMatch of
infinity ->
- Rs;
+ Fields;
Val ->
- lists:sublist(Rs, Val)
+ lists:sublist(Fields, Val)
end
end
end.
+search_fields(_LServer) ->
+ [{<<"User">>, <<"user">>},
+ {<<"Full Name">>, <<"fn">>},
+ {<<"Name">>, <<"first">>},
+ {<<"Middle Name">>, <<"middle">>},
+ {<<"Family Name">>, <<"last">>},
+ {<<"Nickname">>, <<"nick">>},
+ {<<"Birthday">>, <<"bday">>},
+ {<<"Country">>, <<"ctry">>},
+ {<<"City">>, <<"locality">>},
+ {<<"Email">>, <<"email">>},
+ {<<"Organization Name">>, <<"orgname">>},
+ {<<"Organization Unit">>, <<"orgunit">>}].
+
+search_reported(_LServer) ->
+ [{<<"Jabber ID">>, <<"jid">>},
+ {<<"Full Name">>, <<"fn">>},
+ {<<"Name">>, <<"first">>},
+ {<<"Middle Name">>, <<"middle">>},
+ {<<"Family Name">>, <<"last">>},
+ {<<"Nickname">>, <<"nick">>},
+ {<<"Birthday">>, <<"bday">>},
+ {<<"Country">>, <<"ctry">>},
+ {<<"City">>, <<"locality">>},
+ {<<"Email">>, <<"email">>},
+ {<<"Organization Name">>, <<"orgname">>},
+ {<<"Organization Unit">>, <<"orgunit">>}].
+
remove_user(LUser, LServer) ->
US = {LUser, LServer},
F = fun () ->
@@ -88,10 +125,29 @@ remove_user(LUser, LServer) ->
end,
mnesia:transaction(F).
-import(_LServer, #vcard{} = VCard) ->
+import(LServer, <<"vcard">>, [LUser, XML, _TimeStamp]) ->
+ #xmlel{} = El = fxml_stream:parse_element(XML),
+ VCard = #vcard{us = {LUser, LServer}, vcard = El},
mnesia:dirty_write(VCard);
-import(_LServer, #vcard_search{} = S) ->
- mnesia:dirty_write(S).
+import(LServer, <<"vcard_search">>,
+ [User, LUser, FN, LFN,
+ Family, LFamily, Given, LGiven,
+ Middle, LMiddle, Nickname, LNickname,
+ BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
+ EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
+ mnesia:dirty_write(
+ #vcard_search{us = {LUser, LServer},
+ user = {User, LServer}, luser = LUser,
+ fn = FN, lfn = LFN, family = Family,
+ lfamily = LFamily, given = Given,
+ lgiven = LGiven, middle = Middle,
+ lmiddle = LMiddle, nickname = Nickname,
+ lnickname = LNickname, bday = BDay,
+ lbday = LBDay, ctry = CTRY, lctry = LCTRY,
+ locality = Locality, llocality = LLocality,
+ email = EMail, lemail = LEMail,
+ orgname = OrgName, lorgname = LOrgName,
+ orgunit = OrgUnit, lorgunit = LOrgUnit}).
%%%===================================================================
%%% Internal functions
@@ -211,3 +267,19 @@ parts_to_string(Parts) ->
str:strip(list_to_binary(
lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
right, $.).
+
+-spec record_to_item(#vcard_search{}) -> [{binary(), binary()}].
+record_to_item(R) ->
+ {User, Server} = R#vcard_search.user,
+ [{<<"jid">>, <<User/binary, "@", Server/binary>>},
+ {<<"fn">>, (R#vcard_search.fn)},
+ {<<"last">>, (R#vcard_search.family)},
+ {<<"first">>, (R#vcard_search.given)},
+ {<<"middle">>, (R#vcard_search.middle)},
+ {<<"nick">>, (R#vcard_search.nickname)},
+ {<<"bday">>, (R#vcard_search.bday)},
+ {<<"ctry">>, (R#vcard_search.ctry)},
+ {<<"locality">>, (R#vcard_search.locality)},
+ {<<"email">>, (R#vcard_search.email)},
+ {<<"orgname">>, (R#vcard_search.orgname)},
+ {<<"orgunit">>, (R#vcard_search.orgunit)}].
diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl
index 386347387..411ec45fa 100644
--- a/src/mod_vcard_riak.erl
+++ b/src/mod_vcard_riak.erl
@@ -12,9 +12,10 @@
%% API
-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
- import/2]).
+ search_fields/1, search_reported/1, import/3, stop/1]).
+-export([is_search_supported/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_vcard.hrl").
%%%===================================================================
@@ -23,6 +24,12 @@
init(_Host, _Opts) ->
ok.
+stop(_Host) ->
+ ok.
+
+is_search_supported(_LServer) ->
+ false.
+
get_vcard(LUser, LServer) ->
case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
{ok, R} ->
@@ -89,10 +96,18 @@ set_vcard(LUser, LServer, VCARD,
search(_LServer, _Data, _AllowReturnAll, _MaxMatch) ->
[].
+search_fields(_LServer) ->
+ [].
+
+search_reported(_LServer) ->
+ [].
+
remove_user(LUser, LServer) ->
{atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
-import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) ->
+import(LServer, <<"vcard">>, [LUser, XML, _TimeStamp]) ->
+ El = fxml_stream:parse_element(XML),
+ VCard = #vcard{us = {LUser, LServer}, vcard = El},
#vcard_search{fn = FN,
lfn = LFN,
family = Family,
@@ -141,7 +156,7 @@ import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) ->
{<<"lorgname">>, LOrgName},
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
-import(_LServer, #vcard_search{}) ->
+import(_LServer, <<"vcard_search">>, _) ->
ok.
%%%===================================================================
diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl
index b8234bf9c..7fd64c44e 100644
--- a/src/mod_vcard_sql.erl
+++ b/src/mod_vcard_sql.erl
@@ -13,10 +13,11 @@
-behaviour(mod_vcard).
%% API
--export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
- import/1, import/2, export/1]).
+-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+ search_fields/1, search_reported/1, import/3, export/1]).
+-export([is_search_supported/1]).
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_vcard.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -27,6 +28,12 @@
init(_Host, _Opts) ->
ok.
+stop(_Host) ->
+ ok.
+
+is_search_supported(_LServer) ->
+ true.
+
get_vcard(LUser, LServer) ->
case catch sql_queries:get_vcard(LServer, LUser) of
{selected, [{SVCARD}]} ->
@@ -79,7 +86,7 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
infinity ->
<<"">>;
Val ->
- [<<" LIMIT ">>, jlib:integer_to_binary(Val)]
+ [<<" LIMIT ">>, integer_to_binary(Val)]
end,
case catch ejabberd_sql:sql_query(
LServer,
@@ -93,12 +100,40 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
<<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
<<"locality">>, <<"email">>, <<"orgname">>,
<<"orgunit">>], Rs} when is_list(Rs) ->
- Rs;
+ [row_to_item(LServer, R) || R <- Rs];
Error ->
?ERROR_MSG("~p", [Error]), []
end
end.
+search_fields(_LServer) ->
+ [{<<"User">>, <<"user">>},
+ {<<"Full Name">>, <<"fn">>},
+ {<<"Name">>, <<"first">>},
+ {<<"Middle Name">>, <<"middle">>},
+ {<<"Family Name">>, <<"last">>},
+ {<<"Nickname">>, <<"nick">>},
+ {<<"Birthday">>, <<"bday">>},
+ {<<"Country">>, <<"ctry">>},
+ {<<"City">>, <<"locality">>},
+ {<<"Email">>, <<"email">>},
+ {<<"Organization Name">>, <<"orgname">>},
+ {<<"Organization Unit">>, <<"orgunit">>}].
+
+search_reported(_LServer) ->
+ [{<<"Jabber ID">>, <<"jid">>},
+ {<<"Full Name">>, <<"fn">>},
+ {<<"Name">>, <<"first">>},
+ {<<"Middle Name">>, <<"middle">>},
+ {<<"Family Name">>, <<"last">>},
+ {<<"Nickname">>, <<"nick">>},
+ {<<"Birthday">>, <<"bday">>},
+ {<<"Country">>, <<"ctry">>},
+ {<<"City">>, <<"locality">>},
+ {<<"Email">>, <<"email">>},
+ {<<"Organization Name">>, <<"orgname">>},
+ {<<"Organization Unit">>, <<"orgunit">>}].
+
remove_user(LUser, LServer) ->
ejabberd_sql:sql_transaction(
LServer,
@@ -157,37 +192,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, vcard from vcard;">>,
- fun([LUser, SVCard]) ->
- #xmlel{} = VCARD = fxml_stream:parse_element(SVCard),
- #vcard{us = {LUser, LServer}, vcard = VCARD}
- end},
- {<<"select username, lusername, fn, lfn, family, lfamily, "
- "given, lgiven, middle, lmiddle, nickname, lnickname, "
- "bday, lbday, ctry, lctry, locality, llocality, email, "
- "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>,
- fun([User, LUser, FN, LFN,
- Family, LFamily, Given, LGiven,
- Middle, LMiddle, Nickname, LNickname,
- BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
- EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
- #vcard_search{us = {LUser, LServer},
- user = {User, LServer}, luser = LUser,
- fn = FN, lfn = LFN, family = Family,
- lfamily = LFamily, given = Given,
- lgiven = LGiven, middle = Middle,
- lmiddle = LMiddle, nickname = Nickname,
- lnickname = LNickname, bday = BDay,
- lbday = LBDay, ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit}
- end}].
-
-import(_, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
@@ -240,3 +246,18 @@ make_val(Match, Field, Val) ->
<<"">> -> Condition;
_ -> [Match, <<" and ">>, Condition]
end.
+
+row_to_item(LServer, [Username, FN, Family, Given, Middle, Nickname, BDay,
+ CTRY, Locality, EMail, OrgName, OrgUnit]) ->
+ [{<<"jid">>, <<Username/binary, $@, LServer/binary>>},
+ {<<"fn">>, FN},
+ {<<"last">>, Family},
+ {<<"first">>, Given},
+ {<<"middle">>, Middle},
+ {<<"nick">>, Nickname},
+ {<<"bday">>, BDay},
+ {<<"ctry">>, CTRY},
+ {<<"locality">>, Locality},
+ {<<"email">>, EMail},
+ {<<"orgname">>, OrgName},
+ {<<"orgunit">>, OrgUnit}].
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index f2101df91..4d1dfa2fc 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -13,15 +13,15 @@
-export([start/2, stop/1]).
-export([update_presence/3, vcard_set/3, export/1,
- import/1, import/3, mod_opt_type/1, depends/2]).
+ import_info/0, import/5, import_start/2,
+ mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("mod_vcard_xupdate.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #vcard_xupdate{}) -> ok | pass.
+-callback import(binary(), binary(), [binary()]) -> ok.
-callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}.
-callback get_xupdate(binary(), binary()) -> binary() | undefined.
-callback remove_xupdate(binary(), binary()) -> {atomic, any()}.
@@ -52,15 +52,12 @@ depends(_Host, _Opts) ->
%%====================================================================
%% Hooks
%%====================================================================
-
-update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
- User, Host) ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<>> -> presence_with_xupdate(Packet, User, Host);
- _ -> Packet
- end;
+-spec update_presence(presence(), binary(), binary()) -> presence().
+update_presence(#presence{type = available} = Packet, User, Host) ->
+ presence_with_xupdate(Packet, User, Host);
update_presence(Packet, _User, _Host) -> Packet.
+-spec vcard_set(binary(), binary(), xmlel()) -> ok.
vcard_set(LUser, LServer, VCARD) ->
US = {LUser, LServer},
case fxml:get_path_s(VCARD,
@@ -93,48 +90,25 @@ remove_xupdate(LUser, LServer) ->
%%% Presence stanza rebuilding
%%%----------------------------------------------------------------------
-presence_with_xupdate(#xmlel{name = <<"presence">>,
- attrs = Attrs, children = Els},
- User, Host) ->
- XPhotoEl = build_xphotoel(User, Host),
- Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = Els2}.
-
-presence_with_xupdate2([], Els2, XPhotoEl) ->
- lists:reverse([XPhotoEl | Els2]);
-%% This clause assumes that the x element contains only the XMLNS attribute:
-presence_with_xupdate2([#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}]}
- | Els],
- Els2, XPhotoEl) ->
- presence_with_xupdate2(Els, Els2, XPhotoEl);
-presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
- presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
-
-build_xphotoel(User, Host) ->
+presence_with_xupdate(Presence, User, Host) ->
Hash = get_xupdate(User, Host),
- PhotoSubEls = case Hash of
- Hash when is_binary(Hash) -> [{xmlcdata, Hash}];
- _ -> []
- end,
- PhotoEl = [#xmlel{name = <<"photo">>, attrs = [],
- children = PhotoSubEls}],
- #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
- children = PhotoEl}.
+ Presence1 = xmpp:remove_subtag(Presence, #vcard_xupdate{}),
+ xmpp:set_subtag(Presence1, #vcard_xupdate{hash = Hash}).
-export(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:export(LServer).
+import_info() ->
+ [{<<"vcard_xupdate">>, 3}].
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, LA) ->
+import(LServer, {sql, _}, DBType, Tab, [LUser, Hash, TimeStamp]) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, LA).
+ Mod:import(LServer, Tab, [LUser, Hash, TimeStamp]).
+
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl
index f1b1693e4..454d97e25 100644
--- a/src/mod_vcard_xupdate_mnesia.erl
+++ b/src/mod_vcard_xupdate_mnesia.erl
@@ -10,7 +10,7 @@
-behaviour(mod_vcard_xupdate).
%% API
--export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
-include("mod_vcard_xupdate.hrl").
-include("logger.hrl").
@@ -19,7 +19,7 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- mnesia:create_table(vcard_xupdate,
+ ejabberd_mnesia:create(?MODULE, vcard_xupdate,
[{disc_copies, [node()]},
{attributes,
record_info(fields, vcard_xupdate)}]),
@@ -45,8 +45,9 @@ remove_xupdate(LUser, LServer) ->
end,
mnesia:transaction(F).
-import(_LServer, #vcard_xupdate{} = R) ->
- mnesia:dirty_write(R).
+import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) ->
+ mnesia:dirty_write(
+ #vcard_xupdate{us = {LUser, LServer}, hash = Hash}).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl
index 242485bf2..cff77f887 100644
--- a/src/mod_vcard_xupdate_riak.erl
+++ b/src/mod_vcard_xupdate_riak.erl
@@ -11,7 +11,7 @@
-behaviour(mod_vcard_xupdate).
%% API
--export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
-include("mod_vcard_xupdate.hrl").
@@ -36,8 +36,10 @@ get_xupdate(LUser, LServer) ->
remove_xupdate(LUser, LServer) ->
{atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}.
-import(_LServer, #vcard_xupdate{} = R) ->
- ejabberd_riak:put(R, vcard_xupdate_schema()).
+import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) ->
+ ejabberd_riak:put(
+ #vcard_xupdate{us = {LUser, LServer}, hash = Hash},
+ vcard_xupdate_schema()).
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl
index 938114b8f..fd2716c33 100644
--- a/src/mod_vcard_xupdate_sql.erl
+++ b/src/mod_vcard_xupdate_sql.erl
@@ -13,8 +13,8 @@
-behaviour(mod_vcard_xupdate).
%% API
--export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
- import/1, export/1]).
+-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
+ export/1]).
-include("mod_vcard_xupdate.hrl").
-include("ejabberd_sql_pt.hrl").
@@ -62,14 +62,8 @@ export(_Server) ->
[]
end}].
-import(LServer) ->
- [{<<"select username, hash from vcard_xupdate;">>,
- fun([LUser, Hash]) ->
- #vcard_xupdate{us = {LUser, LServer}, hash = Hash}
- end}].
-
-import(_LServer, _) ->
- pass.
+import(_, _, _) ->
+ ok.
%%%===================================================================
%%% Internal functions
diff --git a/src/mod_version.erl b/src/mod_version.erl
index 8a035763f..36bf796ee 100644
--- a/src/mod_version.erl
+++ b/src/mod_version.erl
@@ -31,13 +31,13 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process_local_iq/3,
+-export([start/2, stop/1, process_local_iq/1,
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -50,48 +50,31 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_VERSION).
-process_local_iq(_From, To,
- #iq{id = _ID, type = Type, xmlns = _XMLNS,
- sub_el = SubEl, lang = Lang} =
- IQ) ->
- case Type of
- set ->
- Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
- get ->
- Host = To#jid.lserver,
- OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
- fun(B) when is_boolean(B) -> B end,
- true)
- of
- true -> [get_os()];
- false -> []
- end,
- IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_VERSION}],
- children =
- [#xmlel{name = <<"name">>, attrs = [],
- children =
- [{xmlcdata, <<"ejabberd">>}]},
- #xmlel{name = <<"version">>, attrs = [],
- children = [{xmlcdata, ?VERSION}]}]
- ++ OS}]}
- end.
+process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_local_iq(#iq{type = get, to = To} = IQ) ->
+ Host = To#jid.lserver,
+ OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
+ fun(B) when is_boolean(B) -> B end,
+ true) of
+ true -> get_os();
+ false -> undefined
+ end,
+ xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>,
+ ver = ?VERSION,
+ os = OS}).
get_os() ->
{Osfamily, Osname} = os:type(),
OSType = list_to_binary([atom_to_list(Osfamily), $/, atom_to_list(Osname)]),
OSVersion = case os:version() of
{Major, Minor, Release} ->
- iolist_to_binary(io_lib:format("~w.~w.~w",
+ (str:format("~w.~w.~w",
[Major, Minor, Release]));
VersionString -> VersionString
end,
- OS = <<OSType/binary, " ", OSVersion/binary>>,
- #xmlel{name = <<"os">>, attrs = [],
- children = [{xmlcdata, OS}]}.
+ <<OSType/binary, " ", OSVersion/binary>>.
depends(_Host, _Opts) ->
[].
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index bdaa4a89a..fb2fd1f2e 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/node_club.erl b/src/node_club.erl
index 7f6ae6520..837fa6fbb 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/node_dag.erl b/src/node_dag.erl
index afb610ca7..45f8ade63 100644
--- a/src/node_dag.erl
+++ b/src/node_dag.erl
@@ -28,7 +28,7 @@
-author('bjc@kublai.com').
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
@@ -78,8 +78,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
case find_opt(node_type, Options) of
collection ->
Txt = <<"Publishing items to collection node is not allowed">>,
- {error,
- ?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_allowed(Txt, ?MYLANG),
+ mod_pubsub:err_unsupported('publish'))};
_ ->
node_hometree:publish_item(Nidx, Publisher, Model,
MaxItems, ItemId, Payload, PubOpts)
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index b3af69cd1..0a72b18d5 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -34,7 +34,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
@@ -94,10 +94,12 @@ delete_node(Nodes) ->
subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSubscription,
_RosterGroup, _Options) ->
- {error, ?ERR_FORBIDDEN}.
+ {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(),
+ mod_pubsub:err_unsupported('subscribe'))}.
unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) ->
- {error, ?ERR_FORBIDDEN}.
+ {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(),
+ mod_pubsub:err_unsupported('subscribe'))}.
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
PubOpts) ->
@@ -118,10 +120,12 @@ remove_extra_items(_Nidx, _MaxItems, ItemIds) ->
{result, {ItemIds, []}}.
delete_item(_Nidx, _Publisher, _PublishModel, _ItemId) ->
- {error, ?ERR_ITEM_NOT_FOUND}.
+ {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(),
+ mod_pubsub:err_unsupported('delete-items'))}.
purge_node(_Nidx, _Owner) ->
- {error, ?ERR_FORBIDDEN}.
+ {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(),
+ mod_pubsub:err_unsupported('purge-nodes'))}.
get_entity_affiliations(_Host, _Owner) ->
{result, []}.
diff --git a/src/node_flat.erl b/src/node_flat.erl
index 7ead1d351..55093b0e7 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -34,7 +34,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
@@ -50,12 +50,12 @@
path_to_node/1, can_fetch_item/2, is_subscribed/1]).
init(_Host, _ServerHost, _Opts) ->
- %pubsub_subscription:init(),
- mnesia:create_table(pubsub_state,
+ %pubsub_subscription:init(Host, ServerHost, Opts),
+ ejabberd_mnesia:create(?MODULE, pubsub_state,
[{disc_copies, [node()]},
{type, ordered_set},
{attributes, record_info(fields, pubsub_state)}]),
- mnesia:create_table(pubsub_item,
+ ejabberd_mnesia:create(?MODULE, pubsub_item,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_item)}]),
ItemsFields = record_info(fields, pubsub_item),
@@ -84,6 +84,7 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
+ {title, <<>>},
{presence_based_delivery, false},
{itemreply, none}].
@@ -107,8 +108,8 @@ features() ->
<<"retrieve-items">>,
<<"retrieve-subscriptions">>,
<<"subscribe">>,
+ %%<<"subscription-options">>,
<<"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
@@ -196,27 +197,27 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
Owner = Affiliation == owner,
if not Authorized ->
{error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_invalid_jid())};
(Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
PendingSubscription ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_pending_subscription())};
(AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and (not RosterGroup) and (not Owner) ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
%%ForbiddenAnonymous ->
%% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
+ %% {error, xmpp:err_forbidden()};
true ->
%%SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
{NewSub, SubId} = case Subscriptions of
@@ -265,17 +266,17 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
if
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%% Entity did not specify SubId
%%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% {error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")};
%% Invalid subscription identifier
%%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% {error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
{error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())};
%% Subid supplied, so use that.
SubIdExists ->
Sub = first_in_list(fun
@@ -289,7 +290,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
{result, default};
false ->
{error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())}
end;
%% Asking to remove all subscriptions to the given node
SubId == all ->
@@ -302,7 +303,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
%% No subid and more than one possible subscription match.
true ->
{error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())}
end.
delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
@@ -366,7 +367,7 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
Now = p1_time_compat:timestamp(),
@@ -425,7 +426,7 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
_ -> false
end),
if not Allowed ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
true ->
case lists:member(ItemId, Items) of
true ->
@@ -450,9 +451,9 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
(_, Res) ->
Res
end,
- {error, ?ERR_ITEM_NOT_FOUND}, States);
+ {error, xmpp:err_item_not_found()}, States);
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, xmpp:err_forbidden()}
end
end
end.
@@ -474,7 +475,7 @@ purge_node(Nidx, Owner) ->
States),
{result, {default, broadcast}};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, xmpp:err_forbidden()}
end.
%% @doc <p>Return the current affiliations for the given user</p>
@@ -581,7 +582,7 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) ->
case Subscription of
none ->
{error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_not_subscribed())};
_ ->
new_subscription(Nidx, Owner, Subscription, SubState)
end;
@@ -592,7 +593,7 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) ->
end;
{<<>>, [_ | _]} ->
{error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())};
_ ->
case Subscription of
none -> unsub_with_subid(Nidx, SubId, SubState);
@@ -707,7 +708,7 @@ del_state(Nidx, Key) ->
%% 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}}.
+ {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), undefined}}.
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
SubKey = jid:tolower(JID),
@@ -721,23 +722,23 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
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")};
+ %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
(AccessModel == presence) and not PresenceSubscription ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and not RosterGroup ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and not Whitelisted ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())};
(AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@@ -750,7 +751,7 @@ 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}
+ _ -> {error, xmpp:err_item_not_found()}
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
@@ -762,23 +763,23 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
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")};
+ %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
(Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
(AccessModel == presence) and not PresenceSubscription ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and not RosterGroup ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and not Whitelisted ->
{error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())};
(AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl
index 61156ee06..7e5ce788f 100644
--- a/src/node_flat_sql.erl
+++ b/src/node_flat_sql.erl
@@ -36,7 +36,7 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_sql_pt.hrl").
-export([init/3, terminate/2, options/0, features/0,
@@ -61,7 +61,7 @@
encode_host_like/1]).
init(_Host, _ServerHost, _Opts) ->
- %%pubsub_subscription_sql:init(),
+ %%pubsub_subscription_sql:init(Host, ServerHost, Opts),
ok.
terminate(_Host, _ServerHost) ->
@@ -81,27 +81,27 @@ create_node(Nidx, Owner) ->
J = encode_jid(OwnerKey),
A = encode_affiliation(owner),
S = encode_subscriptions([]),
- catch ejabberd_sql:sql_query_t(
- ?SQL("insert into pubsub_state("
- "nodeid, jid, affiliation, subscriptions) "
- "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into pubsub_state("
+ "nodeid, jid, affiliation, subscriptions) "
+ "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
{result, {default, broadcast}}.
delete_node(Nodes) ->
- Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
- Subscriptions = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(jid)s, @(subscriptions)s "
- "from pubsub_state where nodeid=%(Nidx)d"))
- of
- {selected, RItems} ->
- [{decode_jid(SJID), decode_subscriptions(Subs)} ||
- {SJID, Subs} <- RItems];
- _ ->
- []
- end,
- {PubsubNode, Subscriptions}
- end, Nodes),
+ Reply = lists:map(
+ fun(#pubsub_node{id = Nidx} = PubsubNode) ->
+ Subscriptions =
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(jid)s, @(subscriptions)s "
+ "from pubsub_state where nodeid=%(Nidx)d")) of
+ {selected, 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,
@@ -118,22 +118,25 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
Subscriptions),
Owner = Affiliation == owner,
if not Authorized ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_bad_request(), mod_pubsub:err_invalid_jid())};
(Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
PendingSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_pending_subscription())};
(AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and (not RosterGroup) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@@ -177,7 +180,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
if
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%% Entity did not specify SubId
%%SubId == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
@@ -186,8 +189,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_unexpected_request(),
+ mod_pubsub:err_not_subscribed())};
%% Subid supplied, so use that.
SubIdExists ->
Sub = first_in_list(fun
@@ -200,8 +204,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
{result, default};
false ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ {error, mod_pubsub:extended_error(
+ xmpp:err_unexpected_request(),
+ mod_pubsub:err_not_subscribed())}
end;
%% Asking to remove all subscriptions to the given node
SubId == all ->
@@ -214,8 +219,8 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
{result, default};
%% No subid and more than one possible subscription match.
true ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ {error, mod_pubsub:extended_error(
+ xmpp:err_bad_request(), mod_pubsub:err_subid_required())}
end.
delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
@@ -241,7 +246,7 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
PubId = {p1_time_compat:timestamp(), SubKey},
@@ -277,11 +282,11 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
_ -> false
end),
if not Allowed ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
true ->
case del_item(Nidx, ItemId) of
{updated, 1} -> {result, {default, broadcast}};
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ _ -> {error, xmpp:err_item_not_found()}
end
end.
@@ -299,7 +304,7 @@ purge_node(Nidx, Owner) ->
States),
{result, {default, broadcast}};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, xmpp:err_forbidden()}
end.
get_entity_affiliations(Host, Owner) ->
@@ -307,48 +312,42 @@ get_entity_affiliations(Host, Owner) ->
GenKey = jid:remove_resource(SubKey),
H = encode_host(Host),
J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s "
- "from pubsub_state i, pubsub_node n where "
- "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s"))
- of
- {selected, RItems} ->
- [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), decode_affiliation(A)}
- || {N, T, I, A} <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ {result,
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s "
+ "from pubsub_state i, pubsub_node n where "
+ "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) of
+ {selected, RItems} ->
+ [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
+ decode_affiliation(A)} || {N, T, I, A} <- RItems];
+ _ ->
+ []
+ end}.
get_node_affiliations(Nidx) ->
- Reply = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
- "where nodeid=%(Nidx)d"))
- of
- {selected, RItems} ->
- [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ {result,
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
+ "where nodeid=%(Nidx)d")) of
+ {selected, RItems} ->
+ [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
+ _ ->
+ []
+ end}.
get_affiliation(Nidx, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(affiliation)s from pubsub_state "
- "where nodeid=%(Nidx)d and jid=%(J)s"))
- of
- {selected, [{A}]} ->
- decode_affiliation(A);
- _ ->
- none
- end,
- {result, Reply}.
+ {result,
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(affiliation)s from pubsub_state "
+ "where nodeid=%(Nidx)d and jid=%(J)s")) of
+ {selected, [{A}]} ->
+ decode_affiliation(A);
+ _ ->
+ none
+ end}.
set_affiliation(Nidx, Owner, Affiliation) ->
SubKey = jid:tolower(Owner),
@@ -382,26 +381,26 @@ get_entity_subscriptions(Host, Owner) ->
"where i.nodeid = n.nodeid and"
" jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
end,
- Reply = case catch ejabberd_sql:sql_query_t(Query) of
- {selected, RItems} ->
- lists:foldl(fun ({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_sql: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}.
+ {result,
+ case ejabberd_sql:sql_query_t(Query) of
+ {selected, RItems} ->
+ lists:foldl(
+ fun({N, T, I, J, S}, Acc) ->
+ Node = nodetree_tree_sql: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}.
-spec get_entity_subscriptions_for_send_last(Host :: mod_pubsub:hostPubsub(),
Owner :: jid()) ->
@@ -435,66 +434,62 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
"and val='on_sub_and_presence' and"
" jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
end,
- Reply = case catch ejabberd_sql:sql_query_t(Query) of
- {selected, RItems} ->
- lists:foldl(fun ({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_sql: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}.
+ {result,
+ case ejabberd_sql:sql_query_t(Query) of
+ {selected, RItems} ->
+ lists:foldl(
+ fun ({N, T, I, J, S}, Acc) ->
+ Node = nodetree_tree_sql: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}.
get_node_subscriptions(Nidx) ->
- Reply = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
- "where nodeid=%(Nidx)d"))
- of
- {selected, 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}.
+ {result,
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
+ "where nodeid=%(Nidx)d")) of
+ {selected, 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}.
get_subscriptions(Nidx, Owner) ->
SubKey = jid:tolower(Owner),
J = encode_jid(SubKey),
- Reply = case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(subscriptions)s from pubsub_state"
- " where nodeid=%(Nidx)d and jid=%(J)s"))
- of
- {selected, [{S}]} ->
- decode_subscriptions(S);
- _ ->
- []
- end,
- {result, Reply}.
+ {result,
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(subscriptions)s from pubsub_state"
+ " where nodeid=%(Nidx)d and jid=%(J)s")) of
+ {selected, [{S}]} ->
+ decode_subscriptions(S);
+ _ ->
+ []
+ end}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
SubKey = jid:tolower(Owner),
@@ -503,8 +498,9 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) ->
{_, []} ->
case Subscription of
none ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_bad_request(),
+ mod_pubsub:err_not_subscribed())};
_ ->
new_subscription(Nidx, Owner, Subscription, SubState)
end;
@@ -514,8 +510,9 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) ->
_ -> replace_subscription({Subscription, SID}, SubState)
end;
{<<>>, [_ | _]} ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_bad_request(),
+ mod_pubsub:err_subid_required())};
_ ->
case Subscription of
none -> unsub_with_subid(Nidx, SubId, SubState);
@@ -585,21 +582,19 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}
end.
get_states(Nidx) ->
- case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
- "from pubsub_state where nodeid=%(Nidx)d"))
- of
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
+ "from pubsub_state where nodeid=%(Nidx)d")) of
{selected, RItems} ->
{result,
- lists:map(fun ({SJID, Aff, Subs}) ->
- JID = decode_jid(SJID),
- #pubsub_state{stateid = {JID, Nidx},
- items = itemids(Nidx, JID),
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)}
- end,
- RItems)};
+ lists:map(
+ fun({SJID, Aff, Subs}) ->
+ JID = decode_jid(SJID),
+ #pubsub_state{stateid = {JID, Nidx},
+ items = itemids(Nidx, JID),
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)}
+ end, RItems)};
_ ->
{result, []}
end.
@@ -614,16 +609,14 @@ get_state(Nidx, JID) ->
get_state_without_itemids(Nidx, JID) ->
J = encode_jid(JID),
- case catch
- ejabberd_sql:sql_query_t(
- ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
- "from pubsub_state "
- "where nodeid=%(Nidx)d and jid=%(J)s"))
- of
+ case ejabberd_sql:sql_query_t(
+ ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
+ "from pubsub_state "
+ "where nodeid=%(Nidx)d and jid=%(J)s")) of
{selected, [{SJID, Aff, Subs}]} ->
#pubsub_state{stateid = {decode_jid(SJID), Nidx},
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)};
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)};
_ ->
#pubsub_state{stateid = {JID, Nidx}}
end.
@@ -653,73 +646,60 @@ del_state(Nidx, JID) ->
" where jid=%(J)s and nodeid=%(Nidx)d")),
ok.
-%get_items(Nidx, _From) ->
-% case catch
-% ejabberd_sql: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_sql:sql_query_t(
- ?SQL("select @(val)s from pubsub_node_option "
- "where nodeid=%(Nidx)d and name='max_items'"))
- of
- {selected, [{Value}]} ->
- jlib:expr_to_term(Value);
- _ ->
- ?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_sql:escape(jlib:i2l(M)),
- {Way, Order} = case Direction of
- aft when I == <<>> -> {<<"is not">>, <<"desc">>};
- aft -> {<<"<">>, <<"desc">>};
- before when I == <<>> -> {<<"is not">>, <<"asc">>};
- before -> {<<">">>, <<"asc">>};
- _ -> {<<"is not">>, <<"desc">>}
- end,
+get_items(Nidx, From, undefined) ->
+ MaxItems = case ejabberd_sql:sql_query_t(
+ ?SQL("select @(val)s from pubsub_node_option "
+ "where nodeid=%(Nidx)d and name='max_items'")) of
+ {selected, [{Value}]} ->
+ jlib:expr_to_term(Value);
+ _ ->
+ ?MAXITEMS
+ end,
+ get_items(Nidx, From, #rsm_set{max = MaxItems});
+get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex,
+ 'after' = After, before = Before}) ->
+ {Way, Order} = if After == <<>> -> {<<"is not">>, <<"desc">>};
+ After /= undefined -> {<<"<">>, <<"desc">>};
+ Before == <<>> -> {<<"is not">>, <<"asc">>};
+ Before /= undefined -> {<<">">>, <<"asc">>};
+ true -> {<<"is not">>, <<"desc">>}
+ end,
SNidx = jlib:i2l(Nidx),
- [AttrName, Id] = case I of
- undefined when IncIndex =/= undefined ->
- case catch
- ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
- "where exists ( select count(*) as count1 "
- "from pubsub_item where nodeid='">>, SNidx,
+ I = if After /= undefined -> After;
+ Before /= undefined -> Before;
+ true -> undefined
+ end,
+ [AttrName, Id] =
+ case I of
+ undefined when IncIndex =/= undefined ->
+ case ejabberd_sql:sql_query_t(
+ [<<"select modification from pubsub_item pi "
+ "where exists ( select count(*) as count1 "
+ "from pubsub_item where nodeid='">>, SNidx,
<<"' and modification > pi.modification having count1 = ">>,
- ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
- of
- {selected, [_], [[O]]} ->
- [<<"modification">>, <<"'", O/binary, "'">>];
- _ ->
- [<<"modification">>, <<"null">>]
- end;
- undefined ->
- [<<"modification">>, <<"null">>];
- <<>> ->
- [<<"modification">>, <<"null">>];
- I ->
- [A, B] = str:tokens(ejabberd_sql:escape(jlib:i2l(I)), <<"@">>),
- [A, <<"'", B/binary, "'">>]
- end,
- Count = case catch
- ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>])
- of
- {selected, [_], [[C]]} -> C;
- _ -> <<"0">>
- end,
+ integer_to_binary(IncIndex), <<" );">>]) of
+ {selected, [_], [[O]]} ->
+ [<<"modification">>, <<"'", O/binary, "'">>];
+ _ ->
+ [<<"modification">>, <<"null">>]
+ end;
+ undefined ->
+ [<<"modification">>, <<"null">>];
+ <<>> ->
+ [<<"modification">>, <<"null">>];
+ I ->
+ [A, B] = str:tokens(ejabberd_sql:escape(I), <<"@">>),
+ [A, <<"'", B/binary, "'">>]
+ end,
+ Count = case ejabberd_sql:sql_query_t(
+ [<<"select count(*) from pubsub_item where nodeid='">>,
+ SNidx, <<"';">>]) of
+ {selected, [_], [[C]]} -> binary_to_integer(C);
+ _ -> 0
+ end,
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
- [<<"select top ">>, jlib:i2l(Max),
+ [<<"select top ">>, integer_to_binary(Max),
<<" itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
@@ -729,32 +709,34 @@ get_items(Nidx, _From,
[<<"select itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
- AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
+ AttrName, <<" ">>, Order, <<" limit ">>,
+ integer_to_binary(Max), <<" ;">>])
end,
- case catch ejabberd_sql:sql_query_t(Query) of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ case ejabberd_sql:sql_query_t(Query) of
+ {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>], RItems} ->
case RItems of
[[_, _, _, F, _]|_] ->
- Index = case catch
- ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
- "where nodeid='">>, SNidx, <<"' and ">>,
- AttrName, <<" > '">>, F, <<"';">>])
- of
- %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
- {selected, [_], [[In]]} -> In;
- _ -> <<"0">>
- end,
+ Index = case catch ejabberd_sql:sql_query_t(
+ [<<"select count(*) from pubsub_item "
+ "where nodeid='">>, SNidx, <<"' and ">>,
+ AttrName, <<" > '">>, F, <<"';">>]) of
+ {selected, [_], [[In]]} -> binary_to_integer(In);
+ _ -> 0
+ end,
[_, _, _, L, _] = lists:last(RItems),
- RsmOut = #rsm_out{count = Count, index = Index,
- first = <<"modification@", F/binary>>,
- last = <<"modification@", (jlib:i2l(L))/binary>>},
+ RsmOut = #rsm_set{count = Count,
+ index = Index,
+ first = #rsm_first{
+ index = Index,
+ data = <<"modification@", F/binary>>},
+ last = <<"modification@", L/binary>>},
{result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
[] ->
- {result, {[], #rsm_out{count = Count}}}
+ {result, {[], #rsm_set{count = Count}}}
end;
_ ->
- {result, {[], none}}
+ {result, {[], undefined}}
end.
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
@@ -769,18 +751,20 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
%% 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};
+ {error, xmpp:err_forbidden()};
(AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
(AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@@ -820,9 +804,9 @@ get_item(Nidx, ItemId) ->
{selected, [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
{selected, []} ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, xmpp:err_item_not_found()};
{'EXIT', _} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
@@ -837,18 +821,20 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
%% 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};
+ {error, xmpp:err_forbidden()};
(AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_presence_subscription_required())};
(AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_authorized(),
+ mod_pubsub:err_not_in_roster_group())};
(AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ {error, mod_pubsub:extended_error(
+ xmpp:err_not_allowed(), mod_pubsub:err_closed_node())};
(AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
+ {error, xmpp:err_forbidden()};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@@ -915,7 +901,7 @@ first_in_list(Pred, [H | T]) ->
itemids(Nidx, {_U, _S, _R} = JID) ->
SJID = encode_jid(JID),
- SJIDLike = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "/%">>,
+ SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
case catch
ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where "
diff --git a/src/node_hometree.erl b/src/node_hometree.erl
index def7b983d..67c5e9332 100644
--- a/src/node_hometree.erl
+++ b/src/node_hometree.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl
index d9af49843..661a2aab4 100644
--- a/src/node_hometree_sql.erl
+++ b/src/node_hometree_sql.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/node_mb.erl b/src/node_mb.erl
index 3399422e5..c06c08d67 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -28,7 +28,6 @@
-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:<pre>
diff --git a/src/node_mb_sql.erl b/src/node_mb_sql.erl
index 125674316..0f5c409ff 100644
--- a/src/node_mb_sql.erl
+++ b/src/node_mb_sql.erl
@@ -28,7 +28,6 @@
-author('holger@zedat.fu-berlin.de').
-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,
diff --git a/src/node_online.erl b/src/node_online.erl
index 1c2ab5a03..2620e6a49 100644
--- a/src/node_online.erl
+++ b/src/node_online.erl
@@ -28,7 +28,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("jid.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
@@ -57,6 +57,7 @@ terminate(Host, ServerHost) ->
?MODULE, user_offline, 75),
ok.
+-spec user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> _.
user_offline(_SID, #jid{luser=LUser,lserver=LServer}, _Info) ->
mod_pubsub:remove_user(LUser, LServer).
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 1677ed4bd..f3b5836cf 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -31,7 +31,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
-include("logger.hrl").
-export([init/3, terminate/2, options/0, features/0,
diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
index ec7795475..ac42cb94f 100644
--- a/src/node_pep_sql.erl
+++ b/src/node_pep_sql.erl
@@ -31,7 +31,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
-include("logger.hrl").
-export([init/3, terminate/2, options/0, features/0,
diff --git a/src/node_private.erl b/src/node_private.erl
index 0cd04b9dd..1888ce33d 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/node_public.erl b/src/node_public.erl
index 0786d9995..ca200e002 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -28,7 +28,6 @@
-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,
diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl
index 387d98413..f17f2846d 100644
--- a/src/nodetree_dag.erl
+++ b/src/nodetree_dag.erl
@@ -30,7 +30,7 @@
-include_lib("stdlib/include/qlc.hrl").
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
@@ -69,13 +69,13 @@ create_node(Key, Node, Type, Owner, Options, Parents) ->
Other -> Other
end;
_ ->
- {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
+ {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)}
end.
delete_node(Key, Node) ->
case find_node(Key, Node) of
false ->
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
+ {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
Record ->
lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
NewOpts = remove_config_parent(Node, Opts),
@@ -99,7 +99,7 @@ get_node(Host, Node, _From) ->
get_node(Host, Node) ->
case find_node(Host, Node) of
- false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
+ false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
Record -> Record
end.
@@ -115,7 +115,7 @@ get_nodes(Key) ->
get_parentnodes(Host, Node, _From) ->
case find_node(Host, Node) of
false ->
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
+ {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
#pubsub_node{parents = Parents} ->
Q = qlc:q([N
|| #pubsub_node{nodeid = {NHost, NNode}} = N
@@ -139,7 +139,7 @@ get_subnodes(Host, <<>>) ->
get_subnodes_helper(Host, <<>>);
get_subnodes(Host, Node) ->
case find_node(Host, Node) of
- false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
+ false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
_ -> get_subnodes_helper(Host, Node)
end.
@@ -226,13 +226,13 @@ validate_parentage(Key, Owners, [<<>> | T]) ->
validate_parentage(Key, Owners, [ParentID | T]) ->
case find_node(Key, ParentID) of
false ->
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
+ {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
#pubsub_node{owners = POwners, options = POptions} ->
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
MutualOwners = [O || O <- Owners, PO <- POwners, O == PO],
case {MutualOwners, NodeType} of
- {[], _} -> {error, ?ERR_FORBIDDEN};
+ {[], _} -> {error, xmpp:err_forbidden()};
{_, collection} -> validate_parentage(Key, Owners, T);
- {_, _} -> {error, ?ERR_NOT_ALLOWED}
+ {_, _} -> {error, xmpp:err_not_allowed()}
end
end.
diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl
index 69b50ff9f..eb28e3408 100644
--- a/src/nodetree_tree.erl
+++ b/src/nodetree_tree.erl
@@ -40,7 +40,7 @@
-include_lib("stdlib/include/qlc.hrl").
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
@@ -49,7 +49,7 @@
delete_node/2]).
init(_Host, _ServerHost, _Options) ->
- mnesia:create_table(pubsub_node,
+ ejabberd_mnesia:create(?MODULE, pubsub_node,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
mnesia:add_table_index(pubsub_node, id),
@@ -76,13 +76,13 @@ get_node(Host, Node, _From) ->
get_node(Host, Node) ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
end.
get_node(Nidx) ->
case mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
end.
get_nodes(Host, _From) ->
@@ -180,10 +180,10 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
options = Options}),
{ok, Nidx};
false ->
- {error, ?ERR_FORBIDDEN}
+ {error, xmpp:err_forbidden()}
end;
_ ->
- {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
+ {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)}
end.
delete_node(Host, Node) ->
diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl
index 9f6b6d5a7..5e4462160 100644
--- a/src/nodetree_tree_sql.erl
+++ b/src/nodetree_tree_sql.erl
@@ -40,7 +40,7 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("ejabberd_sql_pt.hrl").
-export([init/3, terminate/2, options/0, set_node/1,
@@ -97,7 +97,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
case Nidx of
none ->
Txt = <<"Node index not found">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, Txt)};
+ {error, xmpp:err_internal_server_error(Txt, ?MYLANG)};
_ ->
lists:foreach(fun ({Key, Value}) ->
SKey = iolist_to_binary(atom_to_list(Key)),
@@ -125,9 +125,9 @@ get_node(Host, Node) ->
{selected, [RItem]} ->
raw_to_node(Host, RItem);
{'EXIT', _Reason} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
_ ->
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
end.
get_node(Nidx) ->
@@ -139,9 +139,9 @@ get_node(Nidx) ->
{selected, [{Host, Node, Parent, Type}]} ->
raw_to_node(Host, {Node, Parent, Type, Nidx});
{'EXIT', _Reason} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
_ ->
- {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
+ {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
end.
get_nodes(Host, _From) ->
@@ -249,12 +249,12 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
Other -> Other
end;
false ->
- {error, ?ERR_FORBIDDEN}
+ {error, xmpp:err_forbidden()}
end;
{result, _} ->
- {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)};
+ {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)};
{error, db_fail} ->
- {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
+ {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}
end.
delete_node(Host, Node) ->
diff --git a/src/nodetree_virtual.erl b/src/nodetree_virtual.erl
index 934950dd2..31802db2b 100644
--- a/src/nodetree_virtual.erl
+++ b/src/nodetree_virtual.erl
@@ -35,7 +35,6 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
--include("jlib.hrl").
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
index 204cfec2f..fcc472dce 100644
--- a/src/prosody2ejabberd.erl
+++ b/src/prosody2ejabberd.erl
@@ -12,7 +12,7 @@
-export([from_dir/1]).
-include("ejabberd.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_roster.hrl").
-include("mod_offline.hrl").
@@ -300,8 +300,8 @@ convert_privacy_item({_, Item}) ->
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
+ try xmpp_util:decode_timestamp(
+ fxml:get_attr_s(<<"stamp">>, Attrs)) of
{_, _, _} = TS ->
Attrs1 = lists:filter(
fun(<<"stamp">>) -> false;
@@ -321,8 +321,8 @@ el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
packet = Packet}];
_ ->
[]
- end;
- _ ->
+ end
+ catch _:{bad_timestamp, _} ->
[]
end.
diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl
index b910a5e7d..986a0b9b2 100644
--- a/src/pubsub_db_sql.erl
+++ b/src/pubsub_db_sql.erl
@@ -75,27 +75,27 @@ add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
Opts),
ok.
-subscription_opt_from_sql({<<"DELIVER">>, Value}) ->
+subscription_opt_from_sql([<<"DELIVER">>, Value]) ->
{deliver, sql_to_boolean(Value)};
-subscription_opt_from_sql({<<"DIGEST">>, Value}) ->
+subscription_opt_from_sql([<<"DIGEST">>, Value]) ->
{digest, sql_to_boolean(Value)};
-subscription_opt_from_sql({<<"DIGEST_FREQUENCY">>, Value}) ->
+subscription_opt_from_sql([<<"DIGEST_FREQUENCY">>, Value]) ->
{digest_frequency, sql_to_integer(Value)};
-subscription_opt_from_sql({<<"EXPIRE">>, Value}) ->
+subscription_opt_from_sql([<<"EXPIRE">>, Value]) ->
{expire, sql_to_timestamp(Value)};
-subscription_opt_from_sql({<<"INCLUDE_BODY">>, Value}) ->
+subscription_opt_from_sql([<<"INCLUDE_BODY">>, Value]) ->
{include_body, sql_to_boolean(Value)};
%%TODO: might be > than 1 show_values value??.
%% need to use compact all in only 1 opt.
-subscription_opt_from_sql({<<"SHOW_VALUES">>, Value}) ->
+subscription_opt_from_sql([<<"SHOW_VALUES">>, Value]) ->
{show_values, Value};
-subscription_opt_from_sql({<<"SUBSCRIPTION_TYPE">>, Value}) ->
+subscription_opt_from_sql([<<"SUBSCRIPTION_TYPE">>, Value]) ->
{subscription_type,
case Value of
<<"items">> -> items;
<<"nodes">> -> nodes
end};
-subscription_opt_from_sql({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
+subscription_opt_from_sql([<<"SUBSCRIPTION_DEPTH">>, Value]) ->
{subscription_depth,
case Value of
<<"all">> -> all;
@@ -127,15 +127,15 @@ subscription_opt_to_sql({subscription_depth, Depth}) ->
N -> integer_to_sql(N)
end}.
-integer_to_sql(N) -> iolist_to_binary(integer_to_list(N)).
+integer_to_sql(N) -> integer_to_binary(N).
boolean_to_sql(true) -> <<"1">>;
boolean_to_sql(false) -> <<"0">>.
-timestamp_to_sql(T) -> jlib:now_to_utc_string(T).
+timestamp_to_sql(T) -> xmpp_util:encode_timestamp(T).
-sql_to_integer(N) -> jlib:binary_to_integer(N).
+sql_to_integer(N) -> binary_to_integer(N).
sql_to_boolean(B) -> B == <<"1">>.
-sql_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
+sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T).
diff --git a/src/pubsub_index.erl b/src/pubsub_index.erl
index 983356a18..45361e141 100644
--- a/src/pubsub_index.erl
+++ b/src/pubsub_index.erl
@@ -34,7 +34,7 @@
-export([init/3, new/1, free/2]).
init(_Host, _ServerHost, _Opts) ->
- mnesia:create_table(pubsub_index,
+ ejabberd_mnesia:create(?MODULE, pubsub_index,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_index)}]).
diff --git a/src/pubsub_migrate.erl b/src/pubsub_migrate.erl
index c493b58f9..a329f3c39 100644
--- a/src/pubsub_migrate.erl
+++ b/src/pubsub_migrate.erl
@@ -253,7 +253,7 @@ update_node_database(Host, ServerHost) ->
end,
{atomic, NewRecords} = mnesia:transaction(F),
{atomic, ok} = mnesia:delete_table(pubsub_node),
- {atomic, ok} = mnesia:create_table(pubsub_node,
+ {atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_node,
[{disc_copies, [node()]},
{attributes,
record_info(fields,
@@ -421,7 +421,7 @@ update_state_database(_Host, _ServerHost) ->
{atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3,
[F, [], pubsub_state]),
{atomic, ok} = mnesia:delete_table(pubsub_state),
- {atomic, ok} = mnesia:create_table(pubsub_state,
+ {atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_state,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_state)}]),
FNew = fun () ->
diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl
index 3ab502184..0ca066dae 100644
--- a/src/pubsub_subscription.erl
+++ b/src/pubsub_subscription.erl
@@ -28,7 +28,7 @@
-author("bjc@kublai.com").
%% API
--export([init/0, subscribe_node/3, unsubscribe_node/3,
+-export([init/3, subscribe_node/3, unsubscribe_node/3,
get_subscription/3, set_subscription/4,
make_subid/0,
get_options_xform/2, parse_options_xform/1]).
@@ -39,7 +39,7 @@
-include("pubsub.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
-define(PUBSUB_DIGEST, <<"pubsub#digest">>).
@@ -73,7 +73,7 @@
%%====================================================================
%% API
%%====================================================================
-init() -> ok = create_table().
+init(_Host, _ServerHost, _Opts) -> ok = create_table().
subscribe_node(JID, NodeId, Options) ->
case catch mnesia:sync_dirty(fun add_subscription/3, [JID, NodeId, Options])
@@ -112,36 +112,21 @@ get_options_xform(Lang, Options) ->
Keys = [deliver, show_values, subscription_type, subscription_depth],
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
{result,
- #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [#xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
- ++ XFields}}.
+ #xdata{type = form,
+ fields = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>,
+ values = [?NS_PUBSUB_SUB_OPTIONS]}|
+ XFields]}}.
parse_options_xform(XFields) ->
- case fxml:remove_cdata(XFields) of
- [#xmlel{name = <<"x">>} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- XData when is_list(XData) ->
- Opts = set_xoption(XData, []),
- {result, Opts};
- Other -> Other
- end;
- _ -> {result, []}
- end.
+ Opts = set_xoption(XFields, []),
+ {result, Opts}.
%%====================================================================
%% Internal functions
%%====================================================================
create_table() ->
- case mnesia:create_table(pubsub_subscription,
+ case ejabberd_mnesia:create(?MODULE, pubsub_subscription,
[{disc_copies, [node()]},
{attributes,
record_info(fields, pubsub_subscription)},
@@ -185,7 +170,7 @@ write_subscription(_JID, _NodeId, SubID, Options) ->
-spec make_subid() -> SubId::mod_pubsub:subId().
make_subid() ->
{T1, T2, T3} = p1_time_compat:timestamp(),
- iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
%%
%% Subscription XForm processing.
@@ -218,26 +203,29 @@ var_xfield(_) -> {error, badarg}.
val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(digest_frequency = Opt, [Val]) ->
- case catch jlib:binary_to_integer(Val) of
+ case catch binary_to_integer(Val) of
N when is_integer(N) -> N;
_ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
+ Txt = {<<"Value of '~s' should be integer">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
+ end;
+val_xfield(expire = Opt, [Val]) ->
+ try xmpp_util:decode_timestamp(Val)
+ catch _:{bad_timestamp, _} ->
+ Txt = {<<"Value of '~s' should be datetime string">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
end;
-val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(show_values, Vals) -> Vals;
val_xfield(subscription_type, [<<"items">>]) -> items;
val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
val_xfield(subscription_depth, [<<"all">>]) -> all;
val_xfield(subscription_depth = Opt, [Depth]) ->
- case catch jlib:binary_to_integer(Depth) of
+ case catch binary_to_integer(Depth) of
N when is_integer(N) -> N;
_ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
+ Txt = {<<"Value of '~s' should be integer">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
end.
%% Convert XForm booleans to Erlang booleans.
@@ -246,12 +234,8 @@ xopt_to_bool(_, <<"1">>) -> true;
xopt_to_bool(_, <<"false">>) -> false;
xopt_to_bool(_, <<"true">>) -> true;
xopt_to_bool(Option, _) ->
- Txt = <<"Value of '~s' should be boolean">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
-
--spec get_option_xfield(Lang :: binary(), Key :: atom(),
- Options :: mod_pubsub:subOptions()) -> xmlel().
+ Txt = {<<"Value of '~s' should be boolean">>, [Option]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
@@ -261,33 +245,22 @@ get_option_xfield(Lang, Key, Options) ->
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
Vals = case lists:keysearch(Key, 1, Options) of
{value, {_, Val}} ->
- [tr_xfield_values(Vals)
- || Vals <- xfield_val(Key, Val)];
- false -> []
+ [xfield_val(Key, Val)];
+ false ->
+ []
end,
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, Var}, {<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)}],
- children = OptEls ++ Vals}.
+ #xdata_field{type = Type, var = Var,
+ label = translate:translate(Lang, Label),
+ values = Vals,
+ options = OptEls}.
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
type_and_options(Type, _Lang) -> {Type, []}.
tr_xfield_options({Value, Label}, Lang) ->
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}]}.
-
-tr_xfield_values(Value) ->
- %% Return the XForm variable name for a subscription option key.
- %% Return the XForm variable type for a subscription option key.
- #xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}.
+ #xdata_option{label = translate:translate(Lang, Label),
+ value = Value}.
xfield_var(deliver) -> ?PUBSUB_DELIVER;
%xfield_var(digest) -> ?PUBSUB_DIGEST;
@@ -298,24 +271,24 @@ xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
-xfield_type(deliver) -> <<"boolean">>;
-%xfield_type(digest) -> <<"boolean">>;
-%xfield_type(digest_frequency) -> <<"text-single">>;
-%xfield_type(expire) -> <<"text-single">>;
-%xfield_type(include_body) -> <<"boolean">>;
+xfield_type(deliver) -> boolean;
+%xfield_type(digest) -> boolean;
+%xfield_type(digest_frequency) -> 'text-single';
+%xfield_type(expire) -> 'text-single';
+%xfield_type(include_body) -> boolean;
xfield_type(show_values) ->
- {<<"list-multi">>,
+ {'list-multi',
[{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
{<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
{<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
{<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
{<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
xfield_type(subscription_type) ->
- {<<"list-single">>,
+ {'list-single',
[{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
{<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
xfield_type(subscription_depth) ->
- {<<"list-single">>,
+ {'list-single',
[{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
{<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
@@ -334,7 +307,7 @@ xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
%xfield_val(digest, Val) -> [bool_to_xopt(Val)];
%xfield_val(digest_frequency, Val) ->
-% [iolist_to_binary(integer_to_list(Val))];
+% [integer_to_binary(Val))];
%xfield_val(expire, Val) ->
% [jlib:now_to_utc_string(Val)];
%xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
@@ -343,7 +316,7 @@ xfield_val(subscription_type, items) -> [<<"items">>];
xfield_val(subscription_type, nodes) -> [<<"nodes">>];
xfield_val(subscription_depth, all) -> [<<"all">>];
xfield_val(subscription_depth, N) ->
- [iolist_to_binary(integer_to_list(N))].
+ [integer_to_binary(N)].
bool_to_xopt(true) -> <<"true">>;
diff --git a/src/pubsub_subscription_sql.erl b/src/pubsub_subscription_sql.erl
index 6e598320c..fddfe881e 100644
--- a/src/pubsub_subscription_sql.erl
+++ b/src/pubsub_subscription_sql.erl
@@ -28,14 +28,14 @@
-author("pablo.polvorin@process-one.net").
%% API
--export([init/0, subscribe_node/3, unsubscribe_node/3,
+-export([init/3, 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").
--include("jlib.hrl").
+-include("xmpp.hrl").
-define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
-define(PUBSUB_DIGEST, <<"pubsub#digest">>).
@@ -71,7 +71,7 @@
%% API
%%====================================================================
-init() -> ok = create_table().
+init(_Host, _ServerHost, _Opts) -> ok = create_table().
-spec subscribe_node(_JID :: _, _NodeId :: _, Options :: [] | mod_pubsub:subOptions()) ->
{result, mod_pubsub:subId()}.
@@ -117,30 +117,15 @@ get_options_xform(Lang, Options) ->
Keys = [deliver, show_values, subscription_type, subscription_depth],
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
{result,
- #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_XDATA}],
- children =
- [#xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
- ++ XFields}}.
+ #xdata{type = form,
+ fields = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>,
+ values = [?NS_PUBSUB_SUB_OPTIONS]}|
+ XFields]}}.
parse_options_xform(XFields) ->
- case fxml:remove_cdata(XFields) of
- [#xmlel{name = <<"x">>} = XEl] ->
- case jlib:parse_xdata_submit(XEl) of
- XData when is_list(XData) ->
- Opts = set_xoption(XData, []),
- {result, Opts};
- Other -> Other
- end;
- _ -> {result, []}
- end.
+ Opts = set_xoption(XFields, []),
+ {result, Opts}.
%%====================================================================
%% Internal functions
@@ -150,7 +135,7 @@ create_table() -> ok.
-spec make_subid() -> mod_pubsub:subId().
make_subid() ->
{T1, T2, T3} = p1_time_compat:timestamp(),
- iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
+ (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
%%
%% Subscription XForm processing.
@@ -183,26 +168,29 @@ var_xfield(_) -> {error, badarg}.
val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(digest_frequency = Opt, [Val]) ->
- case catch jlib:binary_to_integer(Val) of
+ case catch binary_to_integer(Val) of
N when is_integer(N) -> N;
_ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
+ Txt = {<<"Value of '~s' should be integer">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
+ end;
+val_xfield(expire = Opt, [Val]) ->
+ try xmpp_util:decode_timestamp(Val)
+ catch _:{bad_timestamp, _} ->
+ Txt = {<<"Value of '~s' should be datetime string">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
end;
-val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(show_values, Vals) -> Vals;
val_xfield(subscription_type, [<<"items">>]) -> items;
val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
val_xfield(subscription_depth, [<<"all">>]) -> all;
val_xfield(subscription_depth = Opt, [Depth]) ->
- case catch jlib:binary_to_integer(Depth) of
+ case catch binary_to_integer(Depth) of
N when is_integer(N) -> N;
_ ->
- Txt = <<"Value of '~s' should be integer">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
+ Txt = {<<"Value of '~s' should be integer">>, [Opt]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
end.
%% Convert XForm booleans to Erlang booleans.
@@ -211,9 +199,8 @@ xopt_to_bool(_, <<"1">>) -> true;
xopt_to_bool(_, <<"false">>) -> false;
xopt_to_bool(_, <<"true">>) -> true;
xopt_to_bool(Option, _) ->
- Txt = <<"Value of '~s' should be boolean">>,
- ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
- {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
+ Txt = {<<"Value of '~s' should be boolean">>, [Option]},
+ {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
@@ -222,34 +209,23 @@ get_option_xfield(Lang, Key, Options) ->
Label = xfield_label(Key),
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
Vals = case lists:keysearch(Key, 1, Options) of
- {value, {_, Val}} ->
- [tr_xfield_values(Vals)
- || Vals <- xfield_val(Key, Val)];
- false -> []
- end,
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, Var}, {<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)}],
- children = OptEls ++ Vals}.
+ {value, {_, Val}} ->
+ [xfield_val(Key, Val)];
+ false ->
+ []
+ end,
+ #xdata_field{type = Type, var = Var,
+ label = translate:translate(Lang, Label),
+ values = Vals,
+ options = OptEls}.
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
type_and_options(Type, _Lang) -> {Type, []}.
tr_xfield_options({Value, Label}, Lang) ->
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}]}.
-
-tr_xfield_values(Value) ->
- %% Return the XForm variable name for a subscription option key.
- %% Return the XForm variable type for a subscription option key.
- #xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Value}]}.
+ #xdata_option{label = translate:translate(Lang, Label),
+ value = Value}.
xfield_var(deliver) -> ?PUBSUB_DELIVER;
%xfield_var(digest) -> ?PUBSUB_DIGEST;
@@ -260,26 +236,26 @@ xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
-xfield_type(deliver) -> <<"boolean">>;
-%xfield_type(digest) -> <<"boolean">>;
-%xfield_type(digest_frequency) -> <<"text-single">>;
-%xfield_type(expire) -> <<"text-single">>;
-%xfield_type(include_body) -> <<"boolean">>;
+xfield_type(deliver) -> boolean;
+%xfield_type(digest) -> boolean;
+%xfield_type(digest_frequency) -> 'text-single';
+%xfield_type(expire) -> 'text-single';
+%xfield_type(include_body) -> boolean;
xfield_type(show_values) ->
- {<<"list-multi">>,
- [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
- {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
- {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
- {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
- {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
+ {'list-multi',
+ [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
+ {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
+ {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
+ {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
+ {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
xfield_type(subscription_type) ->
- {<<"list-single">>,
- [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
- {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
+ {'list-single',
+ [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
+ {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
xfield_type(subscription_depth) ->
- {<<"list-single">>,
- [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
- {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
+ {'list-single',
+ [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
+ {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
%% Return the XForm variable label for a subscription option key.
xfield_label(deliver) -> ?DELIVER_LABEL;
@@ -296,7 +272,7 @@ xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
%xfield_val(digest, Val) -> [bool_to_xopt(Val)];
%xfield_val(digest_frequency, Val) ->
-% [iolist_to_binary(integer_to_list(Val))];
+% [integer_to_binary(Val))];
%xfield_val(expire, Val) ->
% [jlib:now_to_utc_string(Val)];
%xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
@@ -305,7 +281,7 @@ xfield_val(subscription_type, items) -> [<<"items">>];
xfield_val(subscription_type, nodes) -> [<<"nodes">>];
xfield_val(subscription_depth, all) -> [<<"all">>];
xfield_val(subscription_depth, N) ->
- [iolist_to_binary(integer_to_list(N))].
+ [integer_to_binary(N)].
bool_to_xopt(false) -> <<"false">>;
bool_to_xopt(true) -> <<"true">>.
diff --git a/src/randoms.erl b/src/randoms.erl
index 1353f48af..ae477d27d 100644
--- a/src/randoms.erl
+++ b/src/randoms.erl
@@ -38,7 +38,7 @@ start() ->
get_string() ->
R = crypto:rand_uniform(0, ?THRESHOLD),
- jlib:integer_to_binary(R).
+ integer_to_binary(R).
uniform() ->
crypto:rand_uniform(0, ?THRESHOLD)/?THRESHOLD.
diff --git a/src/rest.erl b/src/rest.erl
index 01b04f66a..091002fa5 100644
--- a/src/rest.erl
+++ b/src/rest.erl
@@ -28,7 +28,7 @@
-behaviour(ejabberd_config).
-export([start/1, stop/1, get/2, get/3, post/4, delete/2,
- request/6, with_retry/4, opt_type/1]).
+ put/4, patch/4, request/6, with_retry/4, opt_type/1]).
-include("logger.hrl").
@@ -36,14 +36,14 @@
-define(CONNECT_TIMEOUT, 8000).
start(Host) ->
- http_p1:start(),
+ p1_http:start(),
Pool_size =
ejabberd_config:get_option({ext_api_http_pool_size, Host},
fun(X) when is_integer(X), X > 0->
X
end,
100),
- http_p1:set_pool_size(Pool_size).
+ p1_http:set_pool_size(Pool_size).
stop(_Host) ->
ok.
@@ -71,18 +71,17 @@ delete(Server, Path) ->
request(Server, delete, Path, [], "application/json", <<>>).
post(Server, Path, Params, Content) ->
- Data = case catch jiffy:encode(Content) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("HTTP content encodage failed:~n"
- "** Content = ~p~n"
- "** Err = ~p",
- [Content, Reason]),
- <<>>;
- Encoded ->
- Encoded
- end,
+ Data = encode_json(Content),
request(Server, post, Path, Params, "application/json", Data).
+put(Server, Path, Params, Content) ->
+ Data = encode_json(Content),
+ request(Server, put, Path, Params, "application/json", Data).
+
+patch(Server, Path, Params, Content) ->
+ Data = encode_json(Content),
+ request(Server, patch, Path, Params, "application/json", Data).
+
request(Server, Method, Path, Params, Mime, Data) ->
URI = url(Server, Path, Params),
Opts = [{connect_timeout, ?CONNECT_TIMEOUT},
@@ -91,7 +90,7 @@ request(Server, Method, Path, Params, Mime, Data) ->
{"content-type", Mime},
{"User-Agent", "ejabberd"}],
Begin = os:timestamp(),
- Result = case catch http_p1:request(Method, URI, Hdrs, Data, Opts) of
+ Result = case catch p1_http:request(Method, URI, Hdrs, Data, Opts) of
{ok, Code, _, <<>>} ->
{ok, Code, []};
{ok, Code, _, <<" ">>} ->
@@ -147,6 +146,18 @@ request(Server, Method, Path, Params, Mime, Data) ->
%%% HTTP helpers
%%%----------------------------------------------------------------------
+encode_json(Content) ->
+ case catch jiffy:encode(Content) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("HTTP content encodage failed:~n"
+ "** Content = ~p~n"
+ "** Err = ~p",
+ [Content, Reason]),
+ <<>>;
+ Encoded ->
+ Encoded
+ end.
+
base_url(Server, Path) ->
Tail = case iolist_to_binary(Path) of
<<$/, Ok/binary>> -> Ok;
diff --git a/src/shaper.erl b/src/shaper.erl
index eb82b8faa..19c9a049d 100644
--- a/src/shaper.erl
+++ b/src/shaper.erl
@@ -50,7 +50,7 @@
-spec start() -> ok.
start() ->
- mnesia:create_table(shaper,
+ ejabberd_mnesia:create(?MODULE, shaper,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, shaper)}]),
diff --git a/src/str.erl b/src/str.erl
index 27d21075a..43fd51878 100644
--- a/src/str.erl
+++ b/src/str.erl
@@ -64,6 +64,7 @@
to_float/1,
prefix/2,
suffix/2,
+ format/2,
to_integer/1]).
%%%===================================================================
@@ -92,7 +93,10 @@ rchr(B, C) ->
-spec str(binary(), binary()) -> non_neg_integer().
str(B1, B2) ->
- string:str(binary_to_list(B1), binary_to_list(B2)).
+ case binary:match(B1, B2) of
+ {R, _Len} -> R+1;
+ _ -> 0
+ end.
-spec rstr(binary(), binary()) -> non_neg_integer().
@@ -112,7 +116,7 @@ cspan(B1, B2) ->
-spec copies(binary(), non_neg_integer()) -> binary().
copies(B, N) ->
- iolist_to_binary(string:copies(binary_to_list(B), N)).
+ binary:copy(B, N).
-spec words(binary()) -> pos_integer().
@@ -200,7 +204,7 @@ join(L, Sep) ->
-spec substr(binary(), pos_integer()) -> binary().
substr(B, N) ->
- iolist_to_binary(string:substr(binary_to_list(B), N)).
+ binary_part(B, N-1, byte_size(B)-N+1).
-spec chr(binary(), char()) -> non_neg_integer().
@@ -220,7 +224,7 @@ chars(C, N) ->
-spec substr(binary(), pos_integer(), non_neg_integer()) -> binary().
substr(B, S, E) ->
- iolist_to_binary(string:substr(binary_to_list(B), S, E)).
+ binary_part(B, S-1, E).
-spec strip(binary(), both | left | right, char()) -> binary().
@@ -277,6 +281,11 @@ prefix(Prefix, B) ->
suffix(B1, B2) ->
lists:suffix(binary_to_list(B1), binary_to_list(B2)).
+-spec format(io:format(), list()) -> binary().
+
+format(Format, Args) ->
+ iolist_to_binary(io_lib:format(Format, Args)).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================